svamp-cli 0.1.64 → 0.1.65

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.
@@ -0,0 +1,156 @@
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ const SVAMP_HOME = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
6
+ function getConfigPath(sessionId) {
7
+ const cwd = process.cwd();
8
+ return join(cwd, ".svamp", sessionId, "config.json");
9
+ }
10
+ function readConfig(configPath) {
11
+ try {
12
+ if (existsSync(configPath)) return JSON.parse(readFileSync(configPath, "utf-8"));
13
+ } catch {
14
+ }
15
+ return {};
16
+ }
17
+ function writeConfig(configPath, config) {
18
+ mkdirSync(dirname(configPath), { recursive: true });
19
+ const tmp = configPath + ".tmp";
20
+ writeFileSync(tmp, JSON.stringify(config, null, 2));
21
+ renameSync(tmp, configPath);
22
+ }
23
+ async function connectAndEmit(event) {
24
+ const ENV_FILE = join(SVAMP_HOME, ".env");
25
+ if (existsSync(ENV_FILE)) {
26
+ const lines = readFileSync(ENV_FILE, "utf-8").split("\n");
27
+ for (const line of lines) {
28
+ const m = line.match(/^([A-Z_]+)=(.*)/);
29
+ if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, "");
30
+ }
31
+ }
32
+ const serverUrl = process.env.HYPHA_SERVER_URL;
33
+ const token = process.env.HYPHA_TOKEN;
34
+ if (!serverUrl || !token) {
35
+ console.error('No Hypha credentials. Run "svamp login" first.');
36
+ process.exit(1);
37
+ }
38
+ const origLog = console.log;
39
+ const origWarn = console.warn;
40
+ const origInfo = console.info;
41
+ console.log = () => {
42
+ };
43
+ console.warn = () => {
44
+ };
45
+ console.info = () => {
46
+ };
47
+ let server;
48
+ try {
49
+ const mod = await import('hypha-rpc');
50
+ const connectToServer = mod.connectToServer || mod.default?.connectToServer;
51
+ server = await connectToServer({ server_url: serverUrl, token, name: "svamp-agent-emit" });
52
+ } catch (err) {
53
+ console.log = origLog;
54
+ console.warn = origWarn;
55
+ console.info = origInfo;
56
+ console.error(`Failed to connect to Hypha: ${err.message}`);
57
+ process.exit(1);
58
+ }
59
+ console.log = origLog;
60
+ console.warn = origWarn;
61
+ console.info = origInfo;
62
+ await server.emit({ ...event, to: "*" });
63
+ await server.disconnect();
64
+ }
65
+ async function sessionSetTitle(title) {
66
+ const sessionId = process.env.SVAMP_SESSION_ID;
67
+ if (!sessionId) {
68
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
69
+ process.exit(1);
70
+ }
71
+ const configPath = getConfigPath(sessionId);
72
+ const config = readConfig(configPath);
73
+ config.title = title.trim();
74
+ writeConfig(configPath, config);
75
+ console.log(`Session title set: "${title.trim()}"`);
76
+ }
77
+ async function sessionSetLink(url, label) {
78
+ const sessionId = process.env.SVAMP_SESSION_ID;
79
+ if (!sessionId) {
80
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
81
+ process.exit(1);
82
+ }
83
+ const resolvedLabel = label?.trim() || (() => {
84
+ try {
85
+ return new URL(url).hostname;
86
+ } catch {
87
+ return "View";
88
+ }
89
+ })();
90
+ const configPath = getConfigPath(sessionId);
91
+ const config = readConfig(configPath);
92
+ config.session_link = { url: url.trim(), label: resolvedLabel };
93
+ writeConfig(configPath, config);
94
+ console.log(`Session link set: "${resolvedLabel}" \u2192 ${url.trim()}`);
95
+ }
96
+ async function sessionNotify(message, level = "info") {
97
+ const sessionId = process.env.SVAMP_SESSION_ID;
98
+ if (!sessionId) {
99
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
100
+ process.exit(1);
101
+ }
102
+ await connectAndEmit({
103
+ type: "svamp:session-notify",
104
+ data: { sessionId, message, level, timestamp: Date.now() }
105
+ });
106
+ console.log(`Notification sent [${level}]: ${message}`);
107
+ }
108
+ async function sessionBroadcast(action, args) {
109
+ const sessionId = process.env.SVAMP_SESSION_ID;
110
+ if (!sessionId) {
111
+ console.error("SVAMP_SESSION_ID not set. This command must be run inside a Svamp session.");
112
+ process.exit(1);
113
+ }
114
+ let payload = { action };
115
+ if (action === "open-canvas") {
116
+ const url = args[0];
117
+ if (!url) {
118
+ console.error("Usage: svamp session broadcast open-canvas <url> [label]");
119
+ process.exit(1);
120
+ }
121
+ const label = args[1] || (() => {
122
+ try {
123
+ return new URL(url).hostname;
124
+ } catch {
125
+ return "View";
126
+ }
127
+ })();
128
+ payload = { action, url, label };
129
+ } else if (action === "close-canvas") ; else if (action === "toast") {
130
+ const message = args[0];
131
+ if (!message) {
132
+ console.error("Usage: svamp session broadcast toast <message>");
133
+ process.exit(1);
134
+ }
135
+ payload = { action, message };
136
+ } else {
137
+ console.error(`Unknown broadcast action: ${action}`);
138
+ console.error("Available actions: open-canvas, close-canvas, toast");
139
+ process.exit(1);
140
+ }
141
+ await connectAndEmit({
142
+ type: "svamp:session-broadcast",
143
+ data: { sessionId, ...payload }
144
+ });
145
+ console.log(`Broadcast sent: ${action}`);
146
+ }
147
+ async function machineNotify(message, level = "info") {
148
+ const machineId = process.env.SVAMP_MACHINE_ID;
149
+ await connectAndEmit({
150
+ type: "svamp:machine-notify",
151
+ data: { machineId, message, level, timestamp: Date.now() }
152
+ });
153
+ console.log(`Machine notification sent [${level}]: ${message}`);
154
+ }
155
+
156
+ export { machineNotify, sessionBroadcast, sessionNotify, sessionSetLink, sessionSetTitle };
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-BImPgXHd.mjs';
1
+ import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-DPhSmbr1.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -109,7 +109,7 @@ async function main() {
109
109
  const { handleServiceCommand } = await import('./commands-CF32XIau.mjs');
110
110
  await handleServiceCommand();
111
111
  } else if (subcommand === "process" || subcommand === "proc") {
112
- const { processCommand } = await import('./commands-BfMlD9o4.mjs');
112
+ const { processCommand } = await import('./commands-Dc_6JdE_.mjs');
113
113
  let machineId;
114
114
  const processArgs = args.slice(1);
115
115
  const mIdx = processArgs.findIndex((a) => a === "--machine" || a === "-m");
@@ -127,7 +127,7 @@ async function main() {
127
127
  } else if (!subcommand || subcommand === "start") {
128
128
  await handleInteractiveCommand();
129
129
  } else if (subcommand === "--version" || subcommand === "-v") {
130
- const pkg = await import('./package-Dg0hQJRC.mjs').catch(() => ({ default: { version: "unknown" } }));
130
+ const pkg = await import('./package-CvCDrFPH.mjs').catch(() => ({ default: { version: "unknown" } }));
131
131
  console.log(`svamp version: ${pkg.default.version}`);
132
132
  } else {
133
133
  console.error(`Unknown command: ${subcommand}`);
@@ -136,7 +136,7 @@ async function main() {
136
136
  }
137
137
  }
138
138
  async function handleInteractiveCommand() {
139
- const { runInteractive } = await import('./run-C7VxH4X8.mjs');
139
+ const { runInteractive } = await import('./run-DTwMJ7dx.mjs');
140
140
  const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
141
141
  let directory = process.cwd();
142
142
  let resumeSessionId;
@@ -181,7 +181,7 @@ async function handleAgentCommand() {
181
181
  return;
182
182
  }
183
183
  if (agentArgs[0] === "list") {
184
- const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-BImPgXHd.mjs').then(function (n) { return n.i; });
184
+ const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.i; });
185
185
  console.log("Known agents:");
186
186
  for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
187
187
  console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
@@ -193,7 +193,7 @@ async function handleAgentCommand() {
193
193
  console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
194
194
  return;
195
195
  }
196
- const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-BImPgXHd.mjs').then(function (n) { return n.i; });
196
+ const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.i; });
197
197
  let cwd = process.cwd();
198
198
  const filteredArgs = [];
199
199
  for (let i = 0; i < agentArgs.length; i++) {
@@ -217,12 +217,12 @@ async function handleAgentCommand() {
217
217
  console.log(`Starting ${config.agentName} agent in ${cwd}...`);
218
218
  let backend;
219
219
  if (KNOWN_MCP_AGENTS[config.agentName]) {
220
- const { CodexMcpBackend } = await import('./run-BImPgXHd.mjs').then(function (n) { return n.j; });
220
+ const { CodexMcpBackend } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.j; });
221
221
  backend = new CodexMcpBackend({ cwd, log: logFn });
222
222
  } else {
223
- const { AcpBackend } = await import('./run-BImPgXHd.mjs').then(function (n) { return n.h; });
224
- const { GeminiTransport } = await import('./run-BImPgXHd.mjs').then(function (n) { return n.G; });
225
- const { DefaultTransport } = await import('./run-BImPgXHd.mjs').then(function (n) { return n.D; });
223
+ const { AcpBackend } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.h; });
224
+ const { GeminiTransport } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.G; });
225
+ const { DefaultTransport } = await import('./run-DPhSmbr1.mjs').then(function (n) { return n.D; });
226
226
  const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
227
227
  backend = new AcpBackend({
228
228
  agentName: config.agentName,
@@ -340,7 +340,7 @@ async function handleSessionCommand() {
340
340
  printSessionHelp();
341
341
  return;
342
342
  }
343
- const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-Brx7D-77.mjs');
343
+ const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-FAWhkKtz.mjs');
344
344
  const parseFlagStr = (flag, shortFlag) => {
345
345
  for (let i = 1; i < sessionArgs.length; i++) {
346
346
  if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
@@ -400,7 +400,7 @@ async function handleSessionCommand() {
400
400
  allowDomain.push(sessionArgs[++i]);
401
401
  }
402
402
  }
403
- const { parseShareArg } = await import('./commands-Brx7D-77.mjs');
403
+ const { parseShareArg } = await import('./commands-FAWhkKtz.mjs');
404
404
  const shareEntries = share.map((s) => parseShareArg(s));
405
405
  await sessionSpawn(agent, dir, targetMachineId, {
406
406
  message,
@@ -484,7 +484,7 @@ async function handleSessionCommand() {
484
484
  console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
485
485
  process.exit(1);
486
486
  }
487
- const { sessionApprove } = await import('./commands-Brx7D-77.mjs');
487
+ const { sessionApprove } = await import('./commands-FAWhkKtz.mjs');
488
488
  const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
489
489
  await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
490
490
  json: hasFlag("--json")
@@ -494,7 +494,7 @@ async function handleSessionCommand() {
494
494
  console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
495
495
  process.exit(1);
496
496
  }
497
- const { sessionDeny } = await import('./commands-Brx7D-77.mjs');
497
+ const { sessionDeny } = await import('./commands-FAWhkKtz.mjs');
498
498
  const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
499
499
  await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
500
500
  json: hasFlag("--json")
@@ -524,6 +524,40 @@ async function handleSessionCommand() {
524
524
  process.exit(1);
525
525
  }
526
526
  await sessionRalphStatus(sessionArgs[1], targetMachineId);
527
+ } else if (sessionSubcommand === "set-title") {
528
+ const title = sessionArgs[1];
529
+ if (!title) {
530
+ console.error("Usage: svamp session set-title <title>");
531
+ process.exit(1);
532
+ }
533
+ const { sessionSetTitle } = await import('./agentCommands-C6iGblcL.mjs');
534
+ await sessionSetTitle(title);
535
+ } else if (sessionSubcommand === "set-link") {
536
+ const url = sessionArgs[1];
537
+ if (!url) {
538
+ console.error("Usage: svamp session set-link <url> [label]");
539
+ process.exit(1);
540
+ }
541
+ const label = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
542
+ const { sessionSetLink } = await import('./agentCommands-C6iGblcL.mjs');
543
+ await sessionSetLink(url, label);
544
+ } else if (sessionSubcommand === "notify") {
545
+ const message = sessionArgs[1];
546
+ if (!message) {
547
+ console.error("Usage: svamp session notify <message> [--level info|warning|error]");
548
+ process.exit(1);
549
+ }
550
+ const level = parseFlagStr("--level") || "info";
551
+ const { sessionNotify } = await import('./agentCommands-C6iGblcL.mjs');
552
+ await sessionNotify(message, level);
553
+ } else if (sessionSubcommand === "broadcast") {
554
+ const action = sessionArgs[1];
555
+ if (!action) {
556
+ console.error("Usage: svamp session broadcast <action> [args...]\nActions: open-canvas <url> [label], close-canvas, toast <message>");
557
+ process.exit(1);
558
+ }
559
+ const { sessionBroadcast } = await import('./agentCommands-C6iGblcL.mjs');
560
+ await sessionBroadcast(action, sessionArgs.slice(2).filter((a) => !a.startsWith("--")));
527
561
  } else if (sessionSubcommand === "queue") {
528
562
  const queueSubcmd = sessionArgs[1];
529
563
  if (queueSubcmd === "add") {
@@ -563,7 +597,7 @@ async function handleMachineCommand() {
563
597
  return;
564
598
  }
565
599
  if (machineSubcommand === "share") {
566
- const { machineShare } = await import('./commands-Brx7D-77.mjs');
600
+ const { machineShare } = await import('./commands-FAWhkKtz.mjs');
567
601
  let machineId;
568
602
  const shareArgs = [];
569
603
  for (let i = 1; i < machineArgs.length; i++) {
@@ -593,7 +627,7 @@ async function handleMachineCommand() {
593
627
  }
594
628
  await machineShare(machineId, { add, remove, list, configPath, showConfig });
595
629
  } else if (machineSubcommand === "exec") {
596
- const { machineExec } = await import('./commands-Brx7D-77.mjs');
630
+ const { machineExec } = await import('./commands-FAWhkKtz.mjs');
597
631
  let machineId;
598
632
  let cwd;
599
633
  const cmdParts = [];
@@ -613,7 +647,7 @@ async function handleMachineCommand() {
613
647
  }
614
648
  await machineExec(machineId, command, cwd);
615
649
  } else if (machineSubcommand === "info") {
616
- const { machineInfo } = await import('./commands-Brx7D-77.mjs');
650
+ const { machineInfo } = await import('./commands-FAWhkKtz.mjs');
617
651
  let machineId;
618
652
  for (let i = 1; i < machineArgs.length; i++) {
619
653
  if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
@@ -621,8 +655,22 @@ async function handleMachineCommand() {
621
655
  }
622
656
  }
623
657
  await machineInfo(machineId);
658
+ } else if (machineSubcommand === "notify") {
659
+ const message = machineArgs[1];
660
+ if (!message) {
661
+ console.error("Usage: svamp machine notify <message> [--level info|warning|error]");
662
+ process.exit(1);
663
+ }
664
+ let level = "info";
665
+ for (let i = 2; i < machineArgs.length; i++) {
666
+ if (machineArgs[i] === "--level" && i + 1 < machineArgs.length) {
667
+ level = machineArgs[++i];
668
+ }
669
+ }
670
+ const { machineNotify } = await import('./agentCommands-C6iGblcL.mjs');
671
+ await machineNotify(message, level);
624
672
  } else if (machineSubcommand === "ls") {
625
- const { machineLs } = await import('./commands-Brx7D-77.mjs');
673
+ const { machineLs } = await import('./commands-FAWhkKtz.mjs');
626
674
  let machineId;
627
675
  let showHidden = false;
628
676
  let path;
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-Brx7D-77.mjs';
3
+ import { connectAndGetMachine } from './commands-FAWhkKtz.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:child_process';
6
6
  import 'node:path';
7
7
  import 'node:os';
8
- import './run-BImPgXHd.mjs';
8
+ import './run-DPhSmbr1.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { resolve, join } from 'node:path';
4
4
  import os from 'node:os';
5
- import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-BImPgXHd.mjs';
5
+ import { l as loadSecurityContextConfig, e as resolveSecurityContext, f as buildSecurityContextFromFlags, m as mergeSecurityContexts, c as connectToHypha } from './run-DPhSmbr1.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
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-BImPgXHd.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-DPhSmbr1.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.64";
2
+ var version = "0.1.65";
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";
@@ -385,21 +385,22 @@ async function registerMachineService(server, machineId, metadata, daemonState,
385
385
  const machineOwner = currentMetadata.sharing?.owner;
386
386
  const isSharedUser = callerEmail && machineOwner && callerEmail.toLowerCase() !== machineOwner.toLowerCase();
387
387
  if (isSharedUser) {
388
- const machineUser = currentMetadata.sharing?.allowedUsers?.find(
389
- (u) => u.email.toLowerCase() === callerEmail.toLowerCase()
390
- );
391
- const callerRole = machineUser?.role || "interact";
392
388
  const sharing = {
393
389
  enabled: true,
394
- owner: machineOwner,
390
+ owner: callerEmail,
391
+ // spawning user owns their session
395
392
  allowedUsers: [
396
- ...options.sharing?.allowedUsers || [],
393
+ // Machine owner gets admin access (can monitor/control sessions on their machine)
397
394
  {
398
- email: callerEmail,
399
- role: callerRole,
395
+ email: machineOwner,
396
+ role: "admin",
400
397
  addedAt: Date.now(),
401
398
  addedBy: "machine-auto"
402
- }
399
+ },
400
+ // Preserve any explicitly requested allowedUsers (e.g. additional collaborators)
401
+ ...(options.sharing?.allowedUsers || []).filter(
402
+ (u) => u.email.toLowerCase() !== machineOwner.toLowerCase()
403
+ )
403
404
  ]
404
405
  };
405
406
  options = { ...options, sharing };
@@ -419,16 +420,11 @@ async function registerMachineService(server, machineId, metadata, daemonState,
419
420
  ...options,
420
421
  securityContext: mergeSecurityContexts(machineCtx, options.securityContext)
421
422
  };
422
- if (machineCtx.role && options.sharing?.enabled) {
423
- const user = options.sharing.allowedUsers?.find(
424
- (u) => u.email.toLowerCase() === callerEmail.toLowerCase()
425
- );
426
- if (user && !user.role) {
427
- user.role = machineCtx.role;
428
- }
429
- }
430
423
  }
431
424
  }
425
+ if (options.injectPlatformGuidance === void 0 && currentMetadata.injectPlatformGuidance !== void 0) {
426
+ options = { ...options, injectPlatformGuidance: currentMetadata.injectPlatformGuidance };
427
+ }
432
428
  const result = await handlers.spawnSession({
433
429
  ...options,
434
430
  machineId
@@ -486,7 +482,8 @@ async function registerMachineService(server, machineId, metadata, daemonState,
486
482
  metadataVersion++;
487
483
  savePersistedMachineMetadata(metadata.svampHomeDir, {
488
484
  sharing: currentMetadata.sharing,
489
- securityContextConfig: currentMetadata.securityContextConfig
485
+ securityContextConfig: currentMetadata.securityContextConfig,
486
+ injectPlatformGuidance: currentMetadata.injectPlatformGuidance
490
487
  });
491
488
  notifyListeners({
492
489
  type: "update-machine",
@@ -546,7 +543,8 @@ async function registerMachineService(server, machineId, metadata, daemonState,
546
543
  metadataVersion++;
547
544
  savePersistedMachineMetadata(metadata.svampHomeDir, {
548
545
  sharing: currentMetadata.sharing,
549
- securityContextConfig: currentMetadata.securityContextConfig
546
+ securityContextConfig: currentMetadata.securityContextConfig,
547
+ injectPlatformGuidance: currentMetadata.injectPlatformGuidance
550
548
  });
551
549
  notifyListeners({
552
550
  type: "update-machine",
@@ -567,7 +565,8 @@ async function registerMachineService(server, machineId, metadata, daemonState,
567
565
  metadataVersion++;
568
566
  savePersistedMachineMetadata(metadata.svampHomeDir, {
569
567
  sharing: currentMetadata.sharing,
570
- securityContextConfig: currentMetadata.securityContextConfig
568
+ securityContextConfig: currentMetadata.securityContextConfig,
569
+ injectPlatformGuidance: currentMetadata.injectPlatformGuidance
571
570
  });
572
571
  notifyListeners({
573
572
  type: "update-machine",
@@ -3754,13 +3753,17 @@ async function verifyNonoIsolation(binaryPath) {
3754
3753
  "-s",
3755
3754
  "--allow",
3756
3755
  workDir,
3757
- "--allow-cwd",
3756
+ // NOTE: Do NOT add --allow-cwd here. If the daemon's CWD happens to be
3757
+ // $HOME (common when started interactively), --allow-cwd would grant
3758
+ // access to $HOME, allowing the probe file write to succeed and making
3759
+ // verification incorrectly fail ("file leaked to host filesystem").
3760
+ // We already grant --allow workDir explicitly, so --allow-cwd is redundant.
3758
3761
  "--trust-override",
3759
3762
  "--",
3760
3763
  "sh",
3761
3764
  "-c",
3762
3765
  testScript
3763
- ], { timeout: 15e3 });
3766
+ ], { timeout: 15e3, cwd: workDir });
3764
3767
  return parseIsolationTestOutput(stdout, probeFile);
3765
3768
  } catch (e) {
3766
3769
  return { passed: false, error: e.message };
@@ -4408,6 +4411,83 @@ class ProcessSupervisor {
4408
4411
 
4409
4412
  const __filename$1 = fileURLToPath(import.meta.url);
4410
4413
  const __dirname$1 = dirname(__filename$1);
4414
+ const CLAUDE_SKILLS_DIR = join(os__default.homedir(), ".claude", "skills");
4415
+ async function installSkillFromEndpoint(name, baseUrl) {
4416
+ const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(15e3) });
4417
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${baseUrl}`);
4418
+ const index = await resp.json();
4419
+ const files = index.files || [];
4420
+ if (files.length === 0) throw new Error(`Skill index at ${baseUrl} has no files`);
4421
+ const targetDir = join(CLAUDE_SKILLS_DIR, name);
4422
+ mkdirSync(targetDir, { recursive: true });
4423
+ for (const filePath of files) {
4424
+ if (!filePath) continue;
4425
+ const url = `${baseUrl}${filePath}`;
4426
+ const fileResp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
4427
+ if (!fileResp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${fileResp.status}`);
4428
+ const content = await fileResp.text();
4429
+ const localPath = join(targetDir, filePath);
4430
+ if (!localPath.startsWith(targetDir + "/")) continue;
4431
+ mkdirSync(dirname(localPath), { recursive: true });
4432
+ writeFileSync(localPath, content, "utf-8");
4433
+ }
4434
+ }
4435
+ async function installSkillFromMarketplace(name) {
4436
+ const BASE = `https://hypha.aicell.io/hypha-cloud/artifacts/${name}`;
4437
+ async function collectFiles(dir = "") {
4438
+ const url = dir ? `${BASE}/files/${dir}` : `${BASE}/files/`;
4439
+ const resp = await fetch(url, { signal: AbortSignal.timeout(15e3) });
4440
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} listing files`);
4441
+ const data = await resp.json();
4442
+ const items = Array.isArray(data) ? data : data.items || [];
4443
+ const result = [];
4444
+ for (const item of items) {
4445
+ const itemPath = dir ? `${dir}/${item.name}` : item.name;
4446
+ if (item.type === "directory") {
4447
+ result.push(...await collectFiles(itemPath));
4448
+ } else {
4449
+ result.push(itemPath);
4450
+ }
4451
+ }
4452
+ return result;
4453
+ }
4454
+ const files = await collectFiles();
4455
+ if (files.length === 0) throw new Error(`Skill ${name} has no files in marketplace`);
4456
+ const targetDir = join(CLAUDE_SKILLS_DIR, name);
4457
+ mkdirSync(targetDir, { recursive: true });
4458
+ for (const filePath of files) {
4459
+ const url = `${BASE}/files/${filePath}`;
4460
+ const resp = await fetch(url, { signal: AbortSignal.timeout(3e4) });
4461
+ if (!resp.ok) throw new Error(`Failed to download ${filePath}: HTTP ${resp.status}`);
4462
+ const content = await resp.text();
4463
+ const localPath = join(targetDir, filePath);
4464
+ if (!localPath.startsWith(targetDir + "/")) continue;
4465
+ mkdirSync(dirname(localPath), { recursive: true });
4466
+ writeFileSync(localPath, content, "utf-8");
4467
+ }
4468
+ }
4469
+ async function ensureAutoInstalledSkills(logger) {
4470
+ const tasks = [
4471
+ {
4472
+ name: "svamp",
4473
+ install: () => installSkillFromMarketplace("svamp")
4474
+ },
4475
+ {
4476
+ name: "hypha",
4477
+ install: () => installSkillFromEndpoint("hypha", "https://hypha.aicell.io/ws/agent-skills/")
4478
+ }
4479
+ ];
4480
+ for (const task of tasks) {
4481
+ const targetDir = join(CLAUDE_SKILLS_DIR, task.name);
4482
+ if (existsSync$1(targetDir)) continue;
4483
+ try {
4484
+ await task.install();
4485
+ logger.log(`[skills] Auto-installed: ${task.name}`);
4486
+ } catch (err) {
4487
+ logger.log(`[skills] Auto-install of "${task.name}" failed (non-fatal): ${err.message}`);
4488
+ }
4489
+ }
4490
+ }
4411
4491
  function loadEnvFile(path) {
4412
4492
  if (!existsSync$1(path)) return false;
4413
4493
  const lines = readFileSync$1(path, "utf-8").split("\n");
@@ -5172,6 +5252,8 @@ async function startDaemon(options) {
5172
5252
  let server = null;
5173
5253
  const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
5174
5254
  await supervisor.init();
5255
+ ensureAutoInstalledSkills(logger).catch(() => {
5256
+ });
5175
5257
  try {
5176
5258
  logger.log("Connecting to Hypha server...");
5177
5259
  server = await connectToHypha({
@@ -5328,7 +5410,8 @@ async function startDaemon(options) {
5328
5410
  sharing: options2.sharing,
5329
5411
  securityContext: options2.securityContext,
5330
5412
  tags: options2.tags,
5331
- parentSessionId: options2.parentSessionId
5413
+ parentSessionId: options2.parentSessionId,
5414
+ ...options2.injectPlatformGuidance !== void 0 && { injectPlatformGuidance: options2.injectPlatformGuidance }
5332
5415
  };
5333
5416
  let claudeProcess = null;
5334
5417
  const allPersisted = loadPersistedSessions();
@@ -5343,6 +5426,9 @@ async function startDaemon(options) {
5343
5426
  const newState = processing ? "running" : "idle";
5344
5427
  if (sessionMetadata.lifecycleState !== newState) {
5345
5428
  sessionMetadata = { ...sessionMetadata, lifecycleState: newState };
5429
+ if (!processing) {
5430
+ sessionMetadata = { ...sessionMetadata, unread: true };
5431
+ }
5346
5432
  sessionService.updateMetadata(sessionMetadata);
5347
5433
  }
5348
5434
  };
@@ -5957,7 +6043,7 @@ The automated loop has finished. Review the progress above and let me know if yo
5957
6043
  isRestartingClaude = false;
5958
6044
  }
5959
6045
  };
5960
- if (sessionMetadata.sharing?.enabled && isolationCapabilities.preferred) {
6046
+ if (sessionMetadata.sharing?.enabled) {
5961
6047
  try {
5962
6048
  stagedCredentials = await stageCredentialsForSharing(sessionId);
5963
6049
  logger.log(`[Session ${sessionId}] Credentials staged at ${stagedCredentials.homePath}`);
@@ -6954,7 +7040,7 @@ The automated loop has finished. Review the progress above and let me know if yo
6954
7040
  const defaultHomeDir = existsSync$1("/data") ? "/data" : os__default.homedir();
6955
7041
  const persistedMachineMeta = loadPersistedMachineMetadata(SVAMP_HOME);
6956
7042
  if (persistedMachineMeta) {
6957
- logger.log(`Restored machine metadata (sharing=${!!persistedMachineMeta.sharing}, securityContextConfig=${!!persistedMachineMeta.securityContextConfig})`);
7043
+ logger.log(`Restored machine metadata (sharing=${!!persistedMachineMeta.sharing}, securityContextConfig=${!!persistedMachineMeta.securityContextConfig}, injectPlatformGuidance=${persistedMachineMeta.injectPlatformGuidance})`);
6958
7044
  }
6959
7045
  const machineMetadata = {
6960
7046
  host: os__default.hostname(),
@@ -6965,9 +7051,10 @@ The automated loop has finished. Review the progress above and let me know if yo
6965
7051
  svampLibDir: join(__dirname$1, ".."),
6966
7052
  displayName: process.env.SVAMP_DISPLAY_NAME || void 0,
6967
7053
  isolationCapabilities,
6968
- // Restore persisted sharing & security context config
7054
+ // Restore persisted sharing, security context config, and platform guidance flag
6969
7055
  ...persistedMachineMeta?.sharing && { sharing: persistedMachineMeta.sharing },
6970
- ...persistedMachineMeta?.securityContextConfig && { securityContextConfig: persistedMachineMeta.securityContextConfig }
7056
+ ...persistedMachineMeta?.securityContextConfig && { securityContextConfig: persistedMachineMeta.securityContextConfig },
7057
+ ...persistedMachineMeta?.injectPlatformGuidance !== void 0 && { injectPlatformGuidance: persistedMachineMeta.injectPlatformGuidance }
6971
7058
  };
6972
7059
  const initialDaemonState = {
6973
7060
  status: "running",
@@ -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-BImPgXHd.mjs';
5
+ import { c as connectToHypha, a as registerSessionService } from './run-DPhSmbr1.mjs';
6
6
  import { createServer } from 'node:http';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { createInterface } from 'node:readline';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.1.64",
3
+ "version": "0.1.65",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",