svamp-cli 0.1.28 → 0.1.30

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 (80) hide show
  1. package/dist/cli.mjs +114 -42
  2. package/dist/commands-B2xQb9u7.mjs +862 -0
  3. package/dist/commands-C5pW2VmI.mjs +862 -0
  4. package/dist/commands-CtO1WRt4.mjs +862 -0
  5. package/dist/commands-TZkNivgV.mjs +862 -0
  6. package/dist/index.mjs +8 -2
  7. package/dist/{package-CgBD49cA.mjs → package-CKOQ5lA7.mjs} +2 -2
  8. package/dist/{run-DjfPjgOb.mjs → run-BCSNMhiz.mjs} +1200 -132
  9. package/dist/{run-Cmostc0S.mjs → run-CtCTd6if.mjs} +1225 -169
  10. package/dist/{run-DYhBROuo.mjs → run-Cxdc5Zmw.mjs} +1253 -211
  11. package/dist/{run-CL-FS4Yc.mjs → run-dBWhjQRf.mjs} +1255 -211
  12. package/package.json +3 -6
  13. package/bin/svamp-agent.mjs +0 -34
  14. package/dist/agent-cli.mjs +0 -453
  15. package/dist/commands-1CYZC6Xh.mjs +0 -481
  16. package/dist/commands-B1DcpgLW.mjs +0 -481
  17. package/dist/commands-BGmdgMAC.mjs +0 -485
  18. package/dist/commands-BOeSil-P.mjs +0 -459
  19. package/dist/commands-BU4GZQuH.mjs +0 -481
  20. package/dist/commands-Ba66PxtQ.mjs +0 -481
  21. package/dist/commands-C0-xqIIc.mjs +0 -481
  22. package/dist/commands-C7Qy5n6d.mjs +0 -481
  23. package/dist/commands-CKpC8R9T.mjs +0 -481
  24. package/dist/commands-CNqOjR1y.mjs +0 -481
  25. package/dist/commands-CVKh1tWr.mjs +0 -485
  26. package/dist/commands-CYBblX73.mjs +0 -485
  27. package/dist/commands-CZBYmj16.mjs +0 -485
  28. package/dist/commands-CcWIvCA4.mjs +0 -481
  29. package/dist/commands-Cfwf-cQG.mjs +0 -481
  30. package/dist/commands-DCNO2m66.mjs +0 -471
  31. package/dist/commands-DRIFvhmC.mjs +0 -481
  32. package/dist/commands-DXmw2dzy.mjs +0 -481
  33. package/dist/commands-DkSvlKFF.mjs +0 -485
  34. package/dist/commands-DnDd4Sew.mjs +0 -481
  35. package/dist/commands-DnpnAFQW.mjs +0 -485
  36. package/dist/commands-Do-TVYFm.mjs +0 -481
  37. package/dist/commands-Kzm0_XNH.mjs +0 -481
  38. package/dist/commands-MQvNbIid.mjs +0 -481
  39. package/dist/commands-_uCC3U1U.mjs +0 -481
  40. package/dist/commands-y2WG29W9.mjs +0 -485
  41. package/dist/hyphaClient-DLkclazm.mjs +0 -39
  42. package/dist/package-B2FOzHaM.mjs +0 -57
  43. package/dist/package-Bk_PFVA0.mjs +0 -57
  44. package/dist/package-Bnij-ZtR.mjs +0 -57
  45. package/dist/package-BtRbHfjz.mjs +0 -57
  46. package/dist/package-C5B0twb8.mjs +0 -57
  47. package/dist/package-CC5d8_0L.mjs +0 -57
  48. package/dist/package-CCJ045H0.mjs +0 -60
  49. package/dist/package-CS219SXn.mjs +0 -57
  50. package/dist/package-Cd-9ktpd.mjs +0 -60
  51. package/dist/package-CvnNnsm7.mjs +0 -60
  52. package/dist/package-DpqWz9Cr.mjs +0 -60
  53. package/dist/package-JqEt5Ib4.mjs +0 -57
  54. package/dist/package-nzkXV1aM.mjs +0 -57
  55. package/dist/package-pNo6GC3a.mjs +0 -60
  56. package/dist/package-pZp14zKI.mjs +0 -57
  57. package/dist/run-4fyJcaRE.mjs +0 -3856
  58. package/dist/run-BI32lPRK.mjs +0 -3870
  59. package/dist/run-BQHneHfW.mjs +0 -3834
  60. package/dist/run-Bb4fyIWZ.mjs +0 -3812
  61. package/dist/run-BglwnB-A.mjs +0 -3889
  62. package/dist/run-BjVWuitO.mjs +0 -3919
  63. package/dist/run-BzUE-JUT.mjs +0 -3708
  64. package/dist/run-BzqS97Sx.mjs +0 -3666
  65. package/dist/run-C6snRxyh.mjs +0 -3826
  66. package/dist/run-C8CI8Ujj.mjs +0 -3693
  67. package/dist/run-CS1Z4GcM.mjs +0 -3786
  68. package/dist/run-CW26vPqj.mjs +0 -3919
  69. package/dist/run-CkTufc0D.mjs +0 -3875
  70. package/dist/run-Cp3kKdzm.mjs +0 -3865
  71. package/dist/run-D0bCTY72.mjs +0 -3816
  72. package/dist/run-DMW8ibIw.mjs +0 -3958
  73. package/dist/run-DO52unxE.mjs +0 -3950
  74. package/dist/run-Dptna3Je.mjs +0 -3867
  75. package/dist/run-DwK3dfHd.mjs +0 -3875
  76. package/dist/run-M_SMt96j.mjs +0 -3913
  77. package/dist/run-MlpxQUPN.mjs +0 -3869
  78. package/dist/run-PuTIelbv.mjs +0 -3706
  79. package/dist/run-h37iSCUB.mjs +0 -3934
  80. package/dist/run-lpV0oguG.mjs +0 -3897
@@ -0,0 +1,862 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import os from 'node:os';
4
+ import { c as connectToHypha } from './run-CtCTd6if.mjs';
5
+ import 'os';
6
+ import 'fs/promises';
7
+ import 'fs';
8
+ import 'path';
9
+ import 'url';
10
+ import 'child_process';
11
+ import 'crypto';
12
+ import 'node:crypto';
13
+ import 'node:child_process';
14
+ import '@agentclientprotocol/sdk';
15
+ import '@modelcontextprotocol/sdk/client/index.js';
16
+ import '@modelcontextprotocol/sdk/client/stdio.js';
17
+ import '@modelcontextprotocol/sdk/types.js';
18
+ import 'zod';
19
+ import 'node:fs/promises';
20
+ import 'node:util';
21
+
22
+ function toMarkdownInline(value) {
23
+ const escaped = value.replace(/`/g, "\\`");
24
+ return `\`${escaped}\``;
25
+ }
26
+ function formatSessionStatus(data) {
27
+ const lines = [
28
+ "## Session Status",
29
+ "",
30
+ `- Session ID: ${toMarkdownInline(data.sessionId)}`,
31
+ `- Agent: ${data.flavor}`
32
+ ];
33
+ if (data.name) lines.push(`- Name: ${data.name}`);
34
+ if (data.summary) lines.push(`- Summary: ${data.summary}`);
35
+ if (data.path) lines.push(`- Path: ${data.path}`);
36
+ if (data.host) lines.push(`- Host: ${data.host}`);
37
+ if (data.lifecycleState) lines.push(`- Lifecycle: ${data.lifecycleState}`);
38
+ lines.push(`- Active: ${data.active ? "yes" : "no"}`);
39
+ lines.push(`- Thinking: ${data.thinking ? "yes" : "no"}`);
40
+ lines.push(`- Agent Status: ${data.active ? "busy" : "idle"}`);
41
+ if (data.startedBy) lines.push(`- Started By: ${data.startedBy}`);
42
+ if (data.claudeSessionId) lines.push(`- Claude Session: ${data.claudeSessionId}`);
43
+ if (data.sessionLink) lines.push(`- Link: ${data.sessionLink}`);
44
+ return lines.join("\n");
45
+ }
46
+ function formatJson(data) {
47
+ return JSON.stringify(data, null, 2);
48
+ }
49
+
50
+ const SVAMP_HOME = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
51
+ const DAEMON_STATE_FILE = join(SVAMP_HOME, "daemon.state.json");
52
+ const ENV_FILE = join(SVAMP_HOME, ".env");
53
+ function loadDotEnv() {
54
+ if (!existsSync(ENV_FILE)) return;
55
+ const lines = readFileSync(ENV_FILE, "utf-8").split("\n");
56
+ for (const line of lines) {
57
+ const trimmed = line.trim();
58
+ if (!trimmed || trimmed.startsWith("#")) continue;
59
+ const eqIdx = trimmed.indexOf("=");
60
+ if (eqIdx === -1) continue;
61
+ const key = trimmed.slice(0, eqIdx).trim();
62
+ const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
63
+ if (!process.env[key]) {
64
+ process.env[key] = value;
65
+ }
66
+ }
67
+ }
68
+ function readDaemonState() {
69
+ if (!existsSync(DAEMON_STATE_FILE)) return null;
70
+ try {
71
+ return JSON.parse(readFileSync(DAEMON_STATE_FILE, "utf-8"));
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function isDaemonAlive(state) {
77
+ try {
78
+ process.kill(state.pid, 0);
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+ async function connectAndGetMachine(machineId) {
85
+ loadDotEnv();
86
+ const state = readDaemonState();
87
+ if (!state || !isDaemonAlive(state)) {
88
+ console.error('Daemon is not running. Start it with "svamp daemon start".');
89
+ process.exit(1);
90
+ }
91
+ const serverUrl = process.env.HYPHA_SERVER_URL || state.hyphaServerUrl;
92
+ const token = process.env.HYPHA_TOKEN;
93
+ if (!serverUrl) {
94
+ console.error('No Hypha server URL. Run "svamp login <url>" first.');
95
+ process.exit(1);
96
+ }
97
+ const origLog = console.log;
98
+ const origWarn = console.warn;
99
+ const origInfo = console.info;
100
+ const origError = console.error;
101
+ const stdoutWrite = process.stdout.write.bind(process.stdout);
102
+ const stderrWrite = process.stderr.write.bind(process.stderr);
103
+ const isHyphaLog = (chunk) => typeof chunk === "string" && (chunk.includes("WebSocket connection") || chunk.includes("Connection established") || chunk.includes("registering service built-in") || chunk.includes("registered service") || chunk.includes("registered all") || chunk.includes("Subscribing to client_") || chunk.includes("subscribed to client_") || chunk.includes("subscribe to client_") || chunk.includes("Cleaning up all sessions") || chunk.includes("WebSocket connection disconnected") || chunk.includes("local RPC disconnection") || chunk.includes("Timeout registering service") || chunk.includes("Failed to subscribe to client_disconnected") || chunk.includes("Timeout subscribing to client_disconnected"));
104
+ console.log = () => {
105
+ };
106
+ console.warn = () => {
107
+ };
108
+ console.info = () => {
109
+ };
110
+ console.error = (...args) => {
111
+ if (args.some((a) => isHyphaLog(a))) return;
112
+ origError(...args);
113
+ };
114
+ process.stdout.write = (chunk, ...args) => {
115
+ if (isHyphaLog(chunk)) return true;
116
+ return stdoutWrite(chunk, ...args);
117
+ };
118
+ process.stderr.write = (chunk, ...args) => {
119
+ if (isHyphaLog(chunk)) return true;
120
+ return stderrWrite(chunk, ...args);
121
+ };
122
+ const restoreConsole = () => {
123
+ console.log = origLog;
124
+ console.warn = origWarn;
125
+ console.info = origInfo;
126
+ console.error = origError;
127
+ };
128
+ let server;
129
+ try {
130
+ server = await connectToHypha({
131
+ serverUrl,
132
+ token,
133
+ name: "svamp-session-cli"
134
+ });
135
+ } catch (err) {
136
+ restoreConsole();
137
+ console.error(`Failed to connect to Hypha: ${err.message}`);
138
+ process.exit(1);
139
+ }
140
+ let machine;
141
+ try {
142
+ const services = await server.listServices({ type: "svamp-machine" });
143
+ if (services.length === 0) {
144
+ restoreConsole();
145
+ console.error("No machine service found. Is the daemon registered on Hypha?");
146
+ await server.disconnect();
147
+ process.exit(1);
148
+ }
149
+ let selectedService;
150
+ if (machineId) {
151
+ const exact = services.find((s) => (s.id || s.name) === machineId);
152
+ if (exact) {
153
+ selectedService = exact;
154
+ } else {
155
+ const prefixMatches = services.filter((s) => {
156
+ const id = s.id || s.name;
157
+ return id.startsWith(machineId);
158
+ });
159
+ if (prefixMatches.length === 1) {
160
+ selectedService = prefixMatches[0];
161
+ } else if (prefixMatches.length === 0) {
162
+ const substringMatches = services.filter((s) => {
163
+ const id = s.id || s.name || "";
164
+ return id.includes(machineId);
165
+ });
166
+ if (substringMatches.length === 1) {
167
+ selectedService = substringMatches[0];
168
+ } else {
169
+ restoreConsole();
170
+ console.error(`No machine found matching: ${machineId}`);
171
+ console.error("Available machines:");
172
+ for (const s of services) {
173
+ console.error(` ${s.id || s.name}`);
174
+ }
175
+ await server.disconnect();
176
+ process.exit(1);
177
+ }
178
+ } else {
179
+ restoreConsole();
180
+ console.error(`Ambiguous machine ID "${machineId}". Matches:`);
181
+ for (const s of prefixMatches) {
182
+ console.error(` ${s.id || s.name}`);
183
+ }
184
+ await server.disconnect();
185
+ process.exit(1);
186
+ }
187
+ }
188
+ } else {
189
+ selectedService = services[0];
190
+ }
191
+ const svcId = selectedService.id || selectedService.name;
192
+ machine = await server.getService(svcId);
193
+ } catch (err) {
194
+ restoreConsole();
195
+ console.error(`Failed to discover machine service: ${err.message}`);
196
+ await server.disconnect();
197
+ process.exit(1);
198
+ }
199
+ restoreConsole();
200
+ return { server, machine };
201
+ }
202
+ async function sessionMachines() {
203
+ loadDotEnv();
204
+ const state = readDaemonState();
205
+ if (!state || !isDaemonAlive(state)) {
206
+ console.error('Daemon is not running. Start it with "svamp daemon start".');
207
+ process.exit(1);
208
+ }
209
+ const serverUrl = process.env.HYPHA_SERVER_URL || state.hyphaServerUrl;
210
+ const token = process.env.HYPHA_TOKEN;
211
+ if (!serverUrl) {
212
+ console.error('No Hypha server URL. Run "svamp login <url>" first.');
213
+ process.exit(1);
214
+ }
215
+ const origLog = console.log;
216
+ const origWarn = console.warn;
217
+ const origInfo = console.info;
218
+ const origError = console.error;
219
+ const stdoutWrite = process.stdout.write.bind(process.stdout);
220
+ const stderrWrite = process.stderr.write.bind(process.stderr);
221
+ const isHyphaLog = (chunk) => typeof chunk === "string" && (chunk.includes("WebSocket connection") || chunk.includes("Connection established") || chunk.includes("registering service built-in") || chunk.includes("registered service") || chunk.includes("registered all") || chunk.includes("Subscribing to client_") || chunk.includes("subscribed to client_") || chunk.includes("subscribe to client_") || chunk.includes("Cleaning up all sessions") || chunk.includes("WebSocket connection disconnected") || chunk.includes("local RPC disconnection") || chunk.includes("Timeout registering service") || chunk.includes("Failed to subscribe to client_disconnected") || chunk.includes("Timeout subscribing to client_disconnected"));
222
+ console.log = () => {
223
+ };
224
+ console.warn = () => {
225
+ };
226
+ console.info = () => {
227
+ };
228
+ console.error = (...args) => {
229
+ if (args.some((a) => isHyphaLog(a))) return;
230
+ origError(...args);
231
+ };
232
+ process.stdout.write = (chunk, ...args) => {
233
+ if (isHyphaLog(chunk)) return true;
234
+ return stdoutWrite(chunk, ...args);
235
+ };
236
+ process.stderr.write = (chunk, ...args) => {
237
+ if (isHyphaLog(chunk)) return true;
238
+ return stderrWrite(chunk, ...args);
239
+ };
240
+ const restoreConsole = () => {
241
+ console.log = origLog;
242
+ console.warn = origWarn;
243
+ console.info = origInfo;
244
+ console.error = origError;
245
+ };
246
+ let server;
247
+ try {
248
+ server = await connectToHypha({
249
+ serverUrl,
250
+ token,
251
+ name: "svamp-session-cli"
252
+ });
253
+ } catch (err) {
254
+ restoreConsole();
255
+ console.error(`Failed to connect to Hypha: ${err.message}`);
256
+ process.exit(1);
257
+ }
258
+ try {
259
+ const services = await server.listServices({ type: "svamp-machine" });
260
+ restoreConsole();
261
+ if (services.length === 0) {
262
+ console.log("No machines found.");
263
+ return;
264
+ }
265
+ const machines = [];
266
+ for (const svc of services) {
267
+ const svcId = svc.id || svc.name;
268
+ try {
269
+ const machineSvc = await server.getService(svcId);
270
+ const info = await machineSvc.getMachineInfo();
271
+ const sessions = await machineSvc.listSessions();
272
+ machines.push({
273
+ serviceId: svcId,
274
+ machineId: info.machineId || svcId,
275
+ displayName: info.metadata?.displayName || info.metadata?.host || "-",
276
+ platform: info.metadata?.platform || "-",
277
+ host: info.metadata?.host || "-",
278
+ sessions: sessions.length,
279
+ status: info.daemonState?.status || "unknown"
280
+ });
281
+ } catch {
282
+ machines.push({
283
+ serviceId: svcId,
284
+ machineId: svcId,
285
+ displayName: "-",
286
+ platform: "-",
287
+ host: "-",
288
+ sessions: -1,
289
+ status: "unreachable"
290
+ });
291
+ }
292
+ }
293
+ const header = `${"MACHINE ID".padEnd(20)} ${"NAME".padEnd(20)} ${"PLATFORM".padEnd(12)} ${"HOST".padEnd(25)} ${"SESSIONS".padEnd(10)} ${"STATUS"}`;
294
+ console.log(header);
295
+ console.log("-".repeat(header.length));
296
+ for (const m of machines) {
297
+ const id = truncate(m.machineId, 18).padEnd(20);
298
+ const name = truncate(m.displayName, 18).padEnd(20);
299
+ const platform = m.platform.padEnd(12);
300
+ const host = truncate(m.host, 23).padEnd(25);
301
+ const sessions = m.sessions >= 0 ? String(m.sessions).padEnd(10) : "-".padEnd(10);
302
+ const status = m.status === "running" ? `\x1B[32m${m.status}\x1B[0m` : m.status === "unreachable" ? `\x1B[31m${m.status}\x1B[0m` : m.status;
303
+ console.log(`${id} ${name} ${platform} ${host} ${sessions} ${status}`);
304
+ }
305
+ console.log(`
306
+ ${machines.length} machine(s) found.`);
307
+ console.log("Use --machine <id> to target a specific machine.");
308
+ } finally {
309
+ await server.disconnect();
310
+ }
311
+ }
312
+ function resolveSessionId(sessions, partial) {
313
+ const exact = sessions.find((s) => s.sessionId === partial);
314
+ if (exact) return exact;
315
+ const matches = sessions.filter((s) => s.sessionId.startsWith(partial));
316
+ if (matches.length === 1) return matches[0];
317
+ if (matches.length === 0) {
318
+ console.error(`No session found matching: ${partial}`);
319
+ console.error('Run "svamp session list" to see active sessions.');
320
+ process.exit(1);
321
+ }
322
+ console.error(`Ambiguous session ID "${partial}". Matches:`);
323
+ for (const s of matches) {
324
+ console.error(` ${s.sessionId}`);
325
+ }
326
+ process.exit(1);
327
+ }
328
+ function truncate(str, max) {
329
+ if (str.length <= max) return str;
330
+ return "..." + str.slice(str.length - max + 3);
331
+ }
332
+ function renderMessage(msg) {
333
+ const content = msg.content;
334
+ if (!content) return;
335
+ const role = content.role;
336
+ if (role === "user") {
337
+ const data = content.content;
338
+ let text;
339
+ if (typeof data === "string") {
340
+ try {
341
+ const parsed = JSON.parse(data);
342
+ text = parsed?.text || parsed?.content?.text || data;
343
+ } catch {
344
+ text = data;
345
+ }
346
+ } else if (data?.text) {
347
+ text = data.text;
348
+ } else if (data?.type === "text") {
349
+ text = data.text || "";
350
+ } else {
351
+ text = typeof data === "object" ? JSON.stringify(data) : String(data || "");
352
+ }
353
+ console.log(`\x1B[36m[user]\x1B[0m ${text}`);
354
+ } else if (role === "agent" || role === "assistant") {
355
+ const data = content.content?.data || content.content;
356
+ if (!data) return;
357
+ if (data.type === "assistant" && Array.isArray(data.content)) {
358
+ for (const block of data.content) {
359
+ if (block.type === "text" && block.text) {
360
+ process.stdout.write(block.text);
361
+ if (!block.text.endsWith("\n")) process.stdout.write("\n");
362
+ } else if (block.type === "tool_use") {
363
+ const argsStr = JSON.stringify(block.input || {}).slice(0, 120);
364
+ console.log(`\x1B[33m[tool]\x1B[0m ${block.name}(${argsStr})`);
365
+ } else if (block.type === "tool_result") {
366
+ const resultStr = typeof block.content === "string" ? block.content : JSON.stringify(block.content || "");
367
+ console.log(`\x1B[90m[result]\x1B[0m ${resultStr.slice(0, 200)}${resultStr.length > 200 ? "..." : ""}`);
368
+ } else if (block.type === "thinking") {
369
+ const text = block.thinking || block.text || "";
370
+ if (text) console.log(`\x1B[90m[thinking] ${text.slice(0, 200)}\x1B[0m`);
371
+ }
372
+ }
373
+ } else if (data.type === "result") {
374
+ if (data.result) console.log(`\x1B[32m[done]\x1B[0m ${data.result}`);
375
+ } else if (data.type === "output") {
376
+ const inner = data.data;
377
+ if (inner?.type === "assistant" && Array.isArray(inner.content)) {
378
+ for (const block of inner.content) {
379
+ if (block.type === "text" && block.text) {
380
+ process.stdout.write(block.text);
381
+ if (!block.text.endsWith("\n")) process.stdout.write("\n");
382
+ } else if (block.type === "tool_use") {
383
+ const argsStr = JSON.stringify(block.input || {}).slice(0, 120);
384
+ console.log(`\x1B[33m[tool]\x1B[0m ${block.name}(${argsStr})`);
385
+ } else if (block.type === "tool_result") {
386
+ const resultStr = typeof block.content === "string" ? block.content : JSON.stringify(block.content || "");
387
+ console.log(`\x1B[90m[result]\x1B[0m ${resultStr.slice(0, 200)}${resultStr.length > 200 ? "..." : ""}`);
388
+ }
389
+ }
390
+ } else if (inner?.type === "result") {
391
+ if (inner.result) console.log(`\x1B[32m[done]\x1B[0m ${inner.result}`);
392
+ }
393
+ }
394
+ } else if (role === "session") {
395
+ const data = content.content?.data;
396
+ if (data?.type === "system" && data?.subtype === "init") {
397
+ console.log(`\x1B[90m[session init]\x1B[0m`);
398
+ }
399
+ }
400
+ }
401
+ function extractMessageText(msg) {
402
+ const content = msg.content;
403
+ if (!content) return null;
404
+ const role = content.role || "unknown";
405
+ let text = "";
406
+ if (role === "user") {
407
+ const data = content.content;
408
+ if (typeof data === "string") {
409
+ try {
410
+ const parsed = JSON.parse(data);
411
+ text = parsed?.text || parsed?.content?.text || data;
412
+ } catch {
413
+ text = data;
414
+ }
415
+ } else if (data?.text) {
416
+ text = data.text;
417
+ } else if (data?.type === "text") {
418
+ text = data.text || "";
419
+ } else {
420
+ text = typeof data === "object" ? JSON.stringify(data) : String(data || "");
421
+ }
422
+ } else if (role === "agent" || role === "assistant") {
423
+ const data = content.content?.data || content.content;
424
+ if (!data) return null;
425
+ if (data.type === "assistant" && Array.isArray(data.content)) {
426
+ const parts = [];
427
+ for (const block of data.content) {
428
+ if (block.type === "text" && block.text) {
429
+ parts.push(block.text);
430
+ } else if (block.type === "tool_use") {
431
+ parts.push(`[tool: ${block.name}]`);
432
+ }
433
+ }
434
+ text = parts.join("\n");
435
+ } else if (data.type === "result") {
436
+ text = data.result || "";
437
+ } else if (data.type === "output") {
438
+ const inner = data.data;
439
+ if (inner?.type === "assistant" && Array.isArray(inner.content)) {
440
+ const parts = [];
441
+ for (const block of inner.content) {
442
+ if (block.type === "text" && block.text) {
443
+ parts.push(block.text);
444
+ } else if (block.type === "tool_use") {
445
+ parts.push(`[tool: ${block.name}]`);
446
+ }
447
+ }
448
+ text = parts.join("\n");
449
+ } else if (inner?.type === "result") {
450
+ text = inner.result || "";
451
+ }
452
+ }
453
+ } else if (role === "session") {
454
+ text = "[session event]";
455
+ }
456
+ return {
457
+ id: msg.id || "",
458
+ seq: msg.seq || 0,
459
+ role,
460
+ text,
461
+ createdAt: msg.createdAt || 0
462
+ };
463
+ }
464
+ async function waitForIdle(server, sessionId, timeoutMs) {
465
+ const svc = await server.getService(`svamp-session-${sessionId}`);
466
+ const pollInterval = 2e3;
467
+ const deadline = Date.now() + timeoutMs;
468
+ while (Date.now() < deadline) {
469
+ const activity = await svc.getActivityState();
470
+ if (activity && !activity.active) {
471
+ return;
472
+ }
473
+ await new Promise((r) => setTimeout(r, pollInterval));
474
+ }
475
+ throw new Error("Timeout waiting for agent to become idle");
476
+ }
477
+ async function waitForBusyThenIdle(server, sessionId, timeoutMs = 3e5, busyTimeoutMs = 1e4) {
478
+ const svc = await server.getService(`svamp-session-${sessionId}`);
479
+ const pollInterval = 2e3;
480
+ const deadline = Date.now() + timeoutMs;
481
+ const busyDeadline = Date.now() + busyTimeoutMs;
482
+ let sawBusy = false;
483
+ while (Date.now() < deadline) {
484
+ const activity = await svc.getActivityState();
485
+ const isActive = activity?.active === true;
486
+ if (isActive) {
487
+ sawBusy = true;
488
+ }
489
+ if (sawBusy && !isActive) {
490
+ return;
491
+ }
492
+ if (!sawBusy && Date.now() > busyDeadline) {
493
+ return;
494
+ }
495
+ await new Promise((r) => setTimeout(r, pollInterval));
496
+ }
497
+ throw new Error("Timeout waiting for agent to become idle");
498
+ }
499
+ async function sessionList(machineId, opts) {
500
+ const { server, machine } = await connectAndGetMachine(machineId);
501
+ try {
502
+ const sessions = await machine.listSessions();
503
+ const filtered = opts?.active ? sessions.filter((s) => s.active) : sessions;
504
+ if (filtered.length === 0) {
505
+ if (opts?.json) {
506
+ console.log(formatJson([]));
507
+ } else {
508
+ console.log("No active sessions.");
509
+ }
510
+ return;
511
+ }
512
+ const enriched = [];
513
+ for (const s of filtered) {
514
+ let flavor = "claude";
515
+ let name = "";
516
+ let path = s.directory || "";
517
+ let host = "";
518
+ if (s.metadata) {
519
+ flavor = s.metadata.flavor || "claude";
520
+ name = s.metadata.name || "";
521
+ }
522
+ if (s.active) {
523
+ try {
524
+ const svc = await server.getService(`svamp-session-${s.sessionId}`);
525
+ const { metadata } = await svc.getMetadata();
526
+ flavor = metadata?.flavor || flavor;
527
+ name = metadata?.name || name;
528
+ path = metadata?.path || path;
529
+ host = metadata?.host || "";
530
+ } catch {
531
+ }
532
+ }
533
+ enriched.push({ ...s, flavor, name, path, host });
534
+ }
535
+ if (opts?.json) {
536
+ console.log(formatJson(enriched.map((s) => ({
537
+ sessionId: s.sessionId,
538
+ agent: s.flavor,
539
+ name: s.name,
540
+ path: s.path,
541
+ host: s.host,
542
+ active: s.active,
543
+ directory: s.directory
544
+ }))));
545
+ } else {
546
+ const header = `${"ID".padEnd(10)} ${"AGENT".padEnd(10)} ${"STATUS".padEnd(9)} ${"NAME".padEnd(25)} ${"DIRECTORY".padEnd(35)}`;
547
+ console.log(header);
548
+ console.log("-".repeat(header.length));
549
+ for (const s of enriched) {
550
+ const id = s.sessionId.slice(0, 8);
551
+ const agent = (s.flavor || "claude").padEnd(10);
552
+ const status = s.active ? "\x1B[32mactive\x1B[0m " : "\x1B[90minactive\x1B[0m";
553
+ const name = truncate(s.name || "-", 25).padEnd(25);
554
+ const dir = truncate(s.directory || "-", 33).padEnd(35);
555
+ console.log(`${id.padEnd(10)} ${agent} ${status} ${name} ${dir}`);
556
+ }
557
+ }
558
+ } finally {
559
+ await server.disconnect();
560
+ }
561
+ }
562
+ async function sessionSpawn(agent, directory, machineId, opts) {
563
+ const { server, machine } = await connectAndGetMachine(machineId);
564
+ try {
565
+ console.log(`Spawning ${agent} session in ${directory}...`);
566
+ const result = await machine.spawnSession({
567
+ directory,
568
+ agent
569
+ });
570
+ if (result.type === "success") {
571
+ console.log(`Session started: ${result.sessionId}`);
572
+ if (result.message) console.log(` ${result.message}`);
573
+ if (opts?.message && result.sessionId) {
574
+ const svc = await server.getService(`svamp-session-${result.sessionId}`);
575
+ const sendResult = await svc.sendMessage(
576
+ JSON.stringify({
577
+ role: "user",
578
+ content: { type: "text", text: opts.message },
579
+ meta: { sentFrom: "svamp-cli" }
580
+ })
581
+ );
582
+ console.log(`Message sent (seq: ${sendResult.seq})`);
583
+ if (opts.wait) {
584
+ console.log("Waiting for agent to become idle...");
585
+ await waitForBusyThenIdle(server, result.sessionId);
586
+ console.log("Agent is idle.");
587
+ }
588
+ }
589
+ } else if (result.type === "requestToApproveDirectoryCreation") {
590
+ console.error(`Directory ${result.directory} does not exist. Create it first or use an existing directory.`);
591
+ process.exit(1);
592
+ } else {
593
+ console.error(`Failed: ${result.errorMessage || "Unknown error"}`);
594
+ process.exit(1);
595
+ }
596
+ } finally {
597
+ await server.disconnect();
598
+ }
599
+ }
600
+ async function sessionStop(sessionId, machineId) {
601
+ const { server, machine } = await connectAndGetMachine(machineId);
602
+ try {
603
+ const sessions = await machine.listSessions();
604
+ const match = resolveSessionId(sessions, sessionId);
605
+ const success = await machine.stopSession(match.sessionId);
606
+ if (success) {
607
+ console.log(`Session ${match.sessionId.slice(0, 8)} stopped.`);
608
+ } else {
609
+ console.error("Failed to stop session (not found on daemon).");
610
+ process.exit(1);
611
+ }
612
+ } finally {
613
+ await server.disconnect();
614
+ }
615
+ }
616
+ async function sessionInfo(sessionId, machineId, opts) {
617
+ const { server, machine } = await connectAndGetMachine(machineId);
618
+ try {
619
+ const sessions = await machine.listSessions();
620
+ const match = resolveSessionId(sessions, sessionId);
621
+ const fullId = match.sessionId;
622
+ let metadata = {};
623
+ let activity = {};
624
+ try {
625
+ const svc = await server.getService(`svamp-session-${fullId}`);
626
+ const metaResult = await svc.getMetadata();
627
+ metadata = metaResult.metadata || {};
628
+ activity = await svc.getActivityState();
629
+ } catch {
630
+ }
631
+ const statusData = {
632
+ sessionId: fullId,
633
+ flavor: metadata.flavor || "claude",
634
+ name: metadata.name || "",
635
+ path: metadata.path || match.directory || "",
636
+ host: metadata.host || "",
637
+ lifecycleState: metadata.lifecycleState || "unknown",
638
+ active: activity.active ?? false,
639
+ thinking: activity.thinking ?? false,
640
+ startedBy: metadata.startedBy || match.startedBy || "",
641
+ summary: metadata.summary?.text || void 0,
642
+ claudeSessionId: metadata.claudeSessionId || void 0,
643
+ sessionLink: metadata.sessionLink?.url || void 0
644
+ };
645
+ if (opts?.json) {
646
+ console.log(formatJson(statusData));
647
+ } else {
648
+ console.log(formatSessionStatus(statusData));
649
+ }
650
+ } finally {
651
+ await server.disconnect();
652
+ }
653
+ }
654
+ async function sessionMessages(sessionId, machineId, opts) {
655
+ const { server, machine } = await connectAndGetMachine(machineId);
656
+ try {
657
+ const sessions = await machine.listSessions();
658
+ const match = resolveSessionId(sessions, sessionId);
659
+ const fullId = match.sessionId;
660
+ const svc = await server.getService(`svamp-session-${fullId}`);
661
+ const afterSeq = opts?.after ?? 0;
662
+ const apiLimit = opts?.limit ?? 1e3;
663
+ const { messages } = await svc.getMessages(afterSeq, apiLimit);
664
+ const toShow = opts?.last ? messages.slice(-opts.last) : messages;
665
+ if (toShow.length === 0) {
666
+ if (opts?.json) {
667
+ console.log(formatJson([]));
668
+ } else {
669
+ console.log("No messages yet.");
670
+ }
671
+ return;
672
+ }
673
+ if (opts?.json) {
674
+ const formatted = [];
675
+ for (const msg of toShow) {
676
+ const extracted = extractMessageText(msg);
677
+ if (extracted) {
678
+ formatted.push(extracted);
679
+ }
680
+ }
681
+ formatted.sort((a, b) => a.createdAt - b.createdAt);
682
+ console.log(formatJson(formatted));
683
+ } else {
684
+ for (const msg of toShow) {
685
+ renderMessage(msg);
686
+ }
687
+ }
688
+ } finally {
689
+ await server.disconnect();
690
+ }
691
+ }
692
+ async function sessionAttach(sessionId, machineId) {
693
+ const { server, machine } = await connectAndGetMachine(machineId);
694
+ const sessions = await machine.listSessions();
695
+ const match = resolveSessionId(sessions, sessionId);
696
+ const fullId = match.sessionId;
697
+ let svc;
698
+ try {
699
+ svc = await server.getService(`svamp-session-${fullId}`);
700
+ } catch (err) {
701
+ console.error(`Could not find session service: ${err.message}`);
702
+ await server.disconnect();
703
+ process.exit(1);
704
+ }
705
+ const { metadata } = await svc.getMetadata();
706
+ const flavor = metadata?.flavor || "claude";
707
+ const name = metadata?.name || fullId.slice(0, 8);
708
+ console.log(`Attached to ${flavor} session "${name}". Commands: /quit /abort /kill
709
+ `);
710
+ const seenMessageIds = /* @__PURE__ */ new Set();
711
+ let replayDone = false;
712
+ await svc.registerListener({
713
+ onUpdate: (update) => {
714
+ if (update.type === "new-message") {
715
+ const msg = update.message;
716
+ if (!msg?.id) return;
717
+ if (seenMessageIds.has(msg.id)) return;
718
+ seenMessageIds.add(msg.id);
719
+ if (!replayDone) return;
720
+ renderMessage(msg);
721
+ } else if (update.type === "activity") {
722
+ if (!replayDone) return;
723
+ if (update.thinking) {
724
+ process.stdout.write("\x1B[90m[thinking...]\x1B[0m\r");
725
+ } else if (!update.active) {
726
+ process.stdout.write("\n> ");
727
+ }
728
+ } else if (update.type === "update-session") ;
729
+ }
730
+ });
731
+ await new Promise((r) => setTimeout(r, 500));
732
+ replayDone = true;
733
+ console.log(`\x1B[90m(${seenMessageIds.size} messages in history)\x1B[0m`);
734
+ process.stdout.write("> ");
735
+ const readline = await import('readline');
736
+ const rl = readline.createInterface({
737
+ input: process.stdin,
738
+ output: process.stdout,
739
+ terminal: true
740
+ });
741
+ rl.on("line", async (line) => {
742
+ const trimmed = line.trim();
743
+ if (!trimmed) {
744
+ process.stdout.write("> ");
745
+ return;
746
+ }
747
+ if (trimmed === "/quit" || trimmed === "/detach") {
748
+ console.log("Detaching (session continues running)...");
749
+ rl.close();
750
+ await server.disconnect();
751
+ process.exit(0);
752
+ }
753
+ if (trimmed === "/abort" || trimmed === "/cancel") {
754
+ try {
755
+ await svc.abort();
756
+ console.log("Abort sent.");
757
+ } catch (err) {
758
+ console.error(`Abort failed: ${err.message}`);
759
+ }
760
+ process.stdout.write("> ");
761
+ return;
762
+ }
763
+ if (trimmed === "/kill") {
764
+ try {
765
+ await svc.killSession();
766
+ console.log("Session killed.");
767
+ } catch (err) {
768
+ console.error(`Kill failed: ${err.message}`);
769
+ }
770
+ rl.close();
771
+ await server.disconnect();
772
+ process.exit(0);
773
+ }
774
+ if (trimmed === "/info") {
775
+ try {
776
+ const { metadata: m } = await svc.getMetadata();
777
+ const act = await svc.getActivityState();
778
+ console.log(` Agent: ${m?.flavor || "claude"}, State: ${m?.lifecycleState || "?"}, Active: ${act?.active}, Thinking: ${act?.thinking}`);
779
+ } catch (err) {
780
+ console.error(`Info failed: ${err.message}`);
781
+ }
782
+ process.stdout.write("> ");
783
+ return;
784
+ }
785
+ try {
786
+ await svc.sendMessage(
787
+ JSON.stringify({
788
+ role: "user",
789
+ content: { type: "text", text: trimmed }
790
+ })
791
+ );
792
+ } catch (err) {
793
+ console.error(`Send failed: ${err.message}`);
794
+ process.stdout.write("> ");
795
+ }
796
+ });
797
+ rl.on("close", async () => {
798
+ await server.disconnect();
799
+ process.exit(0);
800
+ });
801
+ process.on("SIGINT", async () => {
802
+ console.log("\nDetaching (session continues running)...");
803
+ rl.close();
804
+ await server.disconnect();
805
+ process.exit(0);
806
+ });
807
+ }
808
+ async function sessionSend(sessionId, message, machineId, opts) {
809
+ const { server, machine } = await connectAndGetMachine(machineId);
810
+ try {
811
+ const sessions = await machine.listSessions();
812
+ const match = resolveSessionId(sessions, sessionId);
813
+ const fullId = match.sessionId;
814
+ const svc = await server.getService(`svamp-session-${fullId}`);
815
+ const result = await svc.sendMessage(
816
+ JSON.stringify({
817
+ role: "user",
818
+ content: { type: "text", text: message },
819
+ meta: { sentFrom: "svamp-cli" }
820
+ })
821
+ );
822
+ if (opts?.wait) {
823
+ const timeoutMs = (opts.timeout || 300) * 1e3;
824
+ await waitForBusyThenIdle(server, fullId, timeoutMs);
825
+ }
826
+ if (opts?.json) {
827
+ console.log(formatJson({
828
+ sessionId: fullId,
829
+ message,
830
+ sent: true,
831
+ seq: result.seq,
832
+ waited: !!opts.wait
833
+ }));
834
+ } else {
835
+ console.log(`Message sent to session ${fullId.slice(0, 8)} (seq: ${result.seq})`);
836
+ if (opts?.wait) {
837
+ console.log("Agent is idle.");
838
+ }
839
+ }
840
+ } finally {
841
+ await server.disconnect();
842
+ }
843
+ }
844
+ async function sessionWait(sessionId, machineId, opts) {
845
+ const { server, machine } = await connectAndGetMachine(machineId);
846
+ try {
847
+ const sessions = await machine.listSessions();
848
+ const match = resolveSessionId(sessions, sessionId);
849
+ const fullId = match.sessionId;
850
+ const timeoutMs = (opts?.timeout || 300) * 1e3;
851
+ await waitForIdle(server, fullId, timeoutMs);
852
+ console.log(`Session ${fullId.slice(0, 8)} is idle.`);
853
+ } catch (err) {
854
+ const msg = err instanceof Error ? err.message : String(err);
855
+ console.error(msg);
856
+ process.exitCode = 1;
857
+ } finally {
858
+ await server.disconnect();
859
+ }
860
+ }
861
+
862
+ export { connectAndGetMachine, renderMessage, resolveSessionId, sessionAttach, sessionInfo, sessionList, sessionMachines, sessionMessages, sessionSend, sessionSpawn, sessionStop, sessionWait };