trajectories-sh 1.8.0 → 1.8.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.
package/dist/cli.js CHANGED
@@ -81,7 +81,7 @@ async function uploadTrajectoryAction(directory, opts) {
81
81
  Uploading trajectory ${directory} \u2192 ${chalk.cyan(slug)}
82
82
  `));
83
83
  try {
84
- const { uploadJob } = await import("./upload-NTDQN32Q.js");
84
+ const { uploadJob } = await import("./upload-JY5KCP2X.js");
85
85
  const result = await uploadJob(directory, {
86
86
  slug,
87
87
  name: opts.name,
@@ -7,7 +7,7 @@ import {
7
7
  import { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
8
8
  import { join, relative, dirname } from "path";
9
9
  import { tmpdir } from "os";
10
- import { randomBytes } from "crypto";
10
+ import { randomBytes, createHash } from "crypto";
11
11
  var SKIP = /* @__PURE__ */ new Set(["__pycache__", ".DS_Store", ".git", "node_modules"]);
12
12
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
13
13
  var CONCURRENCY = 12;
@@ -120,6 +120,15 @@ async function convertToWebP(dir) {
120
120
  }
121
121
  return { convertedDir: tmpDir, pngsConverted: converted, savedBytes };
122
122
  }
123
+ function computeContentHash(files) {
124
+ const hash = createHash("sha256");
125
+ const sorted = [...files].sort((a, b) => a.relPath.localeCompare(b.relPath));
126
+ for (const f of sorted) {
127
+ hash.update(f.relPath);
128
+ hash.update(readFileSync(f.absPath));
129
+ }
130
+ return hash.digest("hex");
131
+ }
123
132
  function hasTrialData(dir) {
124
133
  const files = collectFiles(dir);
125
134
  const hasTopResult = files.some((f) => f.relPath === "result.json");
@@ -144,7 +153,9 @@ async function uploadJob(dir, opts) {
144
153
  }
145
154
  const files = collectFiles(convertedDir);
146
155
  const totalSize = files.reduce((s, f) => s + f.size, 0);
156
+ const contentHash = computeContentHash(files);
147
157
  console.log(` ${files.length} files, ${(totalSize / 1024 / 1024).toFixed(1)} MB`);
158
+ console.log(` Hash: ${contentHash.slice(0, 16)}...`);
148
159
  try {
149
160
  const auth = await getAuthHeader();
150
161
  const usageResp = await fetch(`${getApiUrl()}/api/cli/usage`, {
@@ -153,9 +164,9 @@ async function uploadJob(dir, opts) {
153
164
  });
154
165
  if (usageResp.status === 200) {
155
166
  const usage = await usageResp.json();
156
- if (usage.plan !== "enterprise") {
167
+ if (usage.plan !== "premiere") {
157
168
  if (usage.trajectory_count >= usage.trajectory_limit) {
158
- const plan = usage.plan === "free" ? "Team" : "Enterprise";
169
+ const plan = usage.plan === "free" ? "Team" : "Premiere";
159
170
  throw new Error(
160
171
  `Trajectory limit reached (${usage.trajectory_count.toLocaleString()}/${usage.trajectory_limit.toLocaleString()}). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
161
172
  );
@@ -165,7 +176,7 @@ async function uploadJob(dir, opts) {
165
176
  const usedGB = (usage.storage_bytes_used / 1024 ** 3).toFixed(1);
166
177
  const uploadGB = (totalSize / 1024 ** 3).toFixed(1);
167
178
  const limitGB = (usage.storage_limit_bytes / 1024 ** 3).toFixed(0);
168
- const plan = usage.plan === "free" ? "Team" : "Enterprise";
179
+ const plan = usage.plan === "free" ? "Team" : "Premiere";
169
180
  throw new Error(
170
181
  `Storage limit would be exceeded (${usedGB} GB used + ${uploadGB} GB upload > ${limitGB} GB limit). Upgrade to ${plan} for more: https://trajectories.sh/pricing`
171
182
  );
@@ -175,8 +186,12 @@ async function uploadJob(dir, opts) {
175
186
  } catch (e) {
176
187
  if (e.message?.includes("limit")) throw e;
177
188
  }
178
- const initResp = await apiPost("/api/cli/push/init", { slug, name, visibility, estimated_bytes: totalSize });
189
+ const initResp = await apiPost("/api/cli/push/init", { slug, name, visibility, estimated_bytes: totalSize, content_hash: contentHash });
179
190
  if (!initResp.ok) {
191
+ if (initResp.status === 409) {
192
+ const err = await initResp.json().catch(() => ({ detail: "Duplicate trajectory" }));
193
+ throw new Error(err.detail);
194
+ }
180
195
  throw new Error(`Init failed: ${await initResp.text()}`);
181
196
  }
182
197
  const { job_id: jobId } = await initResp.json();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trajectories-sh",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "CLI for uploading trajectory jobs to trajectories.sh",
5
5
  "type": "module",
6
6
  "bin": {