trajectories-sh 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -81,7 +81,7 @@ program.command("upload <directory>").description("Upload a trajectory job direc
|
|
|
81
81
|
Pushing ${directory} \u2192 ${chalk.cyan(slug)}
|
|
82
82
|
`));
|
|
83
83
|
try {
|
|
84
|
-
const { uploadJob } = await import("./upload-
|
|
84
|
+
const { uploadJob } = await import("./upload-NTDQN32Q.js");
|
|
85
85
|
const result = await uploadJob(directory, {
|
|
86
86
|
slug,
|
|
87
87
|
name: opts.name,
|
|
@@ -107,7 +107,7 @@ program.command("download <url>").description("Download a trajectory job from tr
|
|
|
107
107
|
Downloading trajectory...
|
|
108
108
|
`));
|
|
109
109
|
try {
|
|
110
|
-
const { downloadJob, parseJobId } = await import("./download-
|
|
110
|
+
const { downloadJob, parseJobId } = await import("./download-JTFEDMLI.js");
|
|
111
111
|
const outputDir = opts.output ?? parseJobId(url).replace(/^org:/, "").replace(/:/g, "-");
|
|
112
112
|
const result = await downloadJob(url, outputDir);
|
|
113
113
|
console.log(chalk.green.bold(` \u2713 Downloaded ${result.files} files`));
|
|
@@ -121,6 +121,68 @@ program.command("download <url>").description("Download a trajectory job from tr
|
|
|
121
121
|
process.exit(1);
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
+
program.command("delete <url>").description("Delete a trajectory job from trajectories.sh").option("-y, --yes", "Skip confirmation prompt").action(async (url, opts) => {
|
|
125
|
+
const chalk = (await import("chalk")).default;
|
|
126
|
+
const { parseJobId } = await import("./download-JTFEDMLI.js");
|
|
127
|
+
const { getApiUrl, getAuthHeader } = await import("./config-NOIAMV2U.js");
|
|
128
|
+
let jobId = parseJobId(url);
|
|
129
|
+
if (jobId.startsWith("org:")) {
|
|
130
|
+
console.error(chalk.red("\n \u2717 Please use a direct job URL or UUID for deletion.\n"));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const auth2 = await getAuthHeader();
|
|
134
|
+
if (!auth2) {
|
|
135
|
+
console.error(chalk.red(" \u2717 Not authenticated. Run: trajectories auth login"));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const apiUrl = getApiUrl();
|
|
139
|
+
const headers = { Authorization: auth2 };
|
|
140
|
+
try {
|
|
141
|
+
const previewResp = await fetch(`${apiUrl}/api/cli/delete/${jobId}/preview`, { headers });
|
|
142
|
+
if (!previewResp.ok) {
|
|
143
|
+
const text = await previewResp.text();
|
|
144
|
+
throw new Error(text);
|
|
145
|
+
}
|
|
146
|
+
const preview = await previewResp.json();
|
|
147
|
+
const sizeMB = (preview.storage_bytes / 1024 / 1024).toFixed(1);
|
|
148
|
+
console.log(chalk.bold(`
|
|
149
|
+
About to delete:
|
|
150
|
+
`));
|
|
151
|
+
console.log(` Name: ${preview.name}`);
|
|
152
|
+
console.log(` Slug: ${preview.slug}`);
|
|
153
|
+
console.log(` Trials: ${preview.n_trials}`);
|
|
154
|
+
console.log(` Files: ${preview.file_count}`);
|
|
155
|
+
console.log(` Storage: ${sizeMB} MB
|
|
156
|
+
`);
|
|
157
|
+
if (!opts.yes) {
|
|
158
|
+
const readline = await import("readline");
|
|
159
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
160
|
+
const answer = await new Promise((resolve) => {
|
|
161
|
+
rl.question(chalk.yellow(" Are you sure? (y/N) "), resolve);
|
|
162
|
+
});
|
|
163
|
+
rl.close();
|
|
164
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
165
|
+
console.log(chalk.dim("\n Cancelled.\n"));
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const deleteResp = await fetch(`${apiUrl}/api/cli/delete/${jobId}`, {
|
|
170
|
+
method: "DELETE",
|
|
171
|
+
headers
|
|
172
|
+
});
|
|
173
|
+
if (!deleteResp.ok) throw new Error(await deleteResp.text());
|
|
174
|
+
const result = await deleteResp.json();
|
|
175
|
+
console.log(chalk.green.bold(`
|
|
176
|
+
\u2713 Deleted successfully`));
|
|
177
|
+
console.log(` Freed: ${(result.storage_freed_bytes / 1024 / 1024).toFixed(1)} MB
|
|
178
|
+
`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.error(chalk.red(`
|
|
181
|
+
\u2717 ${e.message}
|
|
182
|
+
`));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
124
186
|
program.command("whoami").description("Show current authentication status").action(async () => {
|
|
125
187
|
const chalk = (await import("chalk")).default;
|
|
126
188
|
const { status } = await import("./auth-UD3KTSHK.js");
|
|
@@ -55,9 +55,12 @@ async function downloadJob(input, outputDir) {
|
|
|
55
55
|
throw new Error(`Failed to list files: ${text}`);
|
|
56
56
|
}
|
|
57
57
|
const { slug, files } = await listResp.json();
|
|
58
|
+
const totalBytes = files.reduce((s, f) => s + f.size, 0);
|
|
58
59
|
console.log(` Job: ${slug} (${jobId})`);
|
|
59
|
-
console.log(` ${files.length} files
|
|
60
|
+
console.log(` ${files.length} files, ${(totalBytes / 1024 / 1024).toFixed(1)} MB`);
|
|
60
61
|
mkdirSync(outputDir, { recursive: true });
|
|
62
|
+
const { ProgressBar } = await import("./progress-2P5L7X24.js");
|
|
63
|
+
const bar = new ProgressBar({ total: files.length, totalBytes, label: "\u2193" });
|
|
61
64
|
let downloaded = 0;
|
|
62
65
|
let totalSize = 0;
|
|
63
66
|
const concurrency = 8;
|
|
@@ -77,15 +80,13 @@ async function downloadJob(input, outputDir) {
|
|
|
77
80
|
writeFileSync(outPath, data);
|
|
78
81
|
downloaded++;
|
|
79
82
|
totalSize += data.length;
|
|
80
|
-
|
|
81
|
-
process.stdout.write(` \u2193 ${downloaded}/${files.length} downloaded\r`);
|
|
82
|
-
}
|
|
83
|
+
bar.tick(data.length);
|
|
83
84
|
return;
|
|
84
85
|
} catch {
|
|
85
86
|
if (attempt < 2) await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
|
-
|
|
89
|
+
bar.tick(0);
|
|
89
90
|
})();
|
|
90
91
|
pending.push(task);
|
|
91
92
|
if (pending.length >= concurrency) {
|
|
@@ -102,7 +103,7 @@ async function downloadJob(input, outputDir) {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
await Promise.all(pending);
|
|
105
|
-
|
|
106
|
+
bar.done();
|
|
106
107
|
return { files: downloaded, totalSize };
|
|
107
108
|
}
|
|
108
109
|
export {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/progress.ts
|
|
2
|
+
var ProgressBar = class {
|
|
3
|
+
total;
|
|
4
|
+
totalBytes;
|
|
5
|
+
current = 0;
|
|
6
|
+
currentBytes = 0;
|
|
7
|
+
startTime;
|
|
8
|
+
label;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this.total = opts.total;
|
|
11
|
+
this.totalBytes = opts.totalBytes;
|
|
12
|
+
this.label = opts.label;
|
|
13
|
+
this.startTime = Date.now();
|
|
14
|
+
}
|
|
15
|
+
tick(bytes = 0) {
|
|
16
|
+
this.current++;
|
|
17
|
+
this.currentBytes += bytes;
|
|
18
|
+
this.render();
|
|
19
|
+
}
|
|
20
|
+
render() {
|
|
21
|
+
const pct = this.total > 0 ? Math.round(this.current / this.total * 100) : 0;
|
|
22
|
+
const elapsed = (Date.now() - this.startTime) / 1e3;
|
|
23
|
+
const bytesPerSec = elapsed > 0 ? this.currentBytes / elapsed : 0;
|
|
24
|
+
const sizeDone = formatBytes(this.currentBytes);
|
|
25
|
+
const sizeTotal = formatBytes(this.totalBytes);
|
|
26
|
+
const speed = formatBytes(bytesPerSec) + "/s";
|
|
27
|
+
let eta = "";
|
|
28
|
+
if (bytesPerSec > 0 && this.currentBytes < this.totalBytes) {
|
|
29
|
+
const remaining = (this.totalBytes - this.currentBytes) / bytesPerSec;
|
|
30
|
+
eta = remaining < 60 ? `${remaining.toFixed(0)}s` : `${(remaining / 60).toFixed(1)}m`;
|
|
31
|
+
}
|
|
32
|
+
const barWidth = 20;
|
|
33
|
+
const filled = Math.round(this.current / this.total * barWidth);
|
|
34
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
` ${this.label} ${bar} ${pct}% ${this.current}/${this.total} ${sizeDone}/${sizeTotal} ${speed}${eta ? ` ETA ${eta}` : ""}\r`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
done() {
|
|
40
|
+
const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
41
|
+
const size = formatBytes(this.currentBytes);
|
|
42
|
+
process.stdout.write(
|
|
43
|
+
` ${this.label} ${"\u2588".repeat(20)} 100% ${this.current}/${this.total} ${size} in ${elapsed}s
|
|
44
|
+
`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function formatBytes(bytes) {
|
|
49
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
50
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
|
|
51
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
52
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
ProgressBar
|
|
56
|
+
};
|
|
@@ -206,7 +206,9 @@ async function uploadJob(dir, opts) {
|
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
async function uploadDirect(jobId, files) {
|
|
209
|
-
|
|
209
|
+
const { ProgressBar } = await import("./progress-2P5L7X24.js");
|
|
210
|
+
const totalBytes = files.reduce((s, f) => s + f.size, 0);
|
|
211
|
+
const bar = new ProgressBar({ total: files.length, totalBytes, label: "\u2191" });
|
|
210
212
|
let errors = 0;
|
|
211
213
|
for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
|
|
212
214
|
const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
|
|
@@ -222,7 +224,7 @@ async function uploadDirect(jobId, files) {
|
|
|
222
224
|
if (u.signed_url) urlMap.set(u.path, u.signed_url);
|
|
223
225
|
else {
|
|
224
226
|
errors++;
|
|
225
|
-
|
|
227
|
+
bar.tick(0);
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
const pending = [];
|
|
@@ -233,14 +235,10 @@ async function uploadDirect(jobId, files) {
|
|
|
233
235
|
const task = (async () => {
|
|
234
236
|
const ok = await uploadOneFile(file, signedUrl);
|
|
235
237
|
if (ok) {
|
|
236
|
-
|
|
238
|
+
bar.tick(file.size);
|
|
237
239
|
} else {
|
|
238
240
|
errors++;
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
const total = uploaded + errors;
|
|
242
|
-
if (total % 10 === 0 || total === files.length) {
|
|
243
|
-
process.stdout.write(` \u2191 ${uploaded}/${files.length} uploaded\r`);
|
|
241
|
+
bar.tick(0);
|
|
244
242
|
}
|
|
245
243
|
})();
|
|
246
244
|
pending.push(task);
|
|
@@ -257,11 +255,9 @@ async function uploadDirect(jobId, files) {
|
|
|
257
255
|
}
|
|
258
256
|
await Promise.all(pending);
|
|
259
257
|
}
|
|
260
|
-
|
|
258
|
+
bar.done();
|
|
261
259
|
if (errors > 0) {
|
|
262
|
-
console.log(`
|
|
263
|
-
} else {
|
|
264
|
-
console.log(` \u2713 All ${uploaded} files uploaded directly to storage`);
|
|
260
|
+
console.log(` \u26A0 ${errors} file(s) failed to upload`);
|
|
265
261
|
}
|
|
266
262
|
}
|
|
267
263
|
async function uploadOneFile(file, signedUrl) {
|