svamp-cli 0.2.48 → 0.2.50

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.
@@ -3,236 +3,23 @@ import * as fs from 'fs';
3
3
  import * as http from 'http';
4
4
  import * as net from 'net';
5
5
  import * as path from 'path';
6
-
7
- const AUTH0_NAMESPACES = ["https://api.imjoy.io/", "https://amun.ai/"];
8
- const COOKIE_NAME = "svamp_serve_token";
9
- const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
10
- const DEFAULT_CACHE_MAX_SIZE = 1e3;
11
- class TokenCache {
12
- cache = /* @__PURE__ */ new Map();
13
- ttlMs;
14
- maxSize;
15
- constructor(ttlMs = DEFAULT_CACHE_TTL_MS, maxSize = DEFAULT_CACHE_MAX_SIZE) {
16
- this.ttlMs = ttlMs;
17
- this.maxSize = maxSize;
18
- }
19
- get(token) {
20
- const info = this.cache.get(token);
21
- if (!info) return null;
22
- if (Date.now() > info.expiresAt) {
23
- this.cache.delete(token);
24
- return null;
25
- }
26
- return info;
27
- }
28
- set(token, email) {
29
- if (this.cache.size >= this.maxSize) {
30
- const oldest = this.cache.keys().next().value;
31
- if (oldest) this.cache.delete(oldest);
32
- }
33
- this.cache.set(token, {
34
- email,
35
- expiresAt: Date.now() + this.ttlMs
36
- });
37
- }
38
- clear() {
39
- this.cache.clear();
40
- }
41
- }
42
- function hasCookieToken(req) {
43
- return !!parseCookies(req.headers.cookie || "")[COOKIE_NAME];
44
- }
45
- function extractToken(req) {
46
- const cookies = parseCookies(req.headers.cookie || "");
47
- if (cookies[COOKIE_NAME]) return cookies[COOKIE_NAME];
48
- const auth = req.headers.authorization;
49
- if (auth?.startsWith("Bearer ") || auth?.startsWith("bearer ")) {
50
- return auth.slice(7).trim();
51
- }
52
- const url = new URL(req.url || "/", "http://localhost");
53
- const qToken = url.searchParams.get("token");
54
- if (qToken) return qToken;
55
- return null;
56
- }
57
- function parseCookies(header) {
58
- const cookies = {};
59
- for (const pair of header.split(";")) {
60
- const eq = pair.indexOf("=");
61
- if (eq > 0) {
62
- const key = pair.slice(0, eq).trim();
63
- const value = pair.slice(eq + 1).trim();
64
- cookies[key] = value;
65
- }
66
- }
67
- return cookies;
68
- }
69
- function parseJwtEmail(token) {
70
- try {
71
- const parts = token.split(".");
72
- if (parts.length !== 3) return null;
73
- const payload = JSON.parse(
74
- Buffer.from(parts[1], "base64url").toString("utf-8")
75
- );
76
- if (payload.exp && payload.exp * 1e3 < Date.now()) return null;
77
- for (const ns of AUTH0_NAMESPACES) {
78
- const email = payload[ns + "email"];
79
- if (typeof email === "string" && email) return email.toLowerCase();
80
- }
81
- if (typeof payload.email === "string" && payload.email) return payload.email.toLowerCase();
82
- return null;
83
- } catch {
84
- return null;
85
- }
86
- }
87
- async function verifyTokenViaHypha(token, hyphaServerUrl) {
88
- try {
89
- const baseUrl = hyphaServerUrl.replace(/\/$/, "");
90
- const url = `${baseUrl}/public/services/ws/get_user_info`;
91
- const resp = await fetch(url, {
92
- method: "GET",
93
- headers: {
94
- Authorization: `Bearer ${token}`
95
- },
96
- signal: AbortSignal.timeout(1e4)
97
- });
98
- if (!resp.ok) return null;
99
- const data = await resp.json();
100
- const email = data?.email;
101
- if (typeof email === "string" && email) return email.toLowerCase();
102
- return null;
103
- } catch {
104
- return null;
105
- }
106
- }
107
- class ServeAuth {
108
- cache;
109
- hyphaServerUrl;
110
- constructor(options) {
111
- this.hyphaServerUrl = options.hyphaServerUrl.replace(/\/$/, "");
112
- this.cache = new TokenCache(
113
- options.cacheTtlMs || DEFAULT_CACHE_TTL_MS,
114
- options.cacheMaxSize || DEFAULT_CACHE_MAX_SIZE
115
- );
116
- }
117
- /**
118
- * Authenticate a request and return the user's email, or null if not authenticated.
119
- */
120
- async authenticate(req) {
121
- const token = extractToken(req);
122
- if (!token) return null;
123
- const cached = this.cache.get(token);
124
- if (cached) return cached.email;
125
- const localEmail = parseJwtEmail(token);
126
- if (localEmail) {
127
- this.cache.set(token, localEmail);
128
- return localEmail;
129
- }
130
- const serverEmail = await verifyTokenViaHypha(token, this.hyphaServerUrl);
131
- if (serverEmail) {
132
- this.cache.set(token, serverEmail);
133
- return serverEmail;
134
- }
135
- return null;
136
- }
137
- /**
138
- * Check if a user email is authorized for a mount's access level.
139
- */
140
- isAuthorized(email, access, ownerEmail) {
141
- if (access === "public") return true;
142
- if (!email) return false;
143
- if (access === "owner") {
144
- return !!ownerEmail && email.toLowerCase() === ownerEmail.toLowerCase();
145
- }
146
- return access.some((e) => e.toLowerCase() === email.toLowerCase());
147
- }
148
- /**
149
- * Generate the login page HTML. Loads the `hypha-rpc` JS SDK from CDN and
150
- * calls `hyphaWebsocketClient.login({ server_url, login_callback })`, which
151
- * handles opening the login URL, polling, and token retrieval internally.
152
- * Matches the pattern used by bioimage.io — proven to work.
153
- */
154
- getLoginPageHtml(redirectUrl) {
155
- return `<!DOCTYPE html>
156
- <html><head>
157
- <meta charset="utf-8">
158
- <meta name="viewport" content="width=device-width,initial-scale=1">
159
- <title>Sign in \u2014 Svamp File Server</title>
160
- <style>
161
- *{box-sizing:border-box;margin:0;padding:0}
162
- body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#f6f8fa;color:#24292f;padding:16px}
163
- .card{background:#fff;border:1px solid #d0d7de;border-radius:12px;padding:32px;max-width:420px;width:100%;text-align:center;box-shadow:0 4px 12px rgba(31,35,40,0.06)}
164
- h1{font-size:1.25rem;margin-bottom:8px}
165
- .subtitle{color:#656d76;font-size:0.9rem;margin-bottom:24px}
166
- button{background:#0969da;color:#fff;border:none;border-radius:8px;padding:12px 24px;font-size:1rem;cursor:pointer;font-weight:500;width:100%}
167
- button:hover{background:#0860c4}
168
- button:disabled{background:#94d3a2;cursor:wait}
169
- .status{margin-top:16px;color:#656d76;font-size:0.85rem;min-height:20px;word-break:break-word}
170
- .error{color:#cf222e}
171
- .ok{color:#1a7f37}
172
- a{color:#0969da;text-decoration:none}
173
- a:hover{text-decoration:underline}
174
- </style>
175
- <script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.21.28/dist/hypha-rpc-websocket.min.js"><\/script>
176
- </head><body>
177
- <div class="card">
178
- <h1>Sign in required</h1>
179
- <p class="subtitle">This resource is private. Sign in with your Hypha account to continue.</p>
180
- <button id="login-btn">Sign in with Hypha</button>
181
- <div class="status" id="status"></div>
182
- </div>
183
- <script>
184
- const hyphaServer = ${JSON.stringify(this.hyphaServerUrl)};
185
- const redirectUrl = ${JSON.stringify(redirectUrl)};
186
- const cookieName = ${JSON.stringify(COOKIE_NAME)};
187
-
188
- const btn = document.getElementById('login-btn');
189
- const statusEl = document.getElementById('status');
190
-
191
- function setError(msg) {
192
- statusEl.innerHTML = '<span class="error">' + msg + '</span>';
193
- btn.disabled = false;
194
- }
195
-
196
- btn.addEventListener('click', async () => {
197
- if (typeof hyphaWebsocketClient === 'undefined' || !hyphaWebsocketClient.login) {
198
- setError('Hypha SDK failed to load. Check your network connection and retry.');
199
- return;
200
- }
201
-
202
- btn.disabled = true;
203
- statusEl.textContent = 'Opening Hypha sign-in\u2026';
204
-
205
- try {
206
- const token = await hyphaWebsocketClient.login({
207
- server_url: hyphaServer,
208
- login_callback: (context) => {
209
- statusEl.textContent = 'Waiting for you to sign in\u2026';
210
- // Open the login URL in a new tab. Called synchronously from
211
- // inside the SDK's login() after the user's button click, so
212
- // popup blockers generally allow it.
213
- window.open(context.login_url, '_blank');
214
- },
215
- });
216
-
217
- if (!token) throw new Error('No token returned from login');
218
-
219
- // Set the cookie so subsequent requests to this origin are authenticated.
220
- const secure = location.protocol === 'https:' ? '; Secure' : '';
221
- document.cookie = cookieName + '=' + token + '; path=/; SameSite=Lax' + secure;
222
-
223
- statusEl.innerHTML = '<span class="ok">Signed in. Redirecting\u2026</span>';
224
- setTimeout(() => { window.location.replace(redirectUrl); }, 300);
225
- } catch (err) {
226
- setError('Login failed: ' + (err && err.message ? err.message : err));
227
- }
228
- });
229
- <\/script>
230
- </body></html>`;
231
- }
232
- destroy() {
233
- this.cache.clear();
234
- }
235
- }
6
+ import { S as ServeAuth, h as hasCookieToken } from './run-CIPCavEp.mjs';
7
+ import 'os';
8
+ import 'fs/promises';
9
+ import 'url';
10
+ import 'crypto';
11
+ import 'node:fs';
12
+ import 'node:crypto';
13
+ import 'node:path';
14
+ import 'node:child_process';
15
+ import '@agentclientprotocol/sdk';
16
+ import 'node:os';
17
+ import '@modelcontextprotocol/sdk/client/index.js';
18
+ import '@modelcontextprotocol/sdk/client/stdio.js';
19
+ import '@modelcontextprotocol/sdk/types.js';
20
+ import 'zod';
21
+ import 'node:fs/promises';
22
+ import 'node:util';
236
23
 
237
24
  function findFreePort() {
238
25
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.48",
3
+ "version": "0.2.50",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "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-isolation-decision.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-claude-auth.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
23
+ "test": "npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.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-isolation-decision.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-claude-auth.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -1,63 +0,0 @@
1
- var name = "svamp-cli";
2
- var version = "0.2.48";
3
- var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
- var author = "Amun AI AB";
5
- var license = "SEE LICENSE IN LICENSE";
6
- var type = "module";
7
- var bin = {
8
- svamp: "./bin/svamp.mjs"
9
- };
10
- var files = [
11
- "dist",
12
- "bin"
13
- ];
14
- var main = "./dist/index.mjs";
15
- var exports$1 = {
16
- ".": "./dist/index.mjs",
17
- "./cli": "./dist/cli.mjs"
18
- };
19
- var scripts = {
20
- build: "rm -rf dist && tsc --noEmit && pkgroll",
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-isolation-decision.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-claude-auth.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-supervisor-lock.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only",
23
- "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
- dev: "tsx src/cli.ts",
25
- "dev:daemon": "tsx src/cli.ts daemon start-sync",
26
- "test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
27
- "test:frpc": "npx tsx test/test-frpc-e2e.mjs"
28
- };
29
- var dependencies = {
30
- "@agentclientprotocol/sdk": "^0.14.1",
31
- "@modelcontextprotocol/sdk": "^1.25.3",
32
- "hypha-rpc": "0.21.40",
33
- "node-pty": "1.2.0-beta.11",
34
- ws: "^8.18.0",
35
- yaml: "^2.8.2",
36
- zod: "^3.24.4"
37
- };
38
- var devDependencies = {
39
- "@types/node": ">=20",
40
- "@types/ws": "^8.5.14",
41
- pkgroll: "^2.14.2",
42
- tsx: "^4.20.6",
43
- typescript: "5.9.3"
44
- };
45
- var packageManager = "yarn@1.22.22";
46
- var _package = {
47
- name: name,
48
- version: version,
49
- description: description,
50
- author: author,
51
- license: license,
52
- type: type,
53
- bin: bin,
54
- files: files,
55
- main: main,
56
- exports: exports$1,
57
- scripts: scripts,
58
- dependencies: dependencies,
59
- devDependencies: devDependencies,
60
- packageManager: packageManager
61
- };
62
-
63
- export { author, bin, _package as default, dependencies, description, devDependencies, exports$1 as exports, files, license, main, name, packageManager, scripts, type, version };