hypha-cli 0.1.10 → 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.
@@ -1,6 +1,6 @@
1
1
  import { statSync, readFileSync, readdirSync } from 'fs';
2
2
  import { join, extname } from 'path';
3
- import { e as hasFlag, f as formatJson, a as formatTable, p as positionalArgs, j as relativeTime, c as connectToHypha, k as getAllFlags, i as getFlag, g as getFlagInt } from './helpers-BvfSCkvr.mjs';
3
+ import { e as hasFlag, f as formatJson, a as formatTable, p as positionalArgs, j as relativeTime, c as connectToHypha, k as getAllFlags, i as getFlag, g as getFlagInt } from './helpers-BC4AKy6a.mjs';
4
4
  import 'os';
5
5
 
6
6
  const IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -315,7 +315,7 @@ Alias: hypha apps rm`);
315
315
  }
316
316
  const [server, serverApps] = await getServerApps();
317
317
  try {
318
- const artifactManager = await server.getService("artifact-manager");
318
+ const artifactManager = await server.getService("public/artifact-manager");
319
319
  await artifactManager.delete({
320
320
  artifact_id: appId,
321
321
  _rkwargs: true
@@ -1,9 +1,25 @@
1
1
  import { statSync, readFileSync, mkdirSync, createWriteStream, readdirSync } from 'fs';
2
2
  import { join, dirname, basename } from 'path';
3
- import { e as hasFlag, p as positionalArgs, f as formatJson, a as formatTable, r as resolveServerUrl, i as getFlag, g as getFlagInt, c as connectToHypha, b as resolveToken, m as printProgress, h as humanSize } from './helpers-BvfSCkvr.mjs';
3
+ import { e as hasFlag, p as positionalArgs, f as formatJson, a as formatTable, r as resolveServerUrl, i as getFlag, g as getFlagInt, c as connectToHypha, b as resolveToken, m as printAggregateProgress, n as printProgress, h as humanSize } from './helpers-BC4AKy6a.mjs';
4
4
  import { p as parseArtifactPath, b as buildFileUrl, d as determineCpDirection, r as resolveArtifactId } from './artifactPath-DCtvp6Go.mjs';
5
5
  import 'os';
6
6
 
7
+ function envInt(name, fallback) {
8
+ const raw = process.env[name];
9
+ if (!raw) return fallback;
10
+ const n = parseInt(raw, 10);
11
+ return Number.isFinite(n) && n > 0 ? n : fallback;
12
+ }
13
+ const DEFAULT_BATCH_SIZE = 200;
14
+ const DEFAULT_CONCURRENCY = 16;
15
+ function chunk(arr, n) {
16
+ if (n <= 0) throw new Error("chunk size must be positive");
17
+ const out = [];
18
+ for (let i = 0; i < arr.length; i += n) {
19
+ out.push(arr.slice(i, i + n));
20
+ }
21
+ return out;
22
+ }
7
23
  async function uploadFile(artifactManager, artifactId, localPath, remotePath, onProgress) {
8
24
  const stat = statSync(localPath);
9
25
  const totalBytes = stat.size;
@@ -78,16 +94,85 @@ async function downloadFile(artifactManager, artifactId, remotePath, localPath,
78
94
  });
79
95
  onProgress?.({ phase: "complete", bytesTransferred, totalBytes: totalBytes || bytesTransferred, fileName: remotePath });
80
96
  }
97
+ async function uploadFilesBatched(artifactManager, artifactId, files, onProgress, opts = {}) {
98
+ const batchSize = opts.batchSize ?? envInt("HYPHA_UPLOAD_BATCH", DEFAULT_BATCH_SIZE);
99
+ const concurrency = opts.concurrency ?? envInt("HYPHA_UPLOAD_CONCURRENCY", DEFAULT_CONCURRENCY);
100
+ const filesTotal = files.length;
101
+ const bytesTotal = files.reduce((s, f) => s + f.size, 0);
102
+ let filesDone = 0;
103
+ let bytesDone = 0;
104
+ const emit = (currentFile) => {
105
+ onProgress?.({ filesDone, filesTotal, bytesDone, bytesTotal, currentFile });
106
+ };
107
+ emit(filesTotal > 0 ? files[0].remotePath : "");
108
+ for (const group of chunk(files, batchSize)) {
109
+ const paths = group.map((f) => f.remotePath);
110
+ const result = await artifactManager.put_file({
111
+ artifact_id: artifactId,
112
+ file_path: paths,
113
+ _rkwargs: true
114
+ });
115
+ let urlMap;
116
+ if (result && typeof result === "object" && !Array.isArray(result)) {
117
+ urlMap = result;
118
+ } else {
119
+ urlMap = {};
120
+ for (const f of group) {
121
+ const u = await artifactManager.put_file({
122
+ artifact_id: artifactId,
123
+ file_path: f.remotePath,
124
+ _rkwargs: true
125
+ });
126
+ if (typeof u !== "string") {
127
+ throw new Error(`put_file returned unexpected value for ${f.remotePath}`);
128
+ }
129
+ urlMap[f.remotePath] = u;
130
+ }
131
+ }
132
+ let cursor = 0;
133
+ const workers = [];
134
+ const worker = async () => {
135
+ while (true) {
136
+ const i = cursor++;
137
+ if (i >= group.length) return;
138
+ const f = group[i];
139
+ const url = urlMap[f.remotePath];
140
+ if (!url || typeof url !== "string") {
141
+ throw new Error(`No presigned URL returned for ${f.remotePath}`);
142
+ }
143
+ const body = readFileSync(f.localPath);
144
+ const resp = await fetch(url, {
145
+ method: "PUT",
146
+ body,
147
+ headers: {
148
+ "Content-Type": "application/octet-stream",
149
+ "Content-Length": String(f.size)
150
+ }
151
+ });
152
+ if (!resp.ok) {
153
+ const text = await resp.text().catch(() => "");
154
+ throw new Error(`Upload failed for ${f.remotePath}: ${resp.status} ${resp.statusText} ${text}`);
155
+ }
156
+ filesDone++;
157
+ bytesDone += f.size;
158
+ emit(f.remotePath);
159
+ }
160
+ };
161
+ const n = Math.min(concurrency, group.length);
162
+ for (let w = 0; w < n; w++) workers.push(worker());
163
+ await Promise.all(workers);
164
+ }
165
+ return filesDone;
166
+ }
81
167
  async function uploadDirectory(artifactManager, artifactId, localDir, remoteBase, onProgress) {
82
- const files = collectLocalFiles(localDir);
83
- let uploaded = 0;
84
- for (const relPath of files) {
168
+ const relPaths = collectLocalFiles(localDir);
169
+ const files = relPaths.map((relPath) => {
85
170
  const localPath = join(localDir, relPath);
86
171
  const remotePath = remoteBase ? `${remoteBase}/${relPath}` : relPath;
87
- await uploadFile(artifactManager, artifactId, localPath, remotePath, onProgress);
88
- uploaded++;
89
- }
90
- return uploaded;
172
+ const size = statSync(localPath).size;
173
+ return { localPath, remotePath, size };
174
+ });
175
+ return uploadFilesBatched(artifactManager, artifactId, files, onProgress);
91
176
  }
92
177
  async function downloadDirectory(artifactManager, artifactId, remotePath, localDir, onProgress) {
93
178
  const files = await artifactManager.list_files({
@@ -154,6 +239,7 @@ Commands:
154
239
  commit <artifact> [options] Commit staged changes
155
240
  edit <artifact> [options] Edit artifact metadata
156
241
  discard <artifact> Discard staged changes
242
+ serve <artifact> [options] Enable/disable static site hosting
157
243
 
158
244
  Aliases: list \u2192 ls, mkdir \u2192 create, find \u2192 search
159
245
  Shorthand: "hypha art" is an alias for "hypha artifacts"
@@ -186,6 +272,11 @@ function makeProgressCallback() {
186
272
  }
187
273
  };
188
274
  }
275
+ function makeBatchProgressCallback(label) {
276
+ return (p) => {
277
+ printAggregateProgress(label, p.filesDone, p.filesTotal, p.bytesDone, p.bytesTotal);
278
+ };
279
+ }
189
280
  async function fetchWithAuth(url, init) {
190
281
  const token = resolveToken();
191
282
  const headers = new Headers(init?.headers);
@@ -214,9 +305,9 @@ Alias: hypha artifacts list`);
214
305
  const long = hasFlag(args, "--long", "-l");
215
306
  const pos = positionalArgs(args)[0];
216
307
  if (!pos) {
217
- const { server: server2, am } = await connectAndGetArtifactManager();
308
+ const { server: server2, am: am2 } = await connectAndGetArtifactManager();
218
309
  try {
219
- const artifacts = await am.list({
310
+ const artifacts = await am2.list({
220
311
  _rkwargs: true
221
312
  });
222
313
  const list = Array.isArray(artifacts) ? artifacts : artifacts?.items || [];
@@ -252,9 +343,53 @@ Alias: hypha artifacts list`);
252
343
  }
253
344
  const parsed = parseArtifactPath(pos);
254
345
  const serverUrl = resolveServerUrl();
255
- const { server } = await connectAndGetArtifactManager();
346
+ const { server, am } = await connectAndGetArtifactManager();
256
347
  const workspace = parsed.workspace || server.config.workspace;
348
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
257
349
  try {
350
+ if (!parsed.filePath) {
351
+ let info;
352
+ try {
353
+ info = await am.read({ artifact_id: artifactId, _rkwargs: true });
354
+ } catch (err) {
355
+ const msg = String(err?.message || err);
356
+ if (/not found|does not exist/i.test(msg)) {
357
+ console.error(`Artifact not found: ${pos}`);
358
+ process.exit(1);
359
+ }
360
+ throw err;
361
+ }
362
+ if (info?.type === "collection") {
363
+ const children = await am.list({ parent_id: artifactId, _rkwargs: true });
364
+ const list = Array.isArray(children) ? children : children?.items || [];
365
+ if (json) {
366
+ console.log(formatJson(list));
367
+ return;
368
+ }
369
+ if (list.length === 0) {
370
+ console.log("No child artifacts in this collection.");
371
+ return;
372
+ }
373
+ if (long) {
374
+ const rows = [["ALIAS", "TYPE", "MODIFIED", "DESCRIPTION"]];
375
+ for (const art of list) {
376
+ const manifest = art.manifest || {};
377
+ rows.push([
378
+ art.alias || art.id || "",
379
+ art.type || "",
380
+ art.last_modified ? new Date(art.last_modified * 1e3).toISOString().slice(0, 10) : "",
381
+ (manifest.description || "").slice(0, 50)
382
+ ]);
383
+ }
384
+ console.log(formatTable(rows));
385
+ } else {
386
+ for (const art of list) {
387
+ console.log(art.alias || art.id || "");
388
+ }
389
+ }
390
+ return;
391
+ }
392
+ }
258
393
  const url = buildFileUrl(serverUrl, workspace, parsed.alias, parsed.filePath);
259
394
  const resp = await fetchWithAuth(url);
260
395
  if (!resp.ok) {
@@ -388,14 +523,19 @@ Examples:
388
523
  console.error("Source is a directory. Use -r to copy directories.");
389
524
  process.exit(1);
390
525
  }
391
- const count = await uploadDirectory(am, artifactId, src, parsed.filePath || "", onProgress);
526
+ const dirLabel = `${basename(src)}/`;
527
+ const count = await uploadDirectory(
528
+ am,
529
+ artifactId,
530
+ src,
531
+ parsed.filePath || "",
532
+ makeBatchProgressCallback(dirLabel)
533
+ );
392
534
  if (autoCommit) {
393
535
  await am.commit({ artifact_id: artifactId, _rkwargs: true });
394
- console.log(`
395
- Uploaded ${count} files and committed.`);
536
+ console.log(`Uploaded ${count} files and committed.`);
396
537
  } else {
397
- console.log(`
398
- Uploaded ${count} files. Run \`hypha artifacts commit ${parsed.alias}\` to finalize.`);
538
+ console.log(`Uploaded ${count} files. Run \`hypha artifacts commit ${parsed.alias}\` to finalize.`);
399
539
  }
400
540
  } else {
401
541
  const remotePath = parsed.filePath || basename(src);
@@ -516,7 +656,6 @@ Alias: hypha artifacts mkdir`);
516
656
  };
517
657
  if (parent) createOpts.parent_id = parent;
518
658
  const result = await am.create(createOpts);
519
- await am.commit({ artifact_id: result.id || pos, _rkwargs: true });
520
659
  if (json) {
521
660
  console.log(formatJson(result));
522
661
  } else {
@@ -727,6 +866,56 @@ Usage: hypha artifacts discard <artifact>`);
727
866
  await server.disconnect();
728
867
  }
729
868
  }
869
+ async function artifactsServe(args) {
870
+ if (hasFlag(args, "--help", "-h")) {
871
+ console.log(`hypha artifacts serve \u2014 enable or disable static site hosting
872
+
873
+ Usage: hypha artifacts serve <artifact> [options]
874
+
875
+ Options:
876
+ --root <dir> Root directory to serve (default: /)
877
+ --disable Disable static site hosting
878
+
879
+ Examples:
880
+ hypha art serve my-site # Enable \u2014 serve from artifact root
881
+ hypha art serve my-site --root dist # Serve from /dist subdirectory
882
+ hypha art serve my-site --disable # Disable static site hosting`);
883
+ return;
884
+ }
885
+ const pos = positionalArgs(args, ["--root"])[0];
886
+ if (!pos) {
887
+ console.error("Usage: hypha artifacts serve <artifact> [--root <dir>] [--disable]");
888
+ process.exit(1);
889
+ }
890
+ const disable = hasFlag(args, "--disable");
891
+ const root = getFlag(args, "--root") || "/";
892
+ const parsed = parseArtifactPath(pos);
893
+ const { server, am } = await connectAndGetArtifactManager();
894
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
895
+ const serverUrl = resolveServerUrl();
896
+ const workspace = parsed.workspace || server.config.workspace;
897
+ try {
898
+ const config = {
899
+ view_config: disable ? "disabled" : { root_directory: root }
900
+ };
901
+ await am.edit({
902
+ artifact_id: artifactId,
903
+ config,
904
+ _rkwargs: true
905
+ });
906
+ if (disable) {
907
+ console.log(`Static site hosting disabled: ${parsed.alias}`);
908
+ } else {
909
+ const url = `${serverUrl}/${workspace}/view/${parsed.alias}`;
910
+ console.log(`Static site enabled: ${url}`);
911
+ if (root && root !== "/") {
912
+ console.log(` Serving from: ${root}`);
913
+ }
914
+ }
915
+ } finally {
916
+ await server.disconnect();
917
+ }
918
+ }
730
919
  async function handleArtifactsCommand(args) {
731
920
  const sub = args[0];
732
921
  const commandArgs = args.slice(1);
@@ -754,6 +943,8 @@ async function handleArtifactsCommand(args) {
754
943
  await artifactsEdit(commandArgs);
755
944
  } else if (sub === "discard") {
756
945
  await artifactsDiscard(commandArgs);
946
+ } else if (sub === "serve") {
947
+ await artifactsServe(commandArgs);
757
948
  } else {
758
949
  console.error(`Unknown artifacts command: ${sub}`);
759
950
  printArtifactsHelp();
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-DDljPOfV.mjs');
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-BJCjouF4.mjs');
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-B-iBR64g.mjs');
110
+ const { handleArtifactsCommand } = await import('./artifacts-DHPJIrSS.mjs');
111
111
  await handleArtifactsCommand(commandArgs);
112
112
  } else {
113
113
  console.error(`Unknown command: ${subcommand}`);
@@ -62,15 +62,33 @@ async function connectToHypha(opts) {
62
62
  const token = resolveToken(opts);
63
63
  const workspace = resolveWorkspace(opts);
64
64
  if (!token) {
65
- console.error("No token found. Run `hypha login` first, or set HYPHA_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.");
66
69
  process.exit(1);
67
70
  }
68
71
  const { hyphaWebsocketClient } = await import('hypha-rpc');
69
- const server = await hyphaWebsocketClient.connectToServer({
70
- server_url: serverUrl,
71
- token,
72
- workspace: workspace || void 0
73
- });
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
+ }
74
92
  return server;
75
93
  }
76
94
  async function loginToHypha(serverUrl) {
@@ -78,7 +96,8 @@ async function loginToHypha(serverUrl) {
78
96
  const { hyphaWebsocketClient } = await import('hypha-rpc');
79
97
  console.log(`Logging in to ${url}...`);
80
98
  const token = await hyphaWebsocketClient.login({
81
- server_url: url
99
+ server_url: url,
100
+ logger: null
82
101
  });
83
102
  if (!token) {
84
103
  console.error("Login failed \u2014 no token received.");
@@ -86,7 +105,8 @@ async function loginToHypha(serverUrl) {
86
105
  }
87
106
  const server = await hyphaWebsocketClient.connectToServer({
88
107
  server_url: url,
89
- token
108
+ token,
109
+ logger: null
90
110
  });
91
111
  const ws = server.config.workspace;
92
112
  writeEnvValue("HYPHA_SERVER_URL", url);
@@ -184,5 +204,21 @@ function printProgress(label, current, total) {
184
204
  process.stderr.write("\n");
185
205
  }
186
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
+ }
187
223
 
188
- 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, printProgress as m, positionalArgs as p, resolveServerUrl as r };
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-DWQC3Lr8.mjs';
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';
@@ -1,4 +1,4 @@
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-DWQC3Lr8.mjs';
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
2
  import 'fs';
3
3
  import 'path';
4
4
  import 'os';
@@ -120,7 +120,9 @@ Options:
120
120
  console.log(`${clients.length} client(s) in workspace ${targetWs}`);
121
121
  if (clients.length === 0) return;
122
122
  for (const c of clients.slice(0, 50)) {
123
- console.log(` ${c}`);
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 + ")" : ""}`);
124
126
  }
125
127
  if (clients.length > 50) {
126
128
  console.log(` ... and ${clients.length - 50} more`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypha-cli",
3
- "version": "0.1.10",
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
  },