@synap-core/cli 1.13.0 → 1.13.1-next.20261102.246dc73

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 (227) hide show
  1. package/README.md +13 -3
  2. package/dist/build-info.json +2 -3
  3. package/dist/commands/agents.js +12 -2
  4. package/dist/commands/agents.js.map +1 -1
  5. package/dist/commands/capability.d.ts +86 -0
  6. package/dist/commands/capability.js +117 -30
  7. package/dist/commands/capability.js.map +1 -1
  8. package/dist/commands/context.js +2 -1
  9. package/dist/commands/context.js.map +1 -1
  10. package/dist/commands/data-extra.d.ts +13 -0
  11. package/dist/commands/data-extra.js +23 -2
  12. package/dist/commands/data-extra.js.map +1 -1
  13. package/dist/commands/doctor.d.ts +18 -0
  14. package/dist/commands/doctor.js +28 -1
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/init.js +4 -2
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/keys-request.d.ts +48 -0
  19. package/dist/commands/keys-request.js +236 -0
  20. package/dist/commands/keys-request.js.map +1 -0
  21. package/dist/commands/keys-request.test.d.ts +1 -0
  22. package/dist/commands/keys-request.test.js +176 -0
  23. package/dist/commands/keys-request.test.js.map +1 -0
  24. package/dist/commands/knowledge.d.ts +42 -5
  25. package/dist/commands/knowledge.js +90 -27
  26. package/dist/commands/knowledge.js.map +1 -1
  27. package/dist/commands/market.d.ts +89 -31
  28. package/dist/commands/market.js +215 -222
  29. package/dist/commands/market.js.map +1 -1
  30. package/dist/commands/mcp.d.ts +1 -1
  31. package/dist/commands/mcp.js +14 -9
  32. package/dist/commands/mcp.js.map +1 -1
  33. package/dist/commands/pods.js +6 -2
  34. package/dist/commands/pods.js.map +1 -1
  35. package/dist/commands/proposals-actions.js +7 -1
  36. package/dist/commands/proposals-actions.js.map +1 -1
  37. package/dist/commands/providers.d.ts +22 -6
  38. package/dist/commands/providers.js +39 -32
  39. package/dist/commands/providers.js.map +1 -1
  40. package/dist/commands/raycast.d.ts +15 -0
  41. package/dist/commands/raycast.js +36 -10
  42. package/dist/commands/raycast.js.map +1 -1
  43. package/dist/commands/status.js +12 -6
  44. package/dist/commands/status.js.map +1 -1
  45. package/dist/commands/statusline.backoff.test.js +13 -3
  46. package/dist/commands/statusline.backoff.test.js.map +1 -1
  47. package/dist/commands/statusline.d.ts +104 -9
  48. package/dist/commands/statusline.js +392 -148
  49. package/dist/commands/statusline.js.map +1 -1
  50. package/dist/commands/statusline.line2.test.d.ts +15 -0
  51. package/dist/commands/statusline.line2.test.js +226 -0
  52. package/dist/commands/statusline.line2.test.js.map +1 -0
  53. package/dist/commands/statusline.needs-you.test.d.ts +1 -0
  54. package/dist/commands/statusline.needs-you.test.js +17 -0
  55. package/dist/commands/statusline.needs-you.test.js.map +1 -0
  56. package/dist/commands/statusline.now.test.d.ts +10 -0
  57. package/dist/commands/statusline.now.test.js +250 -0
  58. package/dist/commands/statusline.now.test.js.map +1 -0
  59. package/dist/commands/tracks.d.ts +1 -0
  60. package/dist/commands/tracks.js +4 -0
  61. package/dist/commands/tracks.js.map +1 -1
  62. package/dist/commands/upload.js +12 -0
  63. package/dist/commands/upload.js.map +1 -1
  64. package/dist/commands/wait.d.ts +124 -0
  65. package/dist/commands/wait.js +384 -0
  66. package/dist/commands/wait.js.map +1 -0
  67. package/dist/commands/wait.test.d.ts +8 -0
  68. package/dist/commands/wait.test.js +308 -0
  69. package/dist/commands/wait.test.js.map +1 -0
  70. package/dist/commands/worker-github-app.d.ts +63 -0
  71. package/dist/commands/worker-github-app.js +161 -0
  72. package/dist/commands/worker-github-app.js.map +1 -0
  73. package/dist/commands/worker-github-app.test.d.ts +1 -0
  74. package/dist/commands/worker-github-app.test.js +102 -0
  75. package/dist/commands/worker-github-app.test.js.map +1 -0
  76. package/dist/commands/worker-github-broker.d.ts +39 -0
  77. package/dist/commands/worker-github-broker.js +106 -0
  78. package/dist/commands/worker-github-broker.js.map +1 -0
  79. package/dist/commands/worker-github-broker.test.d.ts +1 -0
  80. package/dist/commands/worker-github-broker.test.js +90 -0
  81. package/dist/commands/worker-github-broker.test.js.map +1 -0
  82. package/dist/commands/worker-runs.test.d.ts +1 -0
  83. package/dist/commands/worker-runs.test.js +150 -0
  84. package/dist/commands/worker-runs.test.js.map +1 -0
  85. package/dist/commands/worker-worktrees.d.ts +34 -0
  86. package/dist/commands/worker-worktrees.js +87 -0
  87. package/dist/commands/worker-worktrees.js.map +1 -0
  88. package/dist/commands/worker.d.ts +192 -5
  89. package/dist/commands/worker.js +467 -45
  90. package/dist/commands/worker.js.map +1 -1
  91. package/dist/commands/worker.test.js +269 -3
  92. package/dist/commands/worker.test.js.map +1 -1
  93. package/dist/index.js +59 -4
  94. package/dist/index.js.map +1 -1
  95. package/dist/lib/agents-config.d.ts +9 -0
  96. package/dist/lib/agents-config.js +20 -0
  97. package/dist/lib/agents-config.js.map +1 -1
  98. package/dist/lib/atomic-file.d.ts +20 -1
  99. package/dist/lib/atomic-file.js +100 -2
  100. package/dist/lib/atomic-file.js.map +1 -1
  101. package/dist/lib/auth.d.ts +25 -0
  102. package/dist/lib/auth.js +49 -9
  103. package/dist/lib/auth.js.map +1 -1
  104. package/dist/lib/box/job-server.js +4 -0
  105. package/dist/lib/box/job-server.js.map +1 -1
  106. package/dist/lib/bridge-invocation.test.d.ts +1 -0
  107. package/dist/lib/bridge-invocation.test.js +75 -0
  108. package/dist/lib/bridge-invocation.test.js.map +1 -0
  109. package/dist/lib/bridge-status.d.ts +144 -0
  110. package/dist/lib/bridge-status.js +227 -0
  111. package/dist/lib/bridge-status.js.map +1 -0
  112. package/dist/lib/bridge-status.test.d.ts +6 -0
  113. package/dist/lib/bridge-status.test.js +197 -0
  114. package/dist/lib/bridge-status.test.js.map +1 -0
  115. package/dist/lib/client-header.test.js +19 -1
  116. package/dist/lib/client-header.test.js.map +1 -1
  117. package/dist/lib/conversation-headers.d.ts +67 -2
  118. package/dist/lib/conversation-headers.js +122 -6
  119. package/dist/lib/conversation-headers.js.map +1 -1
  120. package/dist/lib/conversation-headers.test.d.ts +1 -0
  121. package/dist/lib/conversation-headers.test.js +78 -0
  122. package/dist/lib/conversation-headers.test.js.map +1 -0
  123. package/dist/lib/credential-class.d.ts +12 -1
  124. package/dist/lib/credential-class.js +20 -0
  125. package/dist/lib/credential-class.js.map +1 -1
  126. package/dist/lib/device-connect.d.ts +45 -0
  127. package/dist/lib/device-connect.js +60 -0
  128. package/dist/lib/device-connect.js.map +1 -1
  129. package/dist/lib/goal-words.js +6 -3
  130. package/dist/lib/goal-words.js.map +1 -1
  131. package/dist/lib/hub-client.d.ts +59 -1
  132. package/dist/lib/hub-client.js +130 -36
  133. package/dist/lib/hub-client.js.map +1 -1
  134. package/dist/lib/key-locations.d.ts +3 -3
  135. package/dist/lib/key-locations.js +3 -39
  136. package/dist/lib/key-locations.js.map +1 -1
  137. package/dist/lib/machine-instance.d.ts +7 -5
  138. package/dist/lib/machine-instance.js +7 -5
  139. package/dist/lib/machine-instance.js.map +1 -1
  140. package/dist/lib/mcp-bridge.d.ts +43 -0
  141. package/dist/lib/mcp-bridge.js +199 -11
  142. package/dist/lib/mcp-bridge.js.map +1 -1
  143. package/dist/lib/mcp-bridge.test.js +194 -2
  144. package/dist/lib/mcp-bridge.test.js.map +1 -1
  145. package/dist/lib/mcp-call.d.ts +5 -1
  146. package/dist/lib/mcp-call.js +7 -6
  147. package/dist/lib/mcp-call.js.map +1 -1
  148. package/dist/lib/mcp-connector-url.test.d.ts +1 -0
  149. package/dist/lib/mcp-connector-url.test.js +13 -0
  150. package/dist/lib/mcp-connector-url.test.js.map +1 -0
  151. package/dist/lib/mcp-snippets.d.ts +16 -0
  152. package/dist/lib/mcp-snippets.js +20 -2
  153. package/dist/lib/mcp-snippets.js.map +1 -1
  154. package/dist/lib/now-line.d.ts +66 -0
  155. package/dist/lib/now-line.js +193 -0
  156. package/dist/lib/now-line.js.map +1 -0
  157. package/dist/lib/now-line.test.d.ts +5 -0
  158. package/dist/lib/now-line.test.js +177 -0
  159. package/dist/lib/now-line.test.js.map +1 -0
  160. package/dist/lib/package-updates.d.ts +66 -0
  161. package/dist/lib/package-updates.js +76 -0
  162. package/dist/lib/package-updates.js.map +1 -0
  163. package/dist/lib/pod.d.ts +0 -4
  164. package/dist/lib/pod.js +0 -7
  165. package/dist/lib/pod.js.map +1 -1
  166. package/dist/lib/register-claude-code-mcp.test.js +19 -0
  167. package/dist/lib/register-claude-code-mcp.test.js.map +1 -1
  168. package/dist/lib/safe-open.d.ts +5 -3
  169. package/dist/lib/safe-open.js +9 -8
  170. package/dist/lib/safe-open.js.map +1 -1
  171. package/dist/lib/self-host.d.ts +17 -0
  172. package/dist/lib/self-host.js +24 -0
  173. package/dist/lib/self-host.js.map +1 -0
  174. package/dist/lib/targets.d.ts +31 -0
  175. package/dist/lib/targets.js +100 -104
  176. package/dist/lib/targets.js.map +1 -1
  177. package/dist/lib/template-lifecycle.d.ts +0 -31
  178. package/dist/lib/template-lifecycle.js +0 -74
  179. package/dist/lib/template-lifecycle.js.map +1 -1
  180. package/dist/lib/term.d.ts +24 -0
  181. package/dist/lib/term.js +52 -0
  182. package/dist/lib/term.js.map +1 -0
  183. package/dist/lib/term.test.d.ts +1 -0
  184. package/dist/lib/term.test.js +29 -0
  185. package/dist/lib/term.test.js.map +1 -0
  186. package/dist/lib/transcript-index.d.ts +94 -0
  187. package/dist/lib/transcript-index.js +321 -0
  188. package/dist/lib/transcript-index.js.map +1 -0
  189. package/dist/lib/transcript-index.test.d.ts +10 -0
  190. package/dist/lib/transcript-index.test.js +200 -0
  191. package/dist/lib/transcript-index.test.js.map +1 -0
  192. package/dist/lib/wait-help.d.ts +11 -0
  193. package/dist/lib/wait-help.js +25 -0
  194. package/dist/lib/wait-help.js.map +1 -0
  195. package/dist/lib/wait-state.d.ts +57 -0
  196. package/dist/lib/wait-state.js +114 -0
  197. package/dist/lib/wait-state.js.map +1 -0
  198. package/package.json +2 -2
  199. package/skills/synap/SKILL.md +81 -44
  200. package/skills/synap/code-skills.md +15 -1
  201. package/skills/synap/concepts.md +36 -26
  202. package/skills/synap/document-embeds.md +15 -1
  203. package/skills/synap/focus-sessions.md +4 -4
  204. package/skills/synap/from-intent.md +2 -2
  205. package/skills/synap/governance-rules.md +2 -0
  206. package/skills/synap/hand-off.md +10 -6
  207. package/skills/synap/reflexes.md +4 -4
  208. package/skills/synap/work-model.md +1 -1
  209. package/skills/synap/writing-titles.md +7 -0
  210. package/skills/synap-app/SKILL.md +2 -2
  211. package/skills/synap-app/overview.md +2 -2
  212. package/skills/synap-market/SKILL.md +1 -1
  213. package/skills/synap-market/governance-and-catalog.md +1 -1
  214. package/skills/synap-schema/SKILL.md +4 -1
  215. package/skills/synap-schema/intent-vocabulary-ssot.md +4 -1
  216. package/dist/commands/template.d.ts +0 -42
  217. package/dist/commands/template.js +0 -191
  218. package/dist/commands/template.js.map +0 -1
  219. package/dist/lib/browser-auth.d.ts +0 -35
  220. package/dist/lib/browser-auth.js +0 -170
  221. package/dist/lib/browser-auth.js.map +0 -1
  222. package/dist/lib/openclaw.d.ts +0 -32
  223. package/dist/lib/openclaw.js +0 -165
  224. package/dist/lib/openclaw.js.map +0 -1
  225. package/dist/lib/seed.d.ts +0 -13
  226. package/dist/lib/seed.js +0 -135
  227. package/dist/lib/seed.js.map +0 -1
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The status line's 4th line: what this conversation is doing NOW, and what it
3
+ * is waiting on. Built only from local files (Claude Code's process registry,
4
+ * the transcript index, `synap wait` state files): no network, ever.
5
+ *
6
+ * ✋ needs you · permission prompt the registry says it waits for the person
7
+ * ▶ working · 4m · ⏳ 2 · … a turn is running (open waits follow)
8
+ * ⏳ waiting on 2 · devbox w7 running · … idle, with background work still open
9
+ * ■ stopped · nothing running · 12m idle with nothing open: the cue to prompt
10
+ *
11
+ * `buildNowLine` is pure (time, liveness and width come in as arguments) so
12
+ * every state is testable from fixtures.
13
+ */
14
+ import fs from "node:fs";
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+ import { isFinal, stateAndDetail, waitName } from "./wait-state.js";
18
+ import { AMBER as A, BOLD_YELLOW as Y, CYAN as C, DIM as D, GREEN as G, RED as R, RESET as X, safeText } from "./term.js";
19
+ /** A passed wait stays on the line this long, then goes. */
20
+ export const PASSED_VISIBLE_MS = 10 * 60_000;
21
+ /** A failed / infra / error / timeout / died wait stays this long. */
22
+ export const FAILED_VISIBLE_MS = 30 * 60_000;
23
+ /** A wait whose state has not changed for this long is shown as stale. */
24
+ export const STALE_AFTER_MS = 30 * 60_000;
25
+ export const DEFAULT_WIDTH = 120;
26
+ /**
27
+ * The registry file (`~/.claude/sessions/<pid>.json`) whose `sessionId` is
28
+ * this conversation; the most recently updated one when a resumed
29
+ * conversation left several. Null when there is none (or no registry at all).
30
+ */
31
+ export function readRegistry(sessionId, dir = path.join(os.homedir(), ".claude", "sessions")) {
32
+ let names;
33
+ try {
34
+ names = fs.readdirSync(dir);
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ let best = null;
40
+ for (const n of names) {
41
+ if (!n.endsWith(".json"))
42
+ continue;
43
+ try {
44
+ const e = JSON.parse(fs.readFileSync(path.join(dir, n), "utf-8"));
45
+ if (e?.sessionId !== sessionId)
46
+ continue;
47
+ if (!best || (e.statusUpdatedAt ?? 0) > (best.statusUpdatedAt ?? 0))
48
+ best = e;
49
+ }
50
+ catch {
51
+ /* a file mid-write */
52
+ }
53
+ }
54
+ return best;
55
+ }
56
+ // ── Formatting helpers ─────────────────────────────────────────────────
57
+ export function age(ms) {
58
+ const s = Math.max(0, Math.floor(ms / 1000));
59
+ if (s < 60)
60
+ return `${s}s`;
61
+ const m = Math.floor(s / 60);
62
+ if (m < 60)
63
+ return `${m}m`;
64
+ const h = Math.floor(m / 60);
65
+ if (h < 24)
66
+ return `${h}h${String(m % 60).padStart(2, "0")}`;
67
+ return `${Math.floor(h / 24)}d`;
68
+ }
69
+ export function ordinal(n) {
70
+ const t = n % 100;
71
+ if (t >= 11 && t <= 13)
72
+ return `${n}th`;
73
+ return `${n}${{ 1: "st", 2: "nd", 3: "rd" }[n % 10] ?? "th"}`;
74
+ }
75
+ // Colours and OSC 8 links (whose URL is never seen).
76
+ // eslint-disable-next-line no-control-regex
77
+ const ANSI = /\x1b\[[0-9;]*m|\x1b\]8;;[^\x1b]*\x1b\\/g;
78
+ export function visibleLength(s) {
79
+ return [...s.replace(ANSI, "")].length;
80
+ }
81
+ /** A piece of text as the terminal shows it: control characters out (term.ts), at most `max` characters. */
82
+ export function clip(s, max) {
83
+ const chars = [...safeText(s)];
84
+ return chars.length > max ? `${chars.slice(0, max - 1).join("")}…` : chars.join("");
85
+ }
86
+ /** How one typed wait shows, or null when it is no longer worth a place. */
87
+ export function waitItem(w, now, alive) {
88
+ const name = clip(waitName(w), 40);
89
+ const try3 = w.attempt >= 3 ? ` ${A}· ${ordinal(w.attempt)} try${X}` : "";
90
+ // "queued 2nd (light)" already says its state: never "queued queued 2nd".
91
+ const said = clip(stateAndDetail(w.state, w.detail), 56);
92
+ if (isFinal(w)) {
93
+ const since = now - (w.finishedAt ?? w.probedAt);
94
+ if (w.state === "passed") {
95
+ return since <= PASSED_VISIBLE_MS ? { text: `${G}✓ ${name} passed${X} ${D}${age(since)} ago${X}${try3}`, open: false } : null;
96
+ }
97
+ if (since > FAILED_VISIBLE_MS)
98
+ return null;
99
+ return { text: `${R}✗ ${name} ${said}${X}${try3}`, open: false };
100
+ }
101
+ if (!alive(w.pid)) {
102
+ if (now - w.probedAt > FAILED_VISIBLE_MS)
103
+ return null;
104
+ return { text: `${R}✗ ${name} died${X} ${D}(the wait stopped at ${w.state})${X}${try3}`, open: false };
105
+ }
106
+ const waited = ` ${D}${age(now - w.startedAt)}${X}`;
107
+ if (now - w.changedAt > STALE_AFTER_MS) {
108
+ return { text: `${A}${name} ${said} · no change ${age(now - w.changedAt)}${X}${try3}`, open: true };
109
+ }
110
+ if (w.state === "error")
111
+ return { text: `${R}${name} ${said}${X}${waited}${try3}`, open: true };
112
+ if (w.state === "running")
113
+ return { text: `${C}${name} ${said}${X}${waited}${try3}`, open: true };
114
+ return { text: `${D}${name} ${said}${X}${waited}${try3}`, open: true }; // queued, pending
115
+ }
116
+ /** A background task is the typed wait it runs when its command is `synap wait …` for that wait's target. */
117
+ function runsWait(t, w) {
118
+ const cmd = t.command ?? "";
119
+ if (!/\bsynap\b[^|;&]*\bwait\s+(devbox|pr|run)\b/.test(cmd))
120
+ return false;
121
+ if (!cmd.includes(`wait ${w.kind}`))
122
+ return false;
123
+ const target = w.kind === "devbox" ? w.ref : /(?:#| run )(\d+)/.exec(w.ref)?.[1] ?? w.ref; // job name, PR number, run id
124
+ return cmd.includes(target) && w.startedAt >= t.startedAt - 5_000;
125
+ }
126
+ /** Every item worth showing: what is still open (typed waits first), then recent results. */
127
+ export function nowItems(input) {
128
+ const { now, registry, open, waits, alive } = input;
129
+ const items = [];
130
+ const done = [];
131
+ for (const w of [...waits].sort((a, b) => a.startedAt - b.startedAt)) {
132
+ const it = waitItem(w, now, alive);
133
+ if (it)
134
+ (it.open ? items : done).push(it.text);
135
+ }
136
+ // A task launched by an earlier Claude Code process died with it (Claude
137
+ // Code marks it stopped on resume): only this process's tasks are open.
138
+ const since = registry?.startedAt ?? 0;
139
+ // A shell killed by hand (`kill`, a Monitor timeout) leaves no transcript
140
+ // marker, so the index keeps it open. Claude Code itself knows: an ended
141
+ // turn reads `shell` while a background shell is live and `idle` once none
142
+ // is (seen 2026-10-29: every `idle` session's task outputs were held open by
143
+ // no process; `shell` ones had live holders). So `idle` closes the shells.
144
+ const shellsGone = registry?.status === "idle";
145
+ for (const t of [...open].sort((a, b) => a.startedAt - b.startedAt)) {
146
+ if (t.startedAt < since)
147
+ continue;
148
+ if (shellsGone && (t.kind === "bash" || t.kind === "monitor"))
149
+ continue;
150
+ if (waits.some((w) => runsWait(t, w)))
151
+ continue; // shown once, as its typed wait
152
+ items.push(`${clip(t.description || t.kind, 40)} ${D}${age(now - t.startedAt)}${X}`);
153
+ }
154
+ return { open: items, done };
155
+ }
156
+ export function capped(head, items, width) {
157
+ let line = head;
158
+ for (let i = 0; i < items.length; i++) {
159
+ const rest = items.length - i - 1;
160
+ const more = rest > 0 ? ` ${D}· +${rest} more${X}` : "";
161
+ const next = `${line} ${D}·${X} ${items[i]}`;
162
+ if (visibleLength(next) + visibleLength(more) > width && i > 0) {
163
+ return `${line} ${D}· +${items.length - i} more${X}`;
164
+ }
165
+ line = next;
166
+ }
167
+ return line;
168
+ }
169
+ /** The 4th line. */
170
+ export function buildNowLine(input) {
171
+ const { now, registry } = input;
172
+ const width = input.width && input.width > 20 ? input.width : DEFAULT_WIDTH;
173
+ const { open, done } = nowItems(input);
174
+ const all = [...open, ...done];
175
+ const status = registry?.status;
176
+ const since = registry?.statusUpdatedAt ?? input.lastActivityAt;
177
+ if (status === "waiting") {
178
+ return `${Y}✋ needs you${X} ${D}·${X} ${clip(registry?.waitingFor || "your answer", width - 16)}`;
179
+ }
180
+ if (status === "busy") {
181
+ const head = `${C}▶ working${X}${since ? ` ${D}· ${age(now - since)}${X}` : ""}`;
182
+ return capped(open.length ? `${head} ${D}·${X} ⏳ ${open.length}` : head, all, width);
183
+ }
184
+ if (open.length)
185
+ return capped(`${Y}⏳ waiting on ${open.length}${X}`, all, width);
186
+ // Nothing open: the turn is over and nothing will wake it — the cue to prompt.
187
+ // A recent result (✓ / ✗) follows, so the person sees what came back.
188
+ if (!registry)
189
+ return capped(`${D}○ no session status${X}`, done, width);
190
+ // idle, shell (a turn ended) and any status this CLI does not know yet
191
+ return capped(`${A}■ stopped${X} ${D}· nothing running${since ? ` · ${age(now - since)}` : ""}${X}`, done, width);
192
+ }
193
+ //# sourceMappingURL=now-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"now-line.js","sourceRoot":"","sources":["../../src/lib/now-line.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The status line's 4th line, for each state, from fixtures. Pure: time,
3
+ * liveness and width are arguments.
4
+ */
5
+ export {};
@@ -0,0 +1,177 @@
1
+ /**
2
+ * The status line's 4th line, for each state, from fixtures. Pure: time,
3
+ * liveness and width are arguments.
4
+ */
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { describe, it, expect } from "vitest";
9
+ import { buildNowLine, ordinal, readRegistry, visibleLength } from "./now-line.js";
10
+ const NOW = Date.parse("2026-10-29T12:00:00Z");
11
+ const min = (m) => NOW - m * 60_000;
12
+ // eslint-disable-next-line no-control-regex
13
+ const plain = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
14
+ const reg = (status, extra = {}) => ({
15
+ sessionId: "s1",
16
+ pid: 1,
17
+ status,
18
+ statusUpdatedAt: min(4),
19
+ startedAt: min(600),
20
+ ...extra,
21
+ });
22
+ const task = (description, startedMin, extra = {}) => ({
23
+ kind: "bash",
24
+ description,
25
+ toolUseId: `t-${description}`,
26
+ startedAt: min(startedMin),
27
+ ...extra,
28
+ });
29
+ const wait = (extra) => ({
30
+ id: "w1",
31
+ kind: "devbox",
32
+ ref: "w7-regen",
33
+ label: "",
34
+ pid: 4242,
35
+ startedAt: min(6),
36
+ probedAt: min(0.5),
37
+ changedAt: min(2),
38
+ state: "running",
39
+ detail: "be-file · 2/4 done",
40
+ attempt: 1,
41
+ probeErrors: 0,
42
+ ...extra,
43
+ });
44
+ const alive = () => true;
45
+ const dead = () => false;
46
+ const line = (over) => plain(buildNowLine({ now: NOW, registry: reg("idle"), open: [], waits: [], alive, ...over }));
47
+ describe("the four states", () => {
48
+ it("needs you: the registry says waiting, with what for", () => {
49
+ expect(line({ registry: reg("waiting", { waitingFor: "permission prompt" }) })).toBe("✋ needs you · permission prompt");
50
+ });
51
+ it("working: busy, with its age, then the open waits", () => {
52
+ expect(line({ registry: reg("busy") })).toBe("▶ working · 4m");
53
+ expect(line({ registry: reg("busy"), open: [task("Run the suite", 3)] })).toBe("▶ working · 4m · ⏳ 1 · Run the suite 3m");
54
+ });
55
+ it("waiting on n: idle (turn over) with open waits, typed waits first", () => {
56
+ const out = line({ registry: reg("shell"), open: [task("Serve the build", 10)], waits: [wait({})] });
57
+ expect(out).toBe("⏳ waiting on 2 · devbox w7-regen running (be-file · 2/4 done) 6m · Serve the build 10m");
58
+ });
59
+ it("stopped: idle with nothing open, and how long since", () => {
60
+ expect(line({ registry: reg("idle") })).toBe("■ stopped · nothing running · 4m");
61
+ });
62
+ it("no registry entry at all says so instead of guessing", () => {
63
+ expect(line({ registry: null })).toBe("○ no session status");
64
+ });
65
+ });
66
+ describe("which background tasks count", () => {
67
+ it("a task from before this Claude Code process started is not shown (it died with that process)", () => {
68
+ expect(line({ registry: reg("busy", { startedAt: min(5) }), open: [task("old", 30)] })).toBe("▶ working · 4m");
69
+ });
70
+ it("idle means no live background shell, so open shells are dropped; agents and teammates stay", () => {
71
+ const open = [task("Serve the build", 10), task("Audit", 10, { kind: "agent" }), task("@lane-a", 9, { kind: "teammate" })];
72
+ expect(line({ registry: reg("idle"), open })).toBe("⏳ waiting on 2 · Audit 10m · @lane-a 9m");
73
+ expect(line({ registry: reg("shell"), open })).toContain("Serve the build");
74
+ });
75
+ it("a background task that runs `synap wait` for a typed wait is shown once, as the typed wait", () => {
76
+ const t = task("Wait for w7", 7, { command: "synap wait devbox w7-regen --label w7" });
77
+ const out = line({ registry: reg("shell"), open: [t], waits: [wait({})] });
78
+ expect(out).toBe("⏳ waiting on 1 · devbox w7-regen running (be-file · 2/4 done) 6m");
79
+ });
80
+ it("a `synap wait` task for ANOTHER target is still its own item", () => {
81
+ const t = task("Wait for PR", 7, { command: "synap wait pr backend#412" });
82
+ expect(line({ registry: reg("shell"), open: [t], waits: [wait({})] })).toContain("Wait for PR");
83
+ });
84
+ });
85
+ describe("typed wait states", () => {
86
+ const one = (w, alive_ = alive) => line({ registry: reg("shell"), waits: [wait(w)], alive: alive_ });
87
+ const raw = (w) => buildNowLine({ now: NOW, registry: reg("shell"), open: [], waits: [wait(w)], alive });
88
+ it("queued is dim, running cyan", () => {
89
+ expect(one({ state: "queued", detail: "queued 2nd (light)" })).toBe("⏳ waiting on 1 · devbox w7-regen queued 2nd (light) 6m");
90
+ expect(raw({ state: "queued", detail: "queued 2nd (light)" })).toContain("\x1b[2mdevbox w7-regen queued");
91
+ expect(raw({ state: "running" })).toContain("\x1b[36mdevbox w7-regen running");
92
+ });
93
+ it("passed shows green ✓ for 10 min after it finished, then goes", () => {
94
+ const passed = { state: "passed", detail: "4 gates green", exitCode: 0 };
95
+ expect(one({ ...passed, finishedAt: min(3) })).toBe("■ stopped · nothing running · 4m · ✓ devbox w7-regen passed 3m ago");
96
+ expect(raw({ ...passed, finishedAt: min(3) })).toContain("\x1b[32m✓");
97
+ expect(one({ ...passed, finishedAt: min(11) })).toBe("■ stopped · nothing running · 4m");
98
+ });
99
+ it("failed shows red ✗ with its detail", () => {
100
+ const out = one({ state: "failed", detail: "red: be-file (3 tests)", exitCode: 1, finishedAt: min(1) });
101
+ expect(out).toBe("■ stopped · nothing running · 4m · ✗ devbox w7-regen failed (red: be-file (3 tests))");
102
+ expect(raw({ state: "failed", detail: "x", exitCode: 1, finishedAt: min(1) })).toContain("\x1b[31m✗");
103
+ });
104
+ it("infra, error and timeout are red", () => {
105
+ for (const state of ["infra", "error", "timeout"]) {
106
+ expect(raw({ state, detail: "d", exitCode: state === "timeout" ? 2 : 3, finishedAt: min(1) })).toContain(`\x1b[31m✗ devbox w7-regen ${state}`);
107
+ }
108
+ expect(raw({ state: "error", detail: "probe failed 3× in a row" })).toContain("\x1b[31mdevbox w7-regen error");
109
+ });
110
+ it("a result is not something waited on: an open wait is counted, a finished one only shown", () => {
111
+ const out = line({ registry: reg("shell"), waits: [wait({ id: "a" }), wait({ id: "b", ref: "w8", state: "failed", detail: "red: x", exitCode: 1, finishedAt: min(1) })] });
112
+ expect(out).toBe("⏳ waiting on 1 · devbox w7-regen running (be-file · 2/4 done) 6m · ✗ devbox w8 failed (red: x)");
113
+ expect(line({ registry: reg("busy"), waits: [wait({ state: "passed", exitCode: 0, finishedAt: min(1) })] })).toBe("▶ working · 4m · ✓ devbox w7-regen passed 1m ago");
114
+ });
115
+ it("stale: a non-final state that has not changed for 30 min is amber, with how long", () => {
116
+ const out = raw({ state: "queued", detail: "queued 1st (heavy)", changedAt: min(34) });
117
+ expect(out).toContain("\x1b[33mdevbox w7-regen queued");
118
+ expect(plain(out)).toContain("no change 34m");
119
+ });
120
+ it("died: a non-final state whose process is gone is red", () => {
121
+ expect(one({ state: "running" }, dead)).toBe("■ stopped · nothing running · 4m · ✗ devbox w7-regen died (the wait stopped at running)");
122
+ });
123
+ it("the 3rd try on one target is called out in amber", () => {
124
+ expect(one({ attempt: 3 })).toContain("· 3rd try");
125
+ expect(raw({ attempt: 3 })).toContain("\x1b[33m· 3rd try");
126
+ expect(one({ attempt: 2 })).not.toContain("try");
127
+ });
128
+ it("a PR wait drops the Synap-core owner from its name", () => {
129
+ expect(one({ kind: "pr", ref: "Synap-core/backend#412", state: "running", detail: "Release gate running · 2/4 done" })).toContain("pr backend#412 Release gate running · 2/4 done");
130
+ });
131
+ });
132
+ describe("width", () => {
133
+ it("caps the line to the width, saying how many more there are", () => {
134
+ const open = Array.from({ length: 8 }, (_, i) => task(`Background job number ${i}`, 20 - i, { kind: "agent" }));
135
+ const out = line({ registry: reg("idle"), open, width: 100 });
136
+ expect(visibleLength(out)).toBeLessThanOrEqual(100);
137
+ expect(out).toMatch(/· \+\d more$/);
138
+ expect(out.startsWith("⏳ waiting on 8 · Background job number 0")).toBe(true);
139
+ });
140
+ it("defaults to 120 columns", () => {
141
+ const open = Array.from({ length: 20 }, (_, i) => task(`Job ${i} with a long description`, 20, { kind: "agent" }));
142
+ expect(visibleLength(line({ registry: reg("idle"), open }))).toBeLessThanOrEqual(120);
143
+ });
144
+ });
145
+ describe("helpers", () => {
146
+ it("ordinal", () => {
147
+ expect([1, 2, 3, 4, 11, 12, 13, 21, 22, 23, 101].map(ordinal)).toEqual(["1st", "2nd", "3rd", "4th", "11th", "12th", "13th", "21st", "22nd", "23rd", "101st"]);
148
+ });
149
+ it("readRegistry picks the file for this conversation, newest status when several", () => {
150
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "synap-reg-"));
151
+ fs.writeFileSync(path.join(dir, "1.json"), JSON.stringify({ pid: 1, sessionId: "a", status: "idle", statusUpdatedAt: 5 }));
152
+ fs.writeFileSync(path.join(dir, "2.json"), JSON.stringify({ pid: 2, sessionId: "a", status: "busy", statusUpdatedAt: 9 }));
153
+ fs.writeFileSync(path.join(dir, "3.json"), JSON.stringify({ pid: 3, sessionId: "b", status: "waiting", statusUpdatedAt: 99 }));
154
+ fs.writeFileSync(path.join(dir, "3.abc.key"), "not json");
155
+ expect(readRegistry("a", dir)?.status).toBe("busy");
156
+ expect(readRegistry("z", dir)).toBeNull();
157
+ expect(readRegistry("a", path.join(dir, "missing"))).toBeNull();
158
+ });
159
+ });
160
+ describe("no wait, task or prompt text reaches the terminal with its control characters", () => {
161
+ it("a wait label, a task description and a waitingFor are printed inert", () => {
162
+ const raw = buildNowLine({
163
+ now: NOW,
164
+ registry: reg("busy"),
165
+ open: [task("run\x1b]0;pwned\x07 tests", 3)],
166
+ waits: [wait({ label: "PR \x1b[2J#5" })],
167
+ alive,
168
+ });
169
+ expect(raw).not.toContain("\x1b]0;");
170
+ expect(raw).not.toContain("\x1b[2J");
171
+ expect(raw).not.toContain("\x07");
172
+ expect(plain(raw)).toContain("PR [2J#5");
173
+ const asked = buildNowLine({ now: NOW, registry: reg("waiting", { waitingFor: "allow\x1b[8m rm" }), open: [], waits: [], alive });
174
+ expect(asked).not.toContain("\x1b[8m");
175
+ });
176
+ });
177
+ //# sourceMappingURL=now-line.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"now-line.test.js","sourceRoot":"","sources":["../../src/lib/now-line.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * "Does this installed package have an update?" — the POD's answer, read.
3
+ *
4
+ * The pod derives it ONCE for every installed kind (a space's template, the
5
+ * overlay packs on it, capabilities, views, skills, automations) with one
6
+ * rule: the catalog's content-hash version against the one the pod last
7
+ * converged to (`packageHasUpdate`, `@synap-core/types/package-updates`,
8
+ * served by Hub `GET /package-updates` → `installed[]`). Pod admin's Updates
9
+ * tab reads the same list, so the CLI and pod admin can no longer disagree
10
+ * (2 Nov 2026: the CLI said "brand-library: content changed" while pod admin
11
+ * said nothing, because each derived it its own way).
12
+ *
13
+ * This module never compares versions. A pod that cannot answer (an older
14
+ * pod, a key that is not a pod admin's) is said out loud by the caller:
15
+ * never a verdict the CLI made up.
16
+ */
17
+ import { type HubConfig } from "./hub-client.js";
18
+ import type { InstalledKind } from "./installed.js";
19
+ /** One installed package and the pod's verdict (`PackageStanding` on the pod). */
20
+ export interface PackageStanding {
21
+ slug: string;
22
+ kind: InstalledKind;
23
+ name: string;
24
+ installedVersion: string | null;
25
+ latestVersion: string | null;
26
+ /** true / false = the pod compared it; null = it could not (`reason`). */
27
+ hasUpdate: boolean | null;
28
+ reason?: string;
29
+ }
30
+ /** Why the pod's answer could not be read. */
31
+ export type StandingsUnavailable =
32
+ /** The key's person is not a pod admin: the pod answers updates to admins only. */
33
+ "not-pod-admin"
34
+ /** The pod predates the one rule (no `GET /package-updates`, or no `installed[]`). */
35
+ | "pod-too-old";
36
+ export declare class StandingsUnavailableError extends Error {
37
+ readonly reason: StandingsUnavailable;
38
+ constructor(reason: StandingsUnavailable, message: string);
39
+ }
40
+ /**
41
+ * Every installed package with the pod's verdict. Throws
42
+ * {@link StandingsUnavailableError} when the pod cannot answer, and the hub
43
+ * error itself for anything else (unreachable, 5xx): never an empty list for
44
+ * a read that failed.
45
+ */
46
+ export declare function fetchPackageStandings(cfg: HubConfig): Promise<PackageStanding[]>;
47
+ /** The shape `market update` prints and returns in `checks[]`, from the pod's verdicts. */
48
+ export interface StandingCheck {
49
+ slug: string;
50
+ kind: InstalledKind;
51
+ installedVersion: string;
52
+ latestVersion: string | undefined;
53
+ updateAvailable: boolean;
54
+ /** No version stamp on the pod: it could not compare. */
55
+ noVersionInfo: boolean;
56
+ /** The pod's reason when it could not compare. */
57
+ reason?: string;
58
+ }
59
+ /**
60
+ * The pod's verdicts as `market update` checks. `updateAvailable` is the
61
+ * pod's `hasUpdate === true`, nothing else: a package the pod could not
62
+ * compare is never "up to date" (no latest version → "can't check"; no stamp
63
+ * → "version unknown"; a space whose last update did not finish has an update
64
+ * and no stamp → "not finished").
65
+ */
66
+ export declare function checksFromStandings(standings: PackageStanding[]): StandingCheck[];
@@ -0,0 +1,76 @@
1
+ /**
2
+ * "Does this installed package have an update?" — the POD's answer, read.
3
+ *
4
+ * The pod derives it ONCE for every installed kind (a space's template, the
5
+ * overlay packs on it, capabilities, views, skills, automations) with one
6
+ * rule: the catalog's content-hash version against the one the pod last
7
+ * converged to (`packageHasUpdate`, `@synap-core/types/package-updates`,
8
+ * served by Hub `GET /package-updates` → `installed[]`). Pod admin's Updates
9
+ * tab reads the same list, so the CLI and pod admin can no longer disagree
10
+ * (2 Nov 2026: the CLI said "brand-library: content changed" while pod admin
11
+ * said nothing, because each derived it its own way).
12
+ *
13
+ * This module never compares versions. A pod that cannot answer (an older
14
+ * pod, a key that is not a pod admin's) is said out loud by the caller:
15
+ * never a verdict the CLI made up.
16
+ */
17
+ import { hubGet, HubError } from "./hub-client.js";
18
+ export class StandingsUnavailableError extends Error {
19
+ reason;
20
+ constructor(reason, message) {
21
+ super(message);
22
+ this.reason = reason;
23
+ this.name = "StandingsUnavailableError";
24
+ }
25
+ }
26
+ const SENTENCES = {
27
+ "not-pod-admin": "Only a pod admin can read the pod's package updates, and this key's person is not one. Nothing was checked.",
28
+ "pod-too-old": "This pod is too old to answer which packages have an update (no installed[] on GET /package-updates). Update the pod, then run this again. Nothing was checked.",
29
+ };
30
+ /**
31
+ * Every installed package with the pod's verdict. Throws
32
+ * {@link StandingsUnavailableError} when the pod cannot answer, and the hub
33
+ * error itself for anything else (unreachable, 5xx): never an empty list for
34
+ * a read that failed.
35
+ */
36
+ export async function fetchPackageStandings(cfg) {
37
+ let body;
38
+ try {
39
+ // One definition fetch per package with an update, on the pod: a cold
40
+ // catalog can take a while.
41
+ body = await hubGet("/package-updates", {}, cfg, 60_000);
42
+ }
43
+ catch (err) {
44
+ if (err instanceof HubError && err.status === 403 && !/scope/i.test(`${err.message} ${err.detail ?? ""}`)) {
45
+ throw new StandingsUnavailableError("not-pod-admin", SENTENCES["not-pod-admin"]);
46
+ }
47
+ if (err instanceof HubError && err.status === 404) {
48
+ throw new StandingsUnavailableError("pod-too-old", SENTENCES["pod-too-old"]);
49
+ }
50
+ throw err;
51
+ }
52
+ const installed = body?.installed;
53
+ if (!Array.isArray(installed)) {
54
+ throw new StandingsUnavailableError("pod-too-old", SENTENCES["pod-too-old"]);
55
+ }
56
+ return installed;
57
+ }
58
+ /**
59
+ * The pod's verdicts as `market update` checks. `updateAvailable` is the
60
+ * pod's `hasUpdate === true`, nothing else: a package the pod could not
61
+ * compare is never "up to date" (no latest version → "can't check"; no stamp
62
+ * → "version unknown"; a space whose last update did not finish has an update
63
+ * and no stamp → "not finished").
64
+ */
65
+ export function checksFromStandings(standings) {
66
+ return standings.map((s) => ({
67
+ slug: s.slug,
68
+ kind: s.kind,
69
+ installedVersion: s.installedVersion ?? "",
70
+ latestVersion: s.hasUpdate === null && s.installedVersion ? undefined : (s.latestVersion ?? undefined),
71
+ updateAvailable: s.hasUpdate === true,
72
+ noVersionInfo: !s.installedVersion,
73
+ ...(s.reason ? { reason: s.reason } : {}),
74
+ }));
75
+ }
76
+ //# sourceMappingURL=package-updates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-updates.js","sourceRoot":"","sources":["../../src/lib/package-updates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,iBAAiB,CAAC;AAsBnE,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAEvC;IADX,YACW,MAA4B,EACrC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAsB;QAIrC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,SAAS,GAAyC;IACtD,eAAe,EACb,6GAA6G;IAC/G,aAAa,EACX,iKAAiK;CACpK,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,GAAc;IACxD,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,sEAAsE;QACtE,4BAA4B;QAC5B,IAAI,GAAG,MAAM,MAAM,CAAC,kBAAkB,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;YAC1G,MAAM,IAAI,yBAAyB,CAAC,eAAe,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAClD,MAAM,IAAI,yBAAyB,CAAC,aAAa,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,MAAM,SAAS,GAAI,IAAuC,EAAE,SAAS,CAAC;IACtE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,yBAAyB,CAAC,aAAa,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,SAA8B,CAAC;AACxC,CAAC;AAeD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAA4B;IAC9D,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,IAAI,EAAE;QAC1C,aAAa,EAAE,CAAC,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,IAAI,SAAS,CAAC;QACtG,eAAe,EAAE,CAAC,CAAC,SAAS,KAAK,IAAI;QACrC,aAAa,EAAE,CAAC,CAAC,CAAC,gBAAgB;QAClC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC,CAAC;AACN,CAAC"}
package/dist/lib/pod.d.ts CHANGED
@@ -248,7 +248,3 @@ export interface PodStatus {
248
248
  * Check if a Synap pod is healthy.
249
249
  */
250
250
  export declare function checkPodHealth(podUrl: string): Promise<PodStatus>;
251
- /**
252
- * Install Docker and start a self-hosted Synap pod.
253
- */
254
- export declare function startSelfHostedPod(): void;
package/dist/lib/pod.js CHANGED
@@ -4,7 +4,6 @@
4
4
  import { readJsonStrict, writeFileAtomic, ensureSecretDir } from "./atomic-file.js";
5
5
  import { PodMovedError, podFetch } from "./pod-moved.js";
6
6
  import { classifyFetchError, classifyHttpStatus } from "./pod-reach.js";
7
- import { execSync } from "child_process";
8
7
  import fs from "node:fs";
9
8
  import path from "node:path";
10
9
  import os from "node:os";
@@ -488,10 +487,4 @@ export async function checkPodHealth(podUrl) {
488
487
  }
489
488
  return status;
490
489
  }
491
- /**
492
- * Install Docker and start a self-hosted Synap pod.
493
- */
494
- export function startSelfHostedPod() {
495
- execSync('curl -fsSL https://raw.githubusercontent.com/Synap-core/backend/main/install.sh | bash', { stdio: "inherit" });
496
- }
497
490
  //# sourceMappingURL=pod.js.map