hypha-cli 0.1.11 → 0.1.13
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/channel-wm4EXm8s.mjs +101 -0
- package/dist/cli.mjs +19 -11
- package/dist/helpers-BC4AKy6a.mjs +224 -0
- package/dist/index.mjs +1 -1
- package/dist/workspace-DE2jBAXs.mjs +286 -0
- package/package.json +3 -3
|
@@ -0,0 +1,955 @@
|
|
|
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 printAggregateProgress, n as printProgress, h as humanSize } from './helpers-BC4AKy6a.mjs';
|
|
4
|
+
import { p as parseArtifactPath, b as buildFileUrl, d as determineCpDirection, r as resolveArtifactId } from './artifactPath-DCtvp6Go.mjs';
|
|
5
|
+
import 'os';
|
|
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
|
+
}
|
|
23
|
+
async function uploadFile(artifactManager, artifactId, localPath, remotePath, onProgress) {
|
|
24
|
+
const stat = statSync(localPath);
|
|
25
|
+
const totalBytes = stat.size;
|
|
26
|
+
onProgress?.({ phase: "reading", bytesTransferred: 0, totalBytes, fileName: remotePath });
|
|
27
|
+
const putUrl = await artifactManager.put_file({
|
|
28
|
+
artifact_id: artifactId,
|
|
29
|
+
file_path: remotePath,
|
|
30
|
+
_rkwargs: true
|
|
31
|
+
});
|
|
32
|
+
if (!putUrl || typeof putUrl !== "string") {
|
|
33
|
+
throw new Error(`put_file returned invalid URL for ${remotePath}: ${putUrl}`);
|
|
34
|
+
}
|
|
35
|
+
const content = readFileSync(localPath);
|
|
36
|
+
onProgress?.({ phase: "uploading", bytesTransferred: 0, totalBytes, fileName: remotePath });
|
|
37
|
+
const resp = await fetch(putUrl, {
|
|
38
|
+
method: "PUT",
|
|
39
|
+
body: content,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/octet-stream",
|
|
42
|
+
"Content-Length": String(totalBytes)
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (!resp.ok) {
|
|
46
|
+
const body = await resp.text().catch(() => "");
|
|
47
|
+
throw new Error(`Upload failed for ${remotePath}: ${resp.status} ${resp.statusText} ${body}`);
|
|
48
|
+
}
|
|
49
|
+
onProgress?.({ phase: "complete", bytesTransferred: totalBytes, totalBytes, fileName: remotePath });
|
|
50
|
+
}
|
|
51
|
+
async function downloadFile(artifactManager, artifactId, remotePath, localPath, onProgress) {
|
|
52
|
+
const getResult = await artifactManager.get_file({
|
|
53
|
+
artifact_id: artifactId,
|
|
54
|
+
file_path: remotePath,
|
|
55
|
+
_rkwargs: true
|
|
56
|
+
});
|
|
57
|
+
const getUrl = typeof getResult === "string" ? getResult : getResult?.url;
|
|
58
|
+
if (!getUrl || typeof getUrl !== "string") {
|
|
59
|
+
throw new Error(`get_file returned invalid result for ${remotePath}: ${JSON.stringify(getResult)}`);
|
|
60
|
+
}
|
|
61
|
+
const resp = await fetch(getUrl);
|
|
62
|
+
if (!resp.ok) {
|
|
63
|
+
throw new Error(`Download failed for ${remotePath}: ${resp.status} ${resp.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const totalBytes = parseInt(resp.headers.get("content-length") || "0", 10);
|
|
66
|
+
let bytesTransferred = 0;
|
|
67
|
+
onProgress?.({ phase: "downloading", bytesTransferred: 0, totalBytes, fileName: remotePath });
|
|
68
|
+
mkdirSync(dirname(localPath), { recursive: true });
|
|
69
|
+
const fileStream = createWriteStream(localPath);
|
|
70
|
+
const reader = resp.body?.getReader();
|
|
71
|
+
if (!reader) {
|
|
72
|
+
throw new Error("Response body is not readable");
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
while (true) {
|
|
76
|
+
const { done, value } = await reader.read();
|
|
77
|
+
if (done) break;
|
|
78
|
+
fileStream.write(value);
|
|
79
|
+
bytesTransferred += value.length;
|
|
80
|
+
onProgress?.({
|
|
81
|
+
phase: "downloading",
|
|
82
|
+
bytesTransferred,
|
|
83
|
+
totalBytes: totalBytes || bytesTransferred,
|
|
84
|
+
fileName: remotePath
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
fileStream.end();
|
|
89
|
+
reader.releaseLock();
|
|
90
|
+
}
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
fileStream.on("finish", resolve);
|
|
93
|
+
fileStream.on("error", reject);
|
|
94
|
+
});
|
|
95
|
+
onProgress?.({ phase: "complete", bytesTransferred, totalBytes: totalBytes || bytesTransferred, fileName: remotePath });
|
|
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
|
+
}
|
|
167
|
+
async function uploadDirectory(artifactManager, artifactId, localDir, remoteBase, onProgress) {
|
|
168
|
+
const relPaths = collectLocalFiles(localDir);
|
|
169
|
+
const files = relPaths.map((relPath) => {
|
|
170
|
+
const localPath = join(localDir, relPath);
|
|
171
|
+
const remotePath = remoteBase ? `${remoteBase}/${relPath}` : relPath;
|
|
172
|
+
const size = statSync(localPath).size;
|
|
173
|
+
return { localPath, remotePath, size };
|
|
174
|
+
});
|
|
175
|
+
return uploadFilesBatched(artifactManager, artifactId, files, onProgress);
|
|
176
|
+
}
|
|
177
|
+
async function downloadDirectory(artifactManager, artifactId, remotePath, localDir, onProgress) {
|
|
178
|
+
const files = await artifactManager.list_files({
|
|
179
|
+
artifact_id: artifactId,
|
|
180
|
+
dir_path: remotePath || "",
|
|
181
|
+
_rkwargs: true
|
|
182
|
+
});
|
|
183
|
+
const fileList = Array.isArray(files) ? files : files?.items || [];
|
|
184
|
+
let downloaded = 0;
|
|
185
|
+
for (const entry of fileList) {
|
|
186
|
+
if (entry.type === "directory") {
|
|
187
|
+
const subRemote = remotePath ? `${remotePath}/${entry.name}` : entry.name;
|
|
188
|
+
const subLocal = join(localDir, entry.name);
|
|
189
|
+
downloaded += await downloadDirectory(artifactManager, artifactId, subRemote, subLocal, onProgress);
|
|
190
|
+
} else {
|
|
191
|
+
const remoteFile = remotePath ? `${remotePath}/${entry.name}` : entry.name;
|
|
192
|
+
const localFile = join(localDir, entry.name);
|
|
193
|
+
await downloadFile(artifactManager, artifactId, remoteFile, localFile, onProgress);
|
|
194
|
+
downloaded++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return downloaded;
|
|
198
|
+
}
|
|
199
|
+
const IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
200
|
+
"__pycache__",
|
|
201
|
+
".git",
|
|
202
|
+
".venv",
|
|
203
|
+
"node_modules",
|
|
204
|
+
".idea",
|
|
205
|
+
".pytest_cache",
|
|
206
|
+
".mypy_cache",
|
|
207
|
+
"build",
|
|
208
|
+
"dist"
|
|
209
|
+
]);
|
|
210
|
+
function collectLocalFiles(dir, base = "") {
|
|
211
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
212
|
+
const files = [];
|
|
213
|
+
for (const entry of entries) {
|
|
214
|
+
if (entry.name.startsWith(".") && entry.name !== ".env.example") continue;
|
|
215
|
+
if (entry.isDirectory() && IGNORED_DIRS.has(entry.name)) continue;
|
|
216
|
+
const relPath = base ? `${base}/${entry.name}` : entry.name;
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
files.push(...collectLocalFiles(join(dir, entry.name), relPath));
|
|
219
|
+
} else {
|
|
220
|
+
files.push(relPath);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return files;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function printArtifactsHelp() {
|
|
227
|
+
console.log(`hypha artifacts \u2014 manage artifacts
|
|
228
|
+
|
|
229
|
+
Usage: hypha artifacts <command> [options]
|
|
230
|
+
|
|
231
|
+
Commands:
|
|
232
|
+
ls [artifact[:path]] List artifacts or files in artifact
|
|
233
|
+
cat <artifact>:<path> Read file content to stdout
|
|
234
|
+
cp <src> <dest> [-r] Upload/download files (scp-style)
|
|
235
|
+
rm <artifact>[:<path>] Delete artifact or file
|
|
236
|
+
create <alias> [options] Create a new artifact
|
|
237
|
+
info <artifact> [--json] Show artifact metadata
|
|
238
|
+
search <query> [options] Search artifacts
|
|
239
|
+
commit <artifact> [options] Commit staged changes
|
|
240
|
+
edit <artifact> [options] Edit artifact metadata
|
|
241
|
+
discard <artifact> Discard staged changes
|
|
242
|
+
serve <artifact> [options] Enable/disable static site hosting
|
|
243
|
+
|
|
244
|
+
Aliases: list \u2192 ls, mkdir \u2192 create, find \u2192 search
|
|
245
|
+
Shorthand: "hypha art" is an alias for "hypha artifacts"
|
|
246
|
+
|
|
247
|
+
Artifact addressing:
|
|
248
|
+
alias Artifact in current workspace
|
|
249
|
+
workspace/alias Artifact in specific workspace
|
|
250
|
+
artifact:path/to/file File inside artifact
|
|
251
|
+
|
|
252
|
+
Copy examples:
|
|
253
|
+
hypha art cp ./data.csv model:data/train.csv Upload file
|
|
254
|
+
hypha art cp model:weights.bin ./local/ Download file
|
|
255
|
+
hypha art cp ./dataset/ model:data/ -r Upload directory
|
|
256
|
+
|
|
257
|
+
Common options:
|
|
258
|
+
--json Output as JSON
|
|
259
|
+
--long, -l Long listing format (with sizes)
|
|
260
|
+
--recursive, -r Recursive operation
|
|
261
|
+
--force, -f Skip confirmation`);
|
|
262
|
+
}
|
|
263
|
+
async function connectAndGetArtifactManager() {
|
|
264
|
+
const server = await connectToHypha();
|
|
265
|
+
const am = await server.getService("public/artifact-manager");
|
|
266
|
+
return { server, am };
|
|
267
|
+
}
|
|
268
|
+
function makeProgressCallback() {
|
|
269
|
+
return (p) => {
|
|
270
|
+
if (p.totalBytes > 0) {
|
|
271
|
+
printProgress(p.fileName, p.bytesTransferred, p.totalBytes);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function makeBatchProgressCallback(label) {
|
|
276
|
+
return (p) => {
|
|
277
|
+
printAggregateProgress(label, p.filesDone, p.filesTotal, p.bytesDone, p.bytesTotal);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
async function fetchWithAuth(url, init) {
|
|
281
|
+
const token = resolveToken();
|
|
282
|
+
const headers = new Headers(init?.headers);
|
|
283
|
+
if (token) {
|
|
284
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
285
|
+
}
|
|
286
|
+
return fetch(url, { ...init, headers });
|
|
287
|
+
}
|
|
288
|
+
async function artifactsLs(args) {
|
|
289
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
290
|
+
console.log(`hypha artifacts ls \u2014 list artifacts or files
|
|
291
|
+
|
|
292
|
+
Usage: hypha artifacts ls [artifact[:path]] [options]
|
|
293
|
+
|
|
294
|
+
With no argument, lists all artifacts in the workspace.
|
|
295
|
+
With artifact:path, lists files in the artifact.
|
|
296
|
+
|
|
297
|
+
Options:
|
|
298
|
+
--json Output as JSON
|
|
299
|
+
--long, -l Long listing format (with sizes, types)
|
|
300
|
+
|
|
301
|
+
Alias: hypha artifacts list`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const json = hasFlag(args, "--json");
|
|
305
|
+
const long = hasFlag(args, "--long", "-l");
|
|
306
|
+
const pos = positionalArgs(args)[0];
|
|
307
|
+
if (!pos) {
|
|
308
|
+
const { server: server2, am: am2 } = await connectAndGetArtifactManager();
|
|
309
|
+
try {
|
|
310
|
+
const artifacts = await am2.list({
|
|
311
|
+
_rkwargs: true
|
|
312
|
+
});
|
|
313
|
+
const list = Array.isArray(artifacts) ? artifacts : artifacts?.items || [];
|
|
314
|
+
if (json) {
|
|
315
|
+
console.log(formatJson(list));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (list.length === 0) {
|
|
319
|
+
console.log("No artifacts found.");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (long) {
|
|
323
|
+
const rows = [["ALIAS", "TYPE", "MODIFIED", "DESCRIPTION"]];
|
|
324
|
+
for (const art of list) {
|
|
325
|
+
const manifest = art.manifest || {};
|
|
326
|
+
rows.push([
|
|
327
|
+
art.alias || art.id || "",
|
|
328
|
+
art.type || "",
|
|
329
|
+
art.last_modified ? new Date(art.last_modified * 1e3).toISOString().slice(0, 10) : "",
|
|
330
|
+
(manifest.description || "").slice(0, 50)
|
|
331
|
+
]);
|
|
332
|
+
}
|
|
333
|
+
console.log(formatTable(rows));
|
|
334
|
+
} else {
|
|
335
|
+
for (const art of list) {
|
|
336
|
+
console.log(art.alias || art.id || "");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} finally {
|
|
340
|
+
await server2.disconnect();
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const parsed = parseArtifactPath(pos);
|
|
345
|
+
const serverUrl = resolveServerUrl();
|
|
346
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
347
|
+
const workspace = parsed.workspace || server.config.workspace;
|
|
348
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
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
|
+
}
|
|
393
|
+
const url = buildFileUrl(serverUrl, workspace, parsed.alias, parsed.filePath);
|
|
394
|
+
const resp = await fetchWithAuth(url);
|
|
395
|
+
if (!resp.ok) {
|
|
396
|
+
if (resp.status === 404) {
|
|
397
|
+
console.error(`Not found: ${pos}`);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
throw new Error(`List files failed: ${resp.status} ${resp.statusText}`);
|
|
401
|
+
}
|
|
402
|
+
const data = await resp.json();
|
|
403
|
+
const files = Array.isArray(data) ? data : data.items || [];
|
|
404
|
+
if (json) {
|
|
405
|
+
console.log(formatJson(files));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (files.length === 0) {
|
|
409
|
+
console.log("No files found.");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (long) {
|
|
413
|
+
const rows = [["SIZE", "TYPE", "NAME"]];
|
|
414
|
+
for (const f of files) {
|
|
415
|
+
rows.push([
|
|
416
|
+
f.type === "directory" ? "-" : humanSize(f.size || 0),
|
|
417
|
+
f.type || "file",
|
|
418
|
+
f.type === "directory" ? `${f.name}/` : f.name
|
|
419
|
+
]);
|
|
420
|
+
}
|
|
421
|
+
console.log(formatTable(rows));
|
|
422
|
+
} else {
|
|
423
|
+
for (const f of files) {
|
|
424
|
+
console.log(f.type === "directory" ? `${f.name}/` : f.name);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} finally {
|
|
428
|
+
await server.disconnect();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function artifactsCat(args) {
|
|
432
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
433
|
+
console.log(`hypha artifacts cat \u2014 read file content to stdout
|
|
434
|
+
|
|
435
|
+
Usage: hypha artifacts cat <artifact>:<path>
|
|
436
|
+
|
|
437
|
+
Examples:
|
|
438
|
+
hypha art cat model:README.md
|
|
439
|
+
hypha art cat ws/model:data/config.json`);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const pos = positionalArgs(args)[0];
|
|
443
|
+
if (!pos) {
|
|
444
|
+
console.error("Usage: hypha artifacts cat <artifact>:<path>");
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
const parsed = parseArtifactPath(pos);
|
|
448
|
+
if (!parsed.filePath) {
|
|
449
|
+
console.error("File path required. Use artifact:path/to/file syntax.");
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
const serverUrl = resolveServerUrl();
|
|
453
|
+
const { server } = await connectAndGetArtifactManager();
|
|
454
|
+
const workspace = parsed.workspace || server.config.workspace;
|
|
455
|
+
try {
|
|
456
|
+
const url = buildFileUrl(serverUrl, workspace, parsed.alias, parsed.filePath);
|
|
457
|
+
const resp = await fetchWithAuth(url, { redirect: "follow" });
|
|
458
|
+
if (!resp.ok) {
|
|
459
|
+
if (resp.status === 404) {
|
|
460
|
+
console.error(`File not found: ${parsed.filePath}`);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
throw new Error(`Read failed: ${resp.status} ${resp.statusText}`);
|
|
464
|
+
}
|
|
465
|
+
const reader = resp.body?.getReader();
|
|
466
|
+
if (!reader) {
|
|
467
|
+
const text = await resp.text();
|
|
468
|
+
process.stdout.write(text);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const decoder = new TextDecoder();
|
|
472
|
+
while (true) {
|
|
473
|
+
const { done, value } = await reader.read();
|
|
474
|
+
if (done) break;
|
|
475
|
+
process.stdout.write(decoder.decode(value, { stream: true }));
|
|
476
|
+
}
|
|
477
|
+
} finally {
|
|
478
|
+
await server.disconnect();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function artifactsCp(args) {
|
|
482
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
483
|
+
console.log(`hypha artifacts cp \u2014 upload/download files (scp-style)
|
|
484
|
+
|
|
485
|
+
Usage: hypha artifacts cp <src> <dest> [-r] [--commit]
|
|
486
|
+
|
|
487
|
+
Options:
|
|
488
|
+
-r, --recursive Copy directories recursively
|
|
489
|
+
--commit Auto-commit after upload
|
|
490
|
+
|
|
491
|
+
Examples:
|
|
492
|
+
hypha art cp ./data.csv model:data/train.csv Upload file
|
|
493
|
+
hypha art cp model:weights.bin ./local/ Download file
|
|
494
|
+
hypha art cp ./dataset/ model:data/ -r Upload directory
|
|
495
|
+
hypha art cp ./file.txt model:file.txt --commit Upload and commit`);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const pos = positionalArgs(args, []);
|
|
499
|
+
const recursive = hasFlag(args, "--recursive", "-r");
|
|
500
|
+
const autoCommit = hasFlag(args, "--commit");
|
|
501
|
+
if (pos.length < 2) {
|
|
502
|
+
console.error("Usage: hypha artifacts cp <src> <dest> [-r] [--commit]");
|
|
503
|
+
console.error(" Upload: hypha artifacts cp ./file.txt artifact:path/");
|
|
504
|
+
console.error(" Download: hypha artifacts cp artifact:path/file.txt ./local/");
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
const [src, dest] = pos;
|
|
508
|
+
const direction = determineCpDirection(src, dest);
|
|
509
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
510
|
+
const onProgress = makeProgressCallback();
|
|
511
|
+
try {
|
|
512
|
+
if (direction === "upload") {
|
|
513
|
+
const parsed = parseArtifactPath(dest);
|
|
514
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
515
|
+
await am.edit({
|
|
516
|
+
artifact_id: artifactId,
|
|
517
|
+
stage: true,
|
|
518
|
+
_rkwargs: true
|
|
519
|
+
});
|
|
520
|
+
const stat = statSync(src);
|
|
521
|
+
if (stat.isDirectory()) {
|
|
522
|
+
if (!recursive) {
|
|
523
|
+
console.error("Source is a directory. Use -r to copy directories.");
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
const dirLabel = `${basename(src)}/`;
|
|
527
|
+
const count = await uploadDirectory(
|
|
528
|
+
am,
|
|
529
|
+
artifactId,
|
|
530
|
+
src,
|
|
531
|
+
parsed.filePath || "",
|
|
532
|
+
makeBatchProgressCallback(dirLabel)
|
|
533
|
+
);
|
|
534
|
+
if (autoCommit) {
|
|
535
|
+
await am.commit({ artifact_id: artifactId, _rkwargs: true });
|
|
536
|
+
console.log(`Uploaded ${count} files and committed.`);
|
|
537
|
+
} else {
|
|
538
|
+
console.log(`Uploaded ${count} files. Run \`hypha artifacts commit ${parsed.alias}\` to finalize.`);
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
const remotePath = parsed.filePath || basename(src);
|
|
542
|
+
await uploadFile(am, artifactId, src, remotePath, onProgress);
|
|
543
|
+
if (autoCommit) {
|
|
544
|
+
await am.commit({ artifact_id: artifactId, _rkwargs: true });
|
|
545
|
+
console.log(`
|
|
546
|
+
Uploaded and committed: ${remotePath}`);
|
|
547
|
+
} else {
|
|
548
|
+
console.log(`
|
|
549
|
+
Uploaded: ${remotePath}. Run \`hypha artifacts commit ${parsed.alias}\` to finalize.`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
const parsed = parseArtifactPath(src);
|
|
554
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
555
|
+
if (!parsed.filePath) {
|
|
556
|
+
console.error("Source must include a file path (e.g., artifact:path/to/file).");
|
|
557
|
+
console.error("Use `hypha artifacts ls artifact:` to see available files.");
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
560
|
+
if (parsed.filePath.endsWith("/") && recursive) {
|
|
561
|
+
const count = await downloadDirectory(am, artifactId, parsed.filePath.replace(/\/$/, ""), dest, onProgress);
|
|
562
|
+
console.log(`
|
|
563
|
+
Downloaded ${count} files to ${dest}`);
|
|
564
|
+
} else {
|
|
565
|
+
const localDest = dest.endsWith("/") ? join(dest, basename(parsed.filePath)) : dest;
|
|
566
|
+
await downloadFile(am, artifactId, parsed.filePath, localDest, onProgress);
|
|
567
|
+
console.log(`
|
|
568
|
+
Downloaded: ${localDest}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
} finally {
|
|
572
|
+
await server.disconnect();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async function artifactsRm(args) {
|
|
576
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
577
|
+
console.log(`hypha artifacts rm \u2014 delete artifact or file
|
|
578
|
+
|
|
579
|
+
Usage: hypha artifacts rm <artifact>[:<path>] [-r] [-f]
|
|
580
|
+
|
|
581
|
+
Without :path, deletes the entire artifact (requires --force).
|
|
582
|
+
With :path, deletes a single file.
|
|
583
|
+
|
|
584
|
+
Options:
|
|
585
|
+
-r, --recursive Delete recursively (for collections)
|
|
586
|
+
-f, --force Skip confirmation for whole-artifact deletion`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const pos = positionalArgs(args)[0];
|
|
590
|
+
const force = hasFlag(args, "--force", "-f");
|
|
591
|
+
const recursive = hasFlag(args, "--recursive", "-r");
|
|
592
|
+
if (!pos) {
|
|
593
|
+
console.error("Usage: hypha artifacts rm <artifact>[:<path>] [-r] [-f]");
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
const parsed = parseArtifactPath(pos);
|
|
597
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
598
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
599
|
+
try {
|
|
600
|
+
if (parsed.filePath) {
|
|
601
|
+
await am.remove_file({
|
|
602
|
+
artifact_id: artifactId,
|
|
603
|
+
file_path: parsed.filePath,
|
|
604
|
+
_rkwargs: true
|
|
605
|
+
});
|
|
606
|
+
console.log(`Removed: ${parsed.filePath}`);
|
|
607
|
+
} else {
|
|
608
|
+
if (!force) {
|
|
609
|
+
console.error(`This will delete the entire artifact "${parsed.alias}". Use --force to confirm.`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
await am.delete({
|
|
613
|
+
artifact_id: artifactId,
|
|
614
|
+
delete_files: true,
|
|
615
|
+
recursive,
|
|
616
|
+
_rkwargs: true
|
|
617
|
+
});
|
|
618
|
+
console.log(`Deleted: ${parsed.alias}`);
|
|
619
|
+
}
|
|
620
|
+
} finally {
|
|
621
|
+
await server.disconnect();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async function artifactsCreate(args) {
|
|
625
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
626
|
+
console.log(`hypha artifacts create \u2014 create a new artifact
|
|
627
|
+
|
|
628
|
+
Usage: hypha artifacts create <alias> [options]
|
|
629
|
+
|
|
630
|
+
Options:
|
|
631
|
+
--type <type> Artifact type (default: generic)
|
|
632
|
+
--parent <id> Parent artifact ID (for collections)
|
|
633
|
+
--json Output as JSON
|
|
634
|
+
|
|
635
|
+
Types: generic, collection, application, model, dataset
|
|
636
|
+
|
|
637
|
+
Alias: hypha artifacts mkdir`);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const pos = positionalArgs(args, ["--type", "--parent"])[0];
|
|
641
|
+
const type = getFlag(args, "--type") || "generic";
|
|
642
|
+
const parent = getFlag(args, "--parent");
|
|
643
|
+
const json = hasFlag(args, "--json");
|
|
644
|
+
if (!pos) {
|
|
645
|
+
console.error("Usage: hypha artifacts create <alias> [--type T] [--parent P]");
|
|
646
|
+
console.error("Types: generic, collection, application, model, dataset");
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
650
|
+
try {
|
|
651
|
+
const createOpts = {
|
|
652
|
+
alias: pos,
|
|
653
|
+
type,
|
|
654
|
+
manifest: { name: pos },
|
|
655
|
+
_rkwargs: true
|
|
656
|
+
};
|
|
657
|
+
if (parent) createOpts.parent_id = parent;
|
|
658
|
+
const result = await am.create(createOpts);
|
|
659
|
+
if (json) {
|
|
660
|
+
console.log(formatJson(result));
|
|
661
|
+
} else {
|
|
662
|
+
console.log(`Created: ${result.alias || pos} (type: ${type})`);
|
|
663
|
+
}
|
|
664
|
+
} finally {
|
|
665
|
+
await server.disconnect();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function artifactsInfo(args) {
|
|
669
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
670
|
+
console.log(`hypha artifacts info \u2014 show artifact metadata
|
|
671
|
+
|
|
672
|
+
Usage: hypha artifacts info <artifact> [--json]
|
|
673
|
+
|
|
674
|
+
Options:
|
|
675
|
+
--json Output as JSON`);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const pos = positionalArgs(args)[0];
|
|
679
|
+
const json = hasFlag(args, "--json");
|
|
680
|
+
if (!pos) {
|
|
681
|
+
console.error("Usage: hypha artifacts info <artifact> [--json]");
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
const parsed = parseArtifactPath(pos);
|
|
685
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
686
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
687
|
+
try {
|
|
688
|
+
const info = await am.read({
|
|
689
|
+
artifact_id: artifactId,
|
|
690
|
+
_rkwargs: true
|
|
691
|
+
});
|
|
692
|
+
if (json) {
|
|
693
|
+
console.log(formatJson(info));
|
|
694
|
+
} else {
|
|
695
|
+
const manifest = info.manifest || {};
|
|
696
|
+
console.log(`Alias: ${info.alias || artifactId}`);
|
|
697
|
+
console.log(`ID: ${info.id || ""}`);
|
|
698
|
+
console.log(`Type: ${info.type || "generic"}`);
|
|
699
|
+
console.log(`Name: ${manifest.name || ""}`);
|
|
700
|
+
console.log(`Description: ${manifest.description || ""}`);
|
|
701
|
+
console.log(`Workspace: ${info.workspace || ""}`);
|
|
702
|
+
if (info.created_at) console.log(`Created: ${new Date(info.created_at * 1e3).toISOString()}`);
|
|
703
|
+
if (info.last_modified) console.log(`Modified: ${new Date(info.last_modified * 1e3).toISOString()}`);
|
|
704
|
+
if (info.versions?.length) {
|
|
705
|
+
console.log(`Versions: ${info.versions.map((v) => v.name || v.version || v).join(", ")}`);
|
|
706
|
+
}
|
|
707
|
+
if (info.download_count) console.log(`Downloads: ${info.download_count}`);
|
|
708
|
+
if (info.view_count) console.log(`Views: ${info.view_count}`);
|
|
709
|
+
}
|
|
710
|
+
} finally {
|
|
711
|
+
await server.disconnect();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function artifactsSearch(args) {
|
|
715
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
716
|
+
console.log(`hypha artifacts search \u2014 search artifacts
|
|
717
|
+
|
|
718
|
+
Usage: hypha artifacts search <query> [options]
|
|
719
|
+
|
|
720
|
+
Options:
|
|
721
|
+
--type <type> Filter by artifact type
|
|
722
|
+
--limit <n> Max results (default: 20)
|
|
723
|
+
--json Output as JSON
|
|
724
|
+
|
|
725
|
+
Alias: hypha artifacts find`);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const pos = positionalArgs(args, ["--type", "--limit"])[0];
|
|
729
|
+
const type = getFlag(args, "--type");
|
|
730
|
+
const limit = getFlagInt(args, "--limit") || 20;
|
|
731
|
+
const json = hasFlag(args, "--json");
|
|
732
|
+
if (!pos) {
|
|
733
|
+
console.error("Usage: hypha artifacts search <query> [--type T] [--limit N] [--json]");
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
737
|
+
try {
|
|
738
|
+
const searchOpts = {
|
|
739
|
+
keywords: pos,
|
|
740
|
+
limit,
|
|
741
|
+
_rkwargs: true
|
|
742
|
+
};
|
|
743
|
+
if (type) {
|
|
744
|
+
searchOpts.filters = { type };
|
|
745
|
+
}
|
|
746
|
+
const results = await am.list(searchOpts);
|
|
747
|
+
const list = Array.isArray(results) ? results : results?.items || [];
|
|
748
|
+
if (json) {
|
|
749
|
+
console.log(formatJson(list));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (list.length === 0) {
|
|
753
|
+
console.log("No results found.");
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const rows = [["ALIAS", "TYPE", "NAME", "DESCRIPTION"]];
|
|
757
|
+
for (const art of list) {
|
|
758
|
+
const manifest = art.manifest || {};
|
|
759
|
+
rows.push([
|
|
760
|
+
art.alias || art.id || "",
|
|
761
|
+
art.type || "",
|
|
762
|
+
manifest.name || "",
|
|
763
|
+
(manifest.description || "").slice(0, 40)
|
|
764
|
+
]);
|
|
765
|
+
}
|
|
766
|
+
console.log(formatTable(rows));
|
|
767
|
+
} finally {
|
|
768
|
+
await server.disconnect();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
async function artifactsCommit(args) {
|
|
772
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
773
|
+
console.log(`hypha artifacts commit \u2014 commit staged changes
|
|
774
|
+
|
|
775
|
+
Usage: hypha artifacts commit <artifact> [options]
|
|
776
|
+
|
|
777
|
+
Options:
|
|
778
|
+
--version <version> Version tag (e.g., v1.0)
|
|
779
|
+
--message, -m <msg> Commit message`);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const pos = positionalArgs(args, ["--version", "--message"])[0];
|
|
783
|
+
const version = getFlag(args, "--version");
|
|
784
|
+
const message = getFlag(args, "--message", "-m");
|
|
785
|
+
if (!pos) {
|
|
786
|
+
console.error("Usage: hypha artifacts commit <artifact> [--version V] [--message M]");
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
const parsed = parseArtifactPath(pos);
|
|
790
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
791
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
792
|
+
try {
|
|
793
|
+
const commitOpts = {
|
|
794
|
+
artifact_id: artifactId,
|
|
795
|
+
_rkwargs: true
|
|
796
|
+
};
|
|
797
|
+
if (version) commitOpts.version = version;
|
|
798
|
+
if (message) commitOpts.comment = message;
|
|
799
|
+
await am.commit(commitOpts);
|
|
800
|
+
console.log(`Committed: ${parsed.alias}${version ? ` (version: ${version})` : ""}`);
|
|
801
|
+
} finally {
|
|
802
|
+
await server.disconnect();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async function artifactsEdit(args) {
|
|
806
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
807
|
+
console.log(`hypha artifacts edit \u2014 edit artifact metadata
|
|
808
|
+
|
|
809
|
+
Usage: hypha artifacts edit <artifact> [options]
|
|
810
|
+
|
|
811
|
+
Options:
|
|
812
|
+
--name <name> Update display name
|
|
813
|
+
--description <desc> Update description
|
|
814
|
+
--type <type> Update artifact type
|
|
815
|
+
--stage Enter staging mode for file operations`);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const pos = positionalArgs(args, ["--name", "--description", "--type"])[0];
|
|
819
|
+
const name = getFlag(args, "--name");
|
|
820
|
+
const description = getFlag(args, "--description");
|
|
821
|
+
const type = getFlag(args, "--type");
|
|
822
|
+
const stage = hasFlag(args, "--stage");
|
|
823
|
+
if (!pos) {
|
|
824
|
+
console.error("Usage: hypha artifacts edit <artifact> [--name N] [--description D] [--type T] [--stage]");
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
const parsed = parseArtifactPath(pos);
|
|
828
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
829
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
830
|
+
try {
|
|
831
|
+
const editOpts = {
|
|
832
|
+
artifact_id: artifactId,
|
|
833
|
+
_rkwargs: true
|
|
834
|
+
};
|
|
835
|
+
const manifest = {};
|
|
836
|
+
if (name) manifest.name = name;
|
|
837
|
+
if (description) manifest.description = description;
|
|
838
|
+
if (Object.keys(manifest).length > 0) editOpts.manifest = manifest;
|
|
839
|
+
if (type) editOpts.type = type;
|
|
840
|
+
if (stage) editOpts.stage = true;
|
|
841
|
+
await am.edit(editOpts);
|
|
842
|
+
console.log(`Updated: ${parsed.alias}`);
|
|
843
|
+
} finally {
|
|
844
|
+
await server.disconnect();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
async function artifactsDiscard(args) {
|
|
848
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
849
|
+
console.log(`hypha artifacts discard \u2014 discard staged changes
|
|
850
|
+
|
|
851
|
+
Usage: hypha artifacts discard <artifact>`);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const pos = positionalArgs(args)[0];
|
|
855
|
+
if (!pos) {
|
|
856
|
+
console.error("Usage: hypha artifacts discard <artifact>");
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
const parsed = parseArtifactPath(pos);
|
|
860
|
+
const { server, am } = await connectAndGetArtifactManager();
|
|
861
|
+
const artifactId = resolveArtifactId(parsed, server.config.workspace);
|
|
862
|
+
try {
|
|
863
|
+
await am.discard_changes({ artifact_id: artifactId, _rkwargs: true });
|
|
864
|
+
console.log(`Discarded staged changes: ${parsed.alias}`);
|
|
865
|
+
} finally {
|
|
866
|
+
await server.disconnect();
|
|
867
|
+
}
|
|
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
|
+
}
|
|
919
|
+
async function handleArtifactsCommand(args) {
|
|
920
|
+
const sub = args[0];
|
|
921
|
+
const commandArgs = args.slice(1);
|
|
922
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
923
|
+
printArtifactsHelp();
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (sub === "ls" || sub === "list") {
|
|
927
|
+
await artifactsLs(commandArgs);
|
|
928
|
+
} else if (sub === "cat") {
|
|
929
|
+
await artifactsCat(commandArgs);
|
|
930
|
+
} else if (sub === "cp") {
|
|
931
|
+
await artifactsCp(commandArgs);
|
|
932
|
+
} else if (sub === "rm") {
|
|
933
|
+
await artifactsRm(commandArgs);
|
|
934
|
+
} else if (sub === "create" || sub === "mkdir") {
|
|
935
|
+
await artifactsCreate(commandArgs);
|
|
936
|
+
} else if (sub === "info") {
|
|
937
|
+
await artifactsInfo(commandArgs);
|
|
938
|
+
} else if (sub === "search" || sub === "find") {
|
|
939
|
+
await artifactsSearch(commandArgs);
|
|
940
|
+
} else if (sub === "commit") {
|
|
941
|
+
await artifactsCommit(commandArgs);
|
|
942
|
+
} else if (sub === "edit") {
|
|
943
|
+
await artifactsEdit(commandArgs);
|
|
944
|
+
} else if (sub === "discard") {
|
|
945
|
+
await artifactsDiscard(commandArgs);
|
|
946
|
+
} else if (sub === "serve") {
|
|
947
|
+
await artifactsServe(commandArgs);
|
|
948
|
+
} else {
|
|
949
|
+
console.error(`Unknown artifacts command: ${sub}`);
|
|
950
|
+
printArtifactsHelp();
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
export { artifactsLs, handleArtifactsCommand };
|