terminal-pilot 0.0.41 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -3
  3. package/dist/cli.js +789 -9707
  4. package/dist/cli.js.map +4 -4
  5. package/dist/commands/close-session.js.map +1 -1
  6. package/dist/commands/create-session.js.map +1 -1
  7. package/dist/commands/daemon-runtime.js.map +1 -1
  8. package/dist/commands/fill.js.map +1 -1
  9. package/dist/commands/get-session.js.map +1 -1
  10. package/dist/commands/index.js.map +2 -2
  11. package/dist/commands/install.js.map +2 -2
  12. package/dist/commands/installer.js.map +2 -2
  13. package/dist/commands/list-sessions.js.map +1 -1
  14. package/dist/commands/press-key.js.map +1 -1
  15. package/dist/commands/read-history.js.map +1 -1
  16. package/dist/commands/read-screen.js.map +1 -1
  17. package/dist/commands/resize.js.map +1 -1
  18. package/dist/commands/runtime.js.map +1 -1
  19. package/dist/commands/screenshot.js.map +1 -1
  20. package/dist/commands/send-signal.js.map +1 -1
  21. package/dist/commands/type.js.map +1 -1
  22. package/dist/commands/uninstall.js.map +2 -2
  23. package/dist/commands/wait-for-exit.js.map +1 -1
  24. package/dist/commands/wait-for.js.map +1 -1
  25. package/dist/composition.json +35 -0
  26. package/dist/templates/terminal-pilot.md +1 -1
  27. package/dist/testing/cli-repl.js +795 -9713
  28. package/dist/testing/cli-repl.js.map +4 -4
  29. package/dist/testing/qa-cli.js +804 -9722
  30. package/dist/testing/qa-cli.js.map +4 -4
  31. package/node_modules/@poe-code/agent-defs/LICENSE +21 -0
  32. package/node_modules/@poe-code/agent-defs/package.json +12 -3
  33. package/node_modules/@poe-code/agent-skill-config/README.md +25 -17
  34. package/node_modules/@poe-code/agent-skill-config/dist/templates/poe-generate.md +16 -22
  35. package/node_modules/@poe-code/agent-skill-config/dist/templates/terminal-pilot.md +1 -1
  36. package/node_modules/@poe-code/agent-skill-config/package.json +1 -0
  37. package/node_modules/@poe-code/config-mutations/LICENSE +21 -0
  38. package/node_modules/@poe-code/config-mutations/README.md +8 -8
  39. package/node_modules/@poe-code/config-mutations/package.json +11 -2
  40. package/node_modules/@poe-code/frontmatter/LICENSE +21 -0
  41. package/node_modules/@poe-code/frontmatter/README.md +2 -2
  42. package/node_modules/@poe-code/frontmatter/package.json +11 -2
  43. package/node_modules/toolcraft-design/LICENSE +21 -0
  44. package/node_modules/toolcraft-design/README.md +8 -4
  45. package/node_modules/toolcraft-design/dist/dashboard/terminal.js +48 -6
  46. package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +4 -0
  47. package/node_modules/toolcraft-design/dist/explorer/actions.js +1 -0
  48. package/node_modules/toolcraft-design/dist/explorer/events.d.ts +4 -0
  49. package/node_modules/toolcraft-design/dist/explorer/reducer.js +52 -4
  50. package/node_modules/toolcraft-design/dist/explorer/render/list.js +47 -20
  51. package/node_modules/toolcraft-design/dist/explorer/render/modal.js +13 -1
  52. package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.js +1 -1
  53. package/node_modules/toolcraft-design/dist/explorer/runtime.js +3 -0
  54. package/node_modules/toolcraft-design/dist/explorer/state.d.ts +9 -0
  55. package/node_modules/toolcraft-design/package.json +3 -2
  56. package/package.json +2 -1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Poe Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@poe-code/agent-defs",
3
3
  "version": "0.0.1",
4
- "private": true,
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
5
8
  "type": "module",
6
9
  "main": "dist/index.js",
7
10
  "types": "dist/index.d.ts",
@@ -15,6 +18,12 @@
15
18
  "build": "node ../../scripts/guard-package-dist.mjs && tsc"
16
19
  },
17
20
  "files": [
18
- "dist"
19
- ]
21
+ "dist",
22
+ "LICENSE"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/poe-platform/poe-code.git",
27
+ "directory": "packages/agent-defs"
28
+ }
20
29
  }
@@ -13,12 +13,12 @@ Skill refs use one of two forms:
13
13
 
14
14
  Examples:
15
15
 
16
- | Ref | Source |
17
- |-----|--------|
18
- | `"my-helper"` | `~/.poe-code/skills/my-helper` or `<cwd>/.poe-code/skills/my-helper` |
19
- | `"claude/my-helper"` | `~/.claude/skills/my-helper` alias for `claude-code` |
20
- | `"claude-code/my-helper"` | `~/.claude/skills/my-helper` canonical id |
21
- | `"codex/my-helper"` | `~/.codex/skills/my-helper` |
16
+ | Ref | Source |
17
+ | ------------------------- | -------------------------------------------------------------------- |
18
+ | `"my-helper"` | `~/.poe-code/skills/my-helper` or `<cwd>/.poe-code/skills/my-helper` |
19
+ | `"claude/my-helper"` | `~/.claude/skills/my-helper` alias for `claude-code` |
20
+ | `"claude-code/my-helper"` | `~/.claude/skills/my-helper` canonical id |
21
+ | `"codex/my-helper"` | `~/.codex/skills/my-helper` |
22
22
 
23
23
  The agent token accepts canonical ids, aliases, and any casing. It is normalized with `resolveAgentId` from `@poe-code/agent-defs`. The token is not the source agent's native directory name; the source path always comes from `agentSkillConfigs[canonicalId]`, so aliases map to the directory owned by that canonical agent.
24
24
 
@@ -50,20 +50,20 @@ The source ref's agent prefix never appears in the target path. For a Claude Cod
50
50
 
51
51
  Resolution failures abort the whole bridge before any copy. Error messages distinguish:
52
52
 
53
- | Kind | Meaning |
54
- |------|---------|
55
- | `malformed` | Bad syntax; expected `"<name>"` or `"<agentId>/<name>"`. |
53
+ | Kind | Meaning |
54
+ | --------------- | ------------------------------------------------------------------------------------ |
55
+ | `malformed` | Bad syntax; expected `"<name>"` or `"<agentId>/<name>"`. |
56
56
  | `unknown-agent` | Agent token does not resolve to a supported agent; the error lists supported agents. |
57
- | `not-found` | No skill folder exists at any tier; the error lists searched paths in order. |
57
+ | `not-found` | No skill folder exists at any tier; the error lists searched paths in order. |
58
58
 
59
59
  Collisions never abort. The bridge emits one `BridgeWarning` per skipped ref, leaves no state for that ref, and continues the batch.
60
60
 
61
- | Warning kind | Skip condition |
62
- |--------------|----------------|
63
- | `local-collision` | Target folder already exists in the spawning agent's local skill dir. |
64
- | `global-collision` | Target folder already exists in the spawning agent's global skill dir. |
65
- | `self-reference` | The spawning agent references its own native skill. |
66
- | `intra-batch-collision` | Two refs produce the same target basename; first input ref wins. |
61
+ | Warning kind | Skip condition |
62
+ | ----------------------- | ---------------------------------------------------------------------- |
63
+ | `local-collision` | Target folder already exists in the spawning agent's local skill dir. |
64
+ | `global-collision` | Target folder already exists in the spawning agent's global skill dir. |
65
+ | `self-reference` | The spawning agent references its own native skill. |
66
+ | `intra-batch-collision` | Two refs produce the same target basename; first input ref wins. |
67
67
 
68
68
  Native skills are never overwritten. Callers, including the spawn runner, surface `manifest.warnings` through the design-system warning channel before launching the agent.
69
69
 
@@ -84,7 +84,7 @@ import {
84
84
  resolveAgentSupport,
85
85
  getAgentConfig,
86
86
  resolveSkillDir,
87
- supportedAgents,
87
+ supportedAgents
88
88
  } from "@poe-code/agent-skill-config";
89
89
  ```
90
90
 
@@ -92,6 +92,14 @@ Exported types include `AgentSkillConfig`, `AgentSupportResult`, `SkillScope`, `
92
92
 
93
93
  `agentSkillConfigs` is internal and is not exported.
94
94
 
95
+ ## CLI Commands
96
+
97
+ | Command | Purpose |
98
+ | ---------------------------- | --------------------------------------------------------------------------------------------------- |
99
+ | `poe-code skill install` | Install one `SKILL.md` for an agent. Use `--name`, `--file`, `--local`, `--global`, or `--yes`. |
100
+ | `poe-code skill configure` | Create the native skill directory layout for an agent. Supports `--local`, `--global`, and `--yes`. |
101
+ | `poe-code skill unconfigure` | Remove native skill directories. Use `--force` to remove a non-empty directory. |
102
+
95
103
  ## Config Options
96
104
 
97
105
  This package exposes no user-facing config options.
@@ -1,47 +1,41 @@
1
1
  ---
2
2
  name: poe-generate
3
- description: 'Poe code generation skill'
3
+ description: "Poe Code agent prompting guidance"
4
4
  ---
5
5
 
6
- # poe-code generate
6
+ # Poe Code Prompting
7
7
 
8
- Use `poe-code generate` to create text, images, audio, or video via the Poe API.
8
+ Use Poe Code's supported agent surfaces for prompts. The current CLI has no standalone generation command.
9
9
 
10
- ## Text generation
10
+ ## One-shot Poe Agent Prompt
11
11
 
12
12
  ```bash
13
- poe-code generate "Write a short function that parses a JSON string safely."
13
+ poe-code agent "Summarize the current repository."
14
14
  ```
15
15
 
16
- Specify the model/bot:
16
+ Specify a model when the command supports it:
17
17
 
18
18
  ```bash
19
- # CLI option
20
- poe-code generate --model "gpt-4.1" "Summarize this codebase change."
21
-
22
- # Some agent runtimes call the model selector `--bot`
23
- poe-code generate --bot "gpt-4.1" "Summarize this codebase change."
19
+ poe-code agent "Summarize this codebase change." --model "<model-id>"
24
20
  ```
25
21
 
26
- ## Media generation
22
+ ## Coding Agent Prompt
27
23
 
28
- The CLI supports media generation as subcommands:
24
+ Use `spawn` when the prompt should run through a configured coding agent CLI:
29
25
 
30
26
  ```bash
31
- poe-code generate image "A 3D render of a rubber duck wearing sunglasses" --model "gpt-image-1" -o duck.png
32
- poe-code generate video "A cinematic timelapse of a city at night" --model "veo" -o city.mp4
33
- poe-code generate audio "A calm 10 second lo-fi beat" --model "audio-model" -o beat.wav
27
+ poe-code spawn codex "Review the auth module"
28
+ poe-code spawn claude-code "Fix the failing tests" --model "<model-id>"
34
29
  ```
35
30
 
36
- Some agent runtimes expose the same media types as flags. If available, these are equivalent:
31
+ Run against a GitHub locator when the work should happen outside the current checkout:
37
32
 
38
33
  ```bash
39
- poe-code generate --image "A 3D render of a rubber duck wearing sunglasses" --bot "gpt-image-1" -o duck.png
40
- poe-code generate --video "A cinematic timelapse of a city at night" --bot "veo" -o city.mp4
41
- poe-code generate --audio "A calm 10 second lo-fi beat" --bot "audio-model" -o beat.wav
34
+ poe-code spawn codex "Review this package" --cwd github://owner/repo#main:packages/auth
42
35
  ```
43
36
 
44
37
  ## Tips
45
38
 
46
- - Use `--param key=value` to pass provider/model parameters (repeatable).
47
- - Use `--output <path>` (or `-o`) for media outputs.
39
+ - Run `poe-code models` to find model IDs and endpoint support.
40
+ - Use `poe-code auth status` to verify credentials before running prompts.
41
+ - Use `poe-code spawn --help` and `poe-code agent --help` for current flags.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: terminal-pilot
3
- description: 'Terminal automation skill using the terminal-pilot CLI'
3
+ description: "Terminal automation skill using the terminal-pilot CLI"
4
4
  ---
5
5
 
6
6
  # Terminal Pilot
@@ -2,6 +2,7 @@
2
2
  "name": "@poe-code/agent-skill-config",
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
+ "license": "MIT",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Poe Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -38,14 +38,14 @@ await runMutations(
38
38
 
39
39
  `MutationContext` controls execution:
40
40
 
41
- | Option | Type | Description |
42
- | ------ | ---- | ----------- |
43
- | `fs` | `FileSystem` | Required filesystem adapter. |
44
- | `homeDir` | `string` | Required home directory for `~` expansion. |
45
- | `dryRun` | `boolean` | Reports changes without writing them. |
46
- | `observers` | `MutationObservers` | Receives mutation lifecycle events. |
47
- | `templates` | `TemplateLoader` | Loads templates referenced by template mutations. |
48
- | `pathMapper` | `PathMapper` | Redirects target directories for isolated config flows. |
41
+ | Option | Type | Description |
42
+ | ------------ | ------------------- | ------------------------------------------------------- |
43
+ | `fs` | `FileSystem` | Required filesystem adapter. |
44
+ | `homeDir` | `string` | Required home directory for `~` expansion. |
45
+ | `dryRun` | `boolean` | Reports changes without writing them. |
46
+ | `observers` | `MutationObservers` | Receives mutation lifecycle events. |
47
+ | `templates` | `TemplateLoader` | Loads templates referenced by template mutations. |
48
+ | `pathMapper` | `PathMapper` | Redirects target directories for isolated config flows. |
49
49
 
50
50
  Mutation builders also expose per-mutation options such as target path, format,
51
51
  label, force removal, backup behavior, template id, and transform callbacks.
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@poe-code/config-mutations",
3
3
  "version": "0.0.1",
4
- "private": true,
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
5
8
  "type": "module",
6
9
  "main": "dist/index.js",
7
10
  "types": "dist/index.d.ts",
@@ -19,8 +22,14 @@
19
22
  "build": "node ../../scripts/guard-package-dist.mjs && tsc"
20
23
  },
21
24
  "files": [
22
- "dist"
25
+ "dist",
26
+ "LICENSE"
23
27
  ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/poe-platform/poe-code.git",
31
+ "directory": "packages/config-mutations"
32
+ },
24
33
  "dependencies": {
25
34
  "jsonc-parser": "^3.3.1",
26
35
  "smol-toml": "^1.3.0",
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Poe Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -26,10 +26,10 @@ Duplicate mapping keys use YAML's last-wins behavior by default, matching the
26
26
  legacy frontmatter readers. Callers that need strict YAML mappings can pass
27
27
  `{ uniqueKeys: true }` to `parseFrontmatter` or `parseFrontmatterDocument`.
28
28
 
29
- ## Environment
29
+ ## Environment Variables
30
30
 
31
31
  This package reads no environment variables.
32
32
 
33
- ## Config
33
+ ## Configuration Options
34
34
 
35
35
  This package has no configuration options.
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@poe-code/frontmatter",
3
3
  "version": "0.0.1",
4
- "private": true,
4
+ "license": "MIT",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
5
8
  "type": "module",
6
9
  "main": "dist/index.js",
7
10
  "types": "dist/index.d.ts",
@@ -17,8 +20,14 @@
17
20
  "test:unit": "cd ../.. && vitest run packages/frontmatter/src"
18
21
  },
19
22
  "files": [
20
- "dist"
23
+ "dist",
24
+ "LICENSE"
21
25
  ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/poe-platform/poe-code.git",
29
+ "directory": "packages/frontmatter"
30
+ },
22
31
  "dependencies": {
23
32
  "yaml": "^2.8.2"
24
33
  }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Poe Platform
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -118,16 +118,16 @@ Raw HTML in Markdown is escaped by default. Pass `{ allowRawHtml: true }` only w
118
118
 
119
119
  Fenced code block syntax highlighting is opt-in:
120
120
 
121
- ```ts
121
+ ````ts
122
122
  import { renderMarkdown, renderMarkdownHtml } from "toolcraft-design";
123
123
 
124
- const html = renderMarkdownHtml("```ts\nconst value = \"hello\";\n```", {
124
+ const html = renderMarkdownHtml('```ts\nconst value = "hello";\n```', {
125
125
  syntaxHighlight: true
126
126
  });
127
- const terminal = renderMarkdown("```ts\nconst value = \"hello\";\n```", {
127
+ const terminal = renderMarkdown('```ts\nconst value = "hello";\n```', {
128
128
  syntaxHighlight: true
129
129
  });
130
- ```
130
+ ````
131
131
 
132
132
  HTML highlighting emits escaped code text with neutral Toolcraft-owned `<span>` wrappers. The renderer does not ship or inject CSS, so consumers control the appearance:
133
133
 
@@ -340,3 +340,7 @@ nvm exec 18.18 node packages/toolcraft-design/scripts/check-node18.mjs
340
340
  - `NO_COLOR`: disables color when `FORCE_COLOR` does not enable it.
341
341
  - `TERM`: participates in color support detection; an unset or `dumb` terminal disables color unless forced.
342
342
  - `POE_NO_SPINNER`: uses non-animated spinner behavior when set to `1`.
343
+
344
+ ## Configuration Options
345
+
346
+ Use `configureTheme({ brand?, label? })` for process-local brand settings, `withOutputFormat(format, fn)` for scoped output-format overrides, and component-specific options such as `RenderTableOptions`, `RenderMarkdownOptions`, prompt options, dashboard options, and explorer config. Environment variables above are process defaults, not config files.
@@ -118,19 +118,18 @@ export function createTerminalDriver(opts) {
118
118
  if (destroyed) {
119
119
  return () => { };
120
120
  }
121
- const listener = (str, key) => {
122
- const event = toKeypressEvent(str, key);
123
- if (event !== undefined) {
121
+ const listener = (chunk) => {
122
+ for (const event of parseKeypressChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))) {
124
123
  handler(event);
125
124
  }
126
125
  };
127
126
  keypressListeners.add(listener);
128
- stdin.on("keypress", listener);
127
+ stdin.on("data", listener);
129
128
  return () => {
130
129
  if (!keypressListeners.delete(listener)) {
131
130
  return;
132
131
  }
133
- stdin.off("keypress", listener);
132
+ stdin.off("data", listener);
134
133
  };
135
134
  }
136
135
  function destroy() {
@@ -138,7 +137,7 @@ export function createTerminalDriver(opts) {
138
137
  return;
139
138
  }
140
139
  for (const listener of keypressListeners) {
141
- stdin.off("keypress", listener);
140
+ stdin.off("data", listener);
142
141
  }
143
142
  keypressListeners.clear();
144
143
  for (const listener of resizeListeners) {
@@ -173,6 +172,9 @@ export function parseKeypress(data) {
173
172
  if (data.length === 0) {
174
173
  return undefined;
175
174
  }
175
+ if (data.length === 1 && data[0] === 0x1b) {
176
+ return { name: "escape", ctrl: false, meta: false, shift: false };
177
+ }
176
178
  const stream = new PassThrough();
177
179
  const stdin = stream;
178
180
  let event;
@@ -184,6 +186,46 @@ export function parseKeypress(data) {
184
186
  stream.destroy();
185
187
  return event;
186
188
  }
189
+ function parseKeypressChunk(data) {
190
+ const events = [];
191
+ const input = data.toString("utf8");
192
+ let index = 0;
193
+ while (index < input.length) {
194
+ const sequence = nextKeySequence(input, index);
195
+ if (sequence.length === 0) {
196
+ break;
197
+ }
198
+ const event = parseKeypress(Buffer.from(sequence));
199
+ if (event !== undefined) {
200
+ events.push(event);
201
+ }
202
+ index += sequence.length;
203
+ }
204
+ return events;
205
+ }
206
+ function nextKeySequence(input, index) {
207
+ const character = input[index];
208
+ if (character === undefined) {
209
+ return "";
210
+ }
211
+ if (character !== "\u001b") {
212
+ return character;
213
+ }
214
+ const next = input[index + 1];
215
+ if (next === undefined) {
216
+ return character;
217
+ }
218
+ if (next !== "[") {
219
+ return input.slice(index, index + 2);
220
+ }
221
+ for (let cursor = index + 2; cursor < input.length; cursor += 1) {
222
+ const code = input.charCodeAt(cursor);
223
+ if ((code >= 0x40 && code <= 0x7e) || input[cursor] === "~") {
224
+ return input.slice(index, cursor + 1);
225
+ }
226
+ }
227
+ return input.slice(index);
228
+ }
187
229
  function toKeypressEvent(str, key) {
188
230
  const controlCharacter = controlCharacterToKeypress(key?.sequence);
189
231
  if (controlCharacter !== undefined) {
@@ -7,6 +7,10 @@ type ExplorerKeypressEvent = Extract<ExplorerEvent, {
7
7
  export type ActionRuntimeHandles = {
8
8
  refresh: () => Promise<void>;
9
9
  suspendAnd: <T>(fn: () => Promise<T>) => Promise<T>;
10
+ openModal: (content: {
11
+ title: string;
12
+ content: string;
13
+ }) => void;
10
14
  toast: (msg: string, tone?: Tone) => void;
11
15
  confirm: (prompt: string) => Promise<boolean>;
12
16
  exit: (after?: () => void | Promise<void>) => void;
@@ -20,6 +20,7 @@ export function buildActionContext(state, _action, source, runtimeHandles, rowsO
20
20
  filter: state.filter,
21
21
  refresh: runtimeHandles.refresh,
22
22
  suspendAnd: runtimeHandles.suspendAnd,
23
+ openModal: runtimeHandles.openModal,
23
24
  toast: runtimeHandles.toast,
24
25
  confirm: runtimeHandles.confirm,
25
26
  exit: runtimeHandles.exit
@@ -55,6 +55,10 @@ export type ExplorerEvent = {
55
55
  type: "suspendResumed";
56
56
  value: unknown;
57
57
  emit: ExplorerEvent;
58
+ } | {
59
+ type: "modalOpened";
60
+ title: string;
61
+ content: string;
58
62
  } | {
59
63
  type: "modalDismissed";
60
64
  result: unknown;
@@ -5,6 +5,7 @@ const NO_EFFECTS = [];
5
5
  const DEFAULT_ACTION_HANDLES = {
6
6
  refresh: async () => undefined,
7
7
  suspendAnd: async (fn) => fn(),
8
+ openModal: () => undefined,
8
9
  toast: () => undefined,
9
10
  confirm: async () => false,
10
11
  exit: () => undefined
@@ -31,6 +32,13 @@ export function step(state, event, runtimeHandles = DEFAULT_ACTION_HANDLES) {
31
32
  return expireToast(state);
32
33
  case "suspendResumed":
33
34
  return suspendResumed(state, event.emit, runtimeHandles);
35
+ case "modalOpened":
36
+ return setModal(state, {
37
+ kind: "content",
38
+ title: event.title,
39
+ content: event.content,
40
+ scroll: 0
41
+ });
34
42
  case "modalDismissed":
35
43
  return modalDismissed(state, event.result, runtimeHandles);
36
44
  }
@@ -164,6 +172,26 @@ function stepModalKey(state, key, target, runtimeHandles) {
164
172
  }
165
173
  return paletteInput(state, key);
166
174
  }
175
+ if (state.modal?.kind === "content") {
176
+ if (target?.type === "builtin") {
177
+ switch (target.id) {
178
+ case "escape":
179
+ case "confirm":
180
+ return modalDismissed(state, false, runtimeHandles);
181
+ case "cursorDown":
182
+ case "detailScrollDown":
183
+ return scrollContentModal(state, 1);
184
+ case "cursorUp":
185
+ case "detailScrollUp":
186
+ return scrollContentModal(state, -1);
187
+ case "pageDown":
188
+ return scrollContentModal(state, modalBodyHeight(state));
189
+ case "pageUp":
190
+ return scrollContentModal(state, -modalBodyHeight(state));
191
+ }
192
+ }
193
+ return mark(state, 0);
194
+ }
167
195
  return mark(state, 0);
168
196
  }
169
197
  function resize(state, cols, rows) {
@@ -287,7 +315,7 @@ function suspendResumed(state, emit, runtimeHandles) {
287
315
  }
288
316
  function modalDismissed(state, result, runtimeHandles) {
289
317
  const modal = state.modal;
290
- const closed = { ...state, modal: null, dirty: REGION_MODAL | REGION_FOOTER };
318
+ const closed = { ...state, modal: null, dirty: REGION_ALL };
291
319
  if (modal?.kind === "confirm") {
292
320
  modal.resolver(result === true);
293
321
  }
@@ -466,9 +494,6 @@ function selectionChanged(state, selected) {
466
494
  return { state: next, effects: NO_EFFECTS };
467
495
  }
468
496
  function detailScroll(state, delta) {
469
- if (state.focused !== "detail") {
470
- return mark(state, 0);
471
- }
472
497
  const scroll = clamp(state.detail.scroll + delta, 0, maxDetailScroll(state));
473
498
  if (scroll === state.detail.scroll) {
474
499
  return mark(state, 0);
@@ -478,6 +503,29 @@ function detailScroll(state, delta) {
478
503
  effects: NO_EFFECTS
479
504
  };
480
505
  }
506
+ function scrollContentModal(state, delta) {
507
+ if (state.modal?.kind !== "content") {
508
+ return mark(state, 0);
509
+ }
510
+ const scroll = clamp(state.modal.scroll + delta, 0, maxContentModalScroll(state));
511
+ if (scroll === state.modal.scroll) {
512
+ return mark(state, 0);
513
+ }
514
+ return setModal(state, { ...state.modal, scroll });
515
+ }
516
+ function maxContentModalScroll(state) {
517
+ if (state.modal?.kind !== "content") {
518
+ return 0;
519
+ }
520
+ return Math.max(0, state.modal.content.split("\n").length - modalBodyHeight(state));
521
+ }
522
+ function modalBodyHeight(state) {
523
+ const screenHeight = normalizeSize(state.size.rows);
524
+ if (screenHeight <= 2) {
525
+ return 0;
526
+ }
527
+ return Math.max(0, screenHeight - 4);
528
+ }
481
529
  function clampDetailScroll(state) {
482
530
  const scroll = clamp(state.detail.scroll, 0, maxDetailScroll(state));
483
531
  if (scroll === state.detail.scroll) {