@code-yeongyu/senpi 2026.5.23 → 2026.5.29-2

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 (201) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli/args.d.ts +0 -6
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +15 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +4 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +116 -80
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +18 -24
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.d.ts.map +1 -1
  17. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js +4 -2
  18. package/dist/core/extensions/builtin/gpt-apply-patch/streaming-render.js.map +1 -1
  19. package/dist/core/extensions/builtin/history-search/filter.d.ts +3 -0
  20. package/dist/core/extensions/builtin/history-search/filter.d.ts.map +1 -0
  21. package/dist/core/extensions/builtin/history-search/filter.js +22 -0
  22. package/dist/core/extensions/builtin/history-search/filter.js.map +1 -0
  23. package/dist/core/extensions/builtin/history-search/index.d.ts +7 -0
  24. package/dist/core/extensions/builtin/history-search/index.d.ts.map +1 -0
  25. package/dist/core/extensions/builtin/history-search/index.js +45 -0
  26. package/dist/core/extensions/builtin/history-search/index.js.map +1 -0
  27. package/dist/core/extensions/builtin/history-search/indexer.d.ts +3 -0
  28. package/dist/core/extensions/builtin/history-search/indexer.d.ts.map +1 -0
  29. package/dist/core/extensions/builtin/history-search/indexer.js +161 -0
  30. package/dist/core/extensions/builtin/history-search/indexer.js.map +1 -0
  31. package/dist/core/extensions/builtin/history-search/overlay.d.ts +30 -0
  32. package/dist/core/extensions/builtin/history-search/overlay.d.ts.map +1 -0
  33. package/dist/core/extensions/builtin/history-search/overlay.js +115 -0
  34. package/dist/core/extensions/builtin/history-search/overlay.js.map +1 -0
  35. package/dist/core/extensions/builtin/history-search/types.d.ts +8 -0
  36. package/dist/core/extensions/builtin/history-search/types.d.ts.map +1 -0
  37. package/dist/core/extensions/builtin/history-search/types.js +2 -0
  38. package/dist/core/extensions/builtin/history-search/types.js.map +1 -0
  39. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/index.js +4 -0
  41. package/dist/core/extensions/builtin/index.js.map +1 -1
  42. package/dist/core/extensions/builtin/session-observer/index.d.ts +5 -0
  43. package/dist/core/extensions/builtin/session-observer/index.d.ts.map +1 -0
  44. package/dist/core/extensions/builtin/session-observer/index.js +36 -0
  45. package/dist/core/extensions/builtin/session-observer/index.js.map +1 -0
  46. package/dist/core/extensions/builtin/session-observer/loader.d.ts +3 -0
  47. package/dist/core/extensions/builtin/session-observer/loader.d.ts.map +1 -0
  48. package/dist/core/extensions/builtin/session-observer/loader.js +20 -0
  49. package/dist/core/extensions/builtin/session-observer/loader.js.map +1 -0
  50. package/dist/core/extensions/builtin/session-observer/overlay-format.d.ts +7 -0
  51. package/dist/core/extensions/builtin/session-observer/overlay-format.d.ts.map +1 -0
  52. package/dist/core/extensions/builtin/session-observer/overlay-format.js +30 -0
  53. package/dist/core/extensions/builtin/session-observer/overlay-format.js.map +1 -0
  54. package/dist/core/extensions/builtin/session-observer/overlay.d.ts +51 -0
  55. package/dist/core/extensions/builtin/session-observer/overlay.d.ts.map +1 -0
  56. package/dist/core/extensions/builtin/session-observer/overlay.js +234 -0
  57. package/dist/core/extensions/builtin/session-observer/overlay.js.map +1 -0
  58. package/dist/core/extensions/builtin/session-observer/scanner.d.ts +10 -0
  59. package/dist/core/extensions/builtin/session-observer/scanner.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/session-observer/scanner.js +142 -0
  61. package/dist/core/extensions/builtin/session-observer/scanner.js.map +1 -0
  62. package/dist/core/extensions/builtin/session-observer/text.d.ts +7 -0
  63. package/dist/core/extensions/builtin/session-observer/text.d.ts.map +1 -0
  64. package/dist/core/extensions/builtin/session-observer/text.js +37 -0
  65. package/dist/core/extensions/builtin/session-observer/text.js.map +1 -0
  66. package/dist/core/extensions/builtin/session-observer/transcript-entries.d.ts +7 -0
  67. package/dist/core/extensions/builtin/session-observer/transcript-entries.d.ts.map +1 -0
  68. package/dist/core/extensions/builtin/session-observer/transcript-entries.js +71 -0
  69. package/dist/core/extensions/builtin/session-observer/transcript-entries.js.map +1 -0
  70. package/dist/core/extensions/builtin/session-observer/transcript-format.d.ts +11 -0
  71. package/dist/core/extensions/builtin/session-observer/transcript-format.d.ts.map +1 -0
  72. package/dist/core/extensions/builtin/session-observer/transcript-format.js +65 -0
  73. package/dist/core/extensions/builtin/session-observer/transcript-format.js.map +1 -0
  74. package/dist/core/extensions/builtin/session-observer/transcript.d.ts +4 -0
  75. package/dist/core/extensions/builtin/session-observer/transcript.d.ts.map +1 -0
  76. package/dist/core/extensions/builtin/session-observer/transcript.js +81 -0
  77. package/dist/core/extensions/builtin/session-observer/transcript.js.map +1 -0
  78. package/dist/core/extensions/builtin/session-observer/types.d.ts +33 -0
  79. package/dist/core/extensions/builtin/session-observer/types.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/session-observer/types.js +2 -0
  81. package/dist/core/extensions/builtin/session-observer/types.js.map +1 -0
  82. package/dist/core/extensions/loader.d.ts.map +1 -1
  83. package/dist/core/extensions/loader.js +21 -9
  84. package/dist/core/extensions/loader.js.map +1 -1
  85. package/dist/core/extensions/runner.d.ts.map +1 -1
  86. package/dist/core/extensions/runner.js +1 -0
  87. package/dist/core/extensions/runner.js.map +1 -1
  88. package/dist/core/keybindings.d.ts +10 -0
  89. package/dist/core/keybindings.d.ts.map +1 -1
  90. package/dist/core/keybindings.js +3 -0
  91. package/dist/core/keybindings.js.map +1 -1
  92. package/dist/core/output-guard.d.ts +1 -0
  93. package/dist/core/output-guard.d.ts.map +1 -1
  94. package/dist/core/output-guard.js +52 -22
  95. package/dist/core/output-guard.js.map +1 -1
  96. package/dist/core/package-manager.d.ts.map +1 -1
  97. package/dist/core/package-manager.js +16 -4
  98. package/dist/core/package-manager.js.map +1 -1
  99. package/dist/core/session-work-barrier.d.ts +9 -0
  100. package/dist/core/session-work-barrier.d.ts.map +1 -0
  101. package/dist/core/session-work-barrier.js +50 -0
  102. package/dist/core/session-work-barrier.js.map +1 -0
  103. package/dist/main.d.ts.map +1 -1
  104. package/dist/main.js +0 -15
  105. package/dist/main.js.map +1 -1
  106. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/footer.js +74 -63
  108. package/dist/modes/interactive/components/footer.js.map +1 -1
  109. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/user-message.js +1 -1
  111. package/dist/modes/interactive/components/user-message.js.map +1 -1
  112. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  113. package/dist/modes/interactive/interactive-mode.js +24 -0
  114. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  115. package/dist/modes/rpc/rpc-client.d.ts +3 -0
  116. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  117. package/dist/modes/rpc/rpc-client.js +64 -7
  118. package/dist/modes/rpc/rpc-client.js.map +1 -1
  119. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  120. package/dist/modes/rpc/rpc-mode.js +15 -3
  121. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  122. package/dist/utils/paths.d.ts +1 -0
  123. package/dist/utils/paths.d.ts.map +1 -1
  124. package/dist/utils/paths.js +8 -0
  125. package/dist/utils/paths.js.map +1 -1
  126. package/docs/settings.md +3 -1
  127. package/docs/terminal-setup.md +6 -0
  128. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  129. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +18 -24
  130. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  131. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  132. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +249 -39
  133. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +349 -144
  135. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  137. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +54 -12
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  142. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +1 -1
  144. package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  146. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +46 -29
  147. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  148. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  149. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +1 -1
  150. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  151. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  152. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  154. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +2 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  156. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +5 -2
  157. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  159. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.d.ts.map +1 -1
  160. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js +2 -17
  161. package/node_modules/@earendil-works/pi-tui/dist/autocomplete.js.map +1 -1
  162. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  163. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +40 -55
  164. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  165. package/node_modules/@earendil-works/pi-tui/dist/components/input.d.ts.map +1 -1
  166. package/node_modules/@earendil-works/pi-tui/dist/components/input.js +2 -2
  167. package/node_modules/@earendil-works/pi-tui/dist/components/input.js.map +1 -1
  168. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +7 -1
  169. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
  170. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +12 -2
  171. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
  172. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -1
  173. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  174. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  175. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts +3 -0
  176. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.d.ts.map +1 -0
  177. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js +53 -0
  178. package/node_modules/@earendil-works/pi-tui/dist/native-modifiers.js.map +1 -0
  179. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts +3 -0
  180. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.d.ts.map +1 -0
  181. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js +38 -0
  182. package/node_modules/@earendil-works/pi-tui/dist/slash-command-autocomplete.js.map +1 -0
  183. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
  184. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -1
  185. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
  186. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +2 -0
  187. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  188. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +13 -1
  189. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  190. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +5 -1
  191. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  192. package/node_modules/@earendil-works/pi-tui/dist/utils.js +66 -14
  193. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  194. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  195. package/npm-shrinkwrap.json +13 -13
  196. package/package.json +6 -7
  197. package/dist/modes/neo-mode.d.ts +0 -43
  198. package/dist/modes/neo-mode.d.ts.map +0 -1
  199. package/dist/modes/neo-mode.js +0 -142
  200. package/dist/modes/neo-mode.js.map +0 -1
  201. package/dist/neo-tui-bin/senpi-neo-tui-linux-x64 +0 -0
@@ -1,43 +0,0 @@
1
- /**
2
- * `--neo` dispatch: spawn the native Rust + ratatui TUI binary and let it
3
- * own the terminal directly. The Node process waits for the child to exit
4
- * and propagates the status code.
5
- *
6
- * The binary is shipped alongside the npm package under
7
- * `dist/neo-tui-bin/senpi-neo-tui-<platform>-<arch>`. In dev (when running
8
- * from source), set `SENPI_NEO_TUI_DEV=1` to use
9
- * `../neo-tui/target/release/senpi-neo-tui` or `target/debug/senpi-neo-tui`.
10
- */
11
- import type { Args } from "../cli/args.ts";
12
- /**
13
- * Split the original argv between the senpi backend and the neo TUI
14
- * binary using the `--` sentinel. Anything BEFORE the sentinel (with
15
- * `--neo` filtered out) goes to the backend; anything AFTER goes to the
16
- * Rust TUI verbatim. Exported for unit tests; do not call directly from
17
- * production code, use {@link runNeoMode}.
18
- */
19
- export declare function splitNeoArgs(originalArgv: readonly string[]): {
20
- backend: string[];
21
- neo: string[];
22
- };
23
- export interface RunNeoModeOptions {
24
- parsed: Args;
25
- originalArgv: readonly string[];
26
- /**
27
- * Binary the Rust TUI spawns to talk to the senpi backend. In practice
28
- * this is `process.execPath` (Node) and `senpiScript` carries the path
29
- * to the senpi CLI script. Spawning Node directly avoids the
30
- * `senpi` shell-shim layer and works the same on every platform.
31
- */
32
- senpiBin: string;
33
- /** Absolute path to the senpi CLI JS entry, prepended to backend args. */
34
- senpiScript: string;
35
- }
36
- /**
37
- * Launch the Rust TUI binary with stdio inherited so it owns the TTY.
38
- * The Rust binary is expected to spawn `senpi --mode rpc` as its own child
39
- * for the agent backend (T6 wires that up; until then the binary renders
40
- * the demo state).
41
- */
42
- export declare function runNeoMode(options: RunNeoModeOptions): Promise<number>;
43
- //# sourceMappingURL=neo-mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"neo-mode.d.ts","sourceRoot":"","sources":["../../src/modes/neo-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA2D3C;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAMlG;AAED,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,IAAI,CAAC;IACb,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8D5E","sourcesContent":["/**\n * `--neo` dispatch: spawn the native Rust + ratatui TUI binary and let it\n * own the terminal directly. The Node process waits for the child to exit\n * and propagates the status code.\n *\n * The binary is shipped alongside the npm package under\n * `dist/neo-tui-bin/senpi-neo-tui-<platform>-<arch>`. In dev (when running\n * from source), set `SENPI_NEO_TUI_DEV=1` to use\n * `../neo-tui/target/release/senpi-neo-tui` or `target/debug/senpi-neo-tui`.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { constants as osConstants } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\n\nimport type { Args } from \"../cli/args.ts\";\n\nconst SCRIPT_DIR = fileURLToPath(new URL(\".\", import.meta.url));\n\ninterface PlatformInfo {\n\tplatform: string;\n\tarch: string;\n\texe: string;\n}\n\nfunction platformInfo(): PlatformInfo {\n\tconst platformMap: Record<string, string> = {\n\t\tdarwin: \"darwin\",\n\t\tlinux: \"linux\",\n\t\twin32: \"windows\",\n\t};\n\tconst archMap: Record<string, string> = {\n\t\tx64: \"x64\",\n\t\tarm64: \"arm64\",\n\t};\n\tconst platform = platformMap[process.platform] ?? process.platform;\n\tconst arch = archMap[process.arch] ?? process.arch;\n\tconst exe = process.platform === \"win32\" ? \".exe\" : \"\";\n\treturn { platform, arch, exe };\n}\n\nfunction resolveBinaryPath(): { path: string; source: string } | undefined {\n\tconst { platform, arch, exe } = platformInfo();\n\tconst fileName = `senpi-neo-tui-${platform}-${arch}${exe}`;\n\n\t// 1. Explicit override wins over everything else - that is what makes\n\t// it an override.\n\tconst override = process.env.SENPI_NEO_TUI_BIN;\n\tif (override && existsSync(override)) {\n\t\treturn { path: override, source: \"SENPI_NEO_TUI_BIN\" };\n\t}\n\n\t// 2. Production: alongside the dist/cli.js script.\n\tconst distPath = resolve(SCRIPT_DIR, \"..\", \"neo-tui-bin\", fileName);\n\tif (existsSync(distPath)) {\n\t\treturn { path: distPath, source: \"dist\" };\n\t}\n\n\t// 3. Dev: target/{release,debug}/senpi-neo-tui in the workspace tree.\n\tif (process.env.SENPI_NEO_TUI_DEV === \"1\") {\n\t\tconst repoRoot = resolve(SCRIPT_DIR, \"..\", \"..\", \"..\", \"..\");\n\t\tconst releasePath = resolve(repoRoot, \"target\", \"release\", `senpi-neo-tui${exe}`);\n\t\tif (existsSync(releasePath)) {\n\t\t\treturn { path: releasePath, source: \"target/release\" };\n\t\t}\n\t\tconst debugPath = resolve(repoRoot, \"target\", \"debug\", `senpi-neo-tui${exe}`);\n\t\tif (existsSync(debugPath)) {\n\t\t\treturn { path: debugPath, source: \"target/debug\" };\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Split the original argv between the senpi backend and the neo TUI\n * binary using the `--` sentinel. Anything BEFORE the sentinel (with\n * `--neo` filtered out) goes to the backend; anything AFTER goes to the\n * Rust TUI verbatim. Exported for unit tests; do not call directly from\n * production code, use {@link runNeoMode}.\n */\nexport function splitNeoArgs(originalArgv: readonly string[]): { backend: string[]; neo: string[] } {\n\tconst sentinelIdx = originalArgv.indexOf(\"--\");\n\tconst beforeSentinel = sentinelIdx >= 0 ? originalArgv.slice(0, sentinelIdx) : [...originalArgv];\n\tconst neo = sentinelIdx >= 0 ? originalArgv.slice(sentinelIdx + 1) : [];\n\tconst backend = beforeSentinel.filter((arg) => arg !== \"--neo\");\n\treturn { backend, neo };\n}\n\nexport interface RunNeoModeOptions {\n\tparsed: Args;\n\toriginalArgv: readonly string[];\n\t/**\n\t * Binary the Rust TUI spawns to talk to the senpi backend. In practice\n\t * this is `process.execPath` (Node) and `senpiScript` carries the path\n\t * to the senpi CLI script. Spawning Node directly avoids the\n\t * `senpi` shell-shim layer and works the same on every platform.\n\t */\n\tsenpiBin: string;\n\t/** Absolute path to the senpi CLI JS entry, prepended to backend args. */\n\tsenpiScript: string;\n}\n\n/**\n * Launch the Rust TUI binary with stdio inherited so it owns the TTY.\n * The Rust binary is expected to spawn `senpi --mode rpc` as its own child\n * for the agent backend (T6 wires that up; until then the binary renders\n * the demo state).\n */\nexport async function runNeoMode(options: RunNeoModeOptions): Promise<number> {\n\tconst located = resolveBinaryPath();\n\tif (!located) {\n\t\tconst { platform, arch, exe } = platformInfo();\n\t\tconsole.error(\n\t\t\tchalk.red(\n\t\t\t\t[\n\t\t\t\t\t\"Error: --neo TUI binary not found.\",\n\t\t\t\t\t`Expected: dist/neo-tui-bin/senpi-neo-tui-${platform}-${arch}${exe}`,\n\t\t\t\t\t\"For dev, build the crate (cargo build --release --package senpi-neo-tui)\",\n\t\t\t\t\t\"and re-run with SENPI_NEO_TUI_DEV=1, or set SENPI_NEO_TUI_BIN.\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t),\n\t\t);\n\t\treturn 1;\n\t}\n\n\tif (options.parsed.verbose) {\n\t\tconsole.log(chalk.dim(`neo-tui: launching ${located.path} (source: ${located.source})`));\n\t}\n\n\t// Flag forwarding contract:\n\t// senpi --neo [senpi-flags...] -- [neo-tui-flags...]\n\t// Everything BEFORE the `--` sentinel (minus `--neo`) is treated as\n\t// senpi-backend args and routed to `senpi --mode rpc` via env. Anything\n\t// AFTER the sentinel is passed verbatim to the `senpi-neo-tui` binary,\n\t// which has its own clap parser for `--theme`, `--list-themes`,\n\t// `--demo`, `--demo-seconds`, `--backend-bin`, `--backend-args`.\n\t// This lets users keep typing the senpi CLI flags they already know\n\t// while still being able to drive the neo TUI's own flags without a\n\t// naming collision (`--theme` means different things on each side).\n\tconst { backend, neo: neoArgs } = splitNeoArgs(options.originalArgv);\n\n\t// senpi runs as `node <senpiScript> <args>` so prepend the script to\n\t// the arg vector; `--mode rpc` switches the child into the JSONL RPC\n\t// server that the Rust TUI talks to.\n\tconst backendArgs = [options.senpiScript, ...backend, \"--mode\", \"rpc\"];\n\n\tconst env = {\n\t\t...process.env,\n\t\tSENPI_NEO_BACKEND_BIN: options.senpiBin,\n\t\tSENPI_NEO_BACKEND_ARGS: JSON.stringify(backendArgs),\n\t};\n\n\tconst child: ChildProcess = spawn(located.path, neoArgs, {\n\t\tstdio: \"inherit\",\n\t\tenv,\n\t});\n\n\treturn new Promise<number>((resolveExit) => {\n\t\tchild.on(\"exit\", (code, signal) => {\n\t\t\tif (signal) {\n\t\t\t\tresolveExit(128 + (signalNumber(signal) ?? 0));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolveExit(code ?? 0);\n\t\t});\n\t\tchild.on(\"error\", (err) => {\n\t\t\tconsole.error(chalk.red(`neo-tui: failed to launch ${located.path}: ${err.message}`));\n\t\t\tresolveExit(1);\n\t\t});\n\t});\n}\n\nfunction signalNumber(signal: NodeJS.Signals): number | undefined {\n\t// node ships a full POSIX signal -> number table in os.constants.signals\n\t// (SIGKILL=9, SIGSEGV=11, SIGUSR1=10, and so on). Reuse it instead of\n\t// maintaining a hand-rolled map that drops everything outside the\n\t// happy path.\n\tconst signals = osConstants.signals as Readonly<Record<string, number>>;\n\treturn signals[signal];\n}\n"]}
@@ -1,142 +0,0 @@
1
- /**
2
- * `--neo` dispatch: spawn the native Rust + ratatui TUI binary and let it
3
- * own the terminal directly. The Node process waits for the child to exit
4
- * and propagates the status code.
5
- *
6
- * The binary is shipped alongside the npm package under
7
- * `dist/neo-tui-bin/senpi-neo-tui-<platform>-<arch>`. In dev (when running
8
- * from source), set `SENPI_NEO_TUI_DEV=1` to use
9
- * `../neo-tui/target/release/senpi-neo-tui` or `target/debug/senpi-neo-tui`.
10
- */
11
- import { spawn } from "node:child_process";
12
- import { existsSync } from "node:fs";
13
- import { constants as osConstants } from "node:os";
14
- import { resolve } from "node:path";
15
- import { fileURLToPath } from "node:url";
16
- import chalk from "chalk";
17
- const SCRIPT_DIR = fileURLToPath(new URL(".", import.meta.url));
18
- function platformInfo() {
19
- const platformMap = {
20
- darwin: "darwin",
21
- linux: "linux",
22
- win32: "windows",
23
- };
24
- const archMap = {
25
- x64: "x64",
26
- arm64: "arm64",
27
- };
28
- const platform = platformMap[process.platform] ?? process.platform;
29
- const arch = archMap[process.arch] ?? process.arch;
30
- const exe = process.platform === "win32" ? ".exe" : "";
31
- return { platform, arch, exe };
32
- }
33
- function resolveBinaryPath() {
34
- const { platform, arch, exe } = platformInfo();
35
- const fileName = `senpi-neo-tui-${platform}-${arch}${exe}`;
36
- // 1. Explicit override wins over everything else - that is what makes
37
- // it an override.
38
- const override = process.env.SENPI_NEO_TUI_BIN;
39
- if (override && existsSync(override)) {
40
- return { path: override, source: "SENPI_NEO_TUI_BIN" };
41
- }
42
- // 2. Production: alongside the dist/cli.js script.
43
- const distPath = resolve(SCRIPT_DIR, "..", "neo-tui-bin", fileName);
44
- if (existsSync(distPath)) {
45
- return { path: distPath, source: "dist" };
46
- }
47
- // 3. Dev: target/{release,debug}/senpi-neo-tui in the workspace tree.
48
- if (process.env.SENPI_NEO_TUI_DEV === "1") {
49
- const repoRoot = resolve(SCRIPT_DIR, "..", "..", "..", "..");
50
- const releasePath = resolve(repoRoot, "target", "release", `senpi-neo-tui${exe}`);
51
- if (existsSync(releasePath)) {
52
- return { path: releasePath, source: "target/release" };
53
- }
54
- const debugPath = resolve(repoRoot, "target", "debug", `senpi-neo-tui${exe}`);
55
- if (existsSync(debugPath)) {
56
- return { path: debugPath, source: "target/debug" };
57
- }
58
- }
59
- return undefined;
60
- }
61
- /**
62
- * Split the original argv between the senpi backend and the neo TUI
63
- * binary using the `--` sentinel. Anything BEFORE the sentinel (with
64
- * `--neo` filtered out) goes to the backend; anything AFTER goes to the
65
- * Rust TUI verbatim. Exported for unit tests; do not call directly from
66
- * production code, use {@link runNeoMode}.
67
- */
68
- export function splitNeoArgs(originalArgv) {
69
- const sentinelIdx = originalArgv.indexOf("--");
70
- const beforeSentinel = sentinelIdx >= 0 ? originalArgv.slice(0, sentinelIdx) : [...originalArgv];
71
- const neo = sentinelIdx >= 0 ? originalArgv.slice(sentinelIdx + 1) : [];
72
- const backend = beforeSentinel.filter((arg) => arg !== "--neo");
73
- return { backend, neo };
74
- }
75
- /**
76
- * Launch the Rust TUI binary with stdio inherited so it owns the TTY.
77
- * The Rust binary is expected to spawn `senpi --mode rpc` as its own child
78
- * for the agent backend (T6 wires that up; until then the binary renders
79
- * the demo state).
80
- */
81
- export async function runNeoMode(options) {
82
- const located = resolveBinaryPath();
83
- if (!located) {
84
- const { platform, arch, exe } = platformInfo();
85
- console.error(chalk.red([
86
- "Error: --neo TUI binary not found.",
87
- `Expected: dist/neo-tui-bin/senpi-neo-tui-${platform}-${arch}${exe}`,
88
- "For dev, build the crate (cargo build --release --package senpi-neo-tui)",
89
- "and re-run with SENPI_NEO_TUI_DEV=1, or set SENPI_NEO_TUI_BIN.",
90
- ].join("\n")));
91
- return 1;
92
- }
93
- if (options.parsed.verbose) {
94
- console.log(chalk.dim(`neo-tui: launching ${located.path} (source: ${located.source})`));
95
- }
96
- // Flag forwarding contract:
97
- // senpi --neo [senpi-flags...] -- [neo-tui-flags...]
98
- // Everything BEFORE the `--` sentinel (minus `--neo`) is treated as
99
- // senpi-backend args and routed to `senpi --mode rpc` via env. Anything
100
- // AFTER the sentinel is passed verbatim to the `senpi-neo-tui` binary,
101
- // which has its own clap parser for `--theme`, `--list-themes`,
102
- // `--demo`, `--demo-seconds`, `--backend-bin`, `--backend-args`.
103
- // This lets users keep typing the senpi CLI flags they already know
104
- // while still being able to drive the neo TUI's own flags without a
105
- // naming collision (`--theme` means different things on each side).
106
- const { backend, neo: neoArgs } = splitNeoArgs(options.originalArgv);
107
- // senpi runs as `node <senpiScript> <args>` so prepend the script to
108
- // the arg vector; `--mode rpc` switches the child into the JSONL RPC
109
- // server that the Rust TUI talks to.
110
- const backendArgs = [options.senpiScript, ...backend, "--mode", "rpc"];
111
- const env = {
112
- ...process.env,
113
- SENPI_NEO_BACKEND_BIN: options.senpiBin,
114
- SENPI_NEO_BACKEND_ARGS: JSON.stringify(backendArgs),
115
- };
116
- const child = spawn(located.path, neoArgs, {
117
- stdio: "inherit",
118
- env,
119
- });
120
- return new Promise((resolveExit) => {
121
- child.on("exit", (code, signal) => {
122
- if (signal) {
123
- resolveExit(128 + (signalNumber(signal) ?? 0));
124
- return;
125
- }
126
- resolveExit(code ?? 0);
127
- });
128
- child.on("error", (err) => {
129
- console.error(chalk.red(`neo-tui: failed to launch ${located.path}: ${err.message}`));
130
- resolveExit(1);
131
- });
132
- });
133
- }
134
- function signalNumber(signal) {
135
- // node ships a full POSIX signal -> number table in os.constants.signals
136
- // (SIGKILL=9, SIGSEGV=11, SIGUSR1=10, and so on). Reuse it instead of
137
- // maintaining a hand-rolled map that drops everything outside the
138
- // happy path.
139
- const signals = osConstants.signals;
140
- return signals[signal];
141
- }
142
- //# sourceMappingURL=neo-mode.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"neo-mode.js","sourceRoot":"","sources":["../../src/modes/neo-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `--neo` dispatch: spawn the native Rust + ratatui TUI binary and let it\n * own the terminal directly. The Node process waits for the child to exit\n * and propagates the status code.\n *\n * The binary is shipped alongside the npm package under\n * `dist/neo-tui-bin/senpi-neo-tui-<platform>-<arch>`. In dev (when running\n * from source), set `SENPI_NEO_TUI_DEV=1` to use\n * `../neo-tui/target/release/senpi-neo-tui` or `target/debug/senpi-neo-tui`.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { constants as osConstants } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\n\nimport type { Args } from \"../cli/args.ts\";\n\nconst SCRIPT_DIR = fileURLToPath(new URL(\".\", import.meta.url));\n\ninterface PlatformInfo {\n\tplatform: string;\n\tarch: string;\n\texe: string;\n}\n\nfunction platformInfo(): PlatformInfo {\n\tconst platformMap: Record<string, string> = {\n\t\tdarwin: \"darwin\",\n\t\tlinux: \"linux\",\n\t\twin32: \"windows\",\n\t};\n\tconst archMap: Record<string, string> = {\n\t\tx64: \"x64\",\n\t\tarm64: \"arm64\",\n\t};\n\tconst platform = platformMap[process.platform] ?? process.platform;\n\tconst arch = archMap[process.arch] ?? process.arch;\n\tconst exe = process.platform === \"win32\" ? \".exe\" : \"\";\n\treturn { platform, arch, exe };\n}\n\nfunction resolveBinaryPath(): { path: string; source: string } | undefined {\n\tconst { platform, arch, exe } = platformInfo();\n\tconst fileName = `senpi-neo-tui-${platform}-${arch}${exe}`;\n\n\t// 1. Explicit override wins over everything else - that is what makes\n\t// it an override.\n\tconst override = process.env.SENPI_NEO_TUI_BIN;\n\tif (override && existsSync(override)) {\n\t\treturn { path: override, source: \"SENPI_NEO_TUI_BIN\" };\n\t}\n\n\t// 2. Production: alongside the dist/cli.js script.\n\tconst distPath = resolve(SCRIPT_DIR, \"..\", \"neo-tui-bin\", fileName);\n\tif (existsSync(distPath)) {\n\t\treturn { path: distPath, source: \"dist\" };\n\t}\n\n\t// 3. Dev: target/{release,debug}/senpi-neo-tui in the workspace tree.\n\tif (process.env.SENPI_NEO_TUI_DEV === \"1\") {\n\t\tconst repoRoot = resolve(SCRIPT_DIR, \"..\", \"..\", \"..\", \"..\");\n\t\tconst releasePath = resolve(repoRoot, \"target\", \"release\", `senpi-neo-tui${exe}`);\n\t\tif (existsSync(releasePath)) {\n\t\t\treturn { path: releasePath, source: \"target/release\" };\n\t\t}\n\t\tconst debugPath = resolve(repoRoot, \"target\", \"debug\", `senpi-neo-tui${exe}`);\n\t\tif (existsSync(debugPath)) {\n\t\t\treturn { path: debugPath, source: \"target/debug\" };\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Split the original argv between the senpi backend and the neo TUI\n * binary using the `--` sentinel. Anything BEFORE the sentinel (with\n * `--neo` filtered out) goes to the backend; anything AFTER goes to the\n * Rust TUI verbatim. Exported for unit tests; do not call directly from\n * production code, use {@link runNeoMode}.\n */\nexport function splitNeoArgs(originalArgv: readonly string[]): { backend: string[]; neo: string[] } {\n\tconst sentinelIdx = originalArgv.indexOf(\"--\");\n\tconst beforeSentinel = sentinelIdx >= 0 ? originalArgv.slice(0, sentinelIdx) : [...originalArgv];\n\tconst neo = sentinelIdx >= 0 ? originalArgv.slice(sentinelIdx + 1) : [];\n\tconst backend = beforeSentinel.filter((arg) => arg !== \"--neo\");\n\treturn { backend, neo };\n}\n\nexport interface RunNeoModeOptions {\n\tparsed: Args;\n\toriginalArgv: readonly string[];\n\t/**\n\t * Binary the Rust TUI spawns to talk to the senpi backend. In practice\n\t * this is `process.execPath` (Node) and `senpiScript` carries the path\n\t * to the senpi CLI script. Spawning Node directly avoids the\n\t * `senpi` shell-shim layer and works the same on every platform.\n\t */\n\tsenpiBin: string;\n\t/** Absolute path to the senpi CLI JS entry, prepended to backend args. */\n\tsenpiScript: string;\n}\n\n/**\n * Launch the Rust TUI binary with stdio inherited so it owns the TTY.\n * The Rust binary is expected to spawn `senpi --mode rpc` as its own child\n * for the agent backend (T6 wires that up; until then the binary renders\n * the demo state).\n */\nexport async function runNeoMode(options: RunNeoModeOptions): Promise<number> {\n\tconst located = resolveBinaryPath();\n\tif (!located) {\n\t\tconst { platform, arch, exe } = platformInfo();\n\t\tconsole.error(\n\t\t\tchalk.red(\n\t\t\t\t[\n\t\t\t\t\t\"Error: --neo TUI binary not found.\",\n\t\t\t\t\t`Expected: dist/neo-tui-bin/senpi-neo-tui-${platform}-${arch}${exe}`,\n\t\t\t\t\t\"For dev, build the crate (cargo build --release --package senpi-neo-tui)\",\n\t\t\t\t\t\"and re-run with SENPI_NEO_TUI_DEV=1, or set SENPI_NEO_TUI_BIN.\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t),\n\t\t);\n\t\treturn 1;\n\t}\n\n\tif (options.parsed.verbose) {\n\t\tconsole.log(chalk.dim(`neo-tui: launching ${located.path} (source: ${located.source})`));\n\t}\n\n\t// Flag forwarding contract:\n\t// senpi --neo [senpi-flags...] -- [neo-tui-flags...]\n\t// Everything BEFORE the `--` sentinel (minus `--neo`) is treated as\n\t// senpi-backend args and routed to `senpi --mode rpc` via env. Anything\n\t// AFTER the sentinel is passed verbatim to the `senpi-neo-tui` binary,\n\t// which has its own clap parser for `--theme`, `--list-themes`,\n\t// `--demo`, `--demo-seconds`, `--backend-bin`, `--backend-args`.\n\t// This lets users keep typing the senpi CLI flags they already know\n\t// while still being able to drive the neo TUI's own flags without a\n\t// naming collision (`--theme` means different things on each side).\n\tconst { backend, neo: neoArgs } = splitNeoArgs(options.originalArgv);\n\n\t// senpi runs as `node <senpiScript> <args>` so prepend the script to\n\t// the arg vector; `--mode rpc` switches the child into the JSONL RPC\n\t// server that the Rust TUI talks to.\n\tconst backendArgs = [options.senpiScript, ...backend, \"--mode\", \"rpc\"];\n\n\tconst env = {\n\t\t...process.env,\n\t\tSENPI_NEO_BACKEND_BIN: options.senpiBin,\n\t\tSENPI_NEO_BACKEND_ARGS: JSON.stringify(backendArgs),\n\t};\n\n\tconst child: ChildProcess = spawn(located.path, neoArgs, {\n\t\tstdio: \"inherit\",\n\t\tenv,\n\t});\n\n\treturn new Promise<number>((resolveExit) => {\n\t\tchild.on(\"exit\", (code, signal) => {\n\t\t\tif (signal) {\n\t\t\t\tresolveExit(128 + (signalNumber(signal) ?? 0));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolveExit(code ?? 0);\n\t\t});\n\t\tchild.on(\"error\", (err) => {\n\t\t\tconsole.error(chalk.red(`neo-tui: failed to launch ${located.path}: ${err.message}`));\n\t\t\tresolveExit(1);\n\t\t});\n\t});\n}\n\nfunction signalNumber(signal: NodeJS.Signals): number | undefined {\n\t// node ships a full POSIX signal -> number table in os.constants.signals\n\t// (SIGKILL=9, SIGSEGV=11, SIGUSR1=10, and so on). Reuse it instead of\n\t// maintaining a hand-rolled map that drops everything outside the\n\t// happy path.\n\tconst signals = osConstants.signals as Readonly<Record<string, number>>;\n\treturn signals[signal];\n}\n"]}