hypha-cli 0.1.11 → 0.1.12
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/apps-D9WCfzIl.mjs +512 -0
- package/dist/artifacts-DHPJIrSS.mjs +955 -0
- package/dist/cli.mjs +11 -11
- package/dist/helpers-BC4AKy6a.mjs +224 -0
- package/dist/index.mjs +1 -1
- package/dist/workspace-DE2jBAXs.mjs +286 -0
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -77,37 +77,37 @@ async function main() {
|
|
|
77
77
|
}
|
|
78
78
|
const commandArgs = args.slice(1);
|
|
79
79
|
if (subcommand === "login") {
|
|
80
|
-
const { loginCommand } = await import('./workspace-
|
|
80
|
+
const { loginCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
81
81
|
await loginCommand(commandArgs);
|
|
82
82
|
} else if (subcommand === "token") {
|
|
83
|
-
const { tokenCommand } = await import('./workspace-
|
|
83
|
+
const { tokenCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
84
84
|
await tokenCommand(commandArgs);
|
|
85
85
|
} else if (subcommand === "services") {
|
|
86
|
-
const { servicesCommand } = await import('./workspace-
|
|
86
|
+
const { servicesCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
87
87
|
await servicesCommand(commandArgs);
|
|
88
88
|
} else if (subcommand === "info") {
|
|
89
|
-
const { infoCommand } = await import('./workspace-
|
|
89
|
+
const { infoCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
90
90
|
await infoCommand(commandArgs);
|
|
91
91
|
} else if (subcommand === "clients") {
|
|
92
|
-
const { clientsCommand } = await import('./workspace-
|
|
92
|
+
const { clientsCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
93
93
|
await clientsCommand(commandArgs);
|
|
94
94
|
} else if (subcommand === "ping") {
|
|
95
|
-
const { pingCommand } = await import('./workspace-
|
|
95
|
+
const { pingCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
96
96
|
await pingCommand(commandArgs);
|
|
97
97
|
} else if (subcommand === "kick") {
|
|
98
|
-
const { kickCommand } = await import('./workspace-
|
|
98
|
+
const { kickCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
99
99
|
await kickCommand(commandArgs);
|
|
100
100
|
} else if (subcommand === "cleanup") {
|
|
101
|
-
const { cleanupCommand } = await import('./workspace-
|
|
101
|
+
const { cleanupCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
102
102
|
await cleanupCommand(commandArgs);
|
|
103
103
|
} else if (subcommand === "workspace-info") {
|
|
104
|
-
const { workspaceInfoCommand } = await import('./workspace-
|
|
104
|
+
const { workspaceInfoCommand } = await import('./workspace-DE2jBAXs.mjs');
|
|
105
105
|
await workspaceInfoCommand(commandArgs);
|
|
106
106
|
} else if (subcommand === "apps") {
|
|
107
|
-
const { handleAppsCommand } = await import('./apps-
|
|
107
|
+
const { handleAppsCommand } = await import('./apps-D9WCfzIl.mjs');
|
|
108
108
|
await handleAppsCommand(commandArgs);
|
|
109
109
|
} else if (subcommand === "artifacts" || subcommand === "art") {
|
|
110
|
-
const { handleArtifactsCommand } = await import('./artifacts-
|
|
110
|
+
const { handleArtifactsCommand } = await import('./artifacts-DHPJIrSS.mjs');
|
|
111
111
|
await handleArtifactsCommand(commandArgs);
|
|
112
112
|
} else {
|
|
113
113
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
|
|
5
|
+
function getHyphaHome() {
|
|
6
|
+
return process.env.HYPHA_HOME || join(homedir(), ".hypha");
|
|
7
|
+
}
|
|
8
|
+
function getEnvFilePath() {
|
|
9
|
+
return join(getHyphaHome(), ".env");
|
|
10
|
+
}
|
|
11
|
+
function readEnvValue(key) {
|
|
12
|
+
const envFile = getEnvFilePath();
|
|
13
|
+
if (!existsSync(envFile)) return void 0;
|
|
14
|
+
const lines = readFileSync(envFile, "utf-8").split("\n");
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
18
|
+
const eqIdx = trimmed.indexOf("=");
|
|
19
|
+
if (eqIdx === -1) continue;
|
|
20
|
+
const k = trimmed.slice(0, eqIdx).trim();
|
|
21
|
+
const v = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
22
|
+
if (k === key) return v;
|
|
23
|
+
}
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
function writeEnvValue(key, value) {
|
|
27
|
+
const envFile = getEnvFilePath();
|
|
28
|
+
const dir = getHyphaHome();
|
|
29
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
30
|
+
let lines = [];
|
|
31
|
+
if (existsSync(envFile)) {
|
|
32
|
+
lines = readFileSync(envFile, "utf-8").split("\n");
|
|
33
|
+
}
|
|
34
|
+
let found = false;
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
const trimmed = lines[i].trim();
|
|
37
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
38
|
+
const eqIdx = trimmed.indexOf("=");
|
|
39
|
+
if (eqIdx === -1) continue;
|
|
40
|
+
if (trimmed.slice(0, eqIdx).trim() === key) {
|
|
41
|
+
lines[i] = `${key}=${value}`;
|
|
42
|
+
found = true;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!found) {
|
|
47
|
+
lines.push(`${key}=${value}`);
|
|
48
|
+
}
|
|
49
|
+
writeFileSync(envFile, lines.join("\n") + "\n", { mode: 384 });
|
|
50
|
+
}
|
|
51
|
+
function resolveServerUrl(opts) {
|
|
52
|
+
return opts?.serverUrl || process.env.HYPHA_SERVER_URL || readEnvValue("HYPHA_SERVER_URL") || "https://hypha.aicell.io";
|
|
53
|
+
}
|
|
54
|
+
function resolveToken(opts) {
|
|
55
|
+
return opts?.token || process.env.HYPHA_TOKEN || readEnvValue("HYPHA_TOKEN");
|
|
56
|
+
}
|
|
57
|
+
function resolveWorkspace(opts) {
|
|
58
|
+
return opts?.workspace || process.env.HYPHA_WORKSPACE || readEnvValue("HYPHA_WORKSPACE");
|
|
59
|
+
}
|
|
60
|
+
async function connectToHypha(opts) {
|
|
61
|
+
const serverUrl = resolveServerUrl(opts);
|
|
62
|
+
const token = resolveToken(opts);
|
|
63
|
+
const workspace = resolveWorkspace(opts);
|
|
64
|
+
if (!token) {
|
|
65
|
+
const envFile = getEnvFilePath();
|
|
66
|
+
console.error("No HYPHA_TOKEN found.");
|
|
67
|
+
console.error(` Looked in: process.env.HYPHA_TOKEN, ${envFile}`);
|
|
68
|
+
console.error(" Fix: run `hypha login`, or set HYPHA_TOKEN in the environment / that file.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const { hyphaWebsocketClient } = await import('hypha-rpc');
|
|
72
|
+
let server;
|
|
73
|
+
try {
|
|
74
|
+
server = await hyphaWebsocketClient.connectToServer({
|
|
75
|
+
server_url: serverUrl,
|
|
76
|
+
token,
|
|
77
|
+
workspace: workspace || void 0,
|
|
78
|
+
// Suppress hypha-rpc connection/disconnection log noise
|
|
79
|
+
logger: null
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const msg = String(err?.message || err);
|
|
83
|
+
if (workspace && /workspace/i.test(msg) && /(mismatch|denied|permission|forbidden|not allowed|unauthorized)/i.test(msg)) {
|
|
84
|
+
console.error(`Workspace '${workspace}' was requested but the token does not grant it.`);
|
|
85
|
+
console.error(` Source of override: process.env.HYPHA_WORKSPACE or ${getEnvFilePath()}`);
|
|
86
|
+
console.error(" Fix: run `hypha login` to refresh credentials, or unset HYPHA_WORKSPACE.");
|
|
87
|
+
console.error(` (server said: ${msg})`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
return server;
|
|
93
|
+
}
|
|
94
|
+
async function loginToHypha(serverUrl) {
|
|
95
|
+
const url = serverUrl || resolveServerUrl();
|
|
96
|
+
const { hyphaWebsocketClient } = await import('hypha-rpc');
|
|
97
|
+
console.log(`Logging in to ${url}...`);
|
|
98
|
+
const token = await hyphaWebsocketClient.login({
|
|
99
|
+
server_url: url,
|
|
100
|
+
logger: null
|
|
101
|
+
});
|
|
102
|
+
if (!token) {
|
|
103
|
+
console.error("Login failed \u2014 no token received.");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const server = await hyphaWebsocketClient.connectToServer({
|
|
107
|
+
server_url: url,
|
|
108
|
+
token,
|
|
109
|
+
logger: null
|
|
110
|
+
});
|
|
111
|
+
const ws = server.config.workspace;
|
|
112
|
+
writeEnvValue("HYPHA_SERVER_URL", url);
|
|
113
|
+
writeEnvValue("HYPHA_TOKEN", token);
|
|
114
|
+
writeEnvValue("HYPHA_WORKSPACE", ws);
|
|
115
|
+
console.log(`Logged in to workspace: ${ws}`);
|
|
116
|
+
console.log(`Credentials saved to ${getEnvFilePath()}`);
|
|
117
|
+
await server.disconnect();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getFlag(args, flag, shortFlag) {
|
|
121
|
+
for (let i = 0; i < args.length; i++) {
|
|
122
|
+
if ((args[i] === flag || shortFlag && args[i] === shortFlag) && i + 1 < args.length) {
|
|
123
|
+
return args[i + 1];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
function getFlagInt(args, flag, shortFlag) {
|
|
129
|
+
const val = getFlag(args, flag, shortFlag);
|
|
130
|
+
if (val === void 0) return void 0;
|
|
131
|
+
const n = parseInt(val, 10);
|
|
132
|
+
return isNaN(n) ? void 0 : n;
|
|
133
|
+
}
|
|
134
|
+
function getAllFlags(args, flag) {
|
|
135
|
+
const results = [];
|
|
136
|
+
for (let i = 0; i < args.length; i++) {
|
|
137
|
+
if (args[i] === flag && i + 1 < args.length) {
|
|
138
|
+
results.push(args[i + 1]);
|
|
139
|
+
i++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
}
|
|
144
|
+
function hasFlag(args, ...flags) {
|
|
145
|
+
return args.some((a) => flags.includes(a));
|
|
146
|
+
}
|
|
147
|
+
function positionalArgs(args, knownFlags = []) {
|
|
148
|
+
const result = [];
|
|
149
|
+
for (let i = 0; i < args.length; i++) {
|
|
150
|
+
if (args[i].startsWith("-")) {
|
|
151
|
+
if (knownFlags.includes(args[i]) && i + 1 < args.length) {
|
|
152
|
+
i++;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
result.push(args[i]);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
function formatJson(data) {
|
|
161
|
+
return JSON.stringify(data, null, 2);
|
|
162
|
+
}
|
|
163
|
+
function formatTable(rows) {
|
|
164
|
+
if (rows.length === 0) return "";
|
|
165
|
+
const colCount = rows[0].length;
|
|
166
|
+
const widths = new Array(colCount).fill(0);
|
|
167
|
+
for (const row of rows) {
|
|
168
|
+
for (let i = 0; i < row.length; i++) {
|
|
169
|
+
widths[i] = Math.max(widths[i], (row[i] || "").length);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return rows.map(
|
|
173
|
+
(row) => row.map((cell, i) => (cell || "").padEnd(widths[i])).join(" ")
|
|
174
|
+
).join("\n");
|
|
175
|
+
}
|
|
176
|
+
function humanSize(bytes) {
|
|
177
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
178
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
179
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
180
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
181
|
+
}
|
|
182
|
+
function relativeTime(timestamp) {
|
|
183
|
+
const diff = Date.now() - timestamp;
|
|
184
|
+
const seconds = Math.floor(diff / 1e3);
|
|
185
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
186
|
+
const minutes = Math.floor(seconds / 60);
|
|
187
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
188
|
+
const hours = Math.floor(minutes / 60);
|
|
189
|
+
if (hours < 24) return `${hours}h ago`;
|
|
190
|
+
const days = Math.floor(hours / 24);
|
|
191
|
+
return `${days}d ago`;
|
|
192
|
+
}
|
|
193
|
+
function printProgress(label, current, total) {
|
|
194
|
+
if (total <= 0) return;
|
|
195
|
+
const pct = Math.min(100, Math.round(current / total * 100));
|
|
196
|
+
const barWidth = 30;
|
|
197
|
+
const filled = Math.round(barWidth * (current / total));
|
|
198
|
+
const bar = "=".repeat(filled) + (filled < barWidth ? ">" : "") + " ".repeat(Math.max(0, barWidth - filled - 1));
|
|
199
|
+
const sizeStr = `${humanSize(current)}/${humanSize(total)}`;
|
|
200
|
+
const maxLabel = 25;
|
|
201
|
+
const displayLabel = label.length > maxLabel ? "..." + label.slice(-22) : label;
|
|
202
|
+
process.stderr.write(`\r ${displayLabel.padEnd(maxLabel)} [${bar}] ${pct}% ${sizeStr} `);
|
|
203
|
+
if (current >= total) {
|
|
204
|
+
process.stderr.write("\n");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function printAggregateProgress(label, filesDone, filesTotal, bytesDone, bytesTotal) {
|
|
208
|
+
if (filesTotal <= 0) return;
|
|
209
|
+
const pct = Math.min(100, Math.round(filesDone / filesTotal * 100));
|
|
210
|
+
const barWidth = 30;
|
|
211
|
+
const filled = Math.round(barWidth * (filesDone / filesTotal));
|
|
212
|
+
const bar = "=".repeat(filled) + (filled < barWidth ? ">" : "") + " ".repeat(Math.max(0, barWidth - filled - 1));
|
|
213
|
+
const sizeStr = bytesTotal > 0 ? ` ${humanSize(bytesDone)}/${humanSize(bytesTotal)}` : "";
|
|
214
|
+
const maxLabel = 20;
|
|
215
|
+
const displayLabel = label.length > maxLabel ? "..." + label.slice(-17) : label;
|
|
216
|
+
process.stderr.write(
|
|
217
|
+
`\r ${displayLabel.padEnd(maxLabel)} [${bar}] ${pct}% ${filesDone}/${filesTotal} files${sizeStr} `
|
|
218
|
+
);
|
|
219
|
+
if (filesDone >= filesTotal) {
|
|
220
|
+
process.stderr.write("\n");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export { formatTable as a, resolveToken as b, connectToHypha as c, resolveWorkspace as d, hasFlag as e, formatJson as f, getFlagInt as g, humanSize as h, getFlag as i, relativeTime as j, getAllFlags as k, loginToHypha as l, printAggregateProgress as m, printProgress as n, positionalArgs as p, resolveServerUrl as r };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as connectToHypha, f as formatJson, a as formatTable, h as humanSize, l as loginToHypha, r as resolveServerUrl, b as resolveToken, d as resolveWorkspace } from './helpers-
|
|
1
|
+
export { c as connectToHypha, f as formatJson, a as formatTable, h as humanSize, l as loginToHypha, r as resolveServerUrl, b as resolveToken, d as resolveWorkspace } from './helpers-BC4AKy6a.mjs';
|
|
2
2
|
export { d as determineCpDirection, i as isArtifactPath, p as parseArtifactPath } from './artifactPath-DCtvp6Go.mjs';
|
|
3
3
|
import 'fs';
|
|
4
4
|
import 'path';
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { e as hasFlag, l as loginToHypha, c as connectToHypha, g as getFlagInt, i as getFlag, f as formatJson, a as formatTable, r as resolveServerUrl } from './helpers-BC4AKy6a.mjs';
|
|
2
|
+
import 'fs';
|
|
3
|
+
import 'path';
|
|
4
|
+
import 'os';
|
|
5
|
+
|
|
6
|
+
async function loginCommand(args) {
|
|
7
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
8
|
+
console.log(`Usage: hypha login [server-url]
|
|
9
|
+
|
|
10
|
+
Login to a Hypha server via browser-based OAuth.
|
|
11
|
+
Credentials are saved to ~/.hypha/.env.
|
|
12
|
+
|
|
13
|
+
Default server: https://hypha.aicell.io`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const serverUrl = args[0] && !args[0].startsWith("-") ? args[0] : void 0;
|
|
17
|
+
await loginToHypha(serverUrl);
|
|
18
|
+
}
|
|
19
|
+
async function tokenCommand(args) {
|
|
20
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
21
|
+
console.log(`Usage: hypha token [options]
|
|
22
|
+
|
|
23
|
+
Generate a workspace token.
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--expires-in <seconds> Token expiration (default: 3600)
|
|
27
|
+
--permission <perm> Permission level: read, read_write, admin (default: read_write)
|
|
28
|
+
--workspace <ws> Target workspace (default: current)
|
|
29
|
+
--json Output as JSON`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const server = await connectToHypha();
|
|
33
|
+
try {
|
|
34
|
+
const expiresIn = getFlagInt(args, "--expires-in") || 3600;
|
|
35
|
+
const permission = getFlag(args, "--permission") || "read_write";
|
|
36
|
+
const workspace = getFlag(args, "--workspace") || server.config.workspace;
|
|
37
|
+
const json = hasFlag(args, "--json");
|
|
38
|
+
const token = await server.generateToken({
|
|
39
|
+
expires_in: expiresIn,
|
|
40
|
+
permission,
|
|
41
|
+
workspace
|
|
42
|
+
});
|
|
43
|
+
if (json) {
|
|
44
|
+
console.log(formatJson({
|
|
45
|
+
token,
|
|
46
|
+
workspace,
|
|
47
|
+
permission,
|
|
48
|
+
expires_in: expiresIn
|
|
49
|
+
}));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(token);
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
await server.disconnect();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function servicesCommand(args) {
|
|
58
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
59
|
+
console.log(`Usage: hypha services [options]
|
|
60
|
+
|
|
61
|
+
List services in the current workspace.
|
|
62
|
+
|
|
63
|
+
Options:
|
|
64
|
+
--type <type> Filter by service type
|
|
65
|
+
--include-unlisted Include unlisted services
|
|
66
|
+
--json Output as JSON`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const server = await connectToHypha();
|
|
70
|
+
try {
|
|
71
|
+
const type = getFlag(args, "--type");
|
|
72
|
+
const includeUnlisted = hasFlag(args, "--include-unlisted");
|
|
73
|
+
const json = hasFlag(args, "--json");
|
|
74
|
+
const query = { _rkwargs: true };
|
|
75
|
+
if (type) query.type = type;
|
|
76
|
+
if (includeUnlisted) query.include_unlisted = true;
|
|
77
|
+
const services = await server.listServices(query);
|
|
78
|
+
if (json) {
|
|
79
|
+
console.log(formatJson(services));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!services || services.length === 0) {
|
|
83
|
+
console.log("No services found.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const rows = [["ID", "NAME", "TYPE", "DESCRIPTION"]];
|
|
87
|
+
for (const svc of services) {
|
|
88
|
+
rows.push([
|
|
89
|
+
svc.id || "",
|
|
90
|
+
svc.name || "",
|
|
91
|
+
svc.type || "",
|
|
92
|
+
(svc.description || "").slice(0, 60)
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
console.log(formatTable(rows));
|
|
96
|
+
} finally {
|
|
97
|
+
await server.disconnect();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function clientsCommand(args) {
|
|
101
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
102
|
+
console.log(`Usage: hypha clients [workspace] [options]
|
|
103
|
+
|
|
104
|
+
List connected clients in a workspace. Defaults to the connected workspace.
|
|
105
|
+
Pass a workspace name as positional arg to target a different workspace (requires admin).
|
|
106
|
+
|
|
107
|
+
Options:
|
|
108
|
+
--json Output as JSON`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const server = await connectToHypha();
|
|
112
|
+
try {
|
|
113
|
+
const json = hasFlag(args, "--json");
|
|
114
|
+
const targetWs = args[0] && !args[0].startsWith("-") ? args[0] : server.config.workspace;
|
|
115
|
+
const clients = await server.listClients({ workspace: targetWs, _rkwargs: true });
|
|
116
|
+
if (json) {
|
|
117
|
+
console.log(formatJson(clients));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(`${clients.length} client(s) in workspace ${targetWs}`);
|
|
121
|
+
if (clients.length === 0) return;
|
|
122
|
+
for (const c of clients.slice(0, 50)) {
|
|
123
|
+
const id = typeof c === "string" ? c : c.id || String(c);
|
|
124
|
+
const email = typeof c === "object" ? c.user?.email || "" : "";
|
|
125
|
+
console.log(` ${id}${email ? " (" + email + ")" : ""}`);
|
|
126
|
+
}
|
|
127
|
+
if (clients.length > 50) {
|
|
128
|
+
console.log(` ... and ${clients.length - 50} more`);
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
await server.disconnect();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function pingCommand(args) {
|
|
135
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
136
|
+
console.log(`Usage: hypha ping <client-id> [options]
|
|
137
|
+
|
|
138
|
+
Ping a specific client to check if it's alive.
|
|
139
|
+
|
|
140
|
+
Options:
|
|
141
|
+
--timeout <seconds> Ping timeout (default: 5)`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const clientId = args[0] && !args[0].startsWith("-") ? args[0] : void 0;
|
|
145
|
+
if (!clientId) {
|
|
146
|
+
console.error("Usage: hypha ping <client-id>");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
const server = await connectToHypha();
|
|
150
|
+
try {
|
|
151
|
+
const timeout = getFlagInt(args, "--timeout") || 5;
|
|
152
|
+
try {
|
|
153
|
+
const result = await server.ping({ client_id: clientId, timeout, _rkwargs: true });
|
|
154
|
+
console.log(`${clientId}: ${result}`);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.log(`${clientId}: dead (${e.message || e})`);
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
await server.disconnect();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function kickCommand(args) {
|
|
163
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
164
|
+
console.log(`Usage: hypha kick <client-id> [workspace]
|
|
165
|
+
|
|
166
|
+
Disconnect a client and remove all its services. Requires admin.
|
|
167
|
+
If workspace is not specified, uses the connected workspace.
|
|
168
|
+
|
|
169
|
+
Note: delete_client is not in the default workspace interface.
|
|
170
|
+
This command uses the workspace-manager service directly.`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const clientId = args[0] && !args[0].startsWith("-") ? args[0] : void 0;
|
|
174
|
+
if (!clientId) {
|
|
175
|
+
console.error("Usage: hypha kick <client-id> [workspace]");
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const server = await connectToHypha();
|
|
179
|
+
try {
|
|
180
|
+
const targetWs = args[1] && !args[1].startsWith("-") ? args[1] : server.config.workspace;
|
|
181
|
+
const svc = await server.getService(`${server.config.workspace}/*:default`);
|
|
182
|
+
if (svc.deleteClient) {
|
|
183
|
+
await svc.deleteClient({ client_id: clientId, workspace: targetWs, _rkwargs: true });
|
|
184
|
+
} else {
|
|
185
|
+
console.error("delete_client is not available. Use `hypha cleanup` instead.");
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
console.log(`Kicked client ${clientId} from ${targetWs}`);
|
|
189
|
+
} finally {
|
|
190
|
+
await server.disconnect();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function cleanupCommand(args) {
|
|
194
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
195
|
+
console.log(`Usage: hypha cleanup [workspace] [options]
|
|
196
|
+
|
|
197
|
+
Ping all clients in a workspace and remove dead/stale ones.
|
|
198
|
+
Requires admin permission.
|
|
199
|
+
Defaults to the connected workspace. Pass workspace name as positional arg.
|
|
200
|
+
|
|
201
|
+
Options:
|
|
202
|
+
--timeout <seconds> Ping timeout per client (default: 2)
|
|
203
|
+
--json Output as JSON`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const server = await connectToHypha();
|
|
207
|
+
try {
|
|
208
|
+
const timeout = getFlagInt(args, "--timeout") || 2;
|
|
209
|
+
const json = hasFlag(args, "--json");
|
|
210
|
+
const targetWs = args[0] && !args[0].startsWith("-") ? args[0] : server.config.workspace;
|
|
211
|
+
const clients = await server.listClients({ workspace: targetWs, _rkwargs: true });
|
|
212
|
+
console.log(`Workspace ${targetWs}: ${clients.length} client(s)`);
|
|
213
|
+
console.log(`Pinging all clients (timeout=${timeout}s)... this may take a while.`);
|
|
214
|
+
const result = await server.cleanup({ workspace: targetWs, timeout, _rkwargs: true });
|
|
215
|
+
const removed = result?.removed_clients || result?.removedClients || [];
|
|
216
|
+
if (json) {
|
|
217
|
+
console.log(formatJson(result));
|
|
218
|
+
} else {
|
|
219
|
+
console.log(`Removed ${removed.length} dead client(s).`);
|
|
220
|
+
if (removed.length > 0 && removed.length <= 30) {
|
|
221
|
+
for (const c of removed) {
|
|
222
|
+
console.log(` - ${c}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (result?.removed_workspace || result?.removedWorkspace) {
|
|
226
|
+
console.log(`Workspace ${targetWs} was removed (no live clients remaining).`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} finally {
|
|
230
|
+
await server.disconnect();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function workspaceInfoCommand(args) {
|
|
234
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
235
|
+
console.log(`Usage: hypha workspace-info [workspace] [--json]
|
|
236
|
+
|
|
237
|
+
Show detailed workspace information (admin).`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const server = await connectToHypha();
|
|
241
|
+
try {
|
|
242
|
+
const json = hasFlag(args, "--json");
|
|
243
|
+
const targetWs = args[0] && !args[0].startsWith("-") ? args[0] : server.config.workspace;
|
|
244
|
+
const wsInfo = await server.getWorkspaceInfo({ workspace: targetWs, _rkwargs: true });
|
|
245
|
+
if (json) {
|
|
246
|
+
console.log(formatJson(wsInfo));
|
|
247
|
+
} else {
|
|
248
|
+
console.log(`Workspace: ${wsInfo.id || wsInfo.name || targetWs}`);
|
|
249
|
+
for (const [k, v] of Object.entries(wsInfo)) {
|
|
250
|
+
if (k === "id" || k === "name") continue;
|
|
251
|
+
const val = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
252
|
+
console.log(` ${k}: ${val.slice(0, 120)}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} finally {
|
|
256
|
+
await server.disconnect();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function infoCommand(args) {
|
|
260
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
261
|
+
console.log(`Usage: hypha info [--json]
|
|
262
|
+
|
|
263
|
+
Show connection information.`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const server = await connectToHypha();
|
|
267
|
+
try {
|
|
268
|
+
const json = hasFlag(args, "--json");
|
|
269
|
+
const info = {
|
|
270
|
+
server_url: server.config.public_base_url || resolveServerUrl(),
|
|
271
|
+
workspace: server.config.workspace,
|
|
272
|
+
client_id: server.config.client_id
|
|
273
|
+
};
|
|
274
|
+
if (json) {
|
|
275
|
+
console.log(formatJson(info));
|
|
276
|
+
} else {
|
|
277
|
+
console.log(`Server: ${info.server_url}`);
|
|
278
|
+
console.log(`Workspace: ${info.workspace}`);
|
|
279
|
+
console.log(`Client ID: ${info.client_id}`);
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
await server.disconnect();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export { cleanupCommand, clientsCommand, infoCommand, kickCommand, loginCommand, pingCommand, servicesCommand, tokenCommand, workspaceInfoCommand };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypha-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Hypha Cloud CLI — manage workspaces, apps, and artifacts",
|
|
5
5
|
"author": "Amun AI AB",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"postinstall": "node bin/postinstall.mjs",
|
|
22
22
|
"build": "tsc --noEmit && pkgroll",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
|
-
"test": "npx tsx test/test-cli-routing.mjs && npx tsx test/test-artifact-path.mjs && npx tsx test/test-apps-commands.mjs && npx tsx test/test-artifacts-commands.mjs && npx tsx test/test-helpers.mjs",
|
|
24
|
+
"test": "npx tsx test/test-cli-routing.mjs && npx tsx test/test-artifact-path.mjs && npx tsx test/test-apps-commands.mjs && npx tsx test/test-artifacts-commands.mjs && npx tsx test/test-helpers.mjs && npx tsx test/test-transfer.mjs",
|
|
25
25
|
"test:integration": "npx tsx test/test-integration.mjs",
|
|
26
26
|
"dev": "tsx src/cli.ts"
|
|
27
27
|
},
|