trajectories-sh 1.24.1 → 1.25.1
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/{comments-M3EX3VOU.js → chunk-7FAZJWE4.js} +7 -7
- package/dist/{chunk-LOJZPDGI.js → chunk-MMQI3LNY.js} +18 -3
- package/dist/cli.js +399 -27
- package/dist/comments-3UOERFNJ.js +21 -0
- package/dist/{contracts-preview-3J2PLT3U.js → contracts-preview-3DFBYKZV.js} +13 -4
- package/dist/{download-BWJDAVMM.js → download-A3LEBXH5.js} +3 -1
- package/dist/{download-project-XN4OPF3M.js → download-project-66IARJWR.js} +1 -1
- package/dist/outputs-TID2NORZ.js +86 -0
- package/dist/{projects-END2CFCS.js → projects-DDPO354C.js} +1 -1
- package/dist/register-A5MUHXPT.js +46 -0
- package/dist/{upload-6HQ5A2H7.js → upload-OW7RWQVX.js} +29 -17
- package/package.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseJobId
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-72L6XPK6.js";
|
|
3
|
+
} from "./chunk-MMQI3LNY.js";
|
|
5
4
|
import {
|
|
6
5
|
getApiUrl,
|
|
7
6
|
getAuthHeader
|
|
@@ -159,12 +158,13 @@ function formatComment(comment) {
|
|
|
159
158
|
return `[${comment.id}] ${author} \xB7 ${scope}${when ? ` \xB7 ${when}` : ""}
|
|
160
159
|
${comment.body}`;
|
|
161
160
|
}
|
|
161
|
+
|
|
162
162
|
export {
|
|
163
|
-
addComment,
|
|
164
|
-
deleteComment,
|
|
165
|
-
formatComment,
|
|
166
|
-
listComments,
|
|
167
163
|
parseCommentTarget,
|
|
168
164
|
readCommentBody,
|
|
169
|
-
|
|
165
|
+
addComment,
|
|
166
|
+
listComments,
|
|
167
|
+
updateComment,
|
|
168
|
+
deleteComment,
|
|
169
|
+
formatComment
|
|
170
170
|
};
|
|
@@ -15,6 +15,16 @@ var UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
|
15
15
|
var BATCH_SIZE = 100;
|
|
16
16
|
var CONCURRENCY = 25;
|
|
17
17
|
var DOWNLOAD_TIMEOUT_MS = 18e4;
|
|
18
|
+
var MAX_ATTEMPTS = 7;
|
|
19
|
+
function retryDelayMs(attempt, status = 0, retryAfter = null) {
|
|
20
|
+
if (retryAfter) {
|
|
21
|
+
const secs = Number(retryAfter);
|
|
22
|
+
const ms = Number.isFinite(secs) ? secs * 1e3 : Date.parse(retryAfter) - Date.now();
|
|
23
|
+
if (Number.isFinite(ms) && ms > 0) return Math.min(ms, 3e4);
|
|
24
|
+
}
|
|
25
|
+
const base = Math.min(16e3, 500 * 2 ** attempt) * (status === 429 ? 2 : 1);
|
|
26
|
+
return Math.round(base / 2 + Math.random() * (base / 2));
|
|
27
|
+
}
|
|
18
28
|
var EXIT = {
|
|
19
29
|
OK: 0,
|
|
20
30
|
ERROR: 1,
|
|
@@ -154,10 +164,14 @@ async function downloadPlannedJob(plan, outputDir, options) {
|
|
|
154
164
|
const downloadUrl = signedUrl ?? `${apiUrl}/api/cli/download/${jobId}/file?path=${encodeURIComponent(file.path)}`;
|
|
155
165
|
const downloadHeaders = signedUrl ? {} : headers;
|
|
156
166
|
let ok = false;
|
|
157
|
-
for (let attempt = 0; attempt <
|
|
167
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS && !ok; attempt++) {
|
|
168
|
+
let wait = 0;
|
|
158
169
|
try {
|
|
159
170
|
const resp = await fetch(downloadUrl, { headers: downloadHeaders, signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
160
|
-
if (!resp.ok)
|
|
171
|
+
if (!resp.ok) {
|
|
172
|
+
wait = retryDelayMs(attempt, resp.status, resp.headers.get("retry-after"));
|
|
173
|
+
throw new Error(`HTTP ${resp.status}`);
|
|
174
|
+
}
|
|
161
175
|
const data = Buffer.from(await resp.arrayBuffer());
|
|
162
176
|
const outPath = join(outputDir, file.path);
|
|
163
177
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
@@ -167,7 +181,7 @@ async function downloadPlannedJob(plan, outputDir, options) {
|
|
|
167
181
|
bar.tick(data.length);
|
|
168
182
|
ok = true;
|
|
169
183
|
} catch {
|
|
170
|
-
if (attempt <
|
|
184
|
+
if (attempt < MAX_ATTEMPTS - 1) await new Promise((r) => setTimeout(r, wait || retryDelayMs(attempt)));
|
|
171
185
|
}
|
|
172
186
|
}
|
|
173
187
|
if (!ok) {
|
|
@@ -229,6 +243,7 @@ function dirBytes(dir) {
|
|
|
229
243
|
|
|
230
244
|
export {
|
|
231
245
|
DOWNLOAD_TIMEOUT_MS,
|
|
246
|
+
retryDelayMs,
|
|
232
247
|
EXIT,
|
|
233
248
|
CliError,
|
|
234
249
|
httpError,
|
package/dist/cli.js
CHANGED
|
@@ -99,7 +99,7 @@ async function contractRequest(path, init = {}) {
|
|
|
99
99
|
signal: AbortSignal.timeout(12e4)
|
|
100
100
|
});
|
|
101
101
|
if (!response.ok) {
|
|
102
|
-
const body = await response.json().catch(() => ({ detail: "
|
|
102
|
+
const body = await response.json().catch(() => ({ detail: "Agreement request failed" }));
|
|
103
103
|
throw new ContractError(response.status, body.detail);
|
|
104
104
|
}
|
|
105
105
|
if (response.headers.get("content-type")?.includes("application/pdf"))
|
|
@@ -110,8 +110,8 @@ function errorExit(status) {
|
|
|
110
110
|
return status === 401 ? 3 : status === 403 ? 4 : status === 409 ? 5 : status === 422 ? 2 : 1;
|
|
111
111
|
}
|
|
112
112
|
function registerContracts(program2) {
|
|
113
|
-
const group = program2.command("contracts").description(
|
|
114
|
-
"Draft, issue and manage private Refresh
|
|
113
|
+
const group = program2.command("agreements").alias("contracts").description(
|
|
114
|
+
"Draft, issue and manage private Refresh agreements. Never signs for a recipient."
|
|
115
115
|
);
|
|
116
116
|
const wrap = (fn) => async (...args) => {
|
|
117
117
|
try {
|
|
@@ -137,7 +137,7 @@ function registerContracts(program2) {
|
|
|
137
137
|
).requiredOption("--file <path>").requiredOption("--output <path>")
|
|
138
138
|
).action(
|
|
139
139
|
wrap(async (opts) => {
|
|
140
|
-
const { previewContractFile } = await import("./contracts-preview-
|
|
140
|
+
const { previewContractFile } = await import("./contracts-preview-3DFBYKZV.js");
|
|
141
141
|
return previewContractFile(opts.file, opts.output);
|
|
142
142
|
})
|
|
143
143
|
);
|
|
@@ -267,6 +267,330 @@ function registerContracts(program2) {
|
|
|
267
267
|
);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
// src/assets.ts
|
|
271
|
+
import { createHash, randomUUID } from "crypto";
|
|
272
|
+
import { createReadStream, createWriteStream } from "fs";
|
|
273
|
+
import { open, readFile as readFile2, stat, unlink } from "fs/promises";
|
|
274
|
+
import { basename, resolve } from "path";
|
|
275
|
+
import { Readable, Transform } from "stream";
|
|
276
|
+
import { pipeline } from "stream/promises";
|
|
277
|
+
var AssetError = class extends Error {
|
|
278
|
+
constructor(message, status = 0) {
|
|
279
|
+
super(message);
|
|
280
|
+
this.status = status;
|
|
281
|
+
}
|
|
282
|
+
status;
|
|
283
|
+
};
|
|
284
|
+
function uuid(value) {
|
|
285
|
+
if (!/^[a-f\d]{8}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{4}-[a-f\d]{12}$/i.test(value))
|
|
286
|
+
throw new Error(`Expected a stable UUID, received: ${value}`);
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
async function assetRequest(path, init = {}) {
|
|
290
|
+
const authorization = await getAuthHeader();
|
|
291
|
+
if (!authorization)
|
|
292
|
+
throw new AssetError(
|
|
293
|
+
"Authentication required. Run trajectories auth login or set TRAJECTORIES_API_KEY.",
|
|
294
|
+
401
|
|
295
|
+
);
|
|
296
|
+
const response = await fetch(`${getApiUrl()}/api/assets${path}`, {
|
|
297
|
+
...init,
|
|
298
|
+
signal: init.signal || AbortSignal.timeout(18e4),
|
|
299
|
+
headers: {
|
|
300
|
+
Authorization: authorization,
|
|
301
|
+
...typeof init.body === "string" ? { "Content-Type": "application/json" } : {},
|
|
302
|
+
...init.headers
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
if (!response.ok) {
|
|
306
|
+
const payload = await response.json().catch(() => ({}));
|
|
307
|
+
throw new AssetError(
|
|
308
|
+
typeof payload.detail === "string" ? payload.detail : `Asset API returned ${response.status}; inspect input fields and retry.`,
|
|
309
|
+
response.status
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return response.json();
|
|
313
|
+
}
|
|
314
|
+
async function retry(fn, delay = 500) {
|
|
315
|
+
for (let attempt = 0; ; attempt++) {
|
|
316
|
+
try {
|
|
317
|
+
return await fn();
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (attempt >= 3 || error instanceof AssetError && error.status < 500 && error.status !== 429)
|
|
320
|
+
throw error;
|
|
321
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay * 2 ** attempt));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function hashFile(path) {
|
|
326
|
+
const digest = createHash("sha256");
|
|
327
|
+
for await (const data of createReadStream(path)) digest.update(data);
|
|
328
|
+
return digest.digest("hex");
|
|
329
|
+
}
|
|
330
|
+
async function uploadAsset(asset, path, options, report = () => {
|
|
331
|
+
}) {
|
|
332
|
+
uuid(asset);
|
|
333
|
+
const file = await stat(path);
|
|
334
|
+
if (!file.isFile() || !path.endsWith(".zip") || file.size === 0)
|
|
335
|
+
throw new Error(
|
|
336
|
+
"Upload a non-empty .zip file; directories are not executed or packaged automatically."
|
|
337
|
+
);
|
|
338
|
+
const config = await assetRequest("/config");
|
|
339
|
+
if (file.size > config.max_bytes)
|
|
340
|
+
throw new Error(
|
|
341
|
+
`ZIP exceeds the server limit (${config.max_bytes} bytes). Split it or contact your administrator.`
|
|
342
|
+
);
|
|
343
|
+
const sha256 = await hashFile(path);
|
|
344
|
+
const role = options.role || "bundle";
|
|
345
|
+
if (!["bundle", "sample"].includes(role))
|
|
346
|
+
throw new Error("--role must be bundle or sample");
|
|
347
|
+
const key = options.idempotencyKey ? uuid(options.idempotencyKey) : randomUUID();
|
|
348
|
+
report({
|
|
349
|
+
event: "upload.start",
|
|
350
|
+
asset_id: asset,
|
|
351
|
+
idempotency_key: key,
|
|
352
|
+
sha256
|
|
353
|
+
});
|
|
354
|
+
let version2 = options.version ? await assetRequest(`/${asset}/versions/${uuid(options.version)}`) : await retry(
|
|
355
|
+
() => assetRequest(`/${asset}/versions`, {
|
|
356
|
+
method: "POST",
|
|
357
|
+
headers: { "Idempotency-Key": key },
|
|
358
|
+
body: JSON.stringify({
|
|
359
|
+
role,
|
|
360
|
+
filename: basename(path),
|
|
361
|
+
size: file.size,
|
|
362
|
+
sha256
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
report({
|
|
367
|
+
event: "upload.resume",
|
|
368
|
+
asset_id: asset,
|
|
369
|
+
version_id: version2.id,
|
|
370
|
+
command: `trajectories assets upload ${asset} ${JSON.stringify(path)} --role ${role} --version ${version2.id} --json`
|
|
371
|
+
});
|
|
372
|
+
if (version2.sha256 !== sha256 || version2.size !== file.size || version2.role !== role)
|
|
373
|
+
throw new Error(
|
|
374
|
+
"Resume file does not match original size, role or SHA-256; use the unchanged ZIP."
|
|
375
|
+
);
|
|
376
|
+
if (version2.status === "ready") return version2;
|
|
377
|
+
if (version2.status === "expired")
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Upload expired; start another upload without --version and with a new idempotency key."
|
|
380
|
+
);
|
|
381
|
+
version2 = await assetRequest(`/${asset}/versions/${version2.id}`);
|
|
382
|
+
const completed = new Set(version2.parts?.map((p) => p.part));
|
|
383
|
+
const count = Math.ceil(file.size / version2.chunk_bytes);
|
|
384
|
+
const handle = await open(path, "r");
|
|
385
|
+
try {
|
|
386
|
+
for (let part = 0; part < count; part++) {
|
|
387
|
+
if (!completed.has(part)) {
|
|
388
|
+
const data = Buffer.alloc(
|
|
389
|
+
Math.min(version2.chunk_bytes, file.size - part * version2.chunk_bytes)
|
|
390
|
+
);
|
|
391
|
+
const read = await handle.read(
|
|
392
|
+
data,
|
|
393
|
+
0,
|
|
394
|
+
data.length,
|
|
395
|
+
part * version2.chunk_bytes
|
|
396
|
+
);
|
|
397
|
+
if (read.bytesRead !== data.length)
|
|
398
|
+
throw new Error(
|
|
399
|
+
"File changed while uploading. Restore the original file before resuming."
|
|
400
|
+
);
|
|
401
|
+
const checksum = createHash("sha256").update(data).digest("hex");
|
|
402
|
+
await retry(
|
|
403
|
+
() => assetRequest(`/${asset}/versions/${version2.id}/parts/${part}`, {
|
|
404
|
+
method: "PUT",
|
|
405
|
+
headers: {
|
|
406
|
+
"Content-Type": "application/octet-stream",
|
|
407
|
+
"X-Content-SHA256": checksum
|
|
408
|
+
},
|
|
409
|
+
body: data
|
|
410
|
+
})
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
report({
|
|
414
|
+
event: "upload.progress",
|
|
415
|
+
version_id: version2.id,
|
|
416
|
+
completed_parts: part + 1,
|
|
417
|
+
total_parts: count
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
} finally {
|
|
421
|
+
await handle.close();
|
|
422
|
+
}
|
|
423
|
+
return retry(
|
|
424
|
+
() => assetRequest(`/${asset}/versions/${version2.id}/complete`, {
|
|
425
|
+
method: "POST"
|
|
426
|
+
})
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
async function downloadAsset(asset, version2, output2) {
|
|
430
|
+
uuid(asset);
|
|
431
|
+
uuid(version2);
|
|
432
|
+
const result = await assetRequest(`/${asset}/versions/${version2}/download`, { method: "POST" });
|
|
433
|
+
const response = await fetch(result.url, {
|
|
434
|
+
signal: AbortSignal.timeout(18e4)
|
|
435
|
+
});
|
|
436
|
+
if (!response.ok || !response.body)
|
|
437
|
+
throw new Error(
|
|
438
|
+
`Download failed (${response.status}); retry to obtain a new short-lived link.`
|
|
439
|
+
);
|
|
440
|
+
const path = resolve(output2);
|
|
441
|
+
const digest = createHash("sha256");
|
|
442
|
+
let size = 0;
|
|
443
|
+
let created = false;
|
|
444
|
+
const handle = await open(path, "wx", 384);
|
|
445
|
+
created = true;
|
|
446
|
+
try {
|
|
447
|
+
const checker = new Transform({
|
|
448
|
+
transform(chunk, _encoding, callback) {
|
|
449
|
+
size += chunk.length;
|
|
450
|
+
if (size > result.size) {
|
|
451
|
+
callback(new Error("Download exceeds manifest size"));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
digest.update(chunk);
|
|
455
|
+
callback(null, chunk);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
await pipeline(
|
|
459
|
+
Readable.fromWeb(response.body),
|
|
460
|
+
checker,
|
|
461
|
+
createWriteStream(path, { fd: handle.fd, autoClose: false })
|
|
462
|
+
);
|
|
463
|
+
if (size !== result.size || digest.digest("hex") !== result.sha256)
|
|
464
|
+
throw new Error(
|
|
465
|
+
"Download checksum mismatch. Incomplete output removed; retry."
|
|
466
|
+
);
|
|
467
|
+
return {
|
|
468
|
+
asset_id: asset,
|
|
469
|
+
version_id: version2,
|
|
470
|
+
path,
|
|
471
|
+
size,
|
|
472
|
+
sha256: result.sha256
|
|
473
|
+
};
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (created) await unlink(path).catch(() => {
|
|
476
|
+
});
|
|
477
|
+
throw error;
|
|
478
|
+
} finally {
|
|
479
|
+
await handle.close();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function action(fn) {
|
|
483
|
+
return async (...args) => {
|
|
484
|
+
const options = args.at(-1)?.opts?.() || {};
|
|
485
|
+
try {
|
|
486
|
+
const result = await fn(...args);
|
|
487
|
+
console.log(JSON.stringify(result, null, options.json ? void 0 : 2));
|
|
488
|
+
} catch (error) {
|
|
489
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
490
|
+
const status = error instanceof AssetError ? error.status : 0;
|
|
491
|
+
console.error(
|
|
492
|
+
options.json ? JSON.stringify({
|
|
493
|
+
error: {
|
|
494
|
+
message,
|
|
495
|
+
status,
|
|
496
|
+
retryable: status >= 500 || status === 429
|
|
497
|
+
}
|
|
498
|
+
}) : message
|
|
499
|
+
);
|
|
500
|
+
process.exitCode = status === 401 || status === 403 ? 3 : status === 404 ? 4 : 1;
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function registerAssets(program2) {
|
|
505
|
+
const assets = program2.command("assets").description(
|
|
506
|
+
"Private organization asset library; UI and CLI share one API"
|
|
507
|
+
);
|
|
508
|
+
assets.command("config").description("Inspect quotas, organization and staff access").option("--json", "Machine-readable JSON").action(action(() => assetRequest("/config")));
|
|
509
|
+
assets.command("list").description(
|
|
510
|
+
"List/filter one cursor page (staff inventory requires explicit access)"
|
|
511
|
+
).option("--query <text>").option("--kind <type>").option("--tag <tag>").option("--archived").option("--cursor <uuid>").option("--limit <count>", "Page size, 1\u2013100", "30").option("--staff", "Cross-organization staff inventory").option("--json").action(
|
|
512
|
+
action(async (options) => {
|
|
513
|
+
const params = new URLSearchParams({
|
|
514
|
+
status: options.archived ? "archived" : "active",
|
|
515
|
+
limit: options.limit
|
|
516
|
+
});
|
|
517
|
+
for (const [key, value] of Object.entries({
|
|
518
|
+
q: options.query,
|
|
519
|
+
kind: options.kind,
|
|
520
|
+
tag: options.tag,
|
|
521
|
+
cursor: options.cursor
|
|
522
|
+
}))
|
|
523
|
+
if (value) params.set(key, String(value));
|
|
524
|
+
return assetRequest(
|
|
525
|
+
`${options.staff ? "/staff/inventory" : ""}?${params}`
|
|
526
|
+
);
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
assets.command("create").description(
|
|
530
|
+
"Create metadata from a JSON file; authorized Refresh staff may review uploaded material"
|
|
531
|
+
).requiredOption("--metadata <file>").option("--idempotency-key <uuid>").option("--json").action(
|
|
532
|
+
action(async (options) => {
|
|
533
|
+
const payload = JSON.parse(await readFile2(options.metadata, "utf8"));
|
|
534
|
+
const key = options.idempotencyKey ? uuid(options.idempotencyKey) : randomUUID();
|
|
535
|
+
return retry(
|
|
536
|
+
() => assetRequest("", {
|
|
537
|
+
method: "POST",
|
|
538
|
+
headers: { "Idempotency-Key": key },
|
|
539
|
+
body: JSON.stringify(payload)
|
|
540
|
+
})
|
|
541
|
+
);
|
|
542
|
+
})
|
|
543
|
+
);
|
|
544
|
+
assets.command("inspect <asset-id>").option("--json").action(action((id) => assetRequest(`/${uuid(id)}`)));
|
|
545
|
+
assets.command("update <asset-id>").description("Replace editable metadata, preserving immutable versions").requiredOption("--metadata <file>").option("--json").action(
|
|
546
|
+
action(
|
|
547
|
+
async (id, options) => assetRequest(`/${uuid(id)}`, {
|
|
548
|
+
method: "PUT",
|
|
549
|
+
body: JSON.stringify(
|
|
550
|
+
JSON.parse(await readFile2(options.metadata, "utf8"))
|
|
551
|
+
)
|
|
552
|
+
})
|
|
553
|
+
)
|
|
554
|
+
);
|
|
555
|
+
assets.command("upload <asset-id> <zip>").description(
|
|
556
|
+
"Upload or resume a verified ZIP; progress/resume information goes to stderr"
|
|
557
|
+
).option("--role <role>", "bundle or sample", "bundle").option("--version <uuid>", "Resume an existing upload").option("--idempotency-key <uuid>").option("--json").action(
|
|
558
|
+
action(
|
|
559
|
+
(id, path, options) => uploadAsset(
|
|
560
|
+
id,
|
|
561
|
+
path,
|
|
562
|
+
options,
|
|
563
|
+
(event) => console.error(JSON.stringify(event))
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
);
|
|
567
|
+
assets.command("status <asset-id> <version-id>").description(
|
|
568
|
+
"Inspect upload parts, status, checksum and published file manifest"
|
|
569
|
+
).option("--json").action(
|
|
570
|
+
action(
|
|
571
|
+
(id, version2) => assetRequest(`/${uuid(id)}/versions/${uuid(version2)}`)
|
|
572
|
+
)
|
|
573
|
+
);
|
|
574
|
+
assets.command("download <asset-id> <version-id>").description(
|
|
575
|
+
"Download a version with SHA-256 verification; never overwrites a file"
|
|
576
|
+
).requiredOption("--out <file>").option("--json").action(
|
|
577
|
+
action((id, version2, options) => downloadAsset(id, version2, options.out))
|
|
578
|
+
);
|
|
579
|
+
assets.command("archive <asset-id>").option("--json").action(
|
|
580
|
+
action((id) => assetRequest(`/${uuid(id)}/archive`, { method: "POST" }))
|
|
581
|
+
);
|
|
582
|
+
assets.command("delete <asset-id>").description("Permanently revoke access and clean stored files").requiredOption(
|
|
583
|
+
"--confirm <asset-id>",
|
|
584
|
+
"Repeat exact asset ID to confirm irreversible deletion"
|
|
585
|
+
).option("--json").action(
|
|
586
|
+
action((id, options) => {
|
|
587
|
+
if (options.confirm !== id)
|
|
588
|
+
throw new Error("--confirm must exactly match the asset ID");
|
|
589
|
+
return assetRequest(`/${uuid(id)}`, { method: "DELETE" });
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
270
594
|
// src/cli.ts
|
|
271
595
|
import { readFileSync } from "fs";
|
|
272
596
|
import { fileURLToPath } from "url";
|
|
@@ -384,7 +708,7 @@ async function uploadTrajectoryAction(pathArg, opts) {
|
|
|
384
708
|
);
|
|
385
709
|
}
|
|
386
710
|
if (isZip) {
|
|
387
|
-
const { uploadZip } = await import("./upload-
|
|
711
|
+
const { uploadZip } = await import("./upload-OW7RWQVX.js");
|
|
388
712
|
const slug2 = opts.slug ?? pathArg.replace(/\.zip$/i, "").replace(/\/+$/, "").split("/").pop();
|
|
389
713
|
console.log(chalk.bold(`
|
|
390
714
|
Uploading ZIP ${pathArg} \u2192 ${chalk.cyan(slug2)}
|
|
@@ -413,7 +737,7 @@ async function uploadTrajectoryAction(pathArg, opts) {
|
|
|
413
737
|
Uploading trajectory ${pathArg} \u2192 ${chalk.cyan(slug)}
|
|
414
738
|
`));
|
|
415
739
|
try {
|
|
416
|
-
const { uploadJob } = await import("./upload-
|
|
740
|
+
const { uploadJob } = await import("./upload-OW7RWQVX.js");
|
|
417
741
|
const result = await uploadJob(pathArg, {
|
|
418
742
|
slug,
|
|
419
743
|
name: opts.name
|
|
@@ -501,7 +825,7 @@ async function downloadTrajectoryAction(url, opts) {
|
|
|
501
825
|
const chalk = (await import("chalk")).default;
|
|
502
826
|
const json = Boolean(opts.json);
|
|
503
827
|
try {
|
|
504
|
-
const { planJob, downloadPlannedJob, parseJobId } = await import("./download-
|
|
828
|
+
const { planJob, downloadPlannedJob, parseJobId } = await import("./download-A3LEBXH5.js");
|
|
505
829
|
const options = downloadOptions(opts, true);
|
|
506
830
|
const plan = await planJob(url, options);
|
|
507
831
|
const outputDir = opts.out ?? opts.output ?? parseJobId(url).replace(/^org:/, "").replace(/:/g, "-");
|
|
@@ -539,7 +863,7 @@ async function downloadProjectAction(url, opts) {
|
|
|
539
863
|
const chalk = (await import("chalk")).default;
|
|
540
864
|
const json = Boolean(opts.json);
|
|
541
865
|
try {
|
|
542
|
-
const { planProject, downloadProject, defaultProjectDir } = await import("./download-project-
|
|
866
|
+
const { planProject, downloadProject, defaultProjectDir } = await import("./download-project-66IARJWR.js");
|
|
543
867
|
const options = { ...downloadOptions(opts, false), format: opts.format === "jsonl" ? "jsonl" : "json" };
|
|
544
868
|
const plan = await planProject(url, options);
|
|
545
869
|
const outputDir = opts.out ?? defaultProjectDir(plan.project);
|
|
@@ -585,7 +909,7 @@ async function downloadProjectAction(url, opts) {
|
|
|
585
909
|
}
|
|
586
910
|
async function deleteTrajectoryAction(url, opts) {
|
|
587
911
|
const chalk = (await import("chalk")).default;
|
|
588
|
-
const { parseJobId } = await import("./download-
|
|
912
|
+
const { parseJobId } = await import("./download-A3LEBXH5.js");
|
|
589
913
|
const { getApiUrl: getApiUrl2, getAuthHeader: getAuthHeader2 } = await import("./config-QS2IT5DI.js");
|
|
590
914
|
let jobId = parseJobId(url);
|
|
591
915
|
if (jobId.startsWith("org:")) {
|
|
@@ -616,8 +940,8 @@ async function deleteTrajectoryAction(url, opts) {
|
|
|
616
940
|
if (!opts.yes) {
|
|
617
941
|
const readline = await import("readline");
|
|
618
942
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
619
|
-
const answer = await new Promise((
|
|
620
|
-
rl.question(chalk.yellow(" Are you sure? (y/N) "),
|
|
943
|
+
const answer = await new Promise((resolve2) => {
|
|
944
|
+
rl.question(chalk.yellow(" Are you sure? (y/N) "), resolve2);
|
|
621
945
|
});
|
|
622
946
|
rl.close();
|
|
623
947
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -653,7 +977,7 @@ function printCommentError(error, json) {
|
|
|
653
977
|
}
|
|
654
978
|
async function addCommentAction(targetInput, opts) {
|
|
655
979
|
try {
|
|
656
|
-
const { addComment, formatComment, parseCommentTarget, readCommentBody } = await import("./comments-
|
|
980
|
+
const { addComment, formatComment, parseCommentTarget, readCommentBody } = await import("./comments-3UOERFNJ.js");
|
|
657
981
|
const target = parseCommentTarget(targetInput, opts);
|
|
658
982
|
const body = await readCommentBody(opts);
|
|
659
983
|
const comment2 = await addComment(target, body);
|
|
@@ -669,7 +993,7 @@ ${formatComment(comment2)}
|
|
|
669
993
|
}
|
|
670
994
|
async function listCommentsAction(targetInput, opts) {
|
|
671
995
|
try {
|
|
672
|
-
const { formatComment, listComments, parseCommentTarget } = await import("./comments-
|
|
996
|
+
const { formatComment, listComments, parseCommentTarget } = await import("./comments-3UOERFNJ.js");
|
|
673
997
|
const target = parseCommentTarget(targetInput, opts);
|
|
674
998
|
const comments = await listComments(target);
|
|
675
999
|
if (opts.json) {
|
|
@@ -687,7 +1011,7 @@ ${comments.map(formatComment).join("\n\n")}
|
|
|
687
1011
|
}
|
|
688
1012
|
async function updateCommentAction(commentId, opts) {
|
|
689
1013
|
try {
|
|
690
|
-
const { formatComment, readCommentBody, updateComment } = await import("./comments-
|
|
1014
|
+
const { formatComment, readCommentBody, updateComment } = await import("./comments-3UOERFNJ.js");
|
|
691
1015
|
const body = await readCommentBody(opts);
|
|
692
1016
|
const comment2 = await updateComment(commentId, body);
|
|
693
1017
|
if (opts.json) console.log(JSON.stringify(comment2));
|
|
@@ -708,8 +1032,8 @@ async function deleteCommentAction(commentId, opts) {
|
|
|
708
1032
|
}
|
|
709
1033
|
const readline = await import("readline");
|
|
710
1034
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
711
|
-
const answer = await new Promise((
|
|
712
|
-
rl.question(` Delete comment ${commentId}? (y/N) `,
|
|
1035
|
+
const answer = await new Promise((resolve2) => {
|
|
1036
|
+
rl.question(` Delete comment ${commentId}? (y/N) `, resolve2);
|
|
713
1037
|
});
|
|
714
1038
|
rl.close();
|
|
715
1039
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -718,7 +1042,7 @@ async function deleteCommentAction(commentId, opts) {
|
|
|
718
1042
|
return;
|
|
719
1043
|
}
|
|
720
1044
|
}
|
|
721
|
-
const { deleteComment } = await import("./comments-
|
|
1045
|
+
const { deleteComment } = await import("./comments-3UOERFNJ.js");
|
|
722
1046
|
await deleteComment(commentId);
|
|
723
1047
|
const result = { status: "deleted", comment_id: commentId };
|
|
724
1048
|
if (opts.json) console.log(JSON.stringify(result));
|
|
@@ -750,6 +1074,45 @@ Example:
|
|
|
750
1074
|
).action(downloadTrajectoryAction);
|
|
751
1075
|
var del = program.command("delete").description("Delete trajectories");
|
|
752
1076
|
del.command("trajectory <url>").description("Delete a trajectory job from trajectories.sh").option("-y, --yes", "Skip confirmation prompt").action(deleteTrajectoryAction);
|
|
1077
|
+
var output = program.command("output").description("Attach agent output images/videos to trajectories");
|
|
1078
|
+
output.command("upload <target> <files...>").description("Upload output media (50 MiB per file; target is a job URL or UUID)").option("-t, --trial <trial>", "Attach to this trial UUID or name (or use ?trial= in the URL)").option("--json", "Print results as JSON, including successes if a later file fails").action(async (targetInput, files, opts) => {
|
|
1079
|
+
const uploaded = [];
|
|
1080
|
+
try {
|
|
1081
|
+
const { parseOutputTarget, validateOutputFile, uploadOutput } = await import("./outputs-TID2NORZ.js");
|
|
1082
|
+
const target = parseOutputTarget(targetInput, opts.trial);
|
|
1083
|
+
if (files.length > 24) throw new Error("Upload at most 24 files per trajectory");
|
|
1084
|
+
files.forEach(validateOutputFile);
|
|
1085
|
+
for (const file of files) {
|
|
1086
|
+
const result = await uploadOutput(target, file);
|
|
1087
|
+
uploaded.push(result);
|
|
1088
|
+
if (!opts.json) console.log(` \u2713 ${result.filename} attached (${result.id})`);
|
|
1089
|
+
}
|
|
1090
|
+
if (opts.json) console.log(JSON.stringify({ uploaded }));
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
if (opts.json) console.error(JSON.stringify({ uploaded, error: { message: error.message, status: error.status ?? null } }));
|
|
1093
|
+
else console.error(` \u2717 ${error.message}
|
|
1094
|
+
${uploaded.length} file(s) attached. Check output list before retrying.`);
|
|
1095
|
+
process.exitCode = 1;
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
output.command("list <target>").option("-t, --trial <trial>", "Trial UUID or name").option("--json", "Print machine-readable JSON").action(async (input, opts) => {
|
|
1099
|
+
try {
|
|
1100
|
+
const { listOutputs, parseOutputTarget } = await import("./outputs-TID2NORZ.js");
|
|
1101
|
+
const rows = await listOutputs(parseOutputTarget(input, opts.trial));
|
|
1102
|
+
console.log(opts.json ? JSON.stringify(rows) : rows.map((r) => `${r.id} ${r.filename} ${r.media_type}`).join("\n") || "No outputs attached.");
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
printCommentError(error, Boolean(opts.json));
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
output.command("delete <target> <output-id>").description("Remove an output attachment from a trajectory in your organization").option("--json", "Print machine-readable JSON").action(async (input, id, opts) => {
|
|
1108
|
+
try {
|
|
1109
|
+
const { deleteOutput, parseOutputTarget } = await import("./outputs-TID2NORZ.js");
|
|
1110
|
+
const result = await deleteOutput(parseOutputTarget(input), id);
|
|
1111
|
+
console.log(opts.json ? JSON.stringify(result) : "Output deleted.");
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
printCommentError(error, Boolean(opts.json));
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
753
1116
|
var comment = program.command("comment").description("Manage trajectory comments");
|
|
754
1117
|
function addCommentTargetOptions(command) {
|
|
755
1118
|
return command.option("-t, --trial <trial>", "Trial UUID or trial name").option("-s, --step <step>", "ATIF step ID (not an array index)").option("--phase <phase>", "Step phase: action or result").option("--json", "Print machine-readable JSON");
|
|
@@ -768,13 +1131,13 @@ addCommentBodyOptions(
|
|
|
768
1131
|
).action(updateCommentAction);
|
|
769
1132
|
comment.command("delete <comment-id>").description("Delete one of your comments").option("-y, --yes", "Skip confirmation prompt").option("--json", "Print machine-readable JSON").action(deleteCommentAction);
|
|
770
1133
|
async function resolveProject(ref) {
|
|
771
|
-
const { listProjects, resolveProjectRef } = await import("./projects-
|
|
1134
|
+
const { listProjects, resolveProjectRef } = await import("./projects-DDPO354C.js");
|
|
772
1135
|
const projects = await listProjects();
|
|
773
1136
|
return { ...resolveProjectRef(ref, projects), projects };
|
|
774
1137
|
}
|
|
775
1138
|
async function listProjectsAction(opts) {
|
|
776
1139
|
try {
|
|
777
|
-
const { listProjects } = await import("./projects-
|
|
1140
|
+
const { listProjects } = await import("./projects-DDPO354C.js");
|
|
778
1141
|
const projects = await listProjects();
|
|
779
1142
|
if (opts.json) {
|
|
780
1143
|
console.log(JSON.stringify(projects, null, 2));
|
|
@@ -804,7 +1167,7 @@ async function listProjectsAction(opts) {
|
|
|
804
1167
|
}
|
|
805
1168
|
async function showProjectAction(ref, opts) {
|
|
806
1169
|
try {
|
|
807
|
-
const { getProject } = await import("./projects-
|
|
1170
|
+
const { getProject } = await import("./projects-DDPO354C.js");
|
|
808
1171
|
const { id } = await resolveProject(ref);
|
|
809
1172
|
const project2 = await getProject(id);
|
|
810
1173
|
if (opts.json) {
|
|
@@ -833,7 +1196,7 @@ async function showProjectAction(ref, opts) {
|
|
|
833
1196
|
}
|
|
834
1197
|
async function createProjectAction(name, opts) {
|
|
835
1198
|
try {
|
|
836
|
-
const { createProject } = await import("./projects-
|
|
1199
|
+
const { createProject } = await import("./projects-DDPO354C.js");
|
|
837
1200
|
const project2 = await createProject({
|
|
838
1201
|
name,
|
|
839
1202
|
description: opts.description,
|
|
@@ -864,7 +1227,7 @@ async function updateProjectAction(ref, opts) {
|
|
|
864
1227
|
if (Object.keys(patch).length === 0) {
|
|
865
1228
|
throw new Error("Nothing to update. Pass --name, --description, --color, --slug or --visibility");
|
|
866
1229
|
}
|
|
867
|
-
const { updateProject } = await import("./projects-
|
|
1230
|
+
const { updateProject } = await import("./projects-DDPO354C.js");
|
|
868
1231
|
const { id } = await resolveProject(ref);
|
|
869
1232
|
const project2 = await updateProject(id, patch);
|
|
870
1233
|
if (opts.json) console.log(JSON.stringify(project2));
|
|
@@ -877,7 +1240,7 @@ async function updateProjectAction(ref, opts) {
|
|
|
877
1240
|
}
|
|
878
1241
|
async function deleteProjectAction(ref, opts) {
|
|
879
1242
|
try {
|
|
880
|
-
const { deleteProject } = await import("./projects-
|
|
1243
|
+
const { deleteProject } = await import("./projects-DDPO354C.js");
|
|
881
1244
|
const { id, project: project2 } = await resolveProject(ref);
|
|
882
1245
|
const label = project2?.name ? `"${project2.name}"` : id;
|
|
883
1246
|
if (!opts.yes) {
|
|
@@ -886,10 +1249,10 @@ async function deleteProjectAction(ref, opts) {
|
|
|
886
1249
|
}
|
|
887
1250
|
const readline = await import("readline");
|
|
888
1251
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
889
|
-
const answer = await new Promise((
|
|
1252
|
+
const answer = await new Promise((resolve2) => {
|
|
890
1253
|
rl.question(
|
|
891
1254
|
` Delete project ${label}? Its trajectory sets are ungrouped, not deleted. (y/N) `,
|
|
892
|
-
|
|
1255
|
+
resolve2
|
|
893
1256
|
);
|
|
894
1257
|
});
|
|
895
1258
|
rl.close();
|
|
@@ -908,7 +1271,7 @@ async function deleteProjectAction(ref, opts) {
|
|
|
908
1271
|
}
|
|
909
1272
|
async function projectAddAction(ref, sets, opts) {
|
|
910
1273
|
try {
|
|
911
|
-
const { assignJobs } = await import("./projects-
|
|
1274
|
+
const { assignJobs } = await import("./projects-DDPO354C.js");
|
|
912
1275
|
const { id, project: project2 } = await resolveProject(ref);
|
|
913
1276
|
const result = await assignJobs(id, sets, "add");
|
|
914
1277
|
if (opts.json) {
|
|
@@ -927,7 +1290,7 @@ async function projectAddAction(ref, sets, opts) {
|
|
|
927
1290
|
}
|
|
928
1291
|
async function projectRemoveAction(refOrSet, sets, opts) {
|
|
929
1292
|
try {
|
|
930
|
-
const { assignJobs } = await import("./projects-
|
|
1293
|
+
const { assignJobs } = await import("./projects-DDPO354C.js");
|
|
931
1294
|
if (opts.all) {
|
|
932
1295
|
const result2 = await assignJobs(null, [refOrSet, ...sets], "remove");
|
|
933
1296
|
if (opts.json) console.log(JSON.stringify(result2));
|
|
@@ -990,4 +1353,13 @@ if (process.argv.length <= 2) {
|
|
|
990
1353
|
process.exit(1);
|
|
991
1354
|
}
|
|
992
1355
|
registerContracts(program);
|
|
1356
|
+
registerAssets(program);
|
|
1357
|
+
try {
|
|
1358
|
+
const { registerWatch } = await import("./register-A5MUHXPT.js");
|
|
1359
|
+
registerWatch(program);
|
|
1360
|
+
} catch (e) {
|
|
1361
|
+
if (process.env.TRAJECTORIES_DEBUG) {
|
|
1362
|
+
console.error(`watch command unavailable: ${e?.message ?? e}`);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
993
1365
|
await program.parseAsync();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addComment,
|
|
3
|
+
deleteComment,
|
|
4
|
+
formatComment,
|
|
5
|
+
listComments,
|
|
6
|
+
parseCommentTarget,
|
|
7
|
+
readCommentBody,
|
|
8
|
+
updateComment
|
|
9
|
+
} from "./chunk-7FAZJWE4.js";
|
|
10
|
+
import "./chunk-MMQI3LNY.js";
|
|
11
|
+
import "./chunk-72L6XPK6.js";
|
|
12
|
+
import "./chunk-ERO5HP43.js";
|
|
13
|
+
export {
|
|
14
|
+
addComment,
|
|
15
|
+
deleteComment,
|
|
16
|
+
formatComment,
|
|
17
|
+
listComments,
|
|
18
|
+
parseCommentTarget,
|
|
19
|
+
readCommentBody,
|
|
20
|
+
updateComment
|
|
21
|
+
};
|
|
@@ -32,6 +32,8 @@ function validate(value) {
|
|
|
32
32
|
if (!value || typeof value !== "object")
|
|
33
33
|
throw new Error("Contract must be a JSON object.");
|
|
34
34
|
const d = value;
|
|
35
|
+
if (d.kind !== void 0 && !["collaboration", "nda_only"].includes(String(d.kind)))
|
|
36
|
+
throw new Error("Invalid document kind.");
|
|
35
37
|
for (const key of [
|
|
36
38
|
"title",
|
|
37
39
|
"recipient_name",
|
|
@@ -63,6 +65,8 @@ function validate(value) {
|
|
|
63
65
|
if (typeof item[key] !== "string")
|
|
64
66
|
throw new Error(`Deliverable ${key} must be a string.`);
|
|
65
67
|
}
|
|
68
|
+
if (d.kind === "nda_only" && (!d.nda_required || d.amount_minor !== 0 || d.deliverables.length || d.payment_terms || d.submission_instructions || d.agreement_terms))
|
|
69
|
+
throw new Error("NDA-only drafts require an NDA and cannot contain paid scope or collaborator agreement terms.");
|
|
66
70
|
}
|
|
67
71
|
function paymentDetails(d) {
|
|
68
72
|
const terms = d.payment_terms.trim();
|
|
@@ -83,8 +87,12 @@ function paymentDetails(d) {
|
|
|
83
87
|
return terms;
|
|
84
88
|
}
|
|
85
89
|
function renderContractPreview(value) {
|
|
90
|
+
if (value && typeof value === "object" && "kind" in value && value.kind === "nda_only") {
|
|
91
|
+
value = { recipient_name: "", recipient_email: "", currency: "USD", amount_minor: 0, pricing: "fixed", payment_terms: "", submission_instructions: "", agreement_terms: "", deliverables: [], nda_required: true, nda_terms: "", terms_approved: false, ...value };
|
|
92
|
+
}
|
|
86
93
|
validate(value);
|
|
87
94
|
const d = value;
|
|
95
|
+
const ndaOnly = d.kind === "nda_only";
|
|
88
96
|
const amount = new Intl.NumberFormat("en-US", {
|
|
89
97
|
style: "currency",
|
|
90
98
|
currency: d.currency
|
|
@@ -92,6 +100,7 @@ function renderContractPreview(value) {
|
|
|
92
100
|
const basis = d.pricing === "fixed" ? "Fixed total" : `Per ${d.pricing_unit || "accepted item"}`;
|
|
93
101
|
const pending = [
|
|
94
102
|
...d.nda_required && !d.nda_terms.trim() ? ["Approved NDA terms"] : [],
|
|
103
|
+
...ndaOnly && !d.terms_approved ? ["Explicit NDA terms approval"] : [],
|
|
95
104
|
...d.deliverables.some((x) => !x.acceptance.trim()) ? ["Task success / acceptance criteria"] : []
|
|
96
105
|
];
|
|
97
106
|
const paragraph = (text, fallback = "Not specified") => `<p class="prose">${escape(text.trim() || fallback)}</p>`;
|
|
@@ -110,12 +119,12 @@ input{display:block;width:100%;padding:10px;margin:8px 0;border:1px solid #193c3
|
|
|
110
119
|
.single-deliverable{list-style:none;padding-left:0}.single-deliverable>li{padding-left:0}.acceptance-details{margin-top:8px}.acceptance-list{list-style:disc outside;padding-left:1.4rem;margin:8px 0 0}.acceptance-list>li{padding-left:.2rem;margin:8px 0}
|
|
111
120
|
${contractFontCss}
|
|
112
121
|
body{font-family:Geist,Arial,sans-serif;color:#0b0b0b}main{max-width:960px}h1,h2,.amount{font-family:'Instrument Serif',Georgia,serif}.brand,.cover-wordmark{font-family:'Instrument Serif',Georgia,serif}h1,h2{font-weight:400;letter-spacing:-.005em}.cover-card h1{font-size:52px}.cover-photo{height:clamp(380px,56vh,560px)}.cover-card{margin-top:-144px}.contract-cover{margin-bottom:48px}.grid{margin:0 32px;grid-template-columns:minmax(0,1fr) 240px;gap:40px}.contract-section{padding:0;margin:0 0 48px;scroll-margin-top:24px}.contract-section h2{font-size:34px;line-height:1.25;margin:0 0 20px}.contract-section .prose{font-size:14px;line-height:1.8}.contract-summary{border-left:1px solid #0b0b0b1a;padding-left:24px}.muted{color:#6b6a66}@media(max-width:760px){.grid{margin:0 12px;grid-template-columns:1fr;gap:36px}.contract-summary{border-left:0;border-bottom:1px solid #0b0b0b1a;padding:0 0 28px}.contract-section h2{font-size:30px}.cover-card h1{font-size:38px}.cover-photo{height:300px}.cover-card{margin-top:-90px}}@media print{.grid{display:block}.contract-summary{border-left:0;padding-left:0}.contract-section{break-inside:avoid}}
|
|
113
|
-
</style></head><body><main><nav><span class="brand"><img src="${logoImage}" alt="" width="25" height="25">Refresh</span><span class="muted"
|
|
114
|
-
|
|
122
|
+
</style></head><body><main><nav><span class="brand"><img src="${logoImage}" alt="" width="25" height="25">Refresh</span><span class="muted">${ndaOnly ? "NDA draft" : "Contractor draft"}</span><button id="save-pdf">Save as PDF</button></nav><div class="banner">Local draft preview \xB7 Not issued or sent \xB7 Signing disabled</div><header class="contract-cover"><div class="cover-photo"><div class="cover-lockup"><img src="${logoImage}" alt="" width="44" height="44"><span><span class="cover-wordmark">Refresh</span><span class="cover-tagline">Environments & Data</span></span></div></div><div class="cover-card"><span class="badge">Draft</span><h1>${escape(d.title)}</h1><p class="cover-meta">Prepared for ${escape(d.recipient_name)}<span class="muted">${escape(d.recipient_email)}</span></p></div></header><div class="grid"><div>
|
|
123
|
+
${!ndaOnly ? `<section class="contract-section"><h2>What you will deliver</h2><ol class="deliverables-list${d.deliverables.length === 1 ? " single-deliverable" : ""}">${d.deliverables.map((item) => `<li><h3>${escape(item.title)}</h3><ul class="deliverable-details"><li class="prose">${escape(item.description)}</li></ul><div class="acceptance-details"><strong>Acceptance:</strong> ${acceptanceItems(item.acceptance).length > 1 ? `<ul class="acceptance-list">${acceptanceItems(item.acceptance).map((text) => `<li class="prose">${escape(text)}</li>`).join("")}</ul>` : escape(item.acceptance || "To be agreed")}</div>${item.due.trim() ? `<p class="muted">Due: ${escape(item.due)}</p>` : ""}</li>`).join("")}</ol></section>
|
|
115
124
|
<section class="contract-section"><h2>How to submit</h2>${paragraph(d.submission_instructions)}</section>
|
|
116
|
-
<section class="contract-section" id="agreement"><h2>Collaborator Agreement</h2>${d.agreement_terms.trim() ? paragraph(d.agreement_terms) : ""}<p class="muted">By clicking Sign, I agree to this document and intend my typed name to be my signature.</p><label>Full name<input disabled placeholder="Type your full name" aria-label="Full name"></label><button disabled>Sign</button></section
|
|
125
|
+
<section class="contract-section" id="agreement"><h2>Collaborator Agreement</h2>${d.agreement_terms.trim() ? paragraph(d.agreement_terms) : ""}<p class="muted">By clicking Sign, I agree to this document and intend my typed name to be my signature.</p><label>Full name<input disabled placeholder="Type your full name" aria-label="Full name"></label><button disabled>Sign</button></section>` : ""}
|
|
117
126
|
${d.nda_required ? `<section class="contract-section" id="nda"><h2>Non-disclosure agreement</h2>${d.nda_terms.trim() ? paragraph(d.nda_terms) : ""}<p class="muted">By clicking Sign, I agree to this document and intend my typed name to be my signature.</p><label>Full name<input disabled placeholder="Type your full name" aria-label="Full name"></label><button disabled>Sign</button></section>` : ""}
|
|
118
|
-
</div><aside class="contract-summary summary"
|
|
127
|
+
</div><aside class="contract-summary summary">${!ndaOnly ? `<span class="muted">Compensation</span><p class="amount">${escape(amount)}</p><p class="muted">${escape(basis)}</p>${paymentDetails(d) ? paragraph(paymentDetails(d)) : ""}<hr><a href="#agreement">Agreement</a><p class="muted">Not issued</p>` : ""}${d.nda_required ? '<a href="#nda">NDA</a><p class="muted">Required \xB7 Not issued</p>' : ""}</aside></div></main><script>${printScript}</script></body></html>`
|
|
119
128
|
};
|
|
120
129
|
}
|
|
121
130
|
async function previewContractFile(file, output) {
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
parseJobId,
|
|
10
10
|
parseProjectId,
|
|
11
11
|
planJob,
|
|
12
|
+
retryDelayMs,
|
|
12
13
|
writeJsonl
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-MMQI3LNY.js";
|
|
14
15
|
import "./chunk-72L6XPK6.js";
|
|
15
16
|
import "./chunk-ERO5HP43.js";
|
|
16
17
|
export {
|
|
@@ -24,5 +25,6 @@ export {
|
|
|
24
25
|
parseJobId,
|
|
25
26
|
parseProjectId,
|
|
26
27
|
planJob,
|
|
28
|
+
retryDelayMs,
|
|
27
29
|
writeJsonl
|
|
28
30
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseCommentTarget
|
|
3
|
+
} from "./chunk-7FAZJWE4.js";
|
|
4
|
+
import "./chunk-MMQI3LNY.js";
|
|
5
|
+
import "./chunk-72L6XPK6.js";
|
|
6
|
+
import {
|
|
7
|
+
getApiUrl,
|
|
8
|
+
getAuthHeader
|
|
9
|
+
} from "./chunk-ERO5HP43.js";
|
|
10
|
+
|
|
11
|
+
// src/outputs.ts
|
|
12
|
+
import { readFileSync, statSync } from "fs";
|
|
13
|
+
import { basename } from "path";
|
|
14
|
+
var MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
|
|
15
|
+
function parseOutputTarget(input, trial) {
|
|
16
|
+
let target = input;
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(input);
|
|
19
|
+
url.searchParams.delete("step");
|
|
20
|
+
url.searchParams.delete("phase");
|
|
21
|
+
target = url.toString();
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
const { jobId, trialId, trialName } = parseCommentTarget(target, { trial });
|
|
25
|
+
return { jobId, trialId, trialName };
|
|
26
|
+
}
|
|
27
|
+
function pathFor(target) {
|
|
28
|
+
return `/api/org/trajectories/${encodeURIComponent(target.jobId)}/outputs`;
|
|
29
|
+
}
|
|
30
|
+
function scope(target) {
|
|
31
|
+
const params = new URLSearchParams();
|
|
32
|
+
if (target.trialId) params.set("trial_id", target.trialId);
|
|
33
|
+
if (target.trialName) params.set("trial_name", target.trialName);
|
|
34
|
+
return params;
|
|
35
|
+
}
|
|
36
|
+
async function request(path, init = {}, requireAuth = false) {
|
|
37
|
+
const auth = await getAuthHeader();
|
|
38
|
+
if (requireAuth && !auth) throw new Error("Not authenticated. Run: trajectories auth login");
|
|
39
|
+
const response = await fetch(`${getApiUrl()}${path}`, {
|
|
40
|
+
...init,
|
|
41
|
+
headers: { ...init.headers, ...auth ? { Authorization: auth } : {} }
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
let detail = `Request failed (${response.status})`;
|
|
45
|
+
try {
|
|
46
|
+
const body = await response.json();
|
|
47
|
+
if (typeof body.detail === "string") detail = body.detail;
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
throw Object.assign(new Error(detail), { status: response.status });
|
|
51
|
+
}
|
|
52
|
+
return response.json();
|
|
53
|
+
}
|
|
54
|
+
function validateOutputFile(file) {
|
|
55
|
+
const stat = statSync(file);
|
|
56
|
+
if (!stat.isFile() || stat.size === 0 || stat.size > MAX_OUTPUT_BYTES) {
|
|
57
|
+
throw new Error(`${file}: output must be a nonempty file of at most 50 MiB`);
|
|
58
|
+
}
|
|
59
|
+
if (!/\.(png|jpe?g|webp|gif|mp4|webm)$/i.test(file)) {
|
|
60
|
+
throw new Error(`${file}: use PNG, JPEG, WebP, GIF, MP4, or WebM`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function uploadOutput(target, file) {
|
|
64
|
+
validateOutputFile(file);
|
|
65
|
+
const params = scope(target);
|
|
66
|
+
params.set("filename", basename(file));
|
|
67
|
+
return request(`${pathFor(target)}?${params}`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
70
|
+
body: new Uint8Array(readFileSync(file))
|
|
71
|
+
}, true);
|
|
72
|
+
}
|
|
73
|
+
function listOutputs(target) {
|
|
74
|
+
return request(`${pathFor(target)}?${scope(target)}`);
|
|
75
|
+
}
|
|
76
|
+
function deleteOutput(target, id) {
|
|
77
|
+
return request(`${pathFor(target)}/${encodeURIComponent(id)}`, { method: "DELETE" }, true);
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
MAX_OUTPUT_BYTES,
|
|
81
|
+
deleteOutput,
|
|
82
|
+
listOutputs,
|
|
83
|
+
parseOutputTarget,
|
|
84
|
+
uploadOutput,
|
|
85
|
+
validateOutputFile
|
|
86
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// ../watch/src/register.mjs
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { dirname, join } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
var here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
var CANDIDATES = [
|
|
8
|
+
join(here, "server.mjs"),
|
|
9
|
+
join(here, "..", "..", "watch", "src", "server.mjs"),
|
|
10
|
+
join(here, "..", "watch", "src", "server.mjs")
|
|
11
|
+
];
|
|
12
|
+
function serverPath() {
|
|
13
|
+
const found = CANDIDATES.find((path) => existsSync(path));
|
|
14
|
+
if (found) return found;
|
|
15
|
+
throw new Error("trajectories watch: could not find server.mjs. Looked in:\n " + CANDIDATES.join("\n "));
|
|
16
|
+
}
|
|
17
|
+
function runWatch(args = [], { stdio = "inherit" } = {}) {
|
|
18
|
+
const child = spawn(process.execPath, [serverPath(), ...args], { stdio });
|
|
19
|
+
return new Promise((done) => {
|
|
20
|
+
child.on("exit", (code) => done(code ?? 0));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function registerWatch(program) {
|
|
24
|
+
program.command("watch [job]").description("Watch a Harbor trial locally while it runs, or replay a finished one").option("--root <dir>", "Directory holding the jobs directory (default: the current directory)").option("--jobs <dir>", "The jobs directory itself (default: harbor/jobs, then jobs)").option("--port <n>", "Local port (default: 7654)").option("--stale <n>", "Seconds without a trajectory write before a run counts as died (default: 180)").option("--env-file <path>", "Read DAYTONA_API_KEY out of this env-style file").option("--setup", "Ask the first-run questions again, then carry on").option("--no-color", "Never colour the output (NO_COLOR does the same)").option("--url", "Print the live screen address and exit; non-zero when there is none").action(async (job, opts) => {
|
|
25
|
+
const args = [];
|
|
26
|
+
if (job) args.push(job);
|
|
27
|
+
for (const [name, flag] of [
|
|
28
|
+
["root", "--root"],
|
|
29
|
+
["jobs", "--jobs"],
|
|
30
|
+
["port", "--port"],
|
|
31
|
+
["stale", "--stale"],
|
|
32
|
+
["envFile", "--env-file"]
|
|
33
|
+
]) {
|
|
34
|
+
if (opts[name] !== void 0) args.push(flag, String(opts[name]));
|
|
35
|
+
}
|
|
36
|
+
if (opts.setup) args.push("--setup");
|
|
37
|
+
if (opts.color === false) args.push("--no-color");
|
|
38
|
+
if (opts.url) args.push("--url");
|
|
39
|
+
process.exitCode = await runWatch(args);
|
|
40
|
+
});
|
|
41
|
+
return program;
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
registerWatch,
|
|
45
|
+
runWatch
|
|
46
|
+
};
|
|
@@ -9,9 +9,27 @@ import {
|
|
|
9
9
|
getAuthHeader
|
|
10
10
|
} from "./chunk-ERO5HP43.js";
|
|
11
11
|
|
|
12
|
+
// src/converted-image-paths.ts
|
|
13
|
+
import { dirname, resolve } from "path";
|
|
14
|
+
function rewriteConvertedImagePaths(value, trajectoryPath, converted) {
|
|
15
|
+
if (!value || typeof value !== "object") return;
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
for (const item of value) rewriteConvertedImagePaths(item, trajectoryPath, converted);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const object = value;
|
|
21
|
+
if (object.type === "image" && object.source && typeof object.source === "object") {
|
|
22
|
+
const source = object.source;
|
|
23
|
+
if (typeof source.path === "string" && converted.has(resolve(dirname(trajectoryPath), source.path))) {
|
|
24
|
+
source.path = source.path.replace(/\.png$/i, ".webp");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const item of Object.values(object)) rewriteConvertedImagePaths(item, trajectoryPath, converted);
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
// src/upload.ts
|
|
13
31
|
import { readdirSync, statSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, unlinkSync } from "fs";
|
|
14
|
-
import { join, relative
|
|
32
|
+
import { join, relative } from "path";
|
|
15
33
|
import { tmpdir } from "os";
|
|
16
34
|
import { randomBytes, createHash } from "crypto";
|
|
17
35
|
var SKIP = /* @__PURE__ */ new Set(["__pycache__", ".DS_Store", ".git", "node_modules"]);
|
|
@@ -78,7 +96,7 @@ async function uploadZip(zipPath, opts) {
|
|
|
78
96
|
console.log(" Uploading ZIP via resumable upload...");
|
|
79
97
|
const { createReadStream } = await import("fs");
|
|
80
98
|
const tus = await import("tus-js-client");
|
|
81
|
-
await new Promise((
|
|
99
|
+
await new Promise((resolve2, reject) => {
|
|
82
100
|
const fileStream = createReadStream(zipPath);
|
|
83
101
|
const upload = new tus.Upload(fileStream, {
|
|
84
102
|
endpoint: `${uploadInfo.supabase_url}/storage/v1/upload/resumable`,
|
|
@@ -107,7 +125,7 @@ async function uploadZip(zipPath, opts) {
|
|
|
107
125
|
},
|
|
108
126
|
onSuccess() {
|
|
109
127
|
console.log(` \u2191 100% \u2014 ZIP uploaded to storage.`);
|
|
110
|
-
|
|
128
|
+
resolve2();
|
|
111
129
|
}
|
|
112
130
|
});
|
|
113
131
|
upload.start();
|
|
@@ -204,6 +222,7 @@ async function convertToWebP(dir) {
|
|
|
204
222
|
const prevConcurrency = sharp.concurrency();
|
|
205
223
|
sharp.concurrency(1);
|
|
206
224
|
sharp.cache({ memory: 0, files: 0, items: 0 });
|
|
225
|
+
const convertedPaths = /* @__PURE__ */ new Map();
|
|
207
226
|
let converted = 0;
|
|
208
227
|
let savedBytes = 0;
|
|
209
228
|
let processed = 0;
|
|
@@ -216,6 +235,7 @@ async function convertToWebP(dir) {
|
|
|
216
235
|
const origSize = statSync(srcPath).size;
|
|
217
236
|
const newSize = statSync(webpPath).size;
|
|
218
237
|
if (newSize < origSize) {
|
|
238
|
+
convertedPaths.set(srcPath, webpPath);
|
|
219
239
|
unlinkSync(srcPath);
|
|
220
240
|
savedBytes += origSize - newSize;
|
|
221
241
|
converted++;
|
|
@@ -241,20 +261,11 @@ async function convertToWebP(dir) {
|
|
|
241
261
|
const trajFiles = collectFiles(tmpDir).files.filter((f) => f.relPath.endsWith("trajectory.json"));
|
|
242
262
|
for (const tf of trajFiles) {
|
|
243
263
|
try {
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
statSync(webpPath);
|
|
250
|
-
return `screenshots/${name}.webp`;
|
|
251
|
-
} catch {
|
|
252
|
-
return match;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
if (content !== original) {
|
|
256
|
-
writeFileSync(tf.absPath, content);
|
|
257
|
-
}
|
|
264
|
+
const original = readFileSync(tf.absPath, "utf-8");
|
|
265
|
+
const trajectory = JSON.parse(original);
|
|
266
|
+
const before = JSON.stringify(trajectory);
|
|
267
|
+
rewriteConvertedImagePaths(trajectory, tf.absPath, convertedPaths);
|
|
268
|
+
if (JSON.stringify(trajectory) !== before) writeFileSync(tf.absPath, JSON.stringify(trajectory));
|
|
258
269
|
} catch {
|
|
259
270
|
}
|
|
260
271
|
}
|
|
@@ -673,6 +684,7 @@ function truncate(s, n) {
|
|
|
673
684
|
}
|
|
674
685
|
export {
|
|
675
686
|
collectFiles,
|
|
687
|
+
convertToWebP,
|
|
676
688
|
uploadJob,
|
|
677
689
|
uploadZip
|
|
678
690
|
};
|