Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ Run shell commands from natural-language requests:
jst find all files bigger than 500 mb in ~/downloads
```

<p align="center">
<img src="https://jst.sh/demo.gif" width="960" alt="A randomly selected jst terminal demo">
</p>

The demo above is randomly selected on each fresh image request. It is recorded
from a real `jst` run; [see every demo](docs/demos/).

## Install

### Homebrew
Expand Down
Binary file added docs/demos/changed-today.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions docs/demos/changed-today.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Output docs/demos/changed-today.gif

Require jst

Set Shell "zsh"
Set FontFamily "Monaco"
Set FontSize 18
Set Width 1120
Set Height 560
Set Padding 28
Set Margin 34
Set MarginFill "#f3efe6"
Set BorderRadius 14
Set WindowBar "Colorful"
Set WindowBarSize 38
Set Theme "Catppuccin Mocha"
Set TypingSpeed 34ms
Set Framerate 30
Set PlaybackSpeed 1.15

Hide
Type `unset NO_COLOR; export COLUMNS=80; export PS1=$'\n❯ '`
Enter
Type "mkdir -p /tmp/jst-demo-changed; cd /tmp/jst-demo-changed; touch draft.md deploy.log; touch -t 202501010000 old-notes.txt"
Enter
Ctrl+L
Show

Type "jst find every file changed in the last 24 hours"
Enter
Sleep 5s
Sleep 2s
Binary file added docs/demos/clear-port-8080.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions docs/demos/clear-port-8080.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Output docs/demos/clear-port-8080.gif

Require jst

Set Shell "zsh"
Set FontFamily "Monaco"
Set FontSize 18
Set Width 1120
Set Height 720
Set Padding 28
Set Margin 34
Set MarginFill "#f3efe6"
Set BorderRadius 14
Set WindowBar "Colorful"
Set WindowBarSize 38
Set Theme "Catppuccin Mocha"
Set TypingSpeed 34ms
Set Framerate 30
Set PlaybackSpeed 1.15

Hide
Type `unset NO_COLOR; export COLUMNS=80; export PS1=$'\n❯ '`
Enter
Type "python3 -m http.server 8080 >/dev/null 2>&1 &"
Enter
Sleep 1s
Ctrl+L
Show

Type "jst stop whatever is using port 8080"
Enter
Sleep 7s
Type "y"
Enter
Sleep 2s
Type "lsof -nP -iTCP:8080 -sTCP:LISTEN || echo 'port 8080 is free'"
Enter
Sleep 2s
Binary file added docs/demos/remove-ds-store.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions docs/demos/remove-ds-store.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Output docs/demos/remove-ds-store.gif

Require jst

Set Shell "zsh"
Set FontFamily "Monaco"
Set FontSize 18
Set Width 1120
Set Height 720
Set Padding 28
Set Margin 34
Set MarginFill "#f3efe6"
Set BorderRadius 14
Set WindowBar "Colorful"
Set WindowBarSize 38
Set Theme "Catppuccin Mocha"
Set TypingSpeed 34ms
Set Framerate 30
Set PlaybackSpeed 1.15

Hide
Type `unset NO_COLOR; export COLUMNS=80; export PS1=$'\n❯ '`
Enter
Type "mkdir -p /tmp/jst-demo-ds-store/nested; cd /tmp/jst-demo-ds-store; touch .DS_Store nested/.DS_Store"
Enter
Ctrl+L
Show

Type "jst remove all .DS_Store files below the current directory"
Enter
Sleep 7s
Type "n"
Enter
Sleep 2s
Binary file added docs/demos/zip-folder.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions docs/demos/zip-folder.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Output docs/demos/zip-folder.gif

Require jst

Set Shell "zsh"
Set FontFamily "Monaco"
Set FontSize 18
Set Width 1120
Set Height 620
Set Padding 28
Set Margin 34
Set MarginFill "#f3efe6"
Set BorderRadius 14
Set WindowBar "Colorful"
Set WindowBarSize 38
Set Theme "Catppuccin Mocha"
Set TypingSpeed 34ms
Set Framerate 30
Set PlaybackSpeed 1.15

Hide
Type `unset NO_COLOR; export COLUMNS=80; export PS1=$'\n❯ '`
Enter
Type "mkdir -p /tmp/jst-demo-zip/folder/node_modules /tmp/jst-demo-zip/folder/src; cd /tmp/jst-demo-zip; mkfile 1m folder/src/app.js; mkfile 2m folder/node_modules/bundle.js"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 macOS-only demo fixture setup

The tape creates its fixture exclusively with the macOS-specific mkfile utility, so contributors regenerating the GIF on other platforms receive mkfile: command not found and produce an empty or misleading archive demonstration. Use a portable fixture command or explicitly declare the recording environment.

Enter
Ctrl+L
Show

Type "jst zip this folder without node_modules"
Enter
Sleep 5s
Sleep 2s
Type "unzip -l folder.zip"
Enter
Sleep 2s
18 changes: 18 additions & 0 deletions site/demo-gif.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import assert from "node:assert/strict";
import test from "node:test";
import { demoFileAt, demoRedirect } from "../workers/demo-gif.js";
Comment on lines +1 to +3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Js tests not in ci 🐞 Bug ⚙ Maintainability

This PR adds a Node test file for the demo-gif Worker, but the GitHub Actions workflows shown do not
run any Node/npm test command, so these new tests won’t execute in CI and regressions to the worker
behavior can merge undetected.
Agent Prompt
### Issue description
The newly-added Node tests under `site/*.test.js` are not exercised by existing GitHub Actions workflows.

### Issue Context
`package.json` defines `test:demo` as `node --test site/*.test.js`, but `.github/workflows/ci.yml` only runs Rust checks.

### Fix Focus Areas
- site/demo-gif.test.js[1-18]
- package.json[5-9]
- .github/workflows/ci.yml[16-30]

### Suggested change
- Add a CI job/steps to install Node (actions/setup-node), run `npm ci`, then `npm run test:demo`.
- Optionally cache npm to keep CI fast.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


test("the demo endpoint can select every recorded GIF", () => {
assert.equal(demoFileAt(0), "changed-today.gif");
assert.equal(demoFileAt(1), "clear-port-8080.gif");
assert.equal(demoFileAt(2), "remove-ds-store.gif");
assert.equal(demoFileAt(3), "zip-folder.gif");
});

test("the demo endpoint redirects without allowing the selection to be cached", () => {
const response = demoRedirect(3);

assert.equal(response.status, 302);
assert.equal(response.headers.get("Location"), "https://jst.sh/demos/zip-folder.gif");
assert.equal(response.headers.get("Cache-Control"), "no-store");
});
26 changes: 26 additions & 0 deletions workers/demo-gif.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const DEMO_FILES = [
"changed-today.gif",
"clear-port-8080.gif",
"remove-ds-store.gif",
"zip-folder.gif",
];

export function demoFileAt(index) {
return DEMO_FILES[index % DEMO_FILES.length];
}
Comment on lines +8 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Undefined redirect target 🐞 Bug ≡ Correctness

demoFileAt uses a raw modulo result as an array index, so negative/NaN/non-integer inputs (or a
future empty DEMO_FILES) return undefined and demoRedirect will emit Location:
https://jst.sh/demos/undefined. That produces broken redirects and makes the exported helpers unsafe
for reuse beyond the current Math.floor(Math.random()) callsite.
Agent Prompt
### Issue description
`demoFileAt(index)` can return `undefined` for invalid indices (negative, NaN, non-integer) or if the list is ever emptied, and `demoRedirect` will then generate a broken `Location` header.

### Issue Context
These helpers are exported and already imported by unit tests, so they are part of a reusable API surface.

### Fix Focus Areas
- workers/demo-gif.js[1-20]

### Suggested change
- Guard against an empty `DEMO_FILES`.
- Coerce `index` to a finite integer and normalize negatives before indexing.
- Optionally throw (or default to 0) when `index` is invalid so bad calls fail loudly instead of redirecting to `/undefined`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


export function demoRedirect(index) {
return new Response(null, {
status: 302,
headers: {
"Cache-Control": "no-store",
Location: `https://jst.sh/demos/${demoFileAt(index)}`,
},
});
}

export default {
fetch() {
return demoRedirect(Math.floor(Math.random() * DEMO_FILES.length));
},
Comment on lines +22 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

2. No method/path guarding 🐞 Bug ☼ Reliability

The Worker fetch handler ignores the incoming Request, so it returns the same 302 redirect for any
HTTP method and for any path the Worker is routed to. This increases blast radius if the route is
ever broadened/misconfigured, because unrelated requests would start redirecting to demo GIFs.
Agent Prompt
### Issue description
The Worker responds with a demo redirect unconditionally; it does not validate HTTP method or pathname.

### Issue Context
Even if deployment is intended to be scoped to `/demo.gif`, adding an in-code check is a cheap defense-in-depth measure against accidental route changes.

### Fix Focus Areas
- workers/demo-gif.js[22-26]

### Suggested change
- Accept `request` in `fetch(request)`.
- If `request.method` is not `GET`/`HEAD`, return `405`.
- If `new URL(request.url).pathname` is not `/demo.gif`, return `404` (or similar).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

};
Loading