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-3VEY2SEN.js");
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-YWW2BV6K.js");
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 to download`);
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
- if (downloaded % 10 === 0 || downloaded === files.length) {
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
- console.log(` \u2717 ${file.path}`);
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
- console.log("");
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
- let uploaded = 0;
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
- console.log(` \u2717 ${u.path} (no signed URL: ${u.error})`);
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
- uploaded++;
238
+ bar.tick(file.size);
237
239
  } else {
238
240
  errors++;
239
- console.log(` \u2717 ${file.relPath}`);
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
- console.log("");
258
+ bar.done();
261
259
  if (errors > 0) {
262
- console.log(` Uploaded: ${uploaded}, Errors: ${errors}`);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trajectories-sh",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "CLI for uploading trajectory jobs to trajectories.sh",
5
5
  "type": "module",
6
6
  "bin": {