hypha-cli 0.1.8 → 0.1.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.
@@ -0,0 +1,629 @@
1
+ import { statSync, readFileSync, mkdirSync, createWriteStream, readdirSync } from 'fs';
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';
4
+ import { p as parseArtifactPath, b as buildFileUrl, d as determineCpDirection, r as resolveArtifactId } from './artifactPath-DCtvp6Go.mjs';
5
+ import 'os';
6
+
7
+ async function uploadFile(artifactManager, artifactId, localPath, remotePath, onProgress) {
8
+ const stat = statSync(localPath);
9
+ const totalBytes = stat.size;
10
+ onProgress?.({ phase: "reading", bytesTransferred: 0, totalBytes, fileName: remotePath });
11
+ const putUrl = await artifactManager.put_file({
12
+ artifact_id: artifactId,
13
+ file_path: remotePath,
14
+ _rkwargs: true
15
+ });
16
+ if (!putUrl || typeof putUrl !== "string") {
17
+ throw new Error(`put_file returned invalid URL for ${remotePath}: ${putUrl}`);
18
+ }
19
+ const content = readFileSync(localPath);
20
+ onProgress?.({ phase: "uploading", bytesTransferred: 0, totalBytes, fileName: remotePath });
21
+ const resp = await fetch(putUrl, {
22
+ method: "PUT",
23
+ body: content,
24
+ headers: {
25
+ "Content-Type": "application/octet-stream",
26
+ "Content-Length": String(totalBytes)
27
+ }
28
+ });
29
+ if (!resp.ok) {
30
+ const body = await resp.text().catch(() => "");
31
+ throw new Error(`Upload failed for ${remotePath}: ${resp.status} ${resp.statusText} ${body}`);
32
+ }
33
+ onProgress?.({ phase: "complete", bytesTransferred: totalBytes, totalBytes, fileName: remotePath });
34
+ }
35
+ async function downloadFile(artifactManager, artifactId, remotePath, localPath, onProgress) {
36
+ const getResult = await artifactManager.get_file({
37
+ artifact_id: artifactId,
38
+ file_path: remotePath,
39
+ _rkwargs: true
40
+ });
41
+ const getUrl = typeof getResult === "string" ? getResult : getResult?.url;
42
+ if (!getUrl || typeof getUrl !== "string") {
43
+ throw new Error(`get_file returned invalid result for ${remotePath}: ${JSON.stringify(getResult)}`);
44
+ }
45
+ const resp = await fetch(getUrl);
46
+ if (!resp.ok) {
47
+ throw new Error(`Download failed for ${remotePath}: ${resp.status} ${resp.statusText}`);
48
+ }
49
+ const totalBytes = parseInt(resp.headers.get("content-length") || "0", 10);
50
+ let bytesTransferred = 0;
51
+ onProgress?.({ phase: "downloading", bytesTransferred: 0, totalBytes, fileName: remotePath });
52
+ mkdirSync(dirname(localPath), { recursive: true });
53
+ const fileStream = createWriteStream(localPath);
54
+ const reader = resp.body?.getReader();
55
+ if (!reader) {
56
+ throw new Error("Response body is not readable");
57
+ }
58
+ try {
59
+ while (true) {
60
+ const { done, value } = await reader.read();
61
+ if (done) break;
62
+ fileStream.write(value);
63
+ bytesTransferred += value.length;
64
+ onProgress?.({
65
+ phase: "downloading",
66
+ bytesTransferred,
67
+ totalBytes: totalBytes || bytesTransferred,
68
+ fileName: remotePath
69
+ });
70
+ }
71
+ } finally {
72
+ fileStream.end();
73
+ reader.releaseLock();
74
+ }
75
+ await new Promise((resolve, reject) => {
76
+ fileStream.on("finish", resolve);
77
+ fileStream.on("error", reject);
78
+ });
79
+ onProgress?.({ phase: "complete", bytesTransferred, totalBytes: totalBytes || bytesTransferred, fileName: remotePath });
80
+ }
81
+ async function uploadDirectory(artifactManager, artifactId, localDir, remoteBase, onProgress) {
82
+ const files = collectLocalFiles(localDir);
83
+ let uploaded = 0;
84
+ for (const relPath of files) {
85
+ const localPath = join(localDir, relPath);
86
+ const remotePath = remoteBase ? `${remoteBase}/${relPath}` : relPath;
87
+ await uploadFile(artifactManager, artifactId, localPath, remotePath, onProgress);
88
+ uploaded++;
89
+ }
90
+ return uploaded;
91
+ }
92
+ async function downloadDirectory(artifactManager, artifactId, remotePath, localDir, onProgress) {
93
+ const files = await artifactManager.list_files({
94
+ artifact_id: artifactId,
95
+ dir_path: remotePath || "",
96
+ _rkwargs: true
97
+ });
98
+ const fileList = Array.isArray(files) ? files : files?.items || [];
99
+ let downloaded = 0;
100
+ for (const entry of fileList) {
101
+ if (entry.type === "directory") {
102
+ const subRemote = remotePath ? `${remotePath}/${entry.name}` : entry.name;
103
+ const subLocal = join(localDir, entry.name);
104
+ downloaded += await downloadDirectory(artifactManager, artifactId, subRemote, subLocal, onProgress);
105
+ } else {
106
+ const remoteFile = remotePath ? `${remotePath}/${entry.name}` : entry.name;
107
+ const localFile = join(localDir, entry.name);
108
+ await downloadFile(artifactManager, artifactId, remoteFile, localFile, onProgress);
109
+ downloaded++;
110
+ }
111
+ }
112
+ return downloaded;
113
+ }
114
+ const IGNORED_DIRS = /* @__PURE__ */ new Set([
115
+ "__pycache__",
116
+ ".git",
117
+ ".venv",
118
+ "node_modules",
119
+ ".idea",
120
+ ".pytest_cache",
121
+ ".mypy_cache",
122
+ "build",
123
+ "dist"
124
+ ]);
125
+ function collectLocalFiles(dir, base = "") {
126
+ const entries = readdirSync(dir, { withFileTypes: true });
127
+ const files = [];
128
+ for (const entry of entries) {
129
+ if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
130
+ if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
131
+ const relPath = base ? `${base}/${entry.name}` : entry.name;
132
+ if (entry.isDirectory()) {
133
+ files.push(...collectLocalFiles(join(dir, entry.name), relPath));
134
+ } else {
135
+ files.push(relPath);
136
+ }
137
+ }
138
+ return files;
139
+ }
140
+
141
+ function printArtifactsHelp() {
142
+ console.log(`hypha artifacts \u2014 manage artifacts (aliased: hypha art)
143
+
144
+ Usage: hypha artifacts <command> [options]
145
+
146
+ Commands:
147
+ ls [artifact[:path]] List artifacts or files in artifact
148
+ cat <artifact>:<path> Read file content to stdout
149
+ cp <src> <dest> [-r] Upload/download files (scp-style)
150
+ rm <artifact>[:<path>] Delete artifact or file
151
+ create <alias> [options] Create a new artifact
152
+ info <artifact> [--json] Show artifact metadata
153
+ search <query> [options] Search artifacts
154
+ commit <artifact> [options] Commit staged changes
155
+ edit <artifact> [options] Edit artifact metadata
156
+ discard <artifact> Discard staged changes
157
+
158
+ Artifact addressing:
159
+ alias Artifact in current workspace
160
+ workspace/alias Artifact in specific workspace
161
+ artifact:path/to/file File inside artifact
162
+
163
+ Copy examples:
164
+ hypha artifacts cp ./data.csv model:data/train.csv Upload file
165
+ hypha artifacts cp model:weights.bin ./local/ Download file
166
+ hypha artifacts cp ./dataset/ model:data/ -r Upload directory
167
+
168
+ Options:
169
+ --json Output as JSON
170
+ --long, -l Long listing format (with sizes)
171
+ --recursive, -r Recursive operation
172
+ --force, -f Skip confirmation`);
173
+ }
174
+ async function connectAndGetArtifactManager() {
175
+ const server = await connectToHypha();
176
+ const am = await server.getService("artifact-manager");
177
+ return { server, am };
178
+ }
179
+ function makeProgressCallback() {
180
+ return (p) => {
181
+ if (p.totalBytes > 0) {
182
+ printProgress(p.fileName, p.bytesTransferred, p.totalBytes);
183
+ }
184
+ };
185
+ }
186
+ async function fetchWithAuth(url, init) {
187
+ const token = resolveToken();
188
+ const headers = new Headers(init?.headers);
189
+ if (token) {
190
+ headers.set("Authorization", `Bearer ${token}`);
191
+ }
192
+ return fetch(url, { ...init, headers });
193
+ }
194
+ async function artifactsLs(args) {
195
+ const json = hasFlag(args, "--json");
196
+ const long = hasFlag(args, "--long", "-l");
197
+ const pos = positionalArgs(args)[0];
198
+ if (!pos) {
199
+ const { server: server2, am } = await connectAndGetArtifactManager();
200
+ try {
201
+ const artifacts = await am.list({
202
+ _rkwargs: true
203
+ });
204
+ const list = Array.isArray(artifacts) ? artifacts : artifacts?.items || [];
205
+ if (json) {
206
+ console.log(formatJson(list));
207
+ return;
208
+ }
209
+ if (list.length === 0) {
210
+ console.log("No artifacts found.");
211
+ return;
212
+ }
213
+ if (long) {
214
+ const rows = [["ALIAS", "TYPE", "MODIFIED", "DESCRIPTION"]];
215
+ for (const art of list) {
216
+ const manifest = art.manifest || {};
217
+ rows.push([
218
+ art.alias || art.id || "",
219
+ art.type || "",
220
+ art.last_modified ? new Date(art.last_modified * 1e3).toISOString().slice(0, 10) : "",
221
+ (manifest.description || "").slice(0, 50)
222
+ ]);
223
+ }
224
+ console.log(formatTable(rows));
225
+ } else {
226
+ for (const art of list) {
227
+ console.log(art.alias || art.id || "");
228
+ }
229
+ }
230
+ } finally {
231
+ await server2.disconnect();
232
+ }
233
+ return;
234
+ }
235
+ const parsed = parseArtifactPath(pos);
236
+ const serverUrl = resolveServerUrl();
237
+ const { server } = await connectAndGetArtifactManager();
238
+ const workspace = parsed.workspace || server.config.workspace;
239
+ try {
240
+ const url = buildFileUrl(serverUrl, workspace, parsed.alias, parsed.filePath);
241
+ const resp = await fetchWithAuth(url);
242
+ if (!resp.ok) {
243
+ if (resp.status === 404) {
244
+ console.error(`Not found: ${pos}`);
245
+ process.exit(1);
246
+ }
247
+ throw new Error(`List files failed: ${resp.status} ${resp.statusText}`);
248
+ }
249
+ const data = await resp.json();
250
+ const files = Array.isArray(data) ? data : data.items || [];
251
+ if (json) {
252
+ console.log(formatJson(files));
253
+ return;
254
+ }
255
+ if (files.length === 0) {
256
+ console.log("No files found.");
257
+ return;
258
+ }
259
+ if (long) {
260
+ const rows = [["SIZE", "TYPE", "NAME"]];
261
+ for (const f of files) {
262
+ rows.push([
263
+ f.type === "directory" ? "-" : humanSize(f.size || 0),
264
+ f.type || "file",
265
+ f.type === "directory" ? `${f.name}/` : f.name
266
+ ]);
267
+ }
268
+ console.log(formatTable(rows));
269
+ } else {
270
+ for (const f of files) {
271
+ console.log(f.type === "directory" ? `${f.name}/` : f.name);
272
+ }
273
+ }
274
+ } finally {
275
+ await server.disconnect();
276
+ }
277
+ }
278
+ async function artifactsCat(args) {
279
+ const pos = positionalArgs(args)[0];
280
+ if (!pos) {
281
+ console.error("Usage: hypha artifacts cat <artifact>:<path>");
282
+ process.exit(1);
283
+ }
284
+ const parsed = parseArtifactPath(pos);
285
+ if (!parsed.filePath) {
286
+ console.error("File path required. Use artifact:path/to/file syntax.");
287
+ process.exit(1);
288
+ }
289
+ const serverUrl = resolveServerUrl();
290
+ const { server } = await connectAndGetArtifactManager();
291
+ const workspace = parsed.workspace || server.config.workspace;
292
+ try {
293
+ const url = buildFileUrl(serverUrl, workspace, parsed.alias, parsed.filePath);
294
+ const resp = await fetchWithAuth(url, { redirect: "follow" });
295
+ if (!resp.ok) {
296
+ if (resp.status === 404) {
297
+ console.error(`File not found: ${parsed.filePath}`);
298
+ process.exit(1);
299
+ }
300
+ throw new Error(`Read failed: ${resp.status} ${resp.statusText}`);
301
+ }
302
+ const reader = resp.body?.getReader();
303
+ if (!reader) {
304
+ const text = await resp.text();
305
+ process.stdout.write(text);
306
+ return;
307
+ }
308
+ const decoder = new TextDecoder();
309
+ while (true) {
310
+ const { done, value } = await reader.read();
311
+ if (done) break;
312
+ process.stdout.write(decoder.decode(value, { stream: true }));
313
+ }
314
+ } finally {
315
+ await server.disconnect();
316
+ }
317
+ }
318
+ async function artifactsCp(args) {
319
+ const pos = positionalArgs(args, []);
320
+ const recursive = hasFlag(args, "--recursive", "-r");
321
+ if (pos.length < 2) {
322
+ console.error("Usage: hypha artifacts cp <src> <dest> [-r]");
323
+ console.error(" Upload: hypha artifacts cp ./file.txt artifact:path/");
324
+ console.error(" Download: hypha artifacts cp artifact:path/file.txt ./local/");
325
+ process.exit(1);
326
+ }
327
+ const [src, dest] = pos;
328
+ const direction = determineCpDirection(src, dest);
329
+ const { server, am } = await connectAndGetArtifactManager();
330
+ const onProgress = makeProgressCallback();
331
+ try {
332
+ if (direction === "upload") {
333
+ const parsed = parseArtifactPath(dest);
334
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
335
+ await am.edit({
336
+ artifact_id: artifactId,
337
+ stage: true,
338
+ _rkwargs: true
339
+ });
340
+ const stat = statSync(src);
341
+ if (stat.isDirectory()) {
342
+ if (!recursive) {
343
+ console.error("Source is a directory. Use -r to copy directories.");
344
+ process.exit(1);
345
+ }
346
+ const count = await uploadDirectory(am, artifactId, src, parsed.filePath || "", onProgress);
347
+ console.log(`
348
+ Uploaded ${count} files. Run \`hypha artifacts commit ${parsed.alias}\` to finalize.`);
349
+ } else {
350
+ const remotePath = parsed.filePath || basename(src);
351
+ await uploadFile(am, artifactId, src, remotePath, onProgress);
352
+ console.log(`
353
+ Uploaded: ${remotePath}. Run \`hypha artifacts commit ${parsed.alias}\` to finalize.`);
354
+ }
355
+ } else {
356
+ const parsed = parseArtifactPath(src);
357
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
358
+ if (!parsed.filePath) {
359
+ console.error("Source must include a file path (e.g., artifact:path/to/file).");
360
+ console.error("Use `hypha artifacts ls artifact:` to see available files.");
361
+ process.exit(1);
362
+ }
363
+ if (parsed.filePath.endsWith("/") && recursive) {
364
+ const count = await downloadDirectory(am, artifactId, parsed.filePath.replace(/\/$/, ""), dest, onProgress);
365
+ console.log(`
366
+ Downloaded ${count} files to ${dest}`);
367
+ } else {
368
+ const localDest = dest.endsWith("/") ? join(dest, basename(parsed.filePath)) : dest;
369
+ await downloadFile(am, artifactId, parsed.filePath, localDest, onProgress);
370
+ console.log(`
371
+ Downloaded: ${localDest}`);
372
+ }
373
+ }
374
+ } finally {
375
+ await server.disconnect();
376
+ }
377
+ }
378
+ async function artifactsRm(args) {
379
+ const pos = positionalArgs(args)[0];
380
+ const force = hasFlag(args, "--force", "-f");
381
+ const recursive = hasFlag(args, "--recursive", "-r");
382
+ if (!pos) {
383
+ console.error("Usage: hypha artifacts rm <artifact>[:<path>] [-r] [-f]");
384
+ process.exit(1);
385
+ }
386
+ const parsed = parseArtifactPath(pos);
387
+ const { server, am } = await connectAndGetArtifactManager();
388
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
389
+ try {
390
+ if (parsed.filePath) {
391
+ await am.remove_file({
392
+ artifact_id: artifactId,
393
+ file_path: parsed.filePath,
394
+ _rkwargs: true
395
+ });
396
+ console.log(`Removed: ${parsed.filePath}`);
397
+ } else {
398
+ if (!force) {
399
+ console.error(`This will delete the entire artifact "${parsed.alias}". Use --force to confirm.`);
400
+ process.exit(1);
401
+ }
402
+ await am.delete({
403
+ artifact_id: artifactId,
404
+ delete_files: true,
405
+ recursive,
406
+ _rkwargs: true
407
+ });
408
+ console.log(`Deleted: ${parsed.alias}`);
409
+ }
410
+ } finally {
411
+ await server.disconnect();
412
+ }
413
+ }
414
+ async function artifactsCreate(args) {
415
+ const pos = positionalArgs(args, ["--type", "--parent"])[0];
416
+ const type = getFlag(args, "--type") || "generic";
417
+ const parent = getFlag(args, "--parent");
418
+ const json = hasFlag(args, "--json");
419
+ if (!pos) {
420
+ console.error("Usage: hypha artifacts create <alias> [--type T] [--parent P]");
421
+ console.error("Types: generic, collection, application, model, dataset");
422
+ process.exit(1);
423
+ }
424
+ const { server, am } = await connectAndGetArtifactManager();
425
+ try {
426
+ const createOpts = {
427
+ alias: pos,
428
+ type,
429
+ manifest: { name: pos },
430
+ _rkwargs: true
431
+ };
432
+ if (parent) createOpts.parent_id = parent;
433
+ const result = await am.create(createOpts);
434
+ await am.commit({ artifact_id: result.id || pos, _rkwargs: true });
435
+ if (json) {
436
+ console.log(formatJson(result));
437
+ } else {
438
+ console.log(`Created: ${result.alias || pos} (type: ${type})`);
439
+ }
440
+ } finally {
441
+ await server.disconnect();
442
+ }
443
+ }
444
+ async function artifactsInfo(args) {
445
+ const pos = positionalArgs(args)[0];
446
+ const json = hasFlag(args, "--json");
447
+ if (!pos) {
448
+ console.error("Usage: hypha artifacts info <artifact> [--json]");
449
+ process.exit(1);
450
+ }
451
+ const parsed = parseArtifactPath(pos);
452
+ const { server, am } = await connectAndGetArtifactManager();
453
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
454
+ try {
455
+ const info = await am.read({
456
+ artifact_id: artifactId,
457
+ _rkwargs: true
458
+ });
459
+ if (json) {
460
+ console.log(formatJson(info));
461
+ } else {
462
+ const manifest = info.manifest || {};
463
+ console.log(`Alias: ${info.alias || artifactId}`);
464
+ console.log(`ID: ${info.id || ""}`);
465
+ console.log(`Type: ${info.type || "generic"}`);
466
+ console.log(`Name: ${manifest.name || ""}`);
467
+ console.log(`Description: ${manifest.description || ""}`);
468
+ console.log(`Workspace: ${info.workspace || ""}`);
469
+ if (info.created_at) console.log(`Created: ${new Date(info.created_at * 1e3).toISOString()}`);
470
+ if (info.last_modified) console.log(`Modified: ${new Date(info.last_modified * 1e3).toISOString()}`);
471
+ if (info.versions?.length) {
472
+ console.log(`Versions: ${info.versions.map((v) => v.name || v.version || v).join(", ")}`);
473
+ }
474
+ if (info.download_count) console.log(`Downloads: ${info.download_count}`);
475
+ if (info.view_count) console.log(`Views: ${info.view_count}`);
476
+ }
477
+ } finally {
478
+ await server.disconnect();
479
+ }
480
+ }
481
+ async function artifactsSearch(args) {
482
+ const pos = positionalArgs(args, ["--type", "--limit"])[0];
483
+ const type = getFlag(args, "--type");
484
+ const limit = getFlagInt(args, "--limit") || 20;
485
+ const json = hasFlag(args, "--json");
486
+ if (!pos) {
487
+ console.error("Usage: hypha artifacts search <query> [--type T] [--limit N] [--json]");
488
+ process.exit(1);
489
+ }
490
+ const { server, am } = await connectAndGetArtifactManager();
491
+ try {
492
+ const searchOpts = {
493
+ keywords: pos,
494
+ limit,
495
+ _rkwargs: true
496
+ };
497
+ if (type) {
498
+ searchOpts.filters = { type };
499
+ }
500
+ const results = await am.list(searchOpts);
501
+ const list = Array.isArray(results) ? results : results?.items || [];
502
+ if (json) {
503
+ console.log(formatJson(list));
504
+ return;
505
+ }
506
+ if (list.length === 0) {
507
+ console.log("No results found.");
508
+ return;
509
+ }
510
+ const rows = [["ALIAS", "TYPE", "NAME", "DESCRIPTION"]];
511
+ for (const art of list) {
512
+ const manifest = art.manifest || {};
513
+ rows.push([
514
+ art.alias || art.id || "",
515
+ art.type || "",
516
+ manifest.name || "",
517
+ (manifest.description || "").slice(0, 40)
518
+ ]);
519
+ }
520
+ console.log(formatTable(rows));
521
+ } finally {
522
+ await server.disconnect();
523
+ }
524
+ }
525
+ async function artifactsCommit(args) {
526
+ const pos = positionalArgs(args, ["--version", "--message"])[0];
527
+ const version = getFlag(args, "--version");
528
+ const message = getFlag(args, "--message", "-m");
529
+ if (!pos) {
530
+ console.error("Usage: hypha artifacts commit <artifact> [--version V] [--message M]");
531
+ process.exit(1);
532
+ }
533
+ const parsed = parseArtifactPath(pos);
534
+ const { server, am } = await connectAndGetArtifactManager();
535
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
536
+ try {
537
+ const commitOpts = {
538
+ artifact_id: artifactId,
539
+ _rkwargs: true
540
+ };
541
+ if (version) commitOpts.version = version;
542
+ if (message) commitOpts.comment = message;
543
+ await am.commit(commitOpts);
544
+ console.log(`Committed: ${parsed.alias}${version ? ` (version: ${version})` : ""}`);
545
+ } finally {
546
+ await server.disconnect();
547
+ }
548
+ }
549
+ async function artifactsEdit(args) {
550
+ const pos = positionalArgs(args, ["--name", "--description", "--type"])[0];
551
+ const name = getFlag(args, "--name");
552
+ const description = getFlag(args, "--description");
553
+ const type = getFlag(args, "--type");
554
+ const stage = hasFlag(args, "--stage");
555
+ if (!pos) {
556
+ console.error("Usage: hypha artifacts edit <artifact> [--name N] [--description D] [--type T] [--stage]");
557
+ process.exit(1);
558
+ }
559
+ const parsed = parseArtifactPath(pos);
560
+ const { server, am } = await connectAndGetArtifactManager();
561
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
562
+ try {
563
+ const editOpts = {
564
+ artifact_id: artifactId,
565
+ _rkwargs: true
566
+ };
567
+ const manifest = {};
568
+ if (name) manifest.name = name;
569
+ if (description) manifest.description = description;
570
+ if (Object.keys(manifest).length > 0) editOpts.manifest = manifest;
571
+ if (type) editOpts.type = type;
572
+ if (stage) editOpts.stage = true;
573
+ await am.edit(editOpts);
574
+ console.log(`Updated: ${parsed.alias}`);
575
+ } finally {
576
+ await server.disconnect();
577
+ }
578
+ }
579
+ async function artifactsDiscard(args) {
580
+ const pos = positionalArgs(args)[0];
581
+ if (!pos) {
582
+ console.error("Usage: hypha artifacts discard <artifact>");
583
+ process.exit(1);
584
+ }
585
+ const parsed = parseArtifactPath(pos);
586
+ const { server, am } = await connectAndGetArtifactManager();
587
+ const artifactId = resolveArtifactId(parsed, server.config.workspace);
588
+ try {
589
+ await am.discard_changes({ artifact_id: artifactId, _rkwargs: true });
590
+ console.log(`Discarded staged changes: ${parsed.alias}`);
591
+ } finally {
592
+ await server.disconnect();
593
+ }
594
+ }
595
+ async function handleArtifactsCommand(args) {
596
+ const sub = args[0];
597
+ const commandArgs = args.slice(1);
598
+ if (!sub || sub === "--help" || sub === "-h") {
599
+ printArtifactsHelp();
600
+ return;
601
+ }
602
+ if (sub === "ls" || sub === "list") {
603
+ await artifactsLs(commandArgs);
604
+ } else if (sub === "cat") {
605
+ await artifactsCat(commandArgs);
606
+ } else if (sub === "cp") {
607
+ await artifactsCp(commandArgs);
608
+ } else if (sub === "rm") {
609
+ await artifactsRm(commandArgs);
610
+ } else if (sub === "create" || sub === "mkdir") {
611
+ await artifactsCreate(commandArgs);
612
+ } else if (sub === "info") {
613
+ await artifactsInfo(commandArgs);
614
+ } else if (sub === "search" || sub === "find") {
615
+ await artifactsSearch(commandArgs);
616
+ } else if (sub === "commit") {
617
+ await artifactsCommit(commandArgs);
618
+ } else if (sub === "edit") {
619
+ await artifactsEdit(commandArgs);
620
+ } else if (sub === "discard") {
621
+ await artifactsDiscard(commandArgs);
622
+ } else {
623
+ console.error(`Unknown artifacts command: ${sub}`);
624
+ printArtifactsHelp();
625
+ process.exit(1);
626
+ }
627
+ }
628
+
629
+ export { artifactsLs, handleArtifactsCommand };