svamp-cli 0.2.7 → 0.2.9
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.
- package/dist/{agentCommands-vROerKBL.mjs → agentCommands-CrfvZzCn.mjs} +2 -2
- package/dist/cli.mjs +44 -28
- package/dist/{commands-BYbuedOK.mjs → commands-BJJTEZD4.mjs} +1 -1
- package/dist/{commands-B5yjf3Me.mjs → commands-C1xgznG4.mjs} +2 -2
- package/dist/{commands-Bh7MIzIQ.mjs → commands-h1lFrJKK.mjs} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{package-rfyKrDIy.mjs → package-Bx_FLMjC.mjs} +2 -2
- package/dist/{run-1sh7lcBI.mjs → run-IDo93bqK.mjs} +249 -12
- package/dist/{run-CKmnXg7d.mjs → run-wHgMyNHQ.mjs} +1 -1
- package/dist/serveCommands-BguUrQAM.mjs +191 -0
- package/dist/serveManager-BPyT20Q8.mjs +648 -0
- package/dist/{staticServer-CWcmMF5V.mjs → staticServer-_-FoZQpD.mjs} +1 -1
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { spawn as spawn$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync as writeFileSync$1, mkdirSync as mkdirSync$1, appendFileSync } from 'node:fs';
|
|
9
|
-
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
10
10
|
import { join as join$1 } from 'node:path';
|
|
11
11
|
import { spawn, execSync, execFile } from 'node:child_process';
|
|
12
12
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
@@ -718,6 +718,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
718
718
|
if (newSharing.enabled && !newSharing.owner && context?.user?.email) {
|
|
719
719
|
newSharing = { ...newSharing, owner: context.user.email };
|
|
720
720
|
}
|
|
721
|
+
const oldSharing = currentMetadata.sharing;
|
|
721
722
|
currentMetadata = { ...currentMetadata, sharing: newSharing };
|
|
722
723
|
metadataVersion++;
|
|
723
724
|
savePersistedMachineMetadata(metadata.svampHomeDir, {
|
|
@@ -730,6 +731,20 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
730
731
|
machineId,
|
|
731
732
|
metadata: { value: currentMetadata, version: metadataVersion }
|
|
732
733
|
});
|
|
734
|
+
handlers.sharingNotificationSync?.syncSharing(
|
|
735
|
+
`machine-${machineId}`,
|
|
736
|
+
oldSharing,
|
|
737
|
+
newSharing,
|
|
738
|
+
{
|
|
739
|
+
machineId,
|
|
740
|
+
machineServiceId: `${server.config.workspace}/${server.config.client_id}:default`,
|
|
741
|
+
ownerWorkspace: server.config.workspace,
|
|
742
|
+
ownerEmail: newSharing.owner || "",
|
|
743
|
+
label: currentMetadata.displayName || machineId,
|
|
744
|
+
shareType: "machine"
|
|
745
|
+
}
|
|
746
|
+
).catch(() => {
|
|
747
|
+
});
|
|
733
748
|
return { success: true, sharing: newSharing };
|
|
734
749
|
},
|
|
735
750
|
// Get security context config
|
|
@@ -1110,6 +1125,41 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
1110
1125
|
...client.status
|
|
1111
1126
|
}));
|
|
1112
1127
|
},
|
|
1128
|
+
// ── Shared static file server ────────────────────────────────────
|
|
1129
|
+
/** Add a mount to the shared static file server. */
|
|
1130
|
+
serveAdd: async (params, context) => {
|
|
1131
|
+
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
1132
|
+
const sm = handlers.serveManager;
|
|
1133
|
+
if (!sm) throw new Error("Serve manager not available");
|
|
1134
|
+
const ownerEmail = params.ownerEmail || context?.user?.email || void 0;
|
|
1135
|
+
const access = params.access || "owner";
|
|
1136
|
+
return sm.addMount(params.name, params.directory, params.sessionId, access, ownerEmail);
|
|
1137
|
+
},
|
|
1138
|
+
/** Remove a mount from the shared static file server. */
|
|
1139
|
+
serveRemove: async (params, context) => {
|
|
1140
|
+
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
1141
|
+
const sm = handlers.serveManager;
|
|
1142
|
+
if (!sm) throw new Error("Serve manager not available");
|
|
1143
|
+
await sm.removeMount(params.name);
|
|
1144
|
+
return { removed: true };
|
|
1145
|
+
},
|
|
1146
|
+
/** List mounts. Optionally filter by sessionId or return all. */
|
|
1147
|
+
serveList: async (params, context) => {
|
|
1148
|
+
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
1149
|
+
const sm = handlers.serveManager;
|
|
1150
|
+
if (!sm) throw new Error("Serve manager not available");
|
|
1151
|
+
if (params.all) {
|
|
1152
|
+
return sm.listMounts();
|
|
1153
|
+
}
|
|
1154
|
+
return sm.listMounts(params.sessionId);
|
|
1155
|
+
},
|
|
1156
|
+
/** Get shared static file server info. */
|
|
1157
|
+
serveInfo: async (context) => {
|
|
1158
|
+
authorizeRequest(context, currentMetadata.sharing, "view");
|
|
1159
|
+
const sm = handlers.serveManager;
|
|
1160
|
+
if (!sm) throw new Error("Serve manager not available");
|
|
1161
|
+
return sm.getInfo();
|
|
1162
|
+
},
|
|
1113
1163
|
// WISE voice — create ephemeral token for OpenAI Realtime API
|
|
1114
1164
|
wiseCreateEphemeralToken: async (params, context) => {
|
|
1115
1165
|
authorizeRequest(context, currentMetadata.sharing, "interact");
|
|
@@ -1978,7 +2028,7 @@ async function registerDebugService(server, machineId, deps) {
|
|
|
1978
2028
|
};
|
|
1979
2029
|
}
|
|
1980
2030
|
|
|
1981
|
-
const COLLECTION_ALIAS = "svamp-agent-sessions";
|
|
2031
|
+
const COLLECTION_ALIAS$1 = "svamp-agent-sessions";
|
|
1982
2032
|
class SessionArtifactSync {
|
|
1983
2033
|
server;
|
|
1984
2034
|
artifactManager = null;
|
|
@@ -2013,14 +2063,14 @@ class SessionArtifactSync {
|
|
|
2013
2063
|
async ensureCollection() {
|
|
2014
2064
|
try {
|
|
2015
2065
|
const existing = await this.artifactManager.read({
|
|
2016
|
-
artifact_id: COLLECTION_ALIAS,
|
|
2066
|
+
artifact_id: COLLECTION_ALIAS$1,
|
|
2017
2067
|
_rkwargs: true
|
|
2018
2068
|
});
|
|
2019
2069
|
this.collectionId = existing.id;
|
|
2020
2070
|
this.log(`[ARTIFACT SYNC] Found existing collection: ${this.collectionId}`);
|
|
2021
2071
|
} catch {
|
|
2022
2072
|
const collection = await this.artifactManager.create({
|
|
2023
|
-
alias: COLLECTION_ALIAS,
|
|
2073
|
+
alias: COLLECTION_ALIAS$1,
|
|
2024
2074
|
type: "collection",
|
|
2025
2075
|
manifest: {
|
|
2026
2076
|
name: "Svamp Agent Sessions",
|
|
@@ -2299,6 +2349,170 @@ class SessionArtifactSync {
|
|
|
2299
2349
|
}
|
|
2300
2350
|
}
|
|
2301
2351
|
|
|
2352
|
+
const COLLECTION_ALIAS = "svamp-shared-sessions";
|
|
2353
|
+
function emailHash(email) {
|
|
2354
|
+
return createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
|
|
2355
|
+
}
|
|
2356
|
+
function notificationAlias(sessionId, recipientEmail) {
|
|
2357
|
+
return `share-${sessionId.slice(0, 8)}-${emailHash(recipientEmail)}`;
|
|
2358
|
+
}
|
|
2359
|
+
class SharingNotificationSync {
|
|
2360
|
+
server;
|
|
2361
|
+
artifactManager = null;
|
|
2362
|
+
collectionId = null;
|
|
2363
|
+
initialized = false;
|
|
2364
|
+
log;
|
|
2365
|
+
constructor(server, log) {
|
|
2366
|
+
this.server = server;
|
|
2367
|
+
this.log = log;
|
|
2368
|
+
}
|
|
2369
|
+
async init() {
|
|
2370
|
+
try {
|
|
2371
|
+
this.artifactManager = await this.server.getService("public/artifact-manager");
|
|
2372
|
+
if (!this.artifactManager) {
|
|
2373
|
+
this.log("[SHARING NOTIFY] Artifact manager not available");
|
|
2374
|
+
return;
|
|
2375
|
+
}
|
|
2376
|
+
await this.ensureCollection();
|
|
2377
|
+
this.initialized = true;
|
|
2378
|
+
this.log("[SHARING NOTIFY] Initialized");
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
this.log(`[SHARING NOTIFY] Init failed: ${err.message}`);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
async ensureCollection() {
|
|
2384
|
+
try {
|
|
2385
|
+
const existing = await this.artifactManager.read({
|
|
2386
|
+
artifact_id: COLLECTION_ALIAS,
|
|
2387
|
+
_rkwargs: true
|
|
2388
|
+
});
|
|
2389
|
+
this.collectionId = existing.id;
|
|
2390
|
+
} catch {
|
|
2391
|
+
const collection = await this.artifactManager.create({
|
|
2392
|
+
alias: COLLECTION_ALIAS,
|
|
2393
|
+
type: "collection",
|
|
2394
|
+
manifest: {
|
|
2395
|
+
name: "Svamp Shared Sessions",
|
|
2396
|
+
description: "Cross-workspace share notifications for session/machine bookmarks"
|
|
2397
|
+
},
|
|
2398
|
+
config: {
|
|
2399
|
+
permissions: { "*": "r", "@": "rw+" }
|
|
2400
|
+
},
|
|
2401
|
+
_rkwargs: true
|
|
2402
|
+
});
|
|
2403
|
+
this.collectionId = collection.id;
|
|
2404
|
+
await this.artifactManager.commit({
|
|
2405
|
+
artifact_id: this.collectionId,
|
|
2406
|
+
_rkwargs: true
|
|
2407
|
+
});
|
|
2408
|
+
this.log(`[SHARING NOTIFY] Created collection: ${this.collectionId}`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Publish a share notification for a recipient.
|
|
2413
|
+
* Idempotent — uses deterministic alias, so re-sharing updates the existing artifact.
|
|
2414
|
+
*/
|
|
2415
|
+
async notifyShare(params) {
|
|
2416
|
+
if (!this.initialized || !this.collectionId) return;
|
|
2417
|
+
const alias = notificationAlias(params.sessionId, params.recipientEmail);
|
|
2418
|
+
const manifest = {
|
|
2419
|
+
recipientEmail: params.recipientEmail.toLowerCase(),
|
|
2420
|
+
sessionId: params.sessionId,
|
|
2421
|
+
machineId: params.machineId,
|
|
2422
|
+
machineServiceId: params.machineServiceId,
|
|
2423
|
+
ownerWorkspace: params.ownerWorkspace,
|
|
2424
|
+
ownerEmail: params.ownerEmail,
|
|
2425
|
+
label: params.label || "",
|
|
2426
|
+
role: params.role,
|
|
2427
|
+
sharedAt: Date.now(),
|
|
2428
|
+
shareType: params.shareType || "session"
|
|
2429
|
+
};
|
|
2430
|
+
try {
|
|
2431
|
+
const existing = await this.artifactManager.read({
|
|
2432
|
+
artifact_id: alias,
|
|
2433
|
+
parent_id: this.collectionId,
|
|
2434
|
+
_rkwargs: true
|
|
2435
|
+
});
|
|
2436
|
+
await this.artifactManager.edit({
|
|
2437
|
+
artifact_id: existing.id,
|
|
2438
|
+
manifest,
|
|
2439
|
+
_rkwargs: true
|
|
2440
|
+
});
|
|
2441
|
+
await this.artifactManager.commit({
|
|
2442
|
+
artifact_id: existing.id,
|
|
2443
|
+
_rkwargs: true
|
|
2444
|
+
});
|
|
2445
|
+
} catch {
|
|
2446
|
+
try {
|
|
2447
|
+
const artifact = await this.artifactManager.create({
|
|
2448
|
+
alias,
|
|
2449
|
+
parent_id: this.collectionId,
|
|
2450
|
+
type: "share-notification",
|
|
2451
|
+
manifest,
|
|
2452
|
+
_rkwargs: true
|
|
2453
|
+
});
|
|
2454
|
+
await this.artifactManager.commit({
|
|
2455
|
+
artifact_id: artifact.id,
|
|
2456
|
+
_rkwargs: true
|
|
2457
|
+
});
|
|
2458
|
+
} catch (createErr) {
|
|
2459
|
+
this.log(`[SHARING NOTIFY] Failed to create notification for ${params.recipientEmail}: ${createErr.message}`);
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
this.log(`[SHARING NOTIFY] Notified ${params.recipientEmail} about ${params.shareType || "session"} ${params.sessionId.slice(0, 8)}`);
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Remove a share notification when a user is unshared.
|
|
2467
|
+
*/
|
|
2468
|
+
async removeNotification(sessionId, recipientEmail) {
|
|
2469
|
+
if (!this.initialized || !this.collectionId) return;
|
|
2470
|
+
const alias = notificationAlias(sessionId, recipientEmail);
|
|
2471
|
+
try {
|
|
2472
|
+
const existing = await this.artifactManager.read({
|
|
2473
|
+
artifact_id: alias,
|
|
2474
|
+
parent_id: this.collectionId,
|
|
2475
|
+
_rkwargs: true
|
|
2476
|
+
});
|
|
2477
|
+
await this.artifactManager.delete({
|
|
2478
|
+
artifact_id: existing.id,
|
|
2479
|
+
_rkwargs: true
|
|
2480
|
+
});
|
|
2481
|
+
this.log(`[SHARING NOTIFY] Removed notification for ${recipientEmail} on ${sessionId.slice(0, 8)}`);
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Sync all sharing notifications for a session based on its current sharing config.
|
|
2487
|
+
* Adds notifications for new users, removes for removed users.
|
|
2488
|
+
*/
|
|
2489
|
+
async syncSharing(sessionId, oldSharing, newSharing, context) {
|
|
2490
|
+
if (!this.initialized) return;
|
|
2491
|
+
const oldEmails = new Set(
|
|
2492
|
+
(oldSharing?.allowedUsers || []).map((u) => u.email.toLowerCase())
|
|
2493
|
+
);
|
|
2494
|
+
const newUsers = newSharing.allowedUsers || [];
|
|
2495
|
+
const newEmails = new Set(newUsers.map((u) => u.email.toLowerCase()));
|
|
2496
|
+
for (const user of newUsers) {
|
|
2497
|
+
if (!oldEmails.has(user.email.toLowerCase())) {
|
|
2498
|
+
this.notifyShare({
|
|
2499
|
+
recipientEmail: user.email,
|
|
2500
|
+
sessionId,
|
|
2501
|
+
role: user.role,
|
|
2502
|
+
...context
|
|
2503
|
+
}).catch(() => {
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
for (const email of oldEmails) {
|
|
2508
|
+
if (!newEmails.has(email)) {
|
|
2509
|
+
this.removeNotification(sessionId, email).catch(() => {
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2302
2516
|
const DEFAULT_TIMEOUTS = {
|
|
2303
2517
|
init: 6e4,
|
|
2304
2518
|
toolCall: 12e4,
|
|
@@ -5933,6 +6147,8 @@ async function startDaemon(options) {
|
|
|
5933
6147
|
const supervisor = new ProcessSupervisor(join(SVAMP_HOME, "processes"));
|
|
5934
6148
|
await supervisor.init();
|
|
5935
6149
|
const tunnels = /* @__PURE__ */ new Map();
|
|
6150
|
+
const { ServeManager } = await import('./serveManager-BPyT20Q8.mjs');
|
|
6151
|
+
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
5936
6152
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
5937
6153
|
});
|
|
5938
6154
|
preventMachineSleep(logger);
|
|
@@ -6131,6 +6347,7 @@ async function startDaemon(options) {
|
|
|
6131
6347
|
const persisted = allPersisted.find((p) => p.sessionId === sessionId) || (resumeSessionId ? allPersisted.find((p) => p.claudeResumeId === resumeSessionId) : void 0);
|
|
6132
6348
|
let claudeResumeId = persisted?.claudeResumeId || (resumeSessionId || void 0);
|
|
6133
6349
|
let currentPermissionMode = options2.permissionMode || persisted?.permissionMode || "default";
|
|
6350
|
+
const sessionCreatedAt = persisted?.createdAt || Date.now();
|
|
6134
6351
|
let lastSpawnMeta = persisted?.spawnMeta || {};
|
|
6135
6352
|
let sessionWasProcessing = !!options2.wasProcessing;
|
|
6136
6353
|
let lastAssistantText = "";
|
|
@@ -6437,7 +6654,7 @@ async function startDaemon(options) {
|
|
|
6437
6654
|
permissionMode: currentPermissionMode,
|
|
6438
6655
|
spawnMeta: lastSpawnMeta,
|
|
6439
6656
|
metadata: sessionMetadata,
|
|
6440
|
-
createdAt:
|
|
6657
|
+
createdAt: sessionCreatedAt,
|
|
6441
6658
|
machineId,
|
|
6442
6659
|
wasProcessing: false
|
|
6443
6660
|
});
|
|
@@ -6468,7 +6685,7 @@ async function startDaemon(options) {
|
|
|
6468
6685
|
permissionMode: currentPermissionMode,
|
|
6469
6686
|
spawnMeta: lastSpawnMeta,
|
|
6470
6687
|
metadata: sessionMetadata,
|
|
6471
|
-
createdAt:
|
|
6688
|
+
createdAt: sessionCreatedAt,
|
|
6472
6689
|
machineId,
|
|
6473
6690
|
wasProcessing: false
|
|
6474
6691
|
});
|
|
@@ -6697,7 +6914,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6697
6914
|
permissionMode: currentPermissionMode,
|
|
6698
6915
|
spawnMeta: lastSpawnMeta,
|
|
6699
6916
|
metadata: sessionMetadata,
|
|
6700
|
-
createdAt:
|
|
6917
|
+
createdAt: sessionCreatedAt,
|
|
6701
6918
|
machineId,
|
|
6702
6919
|
wasProcessing: sessionWasProcessing
|
|
6703
6920
|
});
|
|
@@ -6806,7 +7023,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
6806
7023
|
permissionMode: currentPermissionMode,
|
|
6807
7024
|
spawnMeta: lastSpawnMeta,
|
|
6808
7025
|
metadata: sessionMetadata,
|
|
6809
|
-
createdAt:
|
|
7026
|
+
createdAt: sessionCreatedAt,
|
|
6810
7027
|
machineId,
|
|
6811
7028
|
wasProcessing: false
|
|
6812
7029
|
});
|
|
@@ -7028,7 +7245,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7028
7245
|
permissionMode: currentPermissionMode,
|
|
7029
7246
|
spawnMeta: lastSpawnMeta,
|
|
7030
7247
|
metadata: sessionMetadata,
|
|
7031
|
-
createdAt:
|
|
7248
|
+
createdAt: sessionCreatedAt,
|
|
7032
7249
|
machineId,
|
|
7033
7250
|
wasProcessing: sessionWasProcessing
|
|
7034
7251
|
});
|
|
@@ -7090,6 +7307,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7090
7307
|
},
|
|
7091
7308
|
onSharingUpdate: (newSharing) => {
|
|
7092
7309
|
logger.log(`[Session ${sessionId}] Sharing config updated \u2014 persisting to disk`);
|
|
7310
|
+
const oldSharing = sessionMetadata.sharing;
|
|
7093
7311
|
sessionMetadata = { ...sessionMetadata, sharing: newSharing };
|
|
7094
7312
|
if (!trackedSession.stopped) {
|
|
7095
7313
|
saveSession({
|
|
@@ -7099,11 +7317,22 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
7099
7317
|
permissionMode: currentPermissionMode,
|
|
7100
7318
|
spawnMeta: lastSpawnMeta,
|
|
7101
7319
|
metadata: sessionMetadata,
|
|
7102
|
-
createdAt:
|
|
7320
|
+
createdAt: sessionCreatedAt,
|
|
7103
7321
|
machineId,
|
|
7104
7322
|
wasProcessing: sessionWasProcessing
|
|
7105
7323
|
});
|
|
7106
7324
|
}
|
|
7325
|
+
const ownerWorkspace = server.config.workspace;
|
|
7326
|
+
const machineServiceId = `${ownerWorkspace}/${server.config.client_id}:default`;
|
|
7327
|
+
sharingNotificationSync.syncSharing(sessionId, oldSharing, newSharing, {
|
|
7328
|
+
machineId,
|
|
7329
|
+
machineServiceId,
|
|
7330
|
+
ownerWorkspace,
|
|
7331
|
+
ownerEmail: newSharing.owner || "",
|
|
7332
|
+
label: sessionMetadata.summary?.text || "",
|
|
7333
|
+
shareType: "session"
|
|
7334
|
+
}).catch(() => {
|
|
7335
|
+
});
|
|
7107
7336
|
},
|
|
7108
7337
|
onApplySystemPrompt: async (prompt) => {
|
|
7109
7338
|
logger.log(`[Session ${sessionId}] System prompt update requested \u2014 restarting agent`);
|
|
@@ -8027,6 +8256,9 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8027
8256
|
pid: process.pid,
|
|
8028
8257
|
startedAt: Date.now()
|
|
8029
8258
|
};
|
|
8259
|
+
const sharingNotificationSync = new SharingNotificationSync(server, logger.log);
|
|
8260
|
+
sharingNotificationSync.init().catch(() => {
|
|
8261
|
+
});
|
|
8030
8262
|
const machineService = await registerMachineService(
|
|
8031
8263
|
server,
|
|
8032
8264
|
machineId,
|
|
@@ -8042,7 +8274,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8042
8274
|
getTrackedSessions: getCurrentChildren,
|
|
8043
8275
|
getSessionRPCHandlers: (sessionId) => {
|
|
8044
8276
|
for (const [, session] of pidToTrackedSession) {
|
|
8045
|
-
if (session.svampSessionId === sessionId && session.sessionRPCHandlers) {
|
|
8277
|
+
if (session.svampSessionId === sessionId && !session.stopped && session.sessionRPCHandlers) {
|
|
8046
8278
|
return session.sessionRPCHandlers;
|
|
8047
8279
|
}
|
|
8048
8280
|
}
|
|
@@ -8058,7 +8290,9 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8058
8290
|
return ids;
|
|
8059
8291
|
},
|
|
8060
8292
|
supervisor,
|
|
8061
|
-
tunnels
|
|
8293
|
+
tunnels,
|
|
8294
|
+
serveManager,
|
|
8295
|
+
sharingNotificationSync
|
|
8062
8296
|
}
|
|
8063
8297
|
);
|
|
8064
8298
|
logger.log(`Machine service registered: svamp-machine-${machineId}`);
|
|
@@ -8080,6 +8314,7 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8080
8314
|
// Legacy; debug service uses session index now
|
|
8081
8315
|
});
|
|
8082
8316
|
logger.log(`Debug service registered: svamp-debug-${machineId}`);
|
|
8317
|
+
await serveManager.restore();
|
|
8083
8318
|
const persistedSessions = loadPersistedSessions();
|
|
8084
8319
|
const sessionsToAutoContinue = [];
|
|
8085
8320
|
const sessionsToRalphResume = [];
|
|
@@ -8420,6 +8655,8 @@ The automated loop has finished. Review the progress above and let me know if yo
|
|
|
8420
8655
|
}
|
|
8421
8656
|
await supervisor.stopAll().catch(() => {
|
|
8422
8657
|
});
|
|
8658
|
+
await serveManager.shutdown().catch(() => {
|
|
8659
|
+
});
|
|
8423
8660
|
for (const [name, client] of tunnels) {
|
|
8424
8661
|
client.destroy();
|
|
8425
8662
|
logger.log(`Tunnel '${name}' destroyed`);
|
|
@@ -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-
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService } from './run-IDo93bqK.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
|
|
3
|
+
function getFlag(args, flag) {
|
|
4
|
+
const idx = args.indexOf(flag);
|
|
5
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
|
|
6
|
+
}
|
|
7
|
+
function hasFlag(args, ...flags) {
|
|
8
|
+
return flags.some((f) => args.includes(f));
|
|
9
|
+
}
|
|
10
|
+
function positionalArgs(args) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
if (args[i].startsWith("-")) {
|
|
14
|
+
if (!args[i].startsWith("--no-") && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
result.push(args[i]);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
async function handleServeCommand() {
|
|
24
|
+
const args = process.argv.slice(3);
|
|
25
|
+
const sub = args[0];
|
|
26
|
+
let machineId;
|
|
27
|
+
const mIdx = args.findIndex((a) => a === "--machine" || a === "-m");
|
|
28
|
+
if (mIdx !== -1 && mIdx + 1 < args.length) {
|
|
29
|
+
machineId = args[mIdx + 1];
|
|
30
|
+
}
|
|
31
|
+
const filteredArgs = args.filter((_a, i) => {
|
|
32
|
+
if (args[i] === "--machine" || args[i] === "-m") return false;
|
|
33
|
+
if (i > 0 && (args[i - 1] === "--machine" || args[i - 1] === "-m")) return false;
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
if (sub === "--help" || sub === "-h") {
|
|
37
|
+
printServeHelp();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (sub === "remove" || sub === "rm") {
|
|
41
|
+
await serveRemove(filteredArgs.slice(1), machineId);
|
|
42
|
+
} else if (sub === "list" || sub === "ls") {
|
|
43
|
+
await serveList(filteredArgs.slice(1), machineId);
|
|
44
|
+
} else if (sub === "info") {
|
|
45
|
+
await serveInfo(machineId);
|
|
46
|
+
} else if (sub === "add") {
|
|
47
|
+
await serveAdd(filteredArgs.slice(1), machineId);
|
|
48
|
+
} else if (sub && !sub.startsWith("-")) {
|
|
49
|
+
await serveAdd(filteredArgs, machineId);
|
|
50
|
+
} else {
|
|
51
|
+
printServeHelp();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function serveAdd(args, machineId) {
|
|
55
|
+
const { connectAndGetMachine } = await import('./commands-h1lFrJKK.mjs');
|
|
56
|
+
const pos = positionalArgs(args);
|
|
57
|
+
const name = pos[0];
|
|
58
|
+
if (!name) {
|
|
59
|
+
console.error("Usage: svamp serve [add] <name> [directory] [--public | --access email1,email2]");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const directory = path.resolve(pos[1] || ".");
|
|
63
|
+
let access = "owner";
|
|
64
|
+
if (hasFlag(args, "--public")) {
|
|
65
|
+
access = "public";
|
|
66
|
+
} else {
|
|
67
|
+
const accessFlag = getFlag(args, "--access");
|
|
68
|
+
if (accessFlag) {
|
|
69
|
+
access = accessFlag.split(",").map((e) => e.trim()).filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const { machine, server } = await connectAndGetMachine(machineId);
|
|
73
|
+
try {
|
|
74
|
+
const result = await machine.serveAdd({ name, directory, access, _rkwargs: true });
|
|
75
|
+
console.log(`Mount added: ${name} \u2192 ${directory}`);
|
|
76
|
+
console.log(`Access: ${access === "public" ? "public" : access === "owner" ? "owner only" : access.join(", ")}`);
|
|
77
|
+
console.log(`URL: ${result.url}`);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(`Error: ${err.message || err}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
} finally {
|
|
82
|
+
await server.disconnect().catch(() => {
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function serveRemove(args, machineId) {
|
|
87
|
+
const { connectAndGetMachine } = await import('./commands-h1lFrJKK.mjs');
|
|
88
|
+
const pos = positionalArgs(args);
|
|
89
|
+
const name = pos[0];
|
|
90
|
+
if (!name) {
|
|
91
|
+
console.error("Usage: svamp serve remove <name>");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const { machine, server } = await connectAndGetMachine(machineId);
|
|
95
|
+
try {
|
|
96
|
+
await machine.serveRemove({ name, _rkwargs: true });
|
|
97
|
+
console.log(`Mount '${name}' removed.`);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(`Error: ${err.message || err}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
} finally {
|
|
102
|
+
await server.disconnect().catch(() => {
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function serveList(args, machineId) {
|
|
107
|
+
const { connectAndGetMachine } = await import('./commands-h1lFrJKK.mjs');
|
|
108
|
+
const all = hasFlag(args, "--all", "-a");
|
|
109
|
+
const json = hasFlag(args, "--json");
|
|
110
|
+
const sessionId = getFlag(args, "--session");
|
|
111
|
+
const { machine, server } = await connectAndGetMachine(machineId);
|
|
112
|
+
try {
|
|
113
|
+
const mounts = await machine.serveList({ sessionId, all, _rkwargs: true });
|
|
114
|
+
if (json) {
|
|
115
|
+
console.log(JSON.stringify(mounts, null, 2));
|
|
116
|
+
} else if (mounts.length === 0) {
|
|
117
|
+
console.log(all ? "No mounts registered." : "No mounts for this session. Use --all to see all mounts.");
|
|
118
|
+
} else {
|
|
119
|
+
const label = all ? "All mounts" : `Mounts${sessionId ? ` (session ${sessionId.slice(0, 8)})` : ""}`;
|
|
120
|
+
console.log(`${label}:
|
|
121
|
+
`);
|
|
122
|
+
for (const m of mounts) {
|
|
123
|
+
const session = m.sessionId ? m.sessionId.slice(0, 8) : "-";
|
|
124
|
+
console.log(` ${m.name}`);
|
|
125
|
+
console.log(` Directory: ${m.directory}`);
|
|
126
|
+
console.log(` Session: ${session}`);
|
|
127
|
+
console.log(` Added: ${new Date(m.addedAt).toISOString().slice(0, 16).replace("T", " ")}`);
|
|
128
|
+
console.log("");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(`Error: ${err.message || err}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
} finally {
|
|
135
|
+
await server.disconnect().catch(() => {
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function serveInfo(machineId) {
|
|
140
|
+
const { connectAndGetMachine } = await import('./commands-h1lFrJKK.mjs');
|
|
141
|
+
const { machine, server } = await connectAndGetMachine(machineId);
|
|
142
|
+
try {
|
|
143
|
+
const info = await machine.serveInfo({ _rkwargs: true });
|
|
144
|
+
console.log(`Static file server:`);
|
|
145
|
+
console.log(` Status: ${info.running ? "running" : "stopped"}`);
|
|
146
|
+
console.log(` Port: ${info.port || "-"}`);
|
|
147
|
+
console.log(` URL: ${info.url || "(not exposed)"}`);
|
|
148
|
+
console.log(` Mounts: ${info.mountCount}`);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(`Error: ${err.message || err}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
} finally {
|
|
153
|
+
await server.disconnect().catch(() => {
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function printServeHelp() {
|
|
158
|
+
console.log(`
|
|
159
|
+
svamp serve \u2014 Shared static file server
|
|
160
|
+
|
|
161
|
+
Serve local directories via a single daemon-managed HTTP server with one public URL.
|
|
162
|
+
Multiple sessions can register different mount points without port conflicts.
|
|
163
|
+
|
|
164
|
+
Usage:
|
|
165
|
+
svamp serve <name> [directory] Add a mount and print its URL (dir defaults to .)
|
|
166
|
+
svamp serve add <name> [directory] Same as above
|
|
167
|
+
svamp serve remove <name> Remove a mount
|
|
168
|
+
svamp serve list [--all] [--json] List mounts (default: current session only)
|
|
169
|
+
svamp serve info Show server status and URL
|
|
170
|
+
|
|
171
|
+
Access control (default: owner only):
|
|
172
|
+
--public Allow anyone to access (no auth)
|
|
173
|
+
--access email1,email2 Allow specific users (comma-separated emails)
|
|
174
|
+
(no flag) Owner only \u2014 requires Hypha login
|
|
175
|
+
|
|
176
|
+
Options:
|
|
177
|
+
-m, --machine <id> Target a specific machine
|
|
178
|
+
--session <id> Filter by session ID
|
|
179
|
+
--all, -a Show mounts from all sessions
|
|
180
|
+
--json Output as JSON
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
svamp serve my-report ./output # Owner-only (default)
|
|
184
|
+
svamp serve dashboard ./dist --public # Anyone can access
|
|
185
|
+
svamp serve data ./csv --access a@x.com,b@y.com # Specific users
|
|
186
|
+
svamp serve list --all # Show all mounts
|
|
187
|
+
svamp serve remove my-report # Stop serving
|
|
188
|
+
`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { handleServeCommand };
|