svamp-cli 0.1.0
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/bin/svamp.mjs +35 -0
- package/dist/cli.mjs +200 -0
- package/dist/index.mjs +15 -0
- package/dist/run-DgPbD8x5.mjs +1659 -0
- package/package.json +39 -0
package/bin/svamp.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Svamp CLI entry point
|
|
4
|
+
// Loads .env from ~/.svamp/.env if present, then runs the CLI
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
|
|
10
|
+
// Simple .env loader — load from SVAMP_HOME or ~/.svamp/
|
|
11
|
+
const envDir = process.env.SVAMP_HOME || join(homedir(), '.svamp');
|
|
12
|
+
const envFile = join(envDir, '.env');
|
|
13
|
+
|
|
14
|
+
if (existsSync(envFile)) {
|
|
15
|
+
const lines = readFileSync(envFile, 'utf-8').split('\n');
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
19
|
+
const eqIdx = trimmed.indexOf('=');
|
|
20
|
+
if (eqIdx === -1) continue;
|
|
21
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
22
|
+
const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
23
|
+
if (!process.env[key]) {
|
|
24
|
+
process.env[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Import and run the CLI
|
|
30
|
+
import('../dist/cli.mjs').catch((err) => {
|
|
31
|
+
// Fallback: try running from source with tsx
|
|
32
|
+
console.error('Failed to load compiled CLI. Try running: npm run build');
|
|
33
|
+
console.error(err.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-DgPbD8x5.mjs';
|
|
2
|
+
import 'os';
|
|
3
|
+
import 'fs/promises';
|
|
4
|
+
import 'fs';
|
|
5
|
+
import 'path';
|
|
6
|
+
import 'url';
|
|
7
|
+
import 'child_process';
|
|
8
|
+
import 'crypto';
|
|
9
|
+
import 'node:crypto';
|
|
10
|
+
import 'node:fs';
|
|
11
|
+
import 'node:path';
|
|
12
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
|
+
import 'node:http';
|
|
14
|
+
import '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
15
|
+
import 'zod';
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const subcommand = args[0];
|
|
19
|
+
const daemonSubcommand = args[1];
|
|
20
|
+
async function main() {
|
|
21
|
+
if (subcommand === "login") {
|
|
22
|
+
await loginToHypha();
|
|
23
|
+
} else if (subcommand === "daemon") {
|
|
24
|
+
if (daemonSubcommand === "start") {
|
|
25
|
+
const { spawn } = await import('child_process');
|
|
26
|
+
const child = spawn(process.execPath, [
|
|
27
|
+
"--no-warnings",
|
|
28
|
+
"--no-deprecation",
|
|
29
|
+
...process.argv.slice(1, 2),
|
|
30
|
+
// the script path
|
|
31
|
+
"daemon",
|
|
32
|
+
"start-sync"
|
|
33
|
+
], {
|
|
34
|
+
detached: true,
|
|
35
|
+
stdio: "ignore",
|
|
36
|
+
env: process.env
|
|
37
|
+
});
|
|
38
|
+
child.unref();
|
|
39
|
+
const { existsSync } = await import('fs');
|
|
40
|
+
const { join } = await import('path');
|
|
41
|
+
const os = await import('os');
|
|
42
|
+
const stateFile = join(
|
|
43
|
+
process.env.SVAMP_HOME || join(os.homedir(), ".svamp"),
|
|
44
|
+
"daemon.state.json"
|
|
45
|
+
);
|
|
46
|
+
let started = false;
|
|
47
|
+
for (let i = 0; i < 100; i++) {
|
|
48
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
49
|
+
if (existsSync(stateFile)) {
|
|
50
|
+
started = true;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (started) {
|
|
55
|
+
console.log("Daemon started successfully");
|
|
56
|
+
} else {
|
|
57
|
+
console.error("Failed to start daemon (timeout waiting for state file)");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
process.exit(0);
|
|
61
|
+
} else if (daemonSubcommand === "start-sync") {
|
|
62
|
+
await startDaemon();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
} else if (daemonSubcommand === "stop") {
|
|
65
|
+
await stopDaemon();
|
|
66
|
+
process.exit(0);
|
|
67
|
+
} else if (daemonSubcommand === "status") {
|
|
68
|
+
daemonStatus();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
} else {
|
|
71
|
+
printDaemonHelp();
|
|
72
|
+
}
|
|
73
|
+
} else if (subcommand === "--help" || subcommand === "-h" || !subcommand) {
|
|
74
|
+
printHelp();
|
|
75
|
+
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
76
|
+
console.log("svamp version: 0.1.0");
|
|
77
|
+
} else {
|
|
78
|
+
console.error(`Unknown command: ${subcommand}`);
|
|
79
|
+
printHelp();
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function loginToHypha() {
|
|
84
|
+
const serverUrl = args[1] || process.env.HYPHA_SERVER_URL;
|
|
85
|
+
if (!serverUrl) {
|
|
86
|
+
console.error("Usage: svamp login <server-url>");
|
|
87
|
+
console.error(" Or set HYPHA_SERVER_URL environment variable");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
console.log(`Logging in to Hypha server: ${serverUrl}`);
|
|
91
|
+
console.log("A browser window will open for authentication...");
|
|
92
|
+
try {
|
|
93
|
+
const mod = await import('hypha-rpc');
|
|
94
|
+
const hyphaWebsocketClient = mod.hyphaWebsocketClient || mod.default?.hyphaWebsocketClient;
|
|
95
|
+
if (!hyphaWebsocketClient?.login) {
|
|
96
|
+
throw new Error("Could not find hyphaWebsocketClient.login in hypha-rpc");
|
|
97
|
+
}
|
|
98
|
+
const token = await hyphaWebsocketClient.login({
|
|
99
|
+
server_url: serverUrl,
|
|
100
|
+
login_callback: (context) => {
|
|
101
|
+
console.log(`
|
|
102
|
+
Please open this URL in your browser:
|
|
103
|
+
${context.login_url}
|
|
104
|
+
`);
|
|
105
|
+
import('child_process').then(({ exec }) => {
|
|
106
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
107
|
+
exec(`${cmd} "${context.login_url}"`);
|
|
108
|
+
}).catch(() => {
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
if (!token) {
|
|
113
|
+
console.error("Login failed \u2014 no token received");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const connectToServer = mod.connectToServer || mod.default && mod.default.connectToServer || hyphaWebsocketClient && hyphaWebsocketClient.connectToServer;
|
|
117
|
+
const server = await connectToServer({ server_url: serverUrl, token, transport: "http" });
|
|
118
|
+
const workspace = server.config.workspace;
|
|
119
|
+
const userId = server.config.user?.id || workspace;
|
|
120
|
+
let longLivedToken = token;
|
|
121
|
+
try {
|
|
122
|
+
const sixMonthsInSeconds = 6 * 30 * 24 * 60 * 60;
|
|
123
|
+
longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds });
|
|
124
|
+
console.log("Generated long-lived token (expires in ~6 months)");
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.warn("Could not generate long-lived token, using login token:", err.message || err);
|
|
127
|
+
}
|
|
128
|
+
await server.disconnect();
|
|
129
|
+
const os = await import('os');
|
|
130
|
+
const { join } = await import('path');
|
|
131
|
+
const fs = await import('fs');
|
|
132
|
+
const homeDir = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
133
|
+
if (!fs.existsSync(homeDir)) {
|
|
134
|
+
fs.mkdirSync(homeDir, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
const envFile = join(homeDir, ".env");
|
|
137
|
+
const envLines = [];
|
|
138
|
+
if (fs.existsSync(envFile)) {
|
|
139
|
+
const existing = fs.readFileSync(envFile, "utf-8").split("\n");
|
|
140
|
+
for (const line of existing) {
|
|
141
|
+
const trimmed = line.trim();
|
|
142
|
+
if (trimmed.startsWith("HYPHA_TOKEN=") || trimmed.startsWith("HYPHA_WORKSPACE=") || trimmed.startsWith("HYPHA_SERVER_URL=")) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
envLines.push(line);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
envLines.push(`HYPHA_SERVER_URL=${serverUrl}`);
|
|
149
|
+
envLines.push(`HYPHA_TOKEN=${longLivedToken}`);
|
|
150
|
+
envLines.push(`HYPHA_WORKSPACE=${userId}`);
|
|
151
|
+
while (envLines.length > 0 && envLines[envLines.length - 1].trim() === "") {
|
|
152
|
+
envLines.pop();
|
|
153
|
+
}
|
|
154
|
+
fs.writeFileSync(envFile, envLines.join("\n") + "\n", "utf-8");
|
|
155
|
+
console.log(`
|
|
156
|
+
Login successful!`);
|
|
157
|
+
console.log(` User: ${userId}`);
|
|
158
|
+
console.log(` Workspace: ${workspace}`);
|
|
159
|
+
console.log(` Token saved to: ${envFile}`);
|
|
160
|
+
console.log(`
|
|
161
|
+
You can now start the daemon: svamp daemon start`);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error("Login failed:", err.message || err);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function printHelp() {
|
|
168
|
+
console.log(`
|
|
169
|
+
svamp \u2014 Svamp CLI with Hypha transport
|
|
170
|
+
|
|
171
|
+
Usage:
|
|
172
|
+
svamp login [url] Login to Hypha (opens browser, stores token)
|
|
173
|
+
svamp daemon start Start the daemon (detached)
|
|
174
|
+
svamp daemon stop Stop the daemon
|
|
175
|
+
svamp daemon status Show daemon status
|
|
176
|
+
svamp --version Show version
|
|
177
|
+
svamp --help Show this help
|
|
178
|
+
|
|
179
|
+
Environment variables:
|
|
180
|
+
HYPHA_SERVER_URL Hypha server URL (required for daemon)
|
|
181
|
+
HYPHA_TOKEN Hypha auth token (stored by login command)
|
|
182
|
+
HYPHA_WORKSPACE Hypha workspace / user ID (stored by login command)
|
|
183
|
+
SVAMP_MACHINE_ID Machine identifier (optional, auto-generated)
|
|
184
|
+
SVAMP_HOME Config directory (default: ~/.svamp)
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
function printDaemonHelp() {
|
|
188
|
+
console.log(`
|
|
189
|
+
svamp daemon \u2014 Daemon management
|
|
190
|
+
|
|
191
|
+
Usage:
|
|
192
|
+
svamp daemon start Start the daemon (detached)
|
|
193
|
+
svamp daemon stop Stop the daemon
|
|
194
|
+
svamp daemon status Show daemon status
|
|
195
|
+
`);
|
|
196
|
+
}
|
|
197
|
+
main().catch((err) => {
|
|
198
|
+
console.error("Fatal error:", err);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
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-DgPbD8x5.mjs';
|
|
2
|
+
import 'os';
|
|
3
|
+
import 'fs/promises';
|
|
4
|
+
import 'fs';
|
|
5
|
+
import 'path';
|
|
6
|
+
import 'url';
|
|
7
|
+
import 'child_process';
|
|
8
|
+
import 'crypto';
|
|
9
|
+
import 'node:crypto';
|
|
10
|
+
import 'node:fs';
|
|
11
|
+
import 'node:path';
|
|
12
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
|
+
import 'node:http';
|
|
14
|
+
import '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
15
|
+
import 'zod';
|