@phnx-labs/agents-cli 1.20.34 → 1.20.36

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 (291) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +185 -21
  3. package/README.md +13 -5
  4. package/dist/commands/audit.d.ts +14 -0
  5. package/dist/commands/audit.js +68 -0
  6. package/dist/commands/browser.js +82 -8
  7. package/dist/commands/check.d.ts +15 -0
  8. package/dist/commands/check.js +84 -0
  9. package/dist/commands/cloud.js +143 -3
  10. package/dist/commands/computer-actions.d.ts +15 -0
  11. package/dist/commands/computer-actions.js +120 -22
  12. package/dist/commands/computer.js +81 -0
  13. package/dist/commands/cost.js +17 -12
  14. package/dist/commands/daemon.js +4 -1
  15. package/dist/commands/doctor.d.ts +1 -0
  16. package/dist/commands/doctor.js +54 -97
  17. package/dist/commands/events.d.ts +16 -0
  18. package/dist/commands/events.js +173 -0
  19. package/dist/commands/exec.d.ts +19 -0
  20. package/dist/commands/exec.js +283 -12
  21. package/dist/commands/hosts.js +37 -29
  22. package/dist/commands/inspect.d.ts +2 -0
  23. package/dist/commands/inspect.js +62 -16
  24. package/dist/commands/lock.d.ts +12 -0
  25. package/dist/commands/lock.js +70 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +139 -0
  28. package/dist/commands/message.d.ts +15 -0
  29. package/dist/commands/message.js +56 -0
  30. package/dist/commands/models.d.ts +3 -0
  31. package/dist/commands/models.js +27 -8
  32. package/dist/commands/permissions.js +9 -2
  33. package/dist/commands/repo.d.ts +34 -0
  34. package/dist/commands/repo.js +243 -65
  35. package/dist/commands/resource-view.d.ts +20 -0
  36. package/dist/commands/resource-view.js +90 -28
  37. package/dist/commands/routines.js +101 -5
  38. package/dist/commands/secrets-migrate.js +106 -57
  39. package/dist/commands/secrets.d.ts +31 -18
  40. package/dist/commands/secrets.js +196 -91
  41. package/dist/commands/serve.d.ts +10 -0
  42. package/dist/commands/serve.js +37 -0
  43. package/dist/commands/sessions-inject.d.ts +14 -0
  44. package/dist/commands/sessions-inject.js +111 -0
  45. package/dist/commands/sessions-picker.d.ts +2 -0
  46. package/dist/commands/sessions-picker.js +24 -3
  47. package/dist/commands/sessions-resume.d.ts +2 -0
  48. package/dist/commands/sessions-resume.js +221 -0
  49. package/dist/commands/sessions-tail.d.ts +10 -0
  50. package/dist/commands/sessions-tail.js +11 -0
  51. package/dist/commands/sessions.d.ts +143 -1
  52. package/dist/commands/sessions.js +628 -80
  53. package/dist/commands/setup.js +4 -2
  54. package/dist/commands/sync.d.ts +3 -1
  55. package/dist/commands/sync.js +156 -4
  56. package/dist/commands/teams.js +217 -0
  57. package/dist/commands/versions.js +2 -4
  58. package/dist/commands/view.d.ts +3 -0
  59. package/dist/commands/view.js +19 -8
  60. package/dist/commands/wallet.d.ts +6 -0
  61. package/dist/commands/wallet.js +22 -5
  62. package/dist/commands/watchdog.d.ts +18 -0
  63. package/dist/commands/watchdog.js +238 -0
  64. package/dist/index.js +82 -32
  65. package/dist/lib/agents.js +30 -9
  66. package/dist/lib/audit/log.d.ts +92 -0
  67. package/dist/lib/audit/log.js +177 -0
  68. package/dist/lib/auto-pull.js +2 -1
  69. package/dist/lib/browser/cdp.js +5 -1
  70. package/dist/lib/browser/chrome.d.ts +10 -0
  71. package/dist/lib/browser/chrome.js +18 -7
  72. package/dist/lib/browser/drivers/ssh.d.ts +8 -0
  73. package/dist/lib/browser/drivers/ssh.js +77 -16
  74. package/dist/lib/browser/har.d.ts +84 -0
  75. package/dist/lib/browser/har.js +77 -0
  76. package/dist/lib/browser/ipc.d.ts +3 -0
  77. package/dist/lib/browser/ipc.js +37 -12
  78. package/dist/lib/browser/profiles.d.ts +1 -1
  79. package/dist/lib/browser/profiles.js +8 -10
  80. package/dist/lib/browser/refs.d.ts +65 -0
  81. package/dist/lib/browser/refs.js +73 -1
  82. package/dist/lib/browser/runtime-state.js +1 -0
  83. package/dist/lib/browser/service.d.ts +38 -2
  84. package/dist/lib/browser/service.js +183 -23
  85. package/dist/lib/browser/types.d.ts +14 -1
  86. package/dist/lib/budget/live-cloud.d.ts +42 -0
  87. package/dist/lib/budget/live-cloud.js +79 -0
  88. package/dist/lib/budget/live-team.d.ts +31 -0
  89. package/dist/lib/budget/live-team.js +115 -0
  90. package/dist/lib/cloud/codex.js +4 -0
  91. package/dist/lib/cloud/rush.d.ts +12 -1
  92. package/dist/lib/cloud/rush.js +13 -3
  93. package/dist/lib/cloud/types.d.ts +9 -0
  94. package/dist/lib/computer/dispatch.d.ts +8 -0
  95. package/dist/lib/computer/dispatch.js +125 -0
  96. package/dist/lib/computer/loop.d.ts +62 -0
  97. package/dist/lib/computer/loop.js +98 -0
  98. package/dist/lib/computer/model.d.ts +44 -0
  99. package/dist/lib/computer/model.js +157 -0
  100. package/dist/lib/concurrency.d.ts +19 -0
  101. package/dist/lib/concurrency.js +33 -0
  102. package/dist/lib/daemon.d.ts +92 -0
  103. package/dist/lib/daemon.js +222 -18
  104. package/dist/lib/devices/connect.d.ts +3 -2
  105. package/dist/lib/devices/connect.js +5 -3
  106. package/dist/lib/devices/registry.d.ts +7 -0
  107. package/dist/lib/devices/registry.js +24 -0
  108. package/dist/lib/devices/tailscale.js +1 -1
  109. package/dist/lib/drift.d.ts +52 -0
  110. package/dist/lib/drift.js +112 -0
  111. package/dist/lib/events.d.ts +10 -3
  112. package/dist/lib/events.js +101 -24
  113. package/dist/lib/exec.d.ts +17 -0
  114. package/dist/lib/exec.js +100 -12
  115. package/dist/lib/git.d.ts +27 -0
  116. package/dist/lib/git.js +56 -1
  117. package/dist/lib/hooks/cache.d.ts +6 -0
  118. package/dist/lib/hooks/cache.js +54 -12
  119. package/dist/lib/hooks.d.ts +27 -0
  120. package/dist/lib/hooks.js +135 -10
  121. package/dist/lib/hosts/dispatch.d.ts +15 -0
  122. package/dist/lib/hosts/dispatch.js +45 -13
  123. package/dist/lib/hosts/logs.d.ts +16 -0
  124. package/dist/lib/hosts/logs.js +74 -0
  125. package/dist/lib/hosts/option.js +1 -1
  126. package/dist/lib/hosts/passthrough.js +3 -1
  127. package/dist/lib/hosts/progress.d.ts +66 -0
  128. package/dist/lib/hosts/progress.js +125 -17
  129. package/dist/lib/hosts/ready.d.ts +51 -7
  130. package/dist/lib/hosts/ready.js +98 -25
  131. package/dist/lib/hosts/reconcile.d.ts +53 -0
  132. package/dist/lib/hosts/reconcile.js +81 -0
  133. package/dist/lib/hosts/registry.d.ts +19 -2
  134. package/dist/lib/hosts/registry.js +58 -2
  135. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  136. package/dist/lib/hosts/remote-cmd.js +70 -1
  137. package/dist/lib/hosts/remote-os.d.ts +17 -0
  138. package/dist/lib/hosts/remote-os.js +30 -0
  139. package/dist/lib/hosts/session-index.d.ts +34 -0
  140. package/dist/lib/hosts/session-index.js +56 -0
  141. package/dist/lib/hosts/tasks.d.ts +22 -0
  142. package/dist/lib/hosts/tasks.js +29 -0
  143. package/dist/lib/lock.d.ts +93 -0
  144. package/dist/lib/lock.js +207 -0
  145. package/dist/lib/loop.js +16 -1
  146. package/dist/lib/machine-id.d.ts +21 -0
  147. package/dist/lib/machine-id.js +26 -0
  148. package/dist/lib/mailbox-target.d.ts +36 -0
  149. package/dist/lib/mailbox-target.js +45 -0
  150. package/dist/lib/mailbox.d.ts +47 -0
  151. package/dist/lib/mailbox.js +194 -0
  152. package/dist/lib/mcp.d.ts +5 -0
  153. package/dist/lib/mcp.js +24 -8
  154. package/dist/lib/migrate.d.ts +19 -0
  155. package/dist/lib/migrate.js +134 -26
  156. package/dist/lib/overdue.js +3 -0
  157. package/dist/lib/permissions.d.ts +4 -0
  158. package/dist/lib/permissions.js +35 -0
  159. package/dist/lib/picker.d.ts +26 -0
  160. package/dist/lib/picker.js +127 -0
  161. package/dist/lib/platform/exec.d.ts +46 -0
  162. package/dist/lib/platform/exec.js +74 -0
  163. package/dist/lib/platform/process.d.ts +31 -0
  164. package/dist/lib/platform/process.js +34 -1
  165. package/dist/lib/platform/winpath.js +2 -0
  166. package/dist/lib/plugin-marketplace.d.ts +30 -0
  167. package/dist/lib/plugin-marketplace.js +215 -2
  168. package/dist/lib/plugins.d.ts +5 -0
  169. package/dist/lib/plugins.js +61 -10
  170. package/dist/lib/profiles.d.ts +25 -0
  171. package/dist/lib/profiles.js +22 -6
  172. package/dist/lib/pty-client.js +2 -1
  173. package/dist/lib/rotate.d.ts +61 -0
  174. package/dist/lib/rotate.js +52 -0
  175. package/dist/lib/routines.d.ts +40 -2
  176. package/dist/lib/routines.js +66 -8
  177. package/dist/lib/runner.d.ts +11 -2
  178. package/dist/lib/runner.js +49 -7
  179. package/dist/lib/scheduler.js +6 -1
  180. package/dist/lib/secrets/bundles.d.ts +60 -4
  181. package/dist/lib/secrets/bundles.js +131 -12
  182. package/dist/lib/secrets/filestore.d.ts +3 -0
  183. package/dist/lib/secrets/filestore.js +42 -16
  184. package/dist/lib/secrets/index.d.ts +43 -2
  185. package/dist/lib/secrets/index.js +102 -3
  186. package/dist/lib/secrets/mcp.d.ts +93 -0
  187. package/dist/lib/secrets/mcp.js +205 -0
  188. package/dist/lib/secrets/remote.js +12 -5
  189. package/dist/lib/secrets/sync.js +83 -4
  190. package/dist/lib/secrets/windows.js +14 -3
  191. package/dist/lib/serve/data.d.ts +81 -0
  192. package/dist/lib/serve/data.js +91 -0
  193. package/dist/lib/serve/page.d.ts +7 -0
  194. package/dist/lib/serve/page.js +140 -0
  195. package/dist/lib/serve/server.d.ts +46 -0
  196. package/dist/lib/serve/server.js +115 -0
  197. package/dist/lib/session/active.d.ts +61 -0
  198. package/dist/lib/session/active.js +196 -17
  199. package/dist/lib/session/discover.d.ts +37 -0
  200. package/dist/lib/session/discover.js +204 -27
  201. package/dist/lib/session/inject.d.ts +18 -0
  202. package/dist/lib/session/inject.js +21 -0
  203. package/dist/lib/session/parse.d.ts +6 -0
  204. package/dist/lib/session/parse.js +286 -2
  205. package/dist/lib/session/pid-registry.d.ts +22 -0
  206. package/dist/lib/session/pid-registry.js +104 -0
  207. package/dist/lib/session/provenance.d.ts +14 -2
  208. package/dist/lib/session/provenance.js +39 -8
  209. package/dist/lib/session/remote-active.d.ts +26 -0
  210. package/dist/lib/session/remote-active.js +153 -0
  211. package/dist/lib/session/remote-list.d.ts +51 -0
  212. package/dist/lib/session/remote-list.js +213 -0
  213. package/dist/lib/session/remote.d.ts +7 -1
  214. package/dist/lib/session/remote.js +18 -8
  215. package/dist/lib/session/render.js +1 -1
  216. package/dist/lib/session/sync/config.d.ts +1 -7
  217. package/dist/lib/session/sync/config.js +4 -11
  218. package/dist/lib/session/types.d.ts +18 -1
  219. package/dist/lib/session/types.js +1 -1
  220. package/dist/lib/shims.d.ts +44 -6
  221. package/dist/lib/shims.js +143 -57
  222. package/dist/lib/ssh-exec.d.ts +26 -3
  223. package/dist/lib/ssh-exec.js +47 -3
  224. package/dist/lib/ssh-tunnel.d.ts +24 -5
  225. package/dist/lib/ssh-tunnel.js +62 -63
  226. package/dist/lib/staleness/writers/hooks.js +1 -1
  227. package/dist/lib/startup/command-registry.d.ts +8 -0
  228. package/dist/lib/startup/command-registry.js +17 -1
  229. package/dist/lib/state.d.ts +13 -0
  230. package/dist/lib/state.js +103 -9
  231. package/dist/lib/sync-umbrella.d.ts +14 -7
  232. package/dist/lib/sync-umbrella.js +17 -9
  233. package/dist/lib/teams/forEach.d.ts +110 -0
  234. package/dist/lib/teams/forEach.js +186 -0
  235. package/dist/lib/teams/index.d.ts +1 -0
  236. package/dist/lib/teams/index.js +1 -0
  237. package/dist/lib/teams/pr-watch.d.ts +226 -0
  238. package/dist/lib/teams/pr-watch.js +371 -0
  239. package/dist/lib/teams/registry.js +25 -9
  240. package/dist/lib/teams/supervisor.d.ts +14 -1
  241. package/dist/lib/teams/supervisor.js +19 -0
  242. package/dist/lib/teams/worktree.d.ts +9 -0
  243. package/dist/lib/teams/worktree.js +32 -0
  244. package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
  245. package/dist/lib/terminal/backends/ghostty.js +69 -0
  246. package/dist/lib/terminal/backends/index.d.ts +19 -0
  247. package/dist/lib/terminal/backends/index.js +31 -0
  248. package/dist/lib/terminal/backends/iterm.d.ts +6 -0
  249. package/dist/lib/terminal/backends/iterm.js +62 -0
  250. package/dist/lib/terminal/backends/tmux.d.ts +14 -0
  251. package/dist/lib/terminal/backends/tmux.js +23 -0
  252. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  253. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  254. package/dist/lib/terminal/engine.d.ts +39 -0
  255. package/dist/lib/terminal/engine.js +54 -0
  256. package/dist/lib/terminal/index.d.ts +17 -0
  257. package/dist/lib/terminal/index.js +8 -0
  258. package/dist/lib/terminal/inject.d.ts +204 -0
  259. package/dist/lib/terminal/inject.js +247 -0
  260. package/dist/lib/terminal/policy.d.ts +11 -0
  261. package/dist/lib/terminal/policy.js +11 -0
  262. package/dist/lib/terminal/quote.d.ts +11 -0
  263. package/dist/lib/terminal/quote.js +13 -0
  264. package/dist/lib/terminal/resolve.d.ts +64 -0
  265. package/dist/lib/terminal/resolve.js +90 -0
  266. package/dist/lib/terminal/shell.d.ts +6 -0
  267. package/dist/lib/terminal/shell.js +23 -0
  268. package/dist/lib/terminal/transport.d.ts +15 -0
  269. package/dist/lib/terminal/transport.js +40 -0
  270. package/dist/lib/terminal/types.d.ts +59 -0
  271. package/dist/lib/terminal/types.js +13 -0
  272. package/dist/lib/triggers/webhook.d.ts +85 -0
  273. package/dist/lib/triggers/webhook.js +141 -0
  274. package/dist/lib/types.d.ts +7 -0
  275. package/dist/lib/versions.d.ts +30 -3
  276. package/dist/lib/versions.js +129 -18
  277. package/dist/lib/watchdog/index.d.ts +3 -0
  278. package/dist/lib/watchdog/index.js +5 -0
  279. package/dist/lib/watchdog/read.d.ts +35 -0
  280. package/dist/lib/watchdog/read.js +149 -0
  281. package/dist/lib/watchdog/runner.d.ts +127 -0
  282. package/dist/lib/watchdog/runner.js +322 -0
  283. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  284. package/dist/lib/watchdog/watchdog.js +166 -0
  285. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  286. package/dist/lib/watchdog/watchdogTail.js +154 -0
  287. package/dist/lib/whats-new.d.ts +9 -0
  288. package/dist/lib/whats-new.js +35 -0
  289. package/dist/lib/workflows.d.ts +166 -0
  290. package/dist/lib/workflows.js +193 -0
  291. package/package.json +5 -4
@@ -0,0 +1,31 @@
1
+ import { itermBackend } from './iterm.js';
2
+ import { ghosttyBackend } from './ghostty.js';
3
+ import { tmuxBackend } from './tmux.js';
4
+ import { vscodiumAgentBackend } from './vscodium-agent.js';
5
+ /** All known interactive backends, keyed by id. */
6
+ export const BACKENDS = {
7
+ iterm: itermBackend,
8
+ ghostty: ghosttyBackend,
9
+ tmux: tmuxBackend,
10
+ 'vscodium-agent': vscodiumAgentBackend,
11
+ };
12
+ /**
13
+ * The backend for the terminal the CLI is currently running in, or null if we
14
+ * can't open a surface into it. tmux wins (via `$TMUX`) because a tmux pane can
15
+ * live inside iTerm/Ghostty; otherwise fall back to `TERM_PROGRAM`.
16
+ */
17
+ export function detectCurrentBackend(ctx) {
18
+ if (ctx.env.TMUX)
19
+ return 'tmux';
20
+ const term = (ctx.env.TERM_PROGRAM || '').toLowerCase();
21
+ if (term.includes('iterm'))
22
+ return 'iterm';
23
+ if (term.includes('ghostty'))
24
+ return 'ghostty';
25
+ return null;
26
+ }
27
+ /** Backends that can actually be driven in this context. */
28
+ export function availableBackends(ctx) {
29
+ return Object.values(BACKENDS).filter((b) => b.isAvailable(ctx));
30
+ }
31
+ export { itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend };
@@ -0,0 +1,6 @@
1
+ import type { TerminalBackend, SplitDirection } from '../types.js';
2
+ /** AppleScript that opens an iTerm tab (a window if none is open) running the command. */
3
+ export declare function itermTabScript(cwd: string, command: string[]): string;
4
+ /** AppleScript that splits the current iTerm session (a window if none is open). */
5
+ export declare function itermSplitScript(cwd: string, command: string[], direction: SplitDirection): string;
6
+ export declare const itermBackend: TerminalBackend;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * iTerm backend — drives iTerm2 via AppleScript (`osascript`).
3
+ *
4
+ * Tab: creates a tab in the current window (or a window when none is open).
5
+ * Split: splits the current session — `split vertically` (side-by-side, a
6
+ * vertical divider) for `right`, `split horizontally` (stacked) for `down`.
7
+ */
8
+ import * as fs from 'fs';
9
+ import { appleScriptStr } from '../quote.js';
10
+ import { loginExec, iLoginShell } from '../shell.js';
11
+ const ITERM_APP = '/Applications/iTerm.app';
12
+ function appExists(p) {
13
+ try {
14
+ return fs.existsSync(p);
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ /** AppleScript that opens an iTerm tab (a window if none is open) running the command. */
21
+ export function itermTabScript(cwd, command) {
22
+ const cmd = appleScriptStr(iLoginShell(loginExec(cwd, command)));
23
+ return [
24
+ 'tell application "iTerm2"',
25
+ ' activate',
26
+ ' if (count of windows) is 0 then',
27
+ ` create window with default profile command ${cmd}`,
28
+ ' else',
29
+ ` tell current window to create tab with default profile command ${cmd}`,
30
+ ' end if',
31
+ 'end tell',
32
+ ].join('\n');
33
+ }
34
+ /** AppleScript that splits the current iTerm session (a window if none is open). */
35
+ export function itermSplitScript(cwd, command, direction) {
36
+ const cmd = appleScriptStr(iLoginShell(loginExec(cwd, command)));
37
+ // iTerm: "split vertically" = a vertical divider = panes side by side (right).
38
+ const verb = direction === 'right' ? 'split vertically' : 'split horizontally';
39
+ return [
40
+ 'tell application "iTerm2"',
41
+ ' activate',
42
+ ' if (count of windows) is 0 then',
43
+ ` create window with default profile command ${cmd}`,
44
+ ' else',
45
+ ` tell current session of current window to ${verb} with default profile command ${cmd}`,
46
+ ' end if',
47
+ 'end tell',
48
+ ].join('\n');
49
+ }
50
+ export const itermBackend = {
51
+ id: 'iterm',
52
+ label: 'iTerm',
53
+ isAvailable(ctx) {
54
+ return ctx.platform === 'darwin' && appExists(ITERM_APP);
55
+ },
56
+ buildTab(cwd, command) {
57
+ return { argv: ['osascript', '-e', itermTabScript(cwd, command)] };
58
+ },
59
+ buildSplit(cwd, command, direction) {
60
+ return { argv: ['osascript', '-e', itermSplitScript(cwd, command, direction)] };
61
+ },
62
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * tmux backend — opens windows and splits in the running tmux server.
3
+ *
4
+ * cwd is passed natively via `-c`, so the command is wrapped in `zsh -ilc` with
5
+ * no `cd`. `split-window -h` = side-by-side (right); `-v` = stacked (down).
6
+ * Available only when the CLI is running inside tmux (`$TMUX` set) — otherwise
7
+ * there is no current window/pane to attach to.
8
+ */
9
+ import type { TerminalBackend, SplitDirection } from '../types.js';
10
+ /** argv that opens a new tmux window running the command in cwd. */
11
+ export declare function tmuxTabArgv(cwd: string, command: string[]): string[];
12
+ /** argv that splits the current tmux pane, running the command in cwd. */
13
+ export declare function tmuxSplitArgv(cwd: string, command: string[], direction: SplitDirection): string[];
14
+ export declare const tmuxBackend: TerminalBackend;
@@ -0,0 +1,23 @@
1
+ import { execOnly, iLoginShell } from '../shell.js';
2
+ /** argv that opens a new tmux window running the command in cwd. */
3
+ export function tmuxTabArgv(cwd, command) {
4
+ return ['tmux', 'new-window', '-c', cwd, iLoginShell(execOnly(command))];
5
+ }
6
+ /** argv that splits the current tmux pane, running the command in cwd. */
7
+ export function tmuxSplitArgv(cwd, command, direction) {
8
+ const flag = direction === 'right' ? '-h' : '-v';
9
+ return ['tmux', 'split-window', flag, '-c', cwd, iLoginShell(execOnly(command))];
10
+ }
11
+ export const tmuxBackend = {
12
+ id: 'tmux',
13
+ label: 'tmux',
14
+ isAvailable(ctx) {
15
+ return Boolean(ctx.env.TMUX);
16
+ },
17
+ buildTab(cwd, command) {
18
+ return { argv: tmuxTabArgv(cwd, command) };
19
+ },
20
+ buildSplit(cwd, command, direction) {
21
+ return { argv: tmuxSplitArgv(cwd, command, direction) };
22
+ },
23
+ };
@@ -0,0 +1,31 @@
1
+ import type { TerminalBackend, SplitDirection } from '../types.js';
2
+ /** An editor flavour that speaks the swarm-ext URI protocol. */
3
+ export interface EditorVariant {
4
+ /** CLI on PATH, invoked with `--open-url`. */
5
+ cli: string;
6
+ /** URL scheme the product registers (must match the CLI's product). */
7
+ scheme: string;
8
+ /** macOS app bundle, for local availability detection. */
9
+ app: string;
10
+ label: string;
11
+ }
12
+ /**
13
+ * Known editors, in preference order. VSCodium first — the backend is named for
14
+ * it and is the user's editor; Cursor and VS Code also ship the extension and
15
+ * are wired here for `makeVscodiumAgentBackend` / future registration.
16
+ */
17
+ export declare const EDITOR_VARIANTS: EditorVariant[];
18
+ /**
19
+ * The `<scheme>://swarmify.swarm-ext/spawn?p=<payload>` URL the extension handles.
20
+ *
21
+ * The payload is base64url-encoded JSON in a single `p` param — NOT one param per
22
+ * field. VS Code percent-*decodes* `uri.query` once before the extension parses
23
+ * it, so a `command`/`cwd` containing `&` (or `=`) would be mis-split by a naive
24
+ * multi-param query. base64url is `[A-Za-z0-9_-]` only (no `&`, `=`, `%`, `+`,
25
+ * `/`), so it survives that decode untouched and round-trips exactly.
26
+ */
27
+ export declare function spawnUri(scheme: string, cwd: string, command: string[], direction?: SplitDirection): string;
28
+ /** Build a backend bound to one editor variant. */
29
+ export declare function makeVscodiumAgentBackend(variant: EditorVariant): TerminalBackend;
30
+ /** Default backend: VSCodium (`codium` CLI, `vscodium://` scheme). */
31
+ export declare const vscodiumAgentBackend: TerminalBackend;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * VSCodium agent-terminal backend — opens an agent terminal in a VSCodium /
3
+ * Cursor / VS Code window via the swarmify `swarm-ext` extension's URI handler.
4
+ *
5
+ * Unlike iTerm/Ghostty (GUI terminal apps driven by AppleScript), the editor is
6
+ * already running; we hand it a `<scheme>://swarmify.swarm-ext/spawn?…` URL via
7
+ * the editor CLI's `--open-url` flag. The extension opens an editor-tab terminal
8
+ * in `cwd`, runs `command`, and its shell-adoption promotes a resume command
9
+ * (e.g. `claude --resume <id>`) to the matching agent chip. Driving the editor
10
+ * CLI (not macOS `open`) means this works over `--host` SSH and on Linux, needs
11
+ * no OS URL-scheme handler registration, and sends the command into an
12
+ * already-interactive login shell — so no `zsh -ilc` wrap (the other backends'
13
+ * wrapper) is applied here.
14
+ */
15
+ import * as fs from 'fs';
16
+ /** The swarmify extension identifier that owns the `/spawn` URI verb. */
17
+ const EXTENSION_AUTHORITY = 'swarmify.swarm-ext';
18
+ /**
19
+ * Known editors, in preference order. VSCodium first — the backend is named for
20
+ * it and is the user's editor; Cursor and VS Code also ship the extension and
21
+ * are wired here for `makeVscodiumAgentBackend` / future registration.
22
+ */
23
+ export const EDITOR_VARIANTS = [
24
+ { cli: 'codium', scheme: 'vscodium', app: '/Applications/VSCodium.app', label: 'VSCodium' },
25
+ { cli: 'cursor', scheme: 'cursor', app: '/Applications/Cursor.app', label: 'Cursor' },
26
+ { cli: 'code', scheme: 'vscode', app: '/Applications/Visual Studio Code.app', label: 'VS Code' },
27
+ ];
28
+ function appExists(p) {
29
+ try {
30
+ return fs.existsSync(p);
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ /**
37
+ * The `<scheme>://swarmify.swarm-ext/spawn?p=<payload>` URL the extension handles.
38
+ *
39
+ * The payload is base64url-encoded JSON in a single `p` param — NOT one param per
40
+ * field. VS Code percent-*decodes* `uri.query` once before the extension parses
41
+ * it, so a `command`/`cwd` containing `&` (or `=`) would be mis-split by a naive
42
+ * multi-param query. base64url is `[A-Za-z0-9_-]` only (no `&`, `=`, `%`, `+`,
43
+ * `/`), so it survives that decode untouched and round-trips exactly.
44
+ */
45
+ export function spawnUri(scheme, cwd, command, direction) {
46
+ const payload = {
47
+ command: command.join(' '),
48
+ cwd,
49
+ };
50
+ if (direction)
51
+ payload.split = direction;
52
+ const p = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
53
+ return `${scheme}://${EXTENSION_AUTHORITY}/spawn?p=${p}`;
54
+ }
55
+ /** Build a backend bound to one editor variant. */
56
+ export function makeVscodiumAgentBackend(variant) {
57
+ return {
58
+ id: 'vscodium-agent',
59
+ label: `${variant.label} agent`,
60
+ isAvailable(ctx) {
61
+ return ctx.platform === 'darwin' && appExists(variant.app);
62
+ },
63
+ buildTab(cwd, command) {
64
+ return { argv: [variant.cli, '--open-url', spawnUri(variant.scheme, cwd, command)] };
65
+ },
66
+ buildSplit(cwd, command, direction) {
67
+ return { argv: [variant.cli, '--open-url', spawnUri(variant.scheme, cwd, command, direction)] };
68
+ },
69
+ };
70
+ }
71
+ /** Default backend: VSCodium (`codium` CLI, `vscodium://` scheme). */
72
+ export const vscodiumAgentBackend = makeVscodiumAgentBackend(EDITOR_VARIANTS[0]);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The engine — turns requests into open surfaces.
3
+ *
4
+ * `specForRequest` and `buildRequests` are pure (planning); `openSurface` and
5
+ * `openSurfaces` add the side-effecting transport. A batch runs sequentially and
6
+ * staggered so a `split-right` lands in the tab that was just opened (the split
7
+ * targets the front pane).
8
+ */
9
+ import type { Backend, EngineContext, LaunchRequest, LaunchResult, LaunchSpec } from './types.js';
10
+ import { type Packing } from './policy.js';
11
+ import { type HostResolver } from './transport.js';
12
+ /** The concrete launch command for a request (pure — no side effects). */
13
+ export declare function specForRequest(req: LaunchRequest): LaunchSpec;
14
+ export interface OpenOptions {
15
+ resolveHost?: HostResolver;
16
+ ctx?: EngineContext;
17
+ }
18
+ /** Open a single surface for one request. Never throws — failures come back in the result. */
19
+ export declare function openSurface(req: LaunchRequest, opts?: OpenOptions): Promise<LaunchResult>;
20
+ /** One command to run as a surface. */
21
+ export interface SurfaceItem {
22
+ cwd: string;
23
+ command: string[];
24
+ }
25
+ export interface BuildRequestsOptions {
26
+ backend: Backend;
27
+ host?: string;
28
+ packing?: Packing;
29
+ }
30
+ /** Turn a list of commands into layout-assigned requests (pure — the planning step). */
31
+ export declare function buildRequests(items: SurfaceItem[], opts: BuildRequestsOptions): LaunchRequest[];
32
+ export interface OpenManyOptions extends OpenOptions, BuildRequestsOptions {
33
+ staggerMs?: number;
34
+ }
35
+ /**
36
+ * Open many surfaces, applying the layout policy (default: two-per-tab).
37
+ * Sequential + staggered so each split follows the tab it splits.
38
+ */
39
+ export declare function openSurfaces(items: SurfaceItem[], opts: OpenManyOptions): Promise<LaunchResult[]>;
@@ -0,0 +1,54 @@
1
+ import { BACKENDS } from './backends/index.js';
2
+ import { planLayouts } from './policy.js';
3
+ import { runSpec } from './transport.js';
4
+ const DEFAULT_STAGGER_MS = 400;
5
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6
+ /** The concrete launch command for a request (pure — no side effects). */
7
+ export function specForRequest(req) {
8
+ const backend = BACKENDS[req.backend];
9
+ if (!backend)
10
+ throw new Error(`unknown backend: ${req.backend}`);
11
+ if (req.layout === 'tab')
12
+ return backend.buildTab(req.cwd, req.command);
13
+ return backend.buildSplit(req.cwd, req.command, req.layout === 'split-down' ? 'down' : 'right');
14
+ }
15
+ /** Open a single surface for one request. Never throws — failures come back in the result. */
16
+ export async function openSurface(req, opts = {}) {
17
+ try {
18
+ // Both can throw: specForRequest on an unknown backend, runSpec when the
19
+ // SSH transport rejects an invalid --host target. Keep them inside the
20
+ // catch so a bad request degrades to a per-surface failure, never a throw.
21
+ const spec = specForRequest(req);
22
+ const res = await runSpec(spec, req.host, opts.resolveHost);
23
+ return { ok: res.ok, request: req, error: res.error };
24
+ }
25
+ catch (err) {
26
+ return { ok: false, request: req, error: err?.message ?? String(err) };
27
+ }
28
+ }
29
+ /** Turn a list of commands into layout-assigned requests (pure — the planning step). */
30
+ export function buildRequests(items, opts) {
31
+ const layouts = planLayouts(items.length, opts.packing ?? 'two-per-tab');
32
+ return items.map((item, i) => ({
33
+ backend: opts.backend,
34
+ layout: layouts[i],
35
+ cwd: item.cwd,
36
+ command: item.command,
37
+ host: opts.host,
38
+ }));
39
+ }
40
+ /**
41
+ * Open many surfaces, applying the layout policy (default: two-per-tab).
42
+ * Sequential + staggered so each split follows the tab it splits.
43
+ */
44
+ export async function openSurfaces(items, opts) {
45
+ const requests = buildRequests(items, opts);
46
+ const stagger = opts.staggerMs ?? DEFAULT_STAGGER_MS;
47
+ const results = [];
48
+ for (let i = 0; i < requests.length; i++) {
49
+ results.push(await openSurface(requests[i], opts));
50
+ if (i < requests.length - 1)
51
+ await sleep(stagger);
52
+ }
53
+ return results;
54
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Terminal launch engine — open an interactive command as a tab or split pane
3
+ * in iTerm / Ghostty / tmux, on this machine or a remote host.
4
+ *
5
+ * Public entry point. Callers typically use `openSurfaces` (a batch with a
6
+ * layout policy) or `openSurface` (a single request), and `availableBackends` /
7
+ * `detectCurrentBackend` to pick a target. See docs/terminal-engine.md.
8
+ */
9
+ export type { Backend, SplitDirection, Layout, EngineContext, LaunchSpec, LaunchRequest, LaunchResult, TerminalBackend, } from './types.js';
10
+ export { currentContext } from './types.js';
11
+ export { BACKENDS, detectCurrentBackend, availableBackends, itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend } from './backends/index.js';
12
+ export { makeVscodiumAgentBackend, spawnUri, EDITOR_VARIANTS, type EditorVariant } from './backends/vscodium-agent.js';
13
+ export { planLayouts, type Packing } from './policy.js';
14
+ export { specForRequest, buildRequests, openSurface, openSurfaces, type OpenOptions, type OpenManyOptions, type BuildRequestsOptions, type SurfaceItem, } from './engine.js';
15
+ export { runLocal, runRemote, runSpec, remoteCommand, type HostResolver, type RunResult } from './transport.js';
16
+ export { injectIntoTerminal, tmuxSendKeysArgv, tmuxInjectSpecs, itermInjectScript, ghosttyInjectScript, appleScriptInjectSpec, vscodiumInjectUri, vscodiumInjectSpec, type InjectTarget, type InjectBackend, type InjectOptions, type InjectResult, } from './inject.js';
17
+ export { resolveInjectTarget, resolveInjectTargetForSession, type InjectResolution, type InjectRail, type ResolveOptions, } from './resolve.js';
@@ -0,0 +1,8 @@
1
+ export { currentContext } from './types.js';
2
+ export { BACKENDS, detectCurrentBackend, availableBackends, itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend } from './backends/index.js';
3
+ export { makeVscodiumAgentBackend, spawnUri, EDITOR_VARIANTS } from './backends/vscodium-agent.js';
4
+ export { planLayouts } from './policy.js';
5
+ export { specForRequest, buildRequests, openSurface, openSurfaces, } from './engine.js';
6
+ export { runLocal, runRemote, runSpec, remoteCommand } from './transport.js';
7
+ export { injectIntoTerminal, tmuxSendKeysArgv, tmuxInjectSpecs, itermInjectScript, ghosttyInjectScript, appleScriptInjectSpec, vscodiumInjectUri, vscodiumInjectSpec, } from './inject.js';
8
+ export { resolveInjectTarget, resolveInjectTargetForSession, } from './resolve.js';
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Terminal injection — Gap 2 of the Terminal Engine.
3
+ *
4
+ * Where the rest of the engine OPENS a surface (a tab / split running a
5
+ * command), injection types into an ALREADY-running surface. It's the primitive
6
+ * a native watchdog (RUSH-1415) needs to nudge a stalled agent with "continue"
7
+ * delivered into the exact terminal that agent lives in — the gap flagged in
8
+ * src/lib/session/provenance.ts:20:
9
+ *
10
+ * > Actually delivering the keystrokes is Gap 2 (pty/tmux send-keys).
11
+ *
12
+ * It mirrors the engine's shape exactly: pure per-backend spec builders (like
13
+ * `tmuxTabArgv` / `itermTabScript`) produce a `LaunchSpec` (argv), and the same
14
+ * `runSpec` transport runs it — so injection inherits the engine's local/remote
15
+ * (`--host` over SSH) execution for free. A `LaunchSpec` never opens anything;
16
+ * building one is side-effect-free and unit-testable without a display.
17
+ *
18
+ * Backends (each addresses the EXACT split, not the frontmost surface):
19
+ * - tmux → `tmux send-keys -t <pane>` — the send-keys primitive addressed by
20
+ * pane id + socket (the exact rail provenance.ts identifies).
21
+ * - iterm (macOS) → AppleScript `tell session id "<uuid>" to write text` —
22
+ * addresses the precise iTerm2 split by its session UUID WITHOUT
23
+ * `activate`, so it never steals focus or types into the wrong split.
24
+ * Uses the engine's `appleScriptStr` escaper; guarded by the iterm
25
+ * backend's `isAvailable` (platform + app installed).
26
+ * - vscodium (VSCodium / Cursor / VS Code) → the editor CLI's `--open-url`
27
+ * into the swarmify `swarm-ext` extension's `/inject` verb, targeting
28
+ * a live-terminals.json terminal by id. Focus-independent, exact
29
+ * terminal, and (like the launch backend, src/lib/terminal/backends/
30
+ * vscodium-agent.ts) works over `--host` SSH and on Linux.
31
+ * - ghostty (macOS) → COARSE only: Ghostty has no scripting dictionary, so
32
+ * there is no per-split addressing — this raises a window and types
33
+ * via System Events keystrokes, stealing focus. The resolver refuses
34
+ * to route here by default (see resolve.ts); it stays behind an
35
+ * explicit opt-in.
36
+ * - pty → the `agents pty write` sidecar path (ptyRequest). Local-only —
37
+ * the sidecar is not an engine transport surface.
38
+ *
39
+ * Ink-TUI Enter semantics: Claude's Ink TUI swallows an Enter fused to the text,
40
+ * so the default path delivers the text and the Enter as TWO SEPARATE writes
41
+ * (swarmify's `sendText(text,false)` then `sendText('\r',false)`). `combined`
42
+ * opts into one fused write for plain shells / REPLs.
43
+ */
44
+ import { type HostResolver } from './transport.js';
45
+ import { type LaunchSpec, type EngineContext } from './types.js';
46
+ /**
47
+ * An already-running surface to type into. A superset of the engine's launch
48
+ * `Backend` — it adds `pty` (the sidecar), which the engine can't launch but can
49
+ * be injected into.
50
+ */
51
+ export type InjectTarget = {
52
+ backend: 'tmux';
53
+ pane: string;
54
+ socket?: string;
55
+ } | {
56
+ backend: 'iterm';
57
+ session?: string;
58
+ }
59
+ /**
60
+ * A VSCodium / Cursor / VS Code integrated terminal, addressed by the id the
61
+ * swarm-ext extension keys `live-terminals.json` on (the session UUID). `cli`
62
+ * is the editor CLI on PATH (`codium` / `cursor` / `code`); `scheme` is its
63
+ * URL scheme (`vscodium` / `cursor` / `vscode`) — the resolver fills both.
64
+ */
65
+ | {
66
+ backend: 'vscodium';
67
+ terminalId: string;
68
+ cli: string;
69
+ scheme: string;
70
+ } | {
71
+ backend: 'ghostty';
72
+ window?: string;
73
+ } | {
74
+ backend: 'pty';
75
+ id: string;
76
+ };
77
+ export type InjectBackend = InjectTarget['backend'];
78
+ export interface InjectOptions {
79
+ /** Append Enter after the text. Default true. */
80
+ enter?: boolean;
81
+ /**
82
+ * Fuse the text and its Enter into a SINGLE write. Default false — the safe
83
+ * default sends the text, then the Enter, as two separate writes (Ink-TUI
84
+ * safe). Set true for plain shells / REPLs that want one atomic line.
85
+ */
86
+ combined?: boolean;
87
+ /** tmux socket override (defaults to `target.socket`, then tmux's default socket). */
88
+ socket?: string;
89
+ /** Remote host for the tmux / AppleScript backends (runs the spec over SSH). Local by default. */
90
+ host?: string;
91
+ /** Resolve a host alias to an ssh target (see the engine's transport). */
92
+ resolveHost?: HostResolver;
93
+ /** Ambient context for the availability check (defaults to the live process context). */
94
+ ctx?: EngineContext;
95
+ /** Don't execute — return the spec(s) that WOULD run. Lets the macOS paths be asserted on Linux. */
96
+ dryRun?: boolean;
97
+ }
98
+ export interface InjectResult {
99
+ ok: boolean;
100
+ backend: InjectBackend;
101
+ /** Discrete writes delivered: 2 for the Ink-safe text+Enter split, 1 when combined or enter=false. */
102
+ writes: number;
103
+ /** For the tmux / AppleScript backends (or any dryRun), the spec(s) that ran / would run. */
104
+ specs?: LaunchSpec[];
105
+ error?: string;
106
+ }
107
+ /**
108
+ * argv for `tmux send-keys` targeting a pane by id. Starts with `tmux` (like the
109
+ * engine's `tmuxTabArgv`) so the same transport runs it locally or over SSH. The
110
+ * socket, when set, is positioned before the subcommand (`-S` must lead). `-l`
111
+ * sends the keys literally; without it tmux interprets a key name like `Enter`.
112
+ *
113
+ * NOTE: distinct from `sendKeys()` in src/lib/tmux/session.ts — that one
114
+ * addresses by session *name* on the default socket (the `agents tmux` surface)
115
+ * and shells out directly; this one addresses by *pane id* + arbitrary socket and
116
+ * returns an SSH-capable `LaunchSpec` for the engine transport. Don't collapse them.
117
+ */
118
+ export declare function tmuxSendKeysArgv(pane: string, keys: string, opts?: {
119
+ literal?: boolean;
120
+ socket?: string;
121
+ }): string[];
122
+ /** The one-or-two send-keys specs for a tmux injection (Ink-safe split by default). */
123
+ export declare function tmuxInjectSpecs(target: Extract<InjectTarget, {
124
+ backend: 'tmux';
125
+ }>, text: string, o: {
126
+ enter: boolean;
127
+ combined: boolean;
128
+ socket?: string;
129
+ }): LaunchSpec[];
130
+ /**
131
+ * AppleScript that types into a SPECIFIC iTerm2 split via `tell session id
132
+ * "<uuid>" to write text` — the exact split provenance's iterm rail identifies,
133
+ * addressed by its session UUID. Crucially there is NO `activate`: `write text`
134
+ * delivers to that session directly, so injection never brings iTerm forward or
135
+ * types into whatever split happens to be focused. Omitting `session` targets
136
+ * the current session (a direct-call convenience; the resolver always supplies
137
+ * one).
138
+ *
139
+ * Enter semantics — iTerm's `write text` appends a trailing newline, which fuses
140
+ * text+Enter into ONE write and Claude's Ink TUI swallows it. So the Ink-safe
141
+ * default (enter, not combined) suppresses that newline (`write text … newline
142
+ * no`) and sends the Return as a SEPARATE `write text` of a lone CR (`character
143
+ * id 13`) — two distinct pty writes, Enter seen on its own. `combined` opts into
144
+ * the single fused `write text` (auto-newline) for plain shells / REPLs.
145
+ *
146
+ * NOTE (macOS-only verification pending): the two-write CR path is the safe
147
+ * choice because it mirrors the tmux/pty Ink-safe split that IS verified here on
148
+ * Linux; whether a single `write text T` would also submit under Ink can only be
149
+ * confirmed on a Mac running iTerm (see the PR body).
150
+ */
151
+ export declare function itermInjectScript(text: string, opts: {
152
+ session?: string;
153
+ enter: boolean;
154
+ combined?: boolean;
155
+ }): string;
156
+ /**
157
+ * AppleScript for Ghostty (no scripting dictionary): raise the target window via
158
+ * System Events, then keystroke the text and a separate Return. `window` matches
159
+ * a window whose title contains the string; omitted → the frontmost Ghostty window.
160
+ */
161
+ export declare function ghosttyInjectScript(text: string, opts: {
162
+ window?: string;
163
+ enter: boolean;
164
+ }): string;
165
+ /** The osascript spec for a macOS injection. One invocation delivers the text + Return. */
166
+ export declare function appleScriptInjectSpec(target: Extract<InjectTarget, {
167
+ backend: 'iterm' | 'ghostty';
168
+ }>, text: string, enter: boolean, combined?: boolean): LaunchSpec;
169
+ /**
170
+ * The `<scheme>://swarmify.swarm-ext/inject?p=<payload>` URL the extension
171
+ * handles to type into an ALREADY-open integrated terminal (the inject sibling
172
+ * of the launch backend's `/spawn`, src/lib/terminal/backends/vscodium-agent.ts).
173
+ *
174
+ * Payload is base64url-encoded JSON in a single `p` param — identical encoding
175
+ * to `spawnUri`, and for the same reason: VS Code percent-decodes `uri.query`
176
+ * once before the extension parses it, so a naive multi-param query would
177
+ * mis-split a text containing `&`/`=`; base64url (`[A-Za-z0-9_-]`) survives that
178
+ * decode untouched. `terminalId` is the id the extension keys live-terminals.json
179
+ * on (the session UUID); `enter` tells the extension to submit; `combined` asks
180
+ * it to fuse text+Enter into one write (default is the Ink-safe two-write split,
181
+ * mirroring the tmux/iterm paths — the extension owns that split on its side).
182
+ *
183
+ * DEPENDENCY: the `/inject` verb ships in the swarm-ext extension via PR #608's
184
+ * successor (PR #608 introduces `/spawn`; the inject verb + `write`/`enter`
185
+ * handling is its follow-up). Until that lands, this routes correctly on the CLI
186
+ * side but the extension will no-op the unknown verb. See the PR body.
187
+ */
188
+ export declare function vscodiumInjectUri(scheme: string, terminalId: string, text: string, opts: {
189
+ enter: boolean;
190
+ combined: boolean;
191
+ }): string;
192
+ /** The editor-CLI spec for a VSCodium/Cursor/VS Code injection (one `--open-url` invocation). */
193
+ export declare function vscodiumInjectSpec(target: Extract<InjectTarget, {
194
+ backend: 'vscodium';
195
+ }>, text: string, opts: {
196
+ enter: boolean;
197
+ combined: boolean;
198
+ }): LaunchSpec;
199
+ /**
200
+ * Deliver `text` (+ Enter) into an existing terminal surface. Resolves the
201
+ * target's backend and routes to the matching primitive. Never throws — launch
202
+ * failures come back in the result, like the engine's `openSurface`.
203
+ */
204
+ export declare function injectIntoTerminal(target: InjectTarget, text: string, opts?: InjectOptions): Promise<InjectResult>;