trajectories-sh 1.18.0 → 1.18.2
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,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getApiUrl,
|
|
3
|
+
getAuthHeader
|
|
4
|
+
} from "./chunk-Q2ZEN43L.js";
|
|
5
|
+
|
|
6
|
+
// src/quota.ts
|
|
7
|
+
async function fetchOrgUsage() {
|
|
8
|
+
const auth = await getAuthHeader();
|
|
9
|
+
if (!auth) return null;
|
|
10
|
+
try {
|
|
11
|
+
const resp = await fetch(`${getApiUrl()}/api/cli/usage`, {
|
|
12
|
+
headers: { Authorization: auth },
|
|
13
|
+
signal: AbortSignal.timeout(1e4)
|
|
14
|
+
});
|
|
15
|
+
if (!resp.ok) return null;
|
|
16
|
+
return await resp.json();
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function planUpgradeLabel(plan) {
|
|
22
|
+
return plan === "free" ? "Team" : "Premiere";
|
|
23
|
+
}
|
|
24
|
+
async function preflightQuota(check) {
|
|
25
|
+
const usage = await fetchOrgUsage();
|
|
26
|
+
if (!usage) return;
|
|
27
|
+
if (usage.plan === "premiere") return;
|
|
28
|
+
const plan = planUpgradeLabel(usage.plan);
|
|
29
|
+
if (check.kind === "trajectory") {
|
|
30
|
+
if (usage.trajectory_count >= usage.trajectory_limit) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Trajectory limit reached (${usage.trajectory_count.toLocaleString()}/${usage.trajectory_limit.toLocaleString()}). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const projected = usage.storage_bytes_used + check.projectedAddedBytes;
|
|
36
|
+
if (projected > usage.storage_limit_bytes) {
|
|
37
|
+
const usedGB = (usage.storage_bytes_used / 1024 ** 3).toFixed(1);
|
|
38
|
+
const uploadGB = (check.projectedAddedBytes / 1024 ** 3).toFixed(1);
|
|
39
|
+
const limitGB = (usage.storage_limit_bytes / 1024 ** 3).toFixed(0);
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Storage limit would be exceeded (${usedGB} GB used + ${uploadGB} GB upload > ${limitGB} GB limit). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (usage.task_count + check.newTaskCount > usage.task_limit) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Task limit reached (${usage.task_count.toLocaleString()}/${usage.task_limit.toLocaleString()}). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
preflightQuota
|
|
55
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -64,11 +64,18 @@ auth.command("status").description("Show current authentication status").action(
|
|
|
64
64
|
console.log(chalk.dim(" Not logged in. Run: trajectories auth login"));
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
-
async function uploadTrajectoryAction(
|
|
67
|
+
async function uploadTrajectoryAction(pathArg, opts) {
|
|
68
68
|
const chalk = (await import("chalk")).default;
|
|
69
69
|
const { existsSync, statSync } = await import("fs");
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const exists = existsSync(pathArg);
|
|
71
|
+
const isZip = pathArg.toLowerCase().endsWith(".zip");
|
|
72
|
+
const isDir = exists && statSync(pathArg).isDirectory();
|
|
73
|
+
if (!exists) {
|
|
74
|
+
console.error(chalk.red(` \u2717 Path not found: ${pathArg}`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
if (!isDir && !isZip) {
|
|
78
|
+
console.error(chalk.red(` \u2717 ${pathArg} is not a directory or .zip file`));
|
|
72
79
|
process.exit(1);
|
|
73
80
|
}
|
|
74
81
|
if (opts.apiKey) {
|
|
@@ -90,13 +97,38 @@ async function uploadTrajectoryAction(directory, opts) {
|
|
|
90
97
|
)
|
|
91
98
|
);
|
|
92
99
|
}
|
|
93
|
-
|
|
100
|
+
if (isZip) {
|
|
101
|
+
const { uploadZip } = await import("./upload-3CRW3BJ6.js");
|
|
102
|
+
const slug2 = opts.slug ?? pathArg.replace(/\.zip$/i, "").replace(/\/+$/, "").split("/").pop();
|
|
103
|
+
console.log(chalk.bold(`
|
|
104
|
+
Uploading ZIP ${pathArg} \u2192 ${chalk.cyan(slug2)}
|
|
105
|
+
`));
|
|
106
|
+
try {
|
|
107
|
+
const results = await uploadZip(pathArg, { slug: slug2, name: opts.name });
|
|
108
|
+
for (const r of results) {
|
|
109
|
+
console.log(chalk.green.bold(` \u2713 ${r.slug}`));
|
|
110
|
+
console.log(` Trials: ${r.nTrials}`);
|
|
111
|
+
if (r.meanReward != null) {
|
|
112
|
+
console.log(` Pass rate: ${(r.meanReward * 100).toFixed(0)}%`);
|
|
113
|
+
}
|
|
114
|
+
console.log(` Viewer: ${r.viewerUrl}`);
|
|
115
|
+
}
|
|
116
|
+
console.log("");
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error(chalk.red(`
|
|
119
|
+
\u2717 ${e.message}
|
|
120
|
+
`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const slug = opts.slug ?? pathArg.replace(/\/+$/, "").split("/").pop();
|
|
94
126
|
console.log(chalk.bold(`
|
|
95
|
-
Uploading trajectory ${
|
|
127
|
+
Uploading trajectory ${pathArg} \u2192 ${chalk.cyan(slug)}
|
|
96
128
|
`));
|
|
97
129
|
try {
|
|
98
|
-
const { uploadJob } = await import("./upload-
|
|
99
|
-
const result = await uploadJob(
|
|
130
|
+
const { uploadJob } = await import("./upload-3CRW3BJ6.js");
|
|
131
|
+
const result = await uploadJob(pathArg, {
|
|
100
132
|
slug,
|
|
101
133
|
name: opts.name
|
|
102
134
|
});
|
|
@@ -141,7 +173,7 @@ async function uploadTaskAction(directory, opts) {
|
|
|
141
173
|
Uploading task ${directory} \u2192 ${chalk.cyan(name)}
|
|
142
174
|
`));
|
|
143
175
|
try {
|
|
144
|
-
const { pushTask } = await import("./push-task-
|
|
176
|
+
const { pushTask } = await import("./push-task-OZTAROKW.js");
|
|
145
177
|
const result = await pushTask(directory, {
|
|
146
178
|
name: opts.name,
|
|
147
179
|
visibility: opts.visibility
|
|
@@ -242,7 +274,7 @@ async function deleteTrajectoryAction(url, opts) {
|
|
|
242
274
|
}
|
|
243
275
|
}
|
|
244
276
|
var upload = program.command("upload").description("Upload trajectories or tasks");
|
|
245
|
-
upload.command("trajectory <
|
|
277
|
+
upload.command("trajectory <path>").description("Upload a trajectory job directory or ZIP file (always uploaded as unlisted)").option("-s, --slug <slug>", "URL slug (default: directory name)").option("-n, --name <name>", "Display name (default: slug)").option("--api-key <key>", "API key (or set TRAJECTORIES_API_KEY in your env)").option("-v, --visibility <vis>", "(deprecated, ignored \u2014 set visibility in the UI)").action(uploadTrajectoryAction);
|
|
246
278
|
upload.command("task <directory>").description("Upload a benchmark task directory").option("-n, --name <name>", "Task name (default: directory name)").option("--api-key <key>", "API key (or set TRAJECTORIES_API_KEY in your env)").option("-v, --visibility <vis>", "public or private", "public").action(uploadTaskAction);
|
|
247
279
|
var download = program.command("download").description("Download trajectories");
|
|
248
280
|
download.command("trajectory <url>").description("Download a trajectory job from trajectories.sh").option("-o, --output <dir>", "Output directory (default: slug name)").action(downloadTrajectoryAction);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
preflightQuota
|
|
3
|
+
} from "./chunk-2ELJH3IV.js";
|
|
1
4
|
import {
|
|
2
5
|
getApiUrl,
|
|
3
6
|
getAuthHeader
|
|
@@ -116,18 +119,9 @@ async function pushTask(dir, opts) {
|
|
|
116
119
|
"No task content found. A task needs at least an instruction.md, Dockerfile, or test file."
|
|
117
120
|
);
|
|
118
121
|
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const usageResp = await fetch(`${apiUrl}/api/cli/usage`, {
|
|
122
|
-
headers: { Authorization: auth },
|
|
123
|
-
signal: AbortSignal.timeout(1e4)
|
|
124
|
-
});
|
|
125
|
-
if (usageResp.ok) {
|
|
126
|
-
const usage = await usageResp.json();
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
}
|
|
122
|
+
await preflightQuota({ kind: "task", newTaskCount: 1 });
|
|
130
123
|
console.log(" Uploading...");
|
|
124
|
+
const apiUrl = getApiUrl();
|
|
131
125
|
const resp = await fetch(`${apiUrl}/api/cli/push-task`, {
|
|
132
126
|
method: "POST",
|
|
133
127
|
headers: {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
preflightQuota
|
|
3
|
+
} from "./chunk-2ELJH3IV.js";
|
|
1
4
|
import {
|
|
2
5
|
getApiUrl,
|
|
3
6
|
getAuthHeader
|
|
@@ -10,7 +13,7 @@ import { tmpdir } from "os";
|
|
|
10
13
|
import { randomBytes, createHash } from "crypto";
|
|
11
14
|
var SKIP = /* @__PURE__ */ new Set(["__pycache__", ".DS_Store", ".git", "node_modules"]);
|
|
12
15
|
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
13
|
-
var CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_CONCURRENCY ?? "
|
|
16
|
+
var CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_CONCURRENCY ?? "256");
|
|
14
17
|
var BATCH_SIZE = Number(process.env.TRAJECTORIES_CLI_BATCH_SIZE ?? "500");
|
|
15
18
|
var WEBP_CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_WEBP_CONCURRENCY ?? "4");
|
|
16
19
|
var HASH_CONCURRENCY = Number(process.env.TRAJECTORIES_CLI_HASH_CONCURRENCY ?? "16");
|
|
@@ -47,6 +50,48 @@ async function apiPost(path, body, timeout = 3e5) {
|
|
|
47
50
|
signal: AbortSignal.timeout(timeout)
|
|
48
51
|
});
|
|
49
52
|
}
|
|
53
|
+
async function uploadZip(zipPath, opts) {
|
|
54
|
+
const { readFileSync: readFileSync2, statSync: statSync2 } = await import("fs");
|
|
55
|
+
const { getApiUrl: _getApiUrl, getAuthHeader: _getAuthHeader } = await import("./config-4XJRECIW.js");
|
|
56
|
+
const auth = await _getAuthHeader();
|
|
57
|
+
if (!auth) throw new Error("Not authenticated. Run: trajectories auth login");
|
|
58
|
+
const fileStat = statSync2(zipPath);
|
|
59
|
+
const sizeMB = (fileStat.size / (1024 * 1024)).toFixed(1);
|
|
60
|
+
console.log(` ZIP size: ${sizeMB} MB`);
|
|
61
|
+
console.log(" Uploading to server (server-side extraction)...");
|
|
62
|
+
const fileData = readFileSync2(zipPath);
|
|
63
|
+
const formData = new FormData();
|
|
64
|
+
formData.append("file", new Blob([fileData]), zipPath.split("/").pop() ?? "upload.zip");
|
|
65
|
+
if (opts.slug) formData.append("slug", opts.slug);
|
|
66
|
+
if (opts.name) formData.append("name", opts.name);
|
|
67
|
+
formData.append("visibility", "unlisted");
|
|
68
|
+
const apiUrl = _getApiUrl();
|
|
69
|
+
const resp = await fetch(`${apiUrl}/api/cli/push/zip`, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { Authorization: auth },
|
|
72
|
+
body: formData,
|
|
73
|
+
signal: AbortSignal.timeout(36e5)
|
|
74
|
+
});
|
|
75
|
+
if (!resp.ok) {
|
|
76
|
+
let detail;
|
|
77
|
+
try {
|
|
78
|
+
const body = await resp.json();
|
|
79
|
+
detail = body.detail || JSON.stringify(body);
|
|
80
|
+
} catch {
|
|
81
|
+
detail = await resp.text();
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`ZIP upload failed (${resp.status}): ${detail}`);
|
|
84
|
+
}
|
|
85
|
+
const result = await resp.json();
|
|
86
|
+
const siteUrl = apiUrl.includes("localhost") ? "http://localhost:3000" : apiUrl.replace("api.", "").replace(/\/$/, "");
|
|
87
|
+
return result.jobs.map((j) => ({
|
|
88
|
+
jobId: j.job_id,
|
|
89
|
+
slug: j.slug,
|
|
90
|
+
nTrials: j.n_trials,
|
|
91
|
+
meanReward: j.mean_reward ?? void 0,
|
|
92
|
+
viewerUrl: `${siteUrl}/t/${j.job_id}`
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
50
95
|
async function convertToWebP(dir) {
|
|
51
96
|
const { files } = collectFiles(dir);
|
|
52
97
|
const pngs = files.filter((f) => f.relPath.toLowerCase().endsWith(".png"));
|
|
@@ -328,40 +373,11 @@ ${samples}${more}
|
|
|
328
373
|
`
|
|
329
374
|
);
|
|
330
375
|
}
|
|
376
|
+
await preflightQuota({ kind: "trajectory", projectedAddedBytes: totalSize });
|
|
331
377
|
process.stdout.write(` Hashing...\r`);
|
|
332
378
|
const t0 = Date.now();
|
|
333
379
|
const contentHash = await computeContentHash(files);
|
|
334
380
|
console.log(` Hash: ${contentHash.slice(0, 16)}... (${((Date.now() - t0) / 1e3).toFixed(1)}s)`);
|
|
335
|
-
try {
|
|
336
|
-
const auth = await getAuthHeader();
|
|
337
|
-
const usageResp = await fetch(`${getApiUrl()}/api/cli/usage`, {
|
|
338
|
-
headers: auth ? { Authorization: auth } : {},
|
|
339
|
-
signal: AbortSignal.timeout(1e4)
|
|
340
|
-
});
|
|
341
|
-
if (usageResp.status === 200) {
|
|
342
|
-
const usage = await usageResp.json();
|
|
343
|
-
if (usage.plan !== "premiere") {
|
|
344
|
-
if (usage.trajectory_count >= usage.trajectory_limit) {
|
|
345
|
-
const plan = usage.plan === "free" ? "Team" : "Premiere";
|
|
346
|
-
throw new Error(
|
|
347
|
-
`Trajectory limit reached (${usage.trajectory_count.toLocaleString()}/${usage.trajectory_limit.toLocaleString()}). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
const projectedStorage = usage.storage_bytes_used + totalSize;
|
|
351
|
-
if (projectedStorage > usage.storage_limit_bytes) {
|
|
352
|
-
const usedGB = (usage.storage_bytes_used / 1024 ** 3).toFixed(1);
|
|
353
|
-
const uploadGB = (totalSize / 1024 ** 3).toFixed(1);
|
|
354
|
-
const limitGB = (usage.storage_limit_bytes / 1024 ** 3).toFixed(0);
|
|
355
|
-
const plan = usage.plan === "free" ? "Team" : "Premiere";
|
|
356
|
-
throw new Error(
|
|
357
|
-
`Storage limit would be exceeded (${usedGB} GB used + ${uploadGB} GB upload > ${limitGB} GB limit). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
} catch (e) {
|
|
363
|
-
if (e.message?.includes("limit")) throw e;
|
|
364
|
-
}
|
|
365
381
|
const initResp = await apiPost("/api/cli/push/init", { slug, name, visibility, estimated_bytes: totalSize, content_hash: contentHash });
|
|
366
382
|
if (!initResp.ok) {
|
|
367
383
|
throw new Error(`Init failed: ${await initResp.text()}`);
|
|
@@ -514,7 +530,7 @@ async function fetchSignedUrlsForBatch(jobId, paths) {
|
|
|
514
530
|
async function uploadOneFileWithError(file, signedUrl) {
|
|
515
531
|
const data = readFileSync(file.absPath);
|
|
516
532
|
let lastError = "unknown error";
|
|
517
|
-
for (let attempt = 0; attempt <
|
|
533
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
518
534
|
try {
|
|
519
535
|
const resp = await fetch(signedUrl, {
|
|
520
536
|
method: "PUT",
|
|
@@ -529,10 +545,14 @@ async function uploadOneFileWithError(file, signedUrl) {
|
|
|
529
545
|
if (body) lastError += ` \u2014 ${body.slice(0, 150).replace(/\s+/g, " ")}`;
|
|
530
546
|
} catch {
|
|
531
547
|
}
|
|
548
|
+
if (resp.status === 503 || resp.status === 429) {
|
|
549
|
+
await sleep(2e3 * (attempt + 1));
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
532
552
|
} catch (e) {
|
|
533
553
|
lastError = `fetch error: ${e?.name ?? "Error"} ${e?.message ?? e}`;
|
|
534
554
|
}
|
|
535
|
-
if (attempt <
|
|
555
|
+
if (attempt < 4) await sleep(1e3 * (attempt + 1));
|
|
536
556
|
}
|
|
537
557
|
return { ok: false, error: lastError };
|
|
538
558
|
}
|
|
@@ -544,5 +564,6 @@ function truncate(s, n) {
|
|
|
544
564
|
}
|
|
545
565
|
export {
|
|
546
566
|
collectFiles,
|
|
547
|
-
uploadJob
|
|
567
|
+
uploadJob,
|
|
568
|
+
uploadZip
|
|
548
569
|
};
|