svamp-cli 0.1.74 → 0.1.76

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.
@@ -1,4 +1,4 @@
1
- import { createServiceGroup, listServiceGroups, getServiceGroup, deleteServiceGroup, addBackend, removeBackend, addPort, removePort, renameSubdomain, getSandboxEnv } from './api-BRbsyqJ4.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { createServiceGroup, listServiceGroups, getServiceGroup, deleteServiceGroup, addBackend, removeBackend, addPort, removePort, renameSubdomain, getSandboxEnv } from './api-BRbsyqJ4.mjs';
2
2
 
3
3
  function getFlag(args, flag) {
4
4
  const idx = args.indexOf(flag);
@@ -304,6 +304,61 @@ Service is live:`);
304
304
  process.exit(1);
305
305
  }
306
306
  }
307
+ async function serviceServe(args) {
308
+ const positional = positionalArgs(args);
309
+ const name = positional[0];
310
+ const directory = positional[1] || ".";
311
+ const subdomain = getFlag(args, "--subdomain");
312
+ const healthPath = getFlag(args, "--health-path");
313
+ const healthIntervalStr = getFlag(args, "--health-interval");
314
+ const noListing = hasFlag(args, "--no-listing");
315
+ if (!name) {
316
+ console.error("Usage: svamp service serve <name> [directory] [--subdomain <sub>] [--no-listing]");
317
+ console.error(" directory defaults to current directory");
318
+ process.exit(1);
319
+ }
320
+ if (subdomain) validateSubdomain(subdomain);
321
+ const healthInterval = healthIntervalStr ? parseInt(healthIntervalStr, 10) : void 0;
322
+ try {
323
+ const { startStaticServer } = await import('./staticServer-B-S9sl6E.mjs');
324
+ const resolvedDir = require("path").resolve(directory);
325
+ console.log(`Serving ${resolvedDir}`);
326
+ const staticServer = await startStaticServer({
327
+ directory: resolvedDir,
328
+ listing: !noListing
329
+ });
330
+ console.log(`Static server listening on 127.0.0.1:${staticServer.port}`);
331
+ const ports = [staticServer.port];
332
+ const env = getSandboxEnv();
333
+ const group = await createServiceGroup(name, ports, {
334
+ subdomain,
335
+ healthPath,
336
+ healthInterval
337
+ });
338
+ if (env.sandboxId) {
339
+ const result = await addBackend(name);
340
+ console.log(`Backend added: ${result.sandbox_id} (${result.pod_ip})`);
341
+ console.log(`
342
+ Serving ${resolvedDir}:`);
343
+ for (const p of group.ports) {
344
+ console.log(` ${p.url}`);
345
+ }
346
+ } else {
347
+ console.log(`No SANDBOX_ID detected \u2014 starting reverse tunnel.`);
348
+ const { runTunnel } = await import('./tunnel-C2kqST5d.mjs');
349
+ await runTunnel(name, ports);
350
+ }
351
+ const cleanup = () => {
352
+ staticServer.close();
353
+ process.exit(0);
354
+ };
355
+ process.on("SIGINT", cleanup);
356
+ process.on("SIGTERM", cleanup);
357
+ } catch (err) {
358
+ console.error(`Error serving directory: ${err.message}`);
359
+ process.exit(1);
360
+ }
361
+ }
307
362
  async function serviceTunnel(args) {
308
363
  const positional = positionalArgs(args);
309
364
  const name = positional[0];
@@ -344,6 +399,8 @@ async function handleServiceCommand() {
344
399
  await serviceRename(commandArgs);
345
400
  } else if (sub === "expose") {
346
401
  await serviceExpose(commandArgs);
402
+ } else if (sub === "serve") {
403
+ await serviceServe(commandArgs);
347
404
  } else if (sub === "tunnel") {
348
405
  await serviceTunnel(commandArgs);
349
406
  } else {
@@ -367,6 +424,7 @@ Usage:
367
424
  svamp service remove-port <name> --port <port> Remove port from group
368
425
  svamp service rename <name> --port <port> --subdomain <sub> Rename subdomain of a port
369
426
  svamp service expose <name> --port <port> [--port <port2>] [options] Create + join (auto-detects tunnel)
427
+ svamp service serve <name> [directory] [options] Serve a directory as static files
370
428
  svamp service tunnel <name> --port <port> [--port <port2>] Tunnel local ports to service group
371
429
 
372
430
  Create/Expose options:
@@ -391,9 +449,15 @@ Environment variables (set by provisioner):
391
449
  SANDBOX_NAMESPACE User's sandbox namespace
392
450
  SANDBOX_ID This pod's sandbox ID
393
451
 
452
+ Serve options:
453
+ --no-listing Disable directory listing
454
+ --subdomain <sub> Custom subdomain (must contain at least 2 hyphens)
455
+
394
456
  Examples:
395
457
  svamp service expose my-api --port 8000
396
458
  svamp service expose my-api --port 8000 --port 3000 --health-path /health
459
+ svamp service serve my-site ./dist
460
+ svamp service serve my-site (serves current directory)
397
461
  svamp service add-port my-api --port 5000
398
462
  svamp service remove-port my-api --port 5000
399
463
  svamp service list
@@ -402,4 +466,4 @@ Examples:
402
466
  `.trim());
403
467
  }
404
468
 
405
- export { handleServiceCommand, serviceAddBackend, serviceAddPort, serviceCreate, serviceDelete, serviceExpose, serviceInfo, serviceList, serviceRemoveBackend, serviceRemovePort, serviceRename, serviceTunnel };
469
+ export { handleServiceCommand, serviceAddBackend, serviceAddPort, serviceCreate, serviceDelete, serviceExpose, serviceInfo, serviceList, serviceRemoveBackend, serviceRemovePort, serviceRename, serviceServe, serviceTunnel };
@@ -1,5 +1,5 @@
1
1
  import os__default from 'os';
2
- import fs from 'fs';
2
+ import fs__default from 'fs';
3
3
  import { join, resolve, relative } from 'path';
4
4
 
5
5
  const SKILLS_SERVER = process.env.HYPHA_SKILLS_SERVER || "https://hypha.aicell.io";
@@ -128,8 +128,8 @@ function normalizeSkill(item) {
128
128
  const SVAMP_HOME = process.env.SVAMP_HOME || join(os__default.homedir(), ".svamp");
129
129
  const ENV_FILE = join(SVAMP_HOME, ".env");
130
130
  function loadDotEnv() {
131
- if (!fs.existsSync(ENV_FILE)) return;
132
- const lines = fs.readFileSync(ENV_FILE, "utf-8").split("\n");
131
+ if (!fs__default.existsSync(ENV_FILE)) return;
132
+ const lines = fs__default.readFileSync(ENV_FILE, "utf-8").split("\n");
133
133
  for (const line of lines) {
134
134
  const trimmed = line.trim();
135
135
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -143,7 +143,7 @@ function loadDotEnv() {
143
143
  }
144
144
  }
145
145
  function ensureSkillsDir() {
146
- fs.mkdirSync(SKILLS_DIR, { recursive: true });
146
+ fs__default.mkdirSync(SKILLS_DIR, { recursive: true });
147
147
  }
148
148
  function formatTable(rows, widths) {
149
149
  if (rows.length === 0) return "";
@@ -190,7 +190,7 @@ async function skillsInstall(skillName, opts) {
190
190
  process.exit(1);
191
191
  }
192
192
  const targetDir = join(SKILLS_DIR, skillName);
193
- if (fs.existsSync(targetDir) && !opts?.force) {
193
+ if (fs__default.existsSync(targetDir) && !opts?.force) {
194
194
  console.error(`Skill "${skillName}" is already installed at ${targetDir}`);
195
195
  console.error("Use --force to overwrite.");
196
196
  process.exit(1);
@@ -223,10 +223,10 @@ async function skillsInstall(skillName, opts) {
223
223
  process.exit(1);
224
224
  }
225
225
  ensureSkillsDir();
226
- if (fs.existsSync(targetDir) && opts?.force) {
227
- fs.rmSync(targetDir, { recursive: true, force: true });
226
+ if (fs__default.existsSync(targetDir) && opts?.force) {
227
+ fs__default.rmSync(targetDir, { recursive: true, force: true });
228
228
  }
229
- fs.mkdirSync(targetDir, { recursive: true });
229
+ fs__default.mkdirSync(targetDir, { recursive: true });
230
230
  let downloaded = 0;
231
231
  const errors = [];
232
232
  for (const filePath of files) {
@@ -237,23 +237,23 @@ async function skillsInstall(skillName, opts) {
237
237
  errors.push(` ${filePath}: path outside skill directory (blocked)`);
238
238
  continue;
239
239
  }
240
- fs.mkdirSync(join(localPath, ".."), { recursive: true });
241
- fs.writeFileSync(localPath, content, "utf-8");
240
+ fs__default.mkdirSync(join(localPath, ".."), { recursive: true });
241
+ fs__default.writeFileSync(localPath, content, "utf-8");
242
242
  downloaded++;
243
243
  } catch (err) {
244
244
  errors.push(` ${filePath}: ${err.message}`);
245
245
  }
246
246
  }
247
247
  if (errors.length > 0) {
248
- fs.rmSync(targetDir, { recursive: true, force: true });
248
+ fs__default.rmSync(targetDir, { recursive: true, force: true });
249
249
  console.error(`Installation failed \u2014 ${errors.length} file(s) could not be downloaded:`);
250
250
  errors.forEach((e) => console.error(e));
251
251
  console.error("Partial installation cleaned up. Run the command again to retry.");
252
252
  process.exit(1);
253
253
  }
254
254
  const skillMdPath = join(targetDir, "SKILL.md");
255
- if (fs.existsSync(skillMdPath)) {
256
- const content = fs.readFileSync(skillMdPath, "utf-8");
255
+ if (fs__default.existsSync(skillMdPath)) {
256
+ const content = fs__default.readFileSync(skillMdPath, "utf-8");
257
257
  const fm = parseFrontmatter(content);
258
258
  if (!fm) {
259
259
  console.error("Warning: Installed SKILL.md has invalid or missing frontmatter (name/description).");
@@ -280,7 +280,7 @@ async function collectAllFiles(skillAlias, dir = "") {
280
280
  }
281
281
  async function skillsList() {
282
282
  ensureSkillsDir();
283
- const entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((e) => e.isDirectory());
283
+ const entries = fs__default.readdirSync(SKILLS_DIR, { withFileTypes: true }).filter((e) => e.isDirectory());
284
284
  if (entries.length === 0) {
285
285
  console.log("No skills installed.");
286
286
  console.log(`Install one with: svamp skills install <name>`);
@@ -293,11 +293,11 @@ async function skillsList() {
293
293
  const skillMd = join(skillDir, "SKILL.md");
294
294
  let description = "-";
295
295
  let status = "ok";
296
- if (!fs.existsSync(skillMd)) {
296
+ if (!fs__default.existsSync(skillMd)) {
297
297
  status = "missing SKILL.md";
298
298
  } else {
299
299
  try {
300
- const content = fs.readFileSync(skillMd, "utf-8");
300
+ const content = fs__default.readFileSync(skillMd, "utf-8");
301
301
  const fm = parseFrontmatter(content);
302
302
  if (fm) {
303
303
  description = fm.description.length > 45 ? fm.description.slice(0, 42) + "..." : fm.description;
@@ -318,34 +318,34 @@ async function skillsRemove(name) {
318
318
  process.exit(1);
319
319
  }
320
320
  const targetDir = join(SKILLS_DIR, name);
321
- if (!fs.existsSync(targetDir)) {
321
+ if (!fs__default.existsSync(targetDir)) {
322
322
  console.error(`Skill "${name}" is not installed.`);
323
323
  process.exit(1);
324
324
  }
325
- if (!fs.statSync(targetDir).isDirectory()) {
325
+ if (!fs__default.statSync(targetDir).isDirectory()) {
326
326
  console.error(`"${name}" is not a skill directory.`);
327
327
  process.exit(1);
328
328
  }
329
- fs.rmSync(targetDir, { recursive: true, force: true });
329
+ fs__default.rmSync(targetDir, { recursive: true, force: true });
330
330
  console.log(`Removed skill "${name}" from ${targetDir}`);
331
331
  }
332
332
  async function skillsPublish(skillPath, opts) {
333
333
  const absPath = resolve(skillPath);
334
- if (!fs.existsSync(absPath)) {
334
+ if (!fs__default.existsSync(absPath)) {
335
335
  console.error(`Path does not exist: ${absPath}`);
336
336
  process.exit(1);
337
337
  }
338
- if (!fs.statSync(absPath).isDirectory()) {
338
+ if (!fs__default.statSync(absPath).isDirectory()) {
339
339
  console.error(`Not a directory: ${absPath}`);
340
340
  process.exit(1);
341
341
  }
342
342
  const skillMdPath = join(absPath, "SKILL.md");
343
- if (!fs.existsSync(skillMdPath)) {
343
+ if (!fs__default.existsSync(skillMdPath)) {
344
344
  console.error(`No SKILL.md found in ${absPath}`);
345
345
  console.error("A valid skill must contain a SKILL.md file with name and description frontmatter.");
346
346
  process.exit(1);
347
347
  }
348
- const skillMdContent = fs.readFileSync(skillMdPath, "utf-8");
348
+ const skillMdContent = fs__default.readFileSync(skillMdPath, "utf-8");
349
349
  const manifest = parseFrontmatter(skillMdContent);
350
350
  if (!manifest) {
351
351
  console.error('SKILL.md must have YAML frontmatter with "name" and "description" fields.');
@@ -455,7 +455,7 @@ async function skillsPublish(skillPath, opts) {
455
455
  for (const relPath of filesToUpload) {
456
456
  try {
457
457
  const fullPath = join(absPath, relPath);
458
- const content = fs.readFileSync(fullPath);
458
+ const content = fs__default.readFileSync(fullPath);
459
459
  const putUrl = await am.put_file({
460
460
  artifact_id: artifactId,
461
461
  file_path: relPath,
@@ -515,7 +515,7 @@ async function skillsPublish(skillPath, opts) {
515
515
  function collectLocalFiles(dir, base) {
516
516
  const root = base || dir;
517
517
  const result = [];
518
- const entries = fs.readdirSync(dir, { withFileTypes: true });
518
+ const entries = fs__default.readdirSync(dir, { withFileTypes: true });
519
519
  for (const entry of entries) {
520
520
  const fullPath = join(dir, entry.name);
521
521
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
@@ -1,9 +1,8 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
- import { randomUUID } from 'node:crypto';
4
3
  import { resolve, join } from 'node:path';
5
4
  import os from 'node:os';
6
- import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-6QgabuQN.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-BnFGIK0c.mjs';
7
6
  import 'os';
8
7
  import 'fs/promises';
9
8
  import 'fs';
@@ -11,6 +10,7 @@ import 'path';
11
10
  import 'url';
12
11
  import 'child_process';
13
12
  import 'crypto';
13
+ import 'node:crypto';
14
14
  import '@agentclientprotocol/sdk';
15
15
  import '@modelcontextprotocol/sdk/client/index.js';
16
16
  import '@modelcontextprotocol/sdk/client/stdio.js';
@@ -1706,75 +1706,31 @@ async function sessionRalphStatus(sessionIdPartial, machineId) {
1706
1706
  await server.disconnect();
1707
1707
  }
1708
1708
  }
1709
- async function sessionQueueAdd(sessionIdPartial, message, machineId) {
1709
+ async function sessionInboxSend(sessionIdPartial, body, machineId, opts) {
1710
1710
  const { server, machine } = await connectAndGetMachine(machineId);
1711
1711
  try {
1712
1712
  const sessions = await machine.listSessions();
1713
1713
  const match = resolveSessionId(sessions, sessionIdPartial);
1714
1714
  const fullId = match.sessionId;
1715
1715
  const svc = await server.getService(`svamp-session-${fullId}`);
1716
- const newMsg = {
1717
- messageId: `msg-${randomUUID()}`,
1718
- body: message,
1719
- timestamp: Date.now(),
1720
- read: false
1721
- };
1722
- await svc.sendInboxMessage(newMsg);
1723
- console.log(`Message queued on session ${fullId.slice(0, 8)}`);
1724
- } finally {
1725
- await server.disconnect();
1726
- }
1727
- }
1728
- async function sessionQueueList(sessionIdPartial, machineId) {
1729
- return sessionInboxList(sessionIdPartial, machineId);
1730
- }
1731
- async function sessionQueueClear(sessionIdPartial, machineId) {
1732
- return sessionInboxClear(sessionIdPartial, machineId);
1733
- }
1734
- async function sessionInboxSend(targetSessionId, machineId, opts) {
1735
- if (!opts?.subject || !opts?.body) {
1736
- console.error('Usage: svamp session inbox send <target-session-id> --subject "..." --body "..."');
1737
- process.exit(1);
1738
- }
1739
- const { server, machine } = await connectAndGetMachine(machineId);
1740
- try {
1741
- const sessions = await machine.listSessions();
1742
- const match = resolveSessionId(sessions, targetSessionId);
1743
- const fullTargetId = match.sessionId;
1744
- const senderSessionId = process.env.SVAMP_SESSION_ID || "cli";
1745
- const senderEmail = process.env.USER_EMAIL || process.env.USER || os.userInfo().username;
1746
- const from = senderSessionId === "cli" ? `user:${senderEmail}` : `agent:${senderSessionId}`;
1716
+ const { randomUUID } = await import('node:crypto');
1747
1717
  const message = {
1748
- messageId: `msg-${randomUUID()}`,
1749
- from,
1750
- fromSession: senderSessionId,
1751
- to: fullTargetId,
1752
- subject: opts.subject,
1753
- body: opts.body,
1754
- urgency: opts.urgency || "normal",
1755
- replyTo: opts.replyTo,
1756
- cc: opts.cc,
1718
+ messageId: randomUUID(),
1719
+ body,
1757
1720
  timestamp: Date.now(),
1758
1721
  read: false,
1759
- threadId: opts.replyTo ? void 0 : void 0
1760
- // will be set by reply logic
1722
+ from: `cli:${os.userInfo().username}`,
1723
+ to: fullId,
1724
+ subject: opts?.subject,
1725
+ urgency: opts?.urgency || "normal",
1726
+ replyTo: opts?.replyTo,
1727
+ threadId: opts?.threadId
1761
1728
  };
1762
- const svc = await server.getService(`svamp-session-${fullTargetId}`);
1763
1729
  const result = await svc.sendInboxMessage(message);
1764
- if (opts.wait) {
1765
- const timeoutMs = (opts.timeout || 300) * 1e3;
1766
- await waitForBusyThenIdle(server, fullTargetId, timeoutMs);
1767
- }
1768
- if (opts.json) {
1769
- console.log(formatJson({
1770
- messageId: message.messageId,
1771
- targetSessionId: fullTargetId,
1772
- subject: opts.subject,
1773
- urgency: message.urgency,
1774
- delivered: result.delivered
1775
- }));
1730
+ if (opts?.json) {
1731
+ console.log(formatJson({ sessionId: fullId, messageId: result.messageId, sent: true }));
1776
1732
  } else {
1777
- console.log(`Inbox message sent to session ${fullTargetId.slice(0, 8)} [${message.urgency}]: "${opts.subject}"`);
1733
+ console.log(`Inbox message sent to session ${fullId.slice(0, 8)} (id: ${result.messageId.slice(0, 8)})`);
1778
1734
  }
1779
1735
  } finally {
1780
1736
  await server.disconnect();
@@ -1787,114 +1743,97 @@ async function sessionInboxList(sessionIdPartial, machineId, opts) {
1787
1743
  const match = resolveSessionId(sessions, sessionIdPartial);
1788
1744
  const fullId = match.sessionId;
1789
1745
  const svc = await server.getService(`svamp-session-${fullId}`);
1790
- const result = await svc.getInbox({
1791
- unreadOnly: opts?.unread || false,
1792
- limit: opts?.limit || 50
1793
- });
1794
- if (opts?.json) ; else {
1795
- const msgs = result.messages || [];
1796
- if (msgs.length === 0) {
1797
- console.log("Inbox is empty.");
1798
- return;
1799
- }
1800
- console.log(`Inbox for session ${fullId.slice(0, 8)} (${msgs.length} message(s), ${result.total} total):
1746
+ const result = await svc.getInbox({ unread: opts?.unread, limit: opts?.limit });
1747
+ const messages = result.messages;
1748
+ if (opts?.json) {
1749
+ console.log(formatJson({ sessionId: fullId, messages }));
1750
+ return;
1751
+ }
1752
+ if (messages.length === 0) {
1753
+ console.log(`Inbox for session ${fullId.slice(0, 8)} is empty.`);
1754
+ return;
1755
+ }
1756
+ const unreadCount = messages.filter((m) => !m.read).length;
1757
+ console.log(`Inbox for session ${fullId.slice(0, 8)} (${messages.length} message(s), ${unreadCount} unread):
1758
+ `);
1759
+ for (const msg of messages) {
1760
+ const status = msg.read ? " " : "\u25CF";
1761
+ const from = msg.from ? ` from ${msg.from}` : "";
1762
+ const subject = msg.subject ? ` \u2014 ${msg.subject}` : "";
1763
+ const urgencyTag = msg.urgency === "urgent" ? " [URGENT]" : "";
1764
+ const date = new Date(msg.timestamp).toLocaleString();
1765
+ const preview = msg.body.length > 100 ? msg.body.slice(0, 97) + "..." : msg.body;
1766
+ console.log(` ${status} ${msg.messageId.slice(0, 8)}${urgencyTag}${from}${subject}`);
1767
+ console.log(` ${date}`);
1768
+ console.log(` ${preview}
1801
1769
  `);
1802
- for (const msg of msgs) {
1803
- const readMark = msg.read ? " " : "*";
1804
- const date = new Date(msg.timestamp).toLocaleString();
1805
- console.log(`${readMark} [${msg.messageId.slice(0, 12)}] ${date}`);
1806
- console.log(` From: ${msg.from} | Subject: ${msg.subject} | Urgency: ${msg.urgency}`);
1807
- if (msg.replyTo) console.log(` Reply-To: ${msg.replyTo.slice(0, 12)}`);
1808
- console.log(` ${msg.body.slice(0, 120)}${msg.body.length > 120 ? "..." : ""}`);
1809
- console.log();
1810
- }
1811
1770
  }
1812
1771
  } finally {
1813
1772
  await server.disconnect();
1814
1773
  }
1815
1774
  }
1816
- async function sessionInboxRead(sessionIdPartial, messageId, machineId, opts) {
1775
+ async function sessionInboxRead(sessionIdPartial, messageId, machineId) {
1817
1776
  const { server, machine } = await connectAndGetMachine(machineId);
1818
1777
  try {
1819
1778
  const sessions = await machine.listSessions();
1820
1779
  const match = resolveSessionId(sessions, sessionIdPartial);
1821
1780
  const fullId = match.sessionId;
1822
1781
  const svc = await server.getService(`svamp-session-${fullId}`);
1823
- const result = await svc.getInbox({});
1824
- const msg = (result.messages || []).find(
1825
- (m) => m.messageId === messageId || m.messageId.startsWith(messageId)
1826
- );
1782
+ const result = await svc.getInbox();
1783
+ const msg = result.messages.find((m) => m.messageId === messageId || m.messageId.startsWith(messageId));
1827
1784
  if (!msg) {
1828
1785
  console.error(`Message ${messageId} not found in inbox.`);
1829
1786
  process.exit(1);
1830
1787
  }
1831
- await svc.markInboxRead([msg.messageId]);
1832
- if (opts?.json) {
1833
- console.log(formatJson(msg));
1834
- } else {
1835
- console.log(`Message-ID: ${msg.messageId}`);
1836
- console.log(`From: ${msg.from}`);
1837
- console.log(`From-Session: ${msg.fromSession}`);
1838
- console.log(`Subject: ${msg.subject}`);
1839
- console.log(`Urgency: ${msg.urgency}`);
1840
- console.log(`Date: ${new Date(msg.timestamp).toLocaleString()}`);
1841
- if (msg.replyTo) console.log(`Reply-To: ${msg.replyTo}`);
1842
- if (msg.cc?.length) console.log(`CC: ${msg.cc.join(", ")}`);
1843
- console.log(`---`);
1844
- console.log(msg.body);
1788
+ if (!msg.read) {
1789
+ await svc.markInboxRead(msg.messageId);
1845
1790
  }
1791
+ console.log(`From: ${msg.from || "(unknown)"}`);
1792
+ if (msg.subject) console.log(`Subject: ${msg.subject}`);
1793
+ console.log(`Date: ${new Date(msg.timestamp).toLocaleString()}`);
1794
+ if (msg.urgency === "urgent") console.log("Urgency: URGENT");
1795
+ if (msg.replyTo) console.log(`Reply-To: ${msg.replyTo.slice(0, 8)}`);
1796
+ if (msg.threadId) console.log(`Thread: ${msg.threadId.slice(0, 8)}`);
1797
+ console.log(`
1798
+ ${msg.body}`);
1846
1799
  } finally {
1847
1800
  await server.disconnect();
1848
1801
  }
1849
1802
  }
1850
- async function sessionInboxReply(sessionIdPartial, messageId, machineId, opts) {
1851
- if (!opts?.body) {
1852
- console.error('Usage: svamp session inbox reply <session-id> <message-id> --body "..."');
1853
- process.exit(1);
1854
- }
1803
+ async function sessionInboxReply(sessionIdPartial, messageId, body, machineId) {
1855
1804
  const { server, machine } = await connectAndGetMachine(machineId);
1856
1805
  try {
1857
1806
  const sessions = await machine.listSessions();
1858
1807
  const match = resolveSessionId(sessions, sessionIdPartial);
1859
1808
  const fullId = match.sessionId;
1860
1809
  const svc = await server.getService(`svamp-session-${fullId}`);
1861
- const inboxResult = await svc.getInbox({});
1862
- const originalMsg = (inboxResult.messages || []).find(
1863
- (m) => m.messageId === messageId || m.messageId.startsWith(messageId)
1864
- );
1865
- if (!originalMsg) {
1866
- console.error(`Message ${messageId} not found in inbox. Cannot reply.`);
1810
+ const result = await svc.getInbox();
1811
+ const original = result.messages.find((m) => m.messageId === messageId || m.messageId.startsWith(messageId));
1812
+ if (!original) {
1813
+ console.error(`Message ${messageId} not found in inbox.`);
1867
1814
  process.exit(1);
1868
1815
  }
1869
- const senderSessionId = process.env.SVAMP_SESSION_ID || fullId;
1870
- const senderEmail = process.env.USER_EMAIL || process.env.USER || os.userInfo().username;
1871
- const from = senderSessionId === "cli" ? `user:${senderEmail}` : `agent:${senderSessionId}`;
1816
+ if (!original.fromSession) {
1817
+ console.error("Cannot reply: original message has no fromSession.");
1818
+ process.exit(1);
1819
+ }
1820
+ const targetSvc = await server.getService(`svamp-session-${original.fromSession}`);
1821
+ const { randomUUID } = await import('node:crypto');
1872
1822
  const reply = {
1873
- messageId: `msg-${randomUUID()}`,
1874
- from,
1875
- fromSession: senderSessionId,
1876
- to: originalMsg.fromSession,
1877
- subject: `Re: ${originalMsg.subject}`,
1878
- body: opts.body,
1879
- urgency: opts.urgency || "normal",
1880
- replyTo: originalMsg.messageId,
1881
- threadId: originalMsg.threadId || originalMsg.messageId,
1823
+ messageId: randomUUID(),
1824
+ body,
1882
1825
  timestamp: Date.now(),
1883
- read: false
1826
+ read: false,
1827
+ from: `session:${fullId}`,
1828
+ fromSession: fullId,
1829
+ to: original.fromSession,
1830
+ subject: original.subject ? `Re: ${original.subject}` : void 0,
1831
+ urgency: "normal",
1832
+ replyTo: original.messageId,
1833
+ threadId: original.threadId || original.messageId
1884
1834
  };
1885
- const targetSvc = await server.getService(`svamp-session-${originalMsg.fromSession}`);
1886
- const result = await targetSvc.sendInboxMessage(reply);
1887
- if (opts.json) {
1888
- console.log(formatJson({
1889
- messageId: reply.messageId,
1890
- targetSessionId: originalMsg.fromSession,
1891
- subject: reply.subject,
1892
- urgency: reply.urgency,
1893
- delivered: result.delivered
1894
- }));
1895
- } else {
1896
- console.log(`Reply sent to session ${originalMsg.fromSession.slice(0, 8)}: "${reply.subject}"`);
1897
- }
1835
+ const sendResult = await targetSvc.sendInboxMessage(reply);
1836
+ console.log(`Reply sent to session ${original.fromSession.slice(0, 8)} (id: ${sendResult.messageId.slice(0, 8)})`);
1898
1837
  } finally {
1899
1838
  await server.disconnect();
1900
1839
  }
@@ -1906,14 +1845,11 @@ async function sessionInboxClear(sessionIdPartial, machineId, opts) {
1906
1845
  const match = resolveSessionId(sessions, sessionIdPartial);
1907
1846
  const fullId = match.sessionId;
1908
1847
  const svc = await server.getService(`svamp-session-${fullId}`);
1909
- const messageIds = opts?.messageId ? [opts.messageId] : void 0;
1910
- const result = await svc.clearInbox(messageIds);
1911
- if (opts?.json) ; else {
1912
- console.log(`Cleared ${result.removed} message(s) from inbox on session ${fullId.slice(0, 8)}`);
1913
- }
1848
+ const result = await svc.clearInbox({ all: opts?.all });
1849
+ console.log(`Cleared ${result.removed} message(s) from inbox on session ${fullId.slice(0, 8)}`);
1914
1850
  } finally {
1915
1851
  await server.disconnect();
1916
1852
  }
1917
1853
  }
1918
1854
 
1919
- export { connectAndGetMachine, createWorktree, generateWorktreeName, machineExec, machineInfo, machineLs, machineShare, parseShareArg, renderMessage, resolveSessionId, sessionApprove, sessionAttach, sessionDeny, sessionInboxClear, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxSend, sessionInfo, sessionList, sessionMachines, sessionMessages, sessionQueueAdd, sessionQueueClear, sessionQueueList, sessionRalphCancel, sessionRalphStart, sessionRalphStatus, sessionSend, sessionShare, sessionSpawn, sessionStop, sessionWait };
1855
+ export { connectAndGetMachine, createWorktree, generateWorktreeName, machineExec, machineInfo, machineLs, machineShare, parseShareArg, renderMessage, resolveSessionId, sessionApprove, sessionAttach, sessionDeny, sessionInboxClear, sessionInboxList, sessionInboxRead, sessionInboxReply, sessionInboxSend, sessionInfo, sessionList, sessionMachines, sessionMessages, sessionRalphCancel, sessionRalphStart, sessionRalphStatus, sessionSend, sessionShare, sessionSpawn, sessionStop, sessionWait };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-6QgabuQN.mjs';
1
+ export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-BnFGIK0c.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.1.74";
2
+ var version = "0.1.76";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -19,7 +19,7 @@ var exports$1 = {
19
19
  var scripts = {
20
20
  build: "rm -rf dist && tsc --noEmit && pkgroll",
21
21
  typecheck: "tsc --noEmit",
22
- test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs",
22
+ test: "npx tsx test/test-authorize.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-ralph-loop.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs",
23
23
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
24
  dev: "tsx src/cli.ts",
25
25
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -28,7 +28,8 @@ var scripts = {
28
28
  var dependencies = {
29
29
  "@agentclientprotocol/sdk": "^0.14.1",
30
30
  "@modelcontextprotocol/sdk": "^1.25.3",
31
- "hypha-rpc": "0.21.29",
31
+ "hypha-rpc": "0.21.34",
32
+ "node-pty": "^1.1.0",
32
33
  ws: "^8.18.0",
33
34
  yaml: "^2.8.2",
34
35
  zod: "^3.24.4"
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
2
2
  import os from 'node:os';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { mkdirSync, writeFileSync, existsSync, unlinkSync, readFileSync, watch } from 'node:fs';
5
- import { c as connectToHypha, a as registerSessionService } from './run-6QgabuQN.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-BnFGIK0c.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';