syntaur 0.13.0 → 0.14.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/.claude-plugin/plugin.json +3 -1
- package/README.md +4 -2
- package/dist/dashboard/server.js +64 -3
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.d.ts +50 -1
- package/dist/db/leases-db.js +123 -1
- package/dist/db/leases-db.js.map +1 -1
- package/dist/index.js +621 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +3 -1
- package/platforms/claude-code/skills/extend-resource/SKILL.md +83 -0
- package/platforms/claude-code/skills/list-resources/SKILL.md +74 -0
- package/platforms/codex/skills/extend-resource/SKILL.md +83 -0
- package/platforms/codex/skills/list-resources/SKILL.md +74 -0
- package/skills/extend-resource/SKILL.md +83 -0
- package/skills/list-resources/SKILL.md +74 -0
package/dist/index.js
CHANGED
|
@@ -9672,6 +9672,65 @@ function createServersRouter(serversDir2, projectsDir2, assignmentsDir2) {
|
|
|
9672
9672
|
import { Router as Router3 } from "express";
|
|
9673
9673
|
import { resolve as resolve16 } from "path";
|
|
9674
9674
|
init_fs();
|
|
9675
|
+
|
|
9676
|
+
// src/utils/transcript.ts
|
|
9677
|
+
import { open } from "fs/promises";
|
|
9678
|
+
var MAX_LINES_SCANNED = 50;
|
|
9679
|
+
async function derivePathFromTranscript(transcriptPath) {
|
|
9680
|
+
if (!transcriptPath) return null;
|
|
9681
|
+
let handle;
|
|
9682
|
+
try {
|
|
9683
|
+
handle = await open(transcriptPath, "r");
|
|
9684
|
+
} catch {
|
|
9685
|
+
return null;
|
|
9686
|
+
}
|
|
9687
|
+
try {
|
|
9688
|
+
const stream = handle.createReadStream({ encoding: "utf-8" });
|
|
9689
|
+
let buffer = "";
|
|
9690
|
+
let scanned = 0;
|
|
9691
|
+
for await (const chunk of stream) {
|
|
9692
|
+
buffer += chunk;
|
|
9693
|
+
let nl = buffer.indexOf("\n");
|
|
9694
|
+
while (nl !== -1) {
|
|
9695
|
+
const line = buffer.slice(0, nl);
|
|
9696
|
+
buffer = buffer.slice(nl + 1);
|
|
9697
|
+
const cwd = extractCwd(line);
|
|
9698
|
+
if (cwd) {
|
|
9699
|
+
stream.destroy();
|
|
9700
|
+
return cwd;
|
|
9701
|
+
}
|
|
9702
|
+
scanned++;
|
|
9703
|
+
if (scanned >= MAX_LINES_SCANNED) {
|
|
9704
|
+
stream.destroy();
|
|
9705
|
+
return null;
|
|
9706
|
+
}
|
|
9707
|
+
nl = buffer.indexOf("\n");
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
if (buffer.length > 0) {
|
|
9711
|
+
const cwd = extractCwd(buffer);
|
|
9712
|
+
if (cwd) return cwd;
|
|
9713
|
+
}
|
|
9714
|
+
return null;
|
|
9715
|
+
} finally {
|
|
9716
|
+
await handle.close().catch(() => {
|
|
9717
|
+
});
|
|
9718
|
+
}
|
|
9719
|
+
}
|
|
9720
|
+
function extractCwd(line) {
|
|
9721
|
+
const trimmed = line.trim();
|
|
9722
|
+
if (trimmed.length === 0 || trimmed[0] !== "{") return null;
|
|
9723
|
+
try {
|
|
9724
|
+
const parsed = JSON.parse(trimmed);
|
|
9725
|
+
if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
|
|
9726
|
+
return parsed.cwd;
|
|
9727
|
+
}
|
|
9728
|
+
} catch {
|
|
9729
|
+
}
|
|
9730
|
+
return null;
|
|
9731
|
+
}
|
|
9732
|
+
|
|
9733
|
+
// src/dashboard/api-agent-sessions.ts
|
|
9675
9734
|
function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
9676
9735
|
const router = Router3();
|
|
9677
9736
|
router.get("/", async (_req, res) => {
|
|
@@ -9719,6 +9778,8 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
9719
9778
|
return;
|
|
9720
9779
|
}
|
|
9721
9780
|
}
|
|
9781
|
+
const derivedPath = await derivePathFromTranscript(transcriptPath);
|
|
9782
|
+
const recordedPath = derivedPath ?? path ?? "";
|
|
9722
9783
|
const session = {
|
|
9723
9784
|
projectSlug: projectSlug || null,
|
|
9724
9785
|
assignmentSlug: assignmentSlug || null,
|
|
@@ -9726,7 +9787,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
9726
9787
|
sessionId,
|
|
9727
9788
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9728
9789
|
status: "active",
|
|
9729
|
-
path:
|
|
9790
|
+
path: recordedPath,
|
|
9730
9791
|
description: description || null,
|
|
9731
9792
|
transcriptPath: transcriptPath || null
|
|
9732
9793
|
};
|
|
@@ -10216,6 +10277,33 @@ function listLeases(filter) {
|
|
|
10216
10277
|
ORDER BY granted_at DESC`
|
|
10217
10278
|
).all(...params2);
|
|
10218
10279
|
}
|
|
10280
|
+
function listMembers(inventory_slug) {
|
|
10281
|
+
const database = getLeasesDb();
|
|
10282
|
+
const inv = database.prepare("SELECT slug FROM inventories WHERE slug = ?").get(inventory_slug);
|
|
10283
|
+
if (!inv) throw new NotFoundError(inventory_slug);
|
|
10284
|
+
return database.prepare(
|
|
10285
|
+
`SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
|
|
10286
|
+
FROM inventory_members WHERE inventory_slug = ?
|
|
10287
|
+
ORDER BY member_id`
|
|
10288
|
+
).all(inventory_slug);
|
|
10289
|
+
}
|
|
10290
|
+
function getLeaseEvents(lease_id, limit = 50) {
|
|
10291
|
+
const database = getLeasesDb();
|
|
10292
|
+
if (lease_id) {
|
|
10293
|
+
return database.prepare(
|
|
10294
|
+
`SELECT id, lease_id, event, at, detail_json
|
|
10295
|
+
FROM lease_events WHERE lease_id = ?
|
|
10296
|
+
ORDER BY at ASC, id ASC
|
|
10297
|
+
LIMIT ?`
|
|
10298
|
+
).all(lease_id, limit);
|
|
10299
|
+
}
|
|
10300
|
+
return database.prepare(
|
|
10301
|
+
`SELECT id, lease_id, event, at, detail_json
|
|
10302
|
+
FROM lease_events
|
|
10303
|
+
ORDER BY at DESC, id DESC
|
|
10304
|
+
LIMIT ?`
|
|
10305
|
+
).all(limit);
|
|
10306
|
+
}
|
|
10219
10307
|
function gcExpiredLeases() {
|
|
10220
10308
|
const database = getLeasesDb();
|
|
10221
10309
|
try {
|
|
@@ -10289,6 +10377,96 @@ function forceReleaseLease(lease_id) {
|
|
|
10289
10377
|
throw err2;
|
|
10290
10378
|
}
|
|
10291
10379
|
}
|
|
10380
|
+
function updateInventory(slug, input4) {
|
|
10381
|
+
if ("kind" in input4) {
|
|
10382
|
+
throw new Error("inventory kind is immutable");
|
|
10383
|
+
}
|
|
10384
|
+
if (input4.default_ttl_s === void 0 && input4.display_name === void 0) {
|
|
10385
|
+
throw new Error("nothing to update");
|
|
10386
|
+
}
|
|
10387
|
+
if (input4.default_ttl_s !== void 0 && input4.default_ttl_s <= 0) {
|
|
10388
|
+
throw new Error("default_ttl_s must be positive");
|
|
10389
|
+
}
|
|
10390
|
+
const database = getLeasesDb();
|
|
10391
|
+
const fn = database.transaction(() => {
|
|
10392
|
+
const existing = database.prepare("SELECT slug FROM inventories WHERE slug = ?").get(slug);
|
|
10393
|
+
if (!existing) throw new NotFoundError(slug);
|
|
10394
|
+
const sets = [];
|
|
10395
|
+
const params2 = [];
|
|
10396
|
+
if (input4.default_ttl_s !== void 0) {
|
|
10397
|
+
sets.push("default_ttl_s = ?");
|
|
10398
|
+
params2.push(input4.default_ttl_s);
|
|
10399
|
+
}
|
|
10400
|
+
if (input4.display_name !== void 0) {
|
|
10401
|
+
sets.push("display_name = ?");
|
|
10402
|
+
params2.push(input4.display_name);
|
|
10403
|
+
}
|
|
10404
|
+
params2.push(slug);
|
|
10405
|
+
database.prepare(`UPDATE inventories SET ${sets.join(", ")} WHERE slug = ?`).run(...params2);
|
|
10406
|
+
return database.prepare(
|
|
10407
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
10408
|
+
FROM inventories WHERE slug = ?`
|
|
10409
|
+
).get(slug);
|
|
10410
|
+
});
|
|
10411
|
+
try {
|
|
10412
|
+
return fn.immediate();
|
|
10413
|
+
} catch (err2) {
|
|
10414
|
+
if (isBusyError(err2)) throw new LeaseContentionError(slug);
|
|
10415
|
+
throw err2;
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
function deleteInventory(slug, opts = {}) {
|
|
10419
|
+
const database = getLeasesDb();
|
|
10420
|
+
let revoked = 0;
|
|
10421
|
+
const fn = database.transaction(() => {
|
|
10422
|
+
const existing = database.prepare("SELECT slug FROM inventories WHERE slug = ?").get(slug);
|
|
10423
|
+
if (!existing) throw new NotFoundError(slug);
|
|
10424
|
+
const activeLeases = database.prepare(
|
|
10425
|
+
`SELECT lease_id FROM leases
|
|
10426
|
+
WHERE inventory_slug = ? AND state = 'active'`
|
|
10427
|
+
).all(slug);
|
|
10428
|
+
if (activeLeases.length > 0 && !opts.force) {
|
|
10429
|
+
throw new MemberInUseError(slug, "*");
|
|
10430
|
+
}
|
|
10431
|
+
revoked = activeLeases.length;
|
|
10432
|
+
database.prepare(
|
|
10433
|
+
`DELETE FROM lease_events
|
|
10434
|
+
WHERE lease_id IN (SELECT lease_id FROM leases WHERE inventory_slug = ?)`
|
|
10435
|
+
).run(slug);
|
|
10436
|
+
database.prepare("DELETE FROM leases WHERE inventory_slug = ?").run(slug);
|
|
10437
|
+
database.prepare("DELETE FROM inventory_members WHERE inventory_slug = ?").run(slug);
|
|
10438
|
+
database.prepare("DELETE FROM inventories WHERE slug = ?").run(slug);
|
|
10439
|
+
});
|
|
10440
|
+
try {
|
|
10441
|
+
fn.immediate();
|
|
10442
|
+
} catch (err2) {
|
|
10443
|
+
if (isBusyError(err2)) throw new LeaseContentionError(slug);
|
|
10444
|
+
throw err2;
|
|
10445
|
+
}
|
|
10446
|
+
return { deleted: true, revoked };
|
|
10447
|
+
}
|
|
10448
|
+
function releaseLeasesByRequestedFor(tag) {
|
|
10449
|
+
const database = getLeasesDb();
|
|
10450
|
+
const rows = database.prepare(
|
|
10451
|
+
`SELECT lease_id FROM leases
|
|
10452
|
+
WHERE state = 'active' AND requested_for = ?`
|
|
10453
|
+
).all(tag);
|
|
10454
|
+
const released = [];
|
|
10455
|
+
const stale = [];
|
|
10456
|
+
for (const { lease_id } of rows) {
|
|
10457
|
+
try {
|
|
10458
|
+
releaseLease(lease_id);
|
|
10459
|
+
released.push(lease_id);
|
|
10460
|
+
} catch (err2) {
|
|
10461
|
+
if (err2 instanceof StaleLeaseError) {
|
|
10462
|
+
stale.push(lease_id);
|
|
10463
|
+
continue;
|
|
10464
|
+
}
|
|
10465
|
+
throw err2;
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
return { released, stale };
|
|
10469
|
+
}
|
|
10292
10470
|
|
|
10293
10471
|
// src/dashboard/api-leases.ts
|
|
10294
10472
|
function createLeasesRouter(broadcast) {
|
|
@@ -12061,7 +12239,7 @@ init_fs();
|
|
|
12061
12239
|
init_config2();
|
|
12062
12240
|
import { execFile as execFile2 } from "child_process";
|
|
12063
12241
|
import { promisify as promisify2 } from "util";
|
|
12064
|
-
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
|
|
12242
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open as open2, rename as rename5 } from "fs/promises";
|
|
12065
12243
|
import { resolve as resolve21, join as join2 } from "path";
|
|
12066
12244
|
import { tmpdir } from "os";
|
|
12067
12245
|
var exec2 = promisify2(execFile2);
|
|
@@ -12116,7 +12294,7 @@ async function acquireLock() {
|
|
|
12116
12294
|
const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
|
|
12117
12295
|
await ensureDir(syntaurRoot());
|
|
12118
12296
|
try {
|
|
12119
|
-
const handle = await
|
|
12297
|
+
const handle = await open2(lockPath, "wx");
|
|
12120
12298
|
await handle.write(String(process.pid));
|
|
12121
12299
|
await handle.close();
|
|
12122
12300
|
return lockPath;
|
|
@@ -14382,7 +14560,11 @@ var KNOWN_SKILL_NAMES = [
|
|
|
14382
14560
|
"add-memory",
|
|
14383
14561
|
"list-assignments",
|
|
14384
14562
|
"log-progress",
|
|
14385
|
-
"set-workspace"
|
|
14563
|
+
"set-workspace",
|
|
14564
|
+
"claim-resource",
|
|
14565
|
+
"release-resource",
|
|
14566
|
+
"extend-resource",
|
|
14567
|
+
"list-resources"
|
|
14386
14568
|
];
|
|
14387
14569
|
var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
|
|
14388
14570
|
async function getSkillsDir() {
|
|
@@ -14445,8 +14627,8 @@ async function skillMatches(srcDir, destDir) {
|
|
|
14445
14627
|
}
|
|
14446
14628
|
async function isSymlink(path) {
|
|
14447
14629
|
try {
|
|
14448
|
-
const
|
|
14449
|
-
return
|
|
14630
|
+
const stat10 = await lstat2(path);
|
|
14631
|
+
return stat10.isSymbolicLink();
|
|
14450
14632
|
} catch {
|
|
14451
14633
|
return false;
|
|
14452
14634
|
}
|
|
@@ -15631,6 +15813,8 @@ async function trackSessionCommand(options) {
|
|
|
15631
15813
|
}
|
|
15632
15814
|
initSessionDb();
|
|
15633
15815
|
const { sessionId } = options;
|
|
15816
|
+
const derivedPath = await derivePathFromTranscript(options.transcriptPath);
|
|
15817
|
+
const recordedPath = derivedPath ?? options.path ?? process.cwd();
|
|
15634
15818
|
await appendSession("", {
|
|
15635
15819
|
projectSlug: options.project || null,
|
|
15636
15820
|
assignmentSlug: options.assignment || null,
|
|
@@ -15638,7 +15822,7 @@ async function trackSessionCommand(options) {
|
|
|
15638
15822
|
sessionId,
|
|
15639
15823
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15640
15824
|
status: "active",
|
|
15641
|
-
path:
|
|
15825
|
+
path: recordedPath,
|
|
15642
15826
|
description: options.description || null,
|
|
15643
15827
|
transcriptPath: options.transcriptPath ?? null
|
|
15644
15828
|
});
|
|
@@ -19281,7 +19465,7 @@ ${entry}`;
|
|
|
19281
19465
|
|
|
19282
19466
|
// src/commands/capture.ts
|
|
19283
19467
|
import { resolve as resolve52, relative as relative4 } from "path";
|
|
19284
|
-
import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as
|
|
19468
|
+
import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as stat7 } from "fs/promises";
|
|
19285
19469
|
|
|
19286
19470
|
// src/utils/assignment-target.ts
|
|
19287
19471
|
init_paths();
|
|
@@ -19461,6 +19645,80 @@ function isArtifactKind(value) {
|
|
|
19461
19645
|
// src/commands/capture.ts
|
|
19462
19646
|
init_fs();
|
|
19463
19647
|
|
|
19648
|
+
// src/utils/screencapture.ts
|
|
19649
|
+
import { spawn as spawn4 } from "child_process";
|
|
19650
|
+
import { mkdtemp as mkdtemp2, rm as rm6, stat as stat6 } from "fs/promises";
|
|
19651
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
19652
|
+
import { join as join7 } from "path";
|
|
19653
|
+
function argsFor(mode, pngPath) {
|
|
19654
|
+
switch (mode) {
|
|
19655
|
+
case "interactive":
|
|
19656
|
+
return ["-i", pngPath];
|
|
19657
|
+
case "window":
|
|
19658
|
+
return ["-iWo", pngPath];
|
|
19659
|
+
case "fullscreen":
|
|
19660
|
+
return ["-x", pngPath];
|
|
19661
|
+
}
|
|
19662
|
+
}
|
|
19663
|
+
function runScreencapture(args) {
|
|
19664
|
+
return new Promise((resolvePromise, reject) => {
|
|
19665
|
+
const child = spawn4("screencapture", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
19666
|
+
let settled = false;
|
|
19667
|
+
child.once("error", (err2) => {
|
|
19668
|
+
if (settled) return;
|
|
19669
|
+
settled = true;
|
|
19670
|
+
reject(err2);
|
|
19671
|
+
});
|
|
19672
|
+
child.once("close", (code) => {
|
|
19673
|
+
if (settled) return;
|
|
19674
|
+
settled = true;
|
|
19675
|
+
resolvePromise(code ?? -1);
|
|
19676
|
+
});
|
|
19677
|
+
});
|
|
19678
|
+
}
|
|
19679
|
+
async function captureScreenshot(mode) {
|
|
19680
|
+
if (process.platform !== "darwin") {
|
|
19681
|
+
throw new Error(
|
|
19682
|
+
"screencapture is only available on macOS. Use --file <path> to attach an existing image."
|
|
19683
|
+
);
|
|
19684
|
+
}
|
|
19685
|
+
const tmpDir = await mkdtemp2(join7(tmpdir2(), "syntaur-screenshot-"));
|
|
19686
|
+
const pngPath = join7(tmpDir, "shot.png");
|
|
19687
|
+
const cleanup = async () => {
|
|
19688
|
+
await rm6(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
19689
|
+
});
|
|
19690
|
+
};
|
|
19691
|
+
try {
|
|
19692
|
+
let code;
|
|
19693
|
+
try {
|
|
19694
|
+
code = await runScreencapture(argsFor(mode, pngPath));
|
|
19695
|
+
} catch (err2) {
|
|
19696
|
+
if (err2 && typeof err2 === "object" && err2.code === "ENOENT") {
|
|
19697
|
+
throw new Error(
|
|
19698
|
+
"screencapture binary not found. Is this macOS with the system utility on PATH?"
|
|
19699
|
+
);
|
|
19700
|
+
}
|
|
19701
|
+
throw err2;
|
|
19702
|
+
}
|
|
19703
|
+
if (code !== 0) {
|
|
19704
|
+
throw new Error(`Screenshot canceled or failed (exit ${code}).`);
|
|
19705
|
+
}
|
|
19706
|
+
let size = 0;
|
|
19707
|
+
try {
|
|
19708
|
+
size = (await stat6(pngPath)).size;
|
|
19709
|
+
} catch {
|
|
19710
|
+
throw new Error("screencapture exited 0 but produced no image.");
|
|
19711
|
+
}
|
|
19712
|
+
if (size === 0) {
|
|
19713
|
+
throw new Error("screencapture exited 0 but produced no image.");
|
|
19714
|
+
}
|
|
19715
|
+
return { pngPath, cleanup };
|
|
19716
|
+
} catch (err2) {
|
|
19717
|
+
await cleanup();
|
|
19718
|
+
throw err2;
|
|
19719
|
+
}
|
|
19720
|
+
}
|
|
19721
|
+
|
|
19464
19722
|
// src/db/proof-db.ts
|
|
19465
19723
|
init_paths();
|
|
19466
19724
|
import Database4 from "better-sqlite3";
|
|
@@ -19565,6 +19823,27 @@ async function captureCommand(target, options = {}) {
|
|
|
19565
19823
|
);
|
|
19566
19824
|
}
|
|
19567
19825
|
const kind = options.kind;
|
|
19826
|
+
const shelloutFlagCount = [
|
|
19827
|
+
options.interactive,
|
|
19828
|
+
options.window,
|
|
19829
|
+
options.fullscreen
|
|
19830
|
+
].filter(Boolean).length;
|
|
19831
|
+
if (shelloutFlagCount > 1) {
|
|
19832
|
+
throw new Error("--interactive, --window, --fullscreen are mutually exclusive.");
|
|
19833
|
+
}
|
|
19834
|
+
const shelloutMode = options.interactive ? "interactive" : options.window ? "window" : options.fullscreen ? "fullscreen" : null;
|
|
19835
|
+
if (shelloutMode) {
|
|
19836
|
+
if (kind !== "screenshot") {
|
|
19837
|
+
throw new Error(
|
|
19838
|
+
"--interactive, --window, and --fullscreen require --kind=screenshot."
|
|
19839
|
+
);
|
|
19840
|
+
}
|
|
19841
|
+
if (options.file) {
|
|
19842
|
+
throw new Error(
|
|
19843
|
+
"--file cannot be combined with --interactive, --window, or --fullscreen."
|
|
19844
|
+
);
|
|
19845
|
+
}
|
|
19846
|
+
}
|
|
19568
19847
|
if (kind === "text") {
|
|
19569
19848
|
if (options.file) {
|
|
19570
19849
|
throw new Error("--kind=text forbids --file. Use --note for text payloads.");
|
|
@@ -19573,7 +19852,7 @@ async function captureCommand(target, options = {}) {
|
|
|
19573
19852
|
throw new Error("--kind=text requires --note.");
|
|
19574
19853
|
}
|
|
19575
19854
|
} else {
|
|
19576
|
-
if (kind !== "http" && !options.file) {
|
|
19855
|
+
if (kind !== "http" && !options.file && !shelloutMode) {
|
|
19577
19856
|
throw new Error(`--kind=${kind} requires --file.`);
|
|
19578
19857
|
}
|
|
19579
19858
|
if (kind === "http" && !options.file && (!options.note || options.note.trim() === "")) {
|
|
@@ -19587,6 +19866,7 @@ async function captureCommand(target, options = {}) {
|
|
|
19587
19866
|
cwd: options.cwd
|
|
19588
19867
|
});
|
|
19589
19868
|
let resolvedSource = null;
|
|
19869
|
+
let shelloutCleanup = null;
|
|
19590
19870
|
if (options.file) {
|
|
19591
19871
|
const expanded = options.file.startsWith("~/") ? resolve52(process.env.HOME ?? "", options.file.slice(2)) : resolve52(options.file);
|
|
19592
19872
|
if (!await fileExists(expanded)) {
|
|
@@ -19600,71 +19880,92 @@ async function captureCommand(target, options = {}) {
|
|
|
19600
19880
|
`--file is unreadable: ${options.file} (${e instanceof Error ? e.message : String(e)})`
|
|
19601
19881
|
);
|
|
19602
19882
|
}
|
|
19603
|
-
const st = await
|
|
19883
|
+
const st = await stat7(real);
|
|
19604
19884
|
if (!st.isFile()) {
|
|
19605
19885
|
throw new Error(`--file is not a regular file: ${options.file}`);
|
|
19606
19886
|
}
|
|
19607
19887
|
resolvedSource = real;
|
|
19888
|
+
} else if (shelloutMode) {
|
|
19889
|
+
const { pngPath, cleanup } = await captureScreenshot(shelloutMode);
|
|
19890
|
+
resolvedSource = pngPath;
|
|
19891
|
+
shelloutCleanup = cleanup;
|
|
19608
19892
|
}
|
|
19609
|
-
|
|
19610
|
-
|
|
19611
|
-
|
|
19612
|
-
|
|
19613
|
-
|
|
19614
|
-
|
|
19615
|
-
|
|
19616
|
-
|
|
19617
|
-
|
|
19618
|
-
|
|
19619
|
-
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19626
|
-
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
|
|
19630
|
-
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
|
|
19634
|
-
|
|
19635
|
-
|
|
19636
|
-
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
19644
|
-
|
|
19893
|
+
try {
|
|
19894
|
+
if (!resolved.id || resolved.id.trim() === "") {
|
|
19895
|
+
throw new Error(
|
|
19896
|
+
`Resolved assignment is missing a frontmatter \`id\`: ${resolved.assignmentDir}. Cannot record artifact.`
|
|
19897
|
+
);
|
|
19898
|
+
}
|
|
19899
|
+
initProofDb();
|
|
19900
|
+
const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
|
|
19901
|
+
const destDir = resolve52(proofDir(resolved.assignmentDir), subdir);
|
|
19902
|
+
if (resolvedSource) await mkdir7(destDir, { recursive: true });
|
|
19903
|
+
const ext = resolvedSource ? extensionForKind(kind) : null;
|
|
19904
|
+
let id = null;
|
|
19905
|
+
let relativeFilePath = null;
|
|
19906
|
+
let absPath = null;
|
|
19907
|
+
let lastErr = null;
|
|
19908
|
+
for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
|
|
19909
|
+
const candidate = generateArtifactId();
|
|
19910
|
+
const candidateAbsPath = resolvedSource && ext ? resolve52(destDir, `${candidate}.${ext}`) : null;
|
|
19911
|
+
const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
|
|
19912
|
+
try {
|
|
19913
|
+
insertArtifact({
|
|
19914
|
+
id: candidate,
|
|
19915
|
+
assignmentId: resolved.id,
|
|
19916
|
+
assignmentDir: resolved.assignmentDir,
|
|
19917
|
+
criterionIndex,
|
|
19918
|
+
kind,
|
|
19919
|
+
filePath: candidateRel,
|
|
19920
|
+
note: options.note ?? null
|
|
19921
|
+
});
|
|
19922
|
+
id = candidate;
|
|
19923
|
+
absPath = candidateAbsPath;
|
|
19924
|
+
relativeFilePath = candidateRel;
|
|
19925
|
+
break;
|
|
19926
|
+
} catch (e) {
|
|
19927
|
+
if (isUniqueConstraintError(e)) {
|
|
19928
|
+
lastErr = e;
|
|
19929
|
+
continue;
|
|
19930
|
+
}
|
|
19931
|
+
throw e;
|
|
19645
19932
|
}
|
|
19646
|
-
throw e;
|
|
19647
19933
|
}
|
|
19648
|
-
|
|
19649
|
-
|
|
19650
|
-
|
|
19651
|
-
|
|
19652
|
-
|
|
19653
|
-
|
|
19654
|
-
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19934
|
+
if (!id) {
|
|
19935
|
+
throw new Error(
|
|
19936
|
+
`Failed to generate a unique artifact id after ${MAX_ID_RETRIES} attempts: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
|
|
19937
|
+
);
|
|
19938
|
+
}
|
|
19939
|
+
if (resolvedSource && absPath) {
|
|
19940
|
+
try {
|
|
19941
|
+
await copyFile3(resolvedSource, absPath);
|
|
19942
|
+
} catch (err2) {
|
|
19943
|
+
if (shelloutCleanup) {
|
|
19944
|
+
console.error(
|
|
19945
|
+
`Screenshot saved at ${resolvedSource} \u2014 DB row inserted but copy to proof dir failed. Recover with: mv ${resolvedSource} ${absPath}`
|
|
19946
|
+
);
|
|
19947
|
+
shelloutCleanup = null;
|
|
19948
|
+
}
|
|
19949
|
+
throw err2;
|
|
19950
|
+
}
|
|
19951
|
+
}
|
|
19952
|
+
const ref = resolved.standalone ? resolved.id : `${resolved.projectSlug}/${resolved.assignmentSlug}`;
|
|
19953
|
+
const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
|
|
19954
|
+
console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
|
|
19955
|
+
if (relativeFilePath) {
|
|
19956
|
+
console.log(` file: ${resolve52(resolved.assignmentDir, relativeFilePath)}`);
|
|
19957
|
+
}
|
|
19958
|
+
} finally {
|
|
19959
|
+
if (shelloutCleanup) {
|
|
19960
|
+
await shelloutCleanup();
|
|
19961
|
+
shelloutCleanup = null;
|
|
19962
|
+
}
|
|
19662
19963
|
}
|
|
19663
19964
|
}
|
|
19664
19965
|
|
|
19665
19966
|
// src/commands/proof.ts
|
|
19666
19967
|
import { Command as Command5 } from "commander";
|
|
19667
|
-
import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as
|
|
19968
|
+
import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as stat8 } from "fs/promises";
|
|
19668
19969
|
import { resolve as resolve53, relative as relative5, isAbsolute as isAbsolute8 } from "path";
|
|
19669
19970
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
19670
19971
|
|
|
@@ -19928,7 +20229,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
19928
20229
|
out.set(r.file_path, null);
|
|
19929
20230
|
continue;
|
|
19930
20231
|
}
|
|
19931
|
-
const st = await
|
|
20232
|
+
const st = await stat8(abs);
|
|
19932
20233
|
if (st.size > INLINE_TEXT_LIMIT_BYTES) {
|
|
19933
20234
|
out.set(r.file_path, null);
|
|
19934
20235
|
continue;
|
|
@@ -20075,38 +20376,75 @@ memberCommand.command("retire").description("Retire a member (no longer claimabl
|
|
|
20075
20376
|
process.exit(1);
|
|
20076
20377
|
}
|
|
20077
20378
|
});
|
|
20078
|
-
leaseCommand.command("claim").description("Claim an idle member of an inventory. Fail-fast
|
|
20079
|
-
|
|
20080
|
-
|
|
20081
|
-
|
|
20082
|
-
|
|
20083
|
-
|
|
20084
|
-
|
|
20085
|
-
|
|
20086
|
-
|
|
20087
|
-
|
|
20088
|
-
|
|
20089
|
-
|
|
20090
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
|
|
20100
|
-
|
|
20101
|
-
|
|
20102
|
-
|
|
20379
|
+
leaseCommand.command("claim").description("Claim an idle member of an inventory. Fail-fast unless --wait is set.").argument("<inventory>", "Inventory slug").option("--ttl <duration>", "Lease TTL (overrides inventory default)").option("--for <tag>", "Free-form requester tag (session id, assignment slug, etc.)").option(
|
|
20380
|
+
"--wait <duration>",
|
|
20381
|
+
"Block up to <duration> waiting for an idle member. Backoff: 100ms \u2192 200ms \u2192 400ms \u2192 800ms \u2192 1s cap."
|
|
20382
|
+
).option("--json", "Output JSON instead of a one-line summary").action(
|
|
20383
|
+
async (inventory, opts) => {
|
|
20384
|
+
try {
|
|
20385
|
+
initLeasesDb();
|
|
20386
|
+
const detail = getInventoryDetail(inventory);
|
|
20387
|
+
if (!detail) {
|
|
20388
|
+
console.error(`Error: inventory '${inventory}' not found`);
|
|
20389
|
+
process.exit(1);
|
|
20390
|
+
return;
|
|
20391
|
+
}
|
|
20392
|
+
const ttl_s = parseDuration(opts.ttl, detail.inventory.default_ttl_s);
|
|
20393
|
+
const waitBudgetMs = opts.wait !== void 0 ? parseDuration(opts.wait, 0) * 1e3 : 0;
|
|
20394
|
+
const tryClaim = () => claimLease(inventory, ttl_s, opts.for);
|
|
20395
|
+
let result;
|
|
20396
|
+
if (waitBudgetMs <= 0) {
|
|
20397
|
+
result = tryClaim();
|
|
20398
|
+
} else {
|
|
20399
|
+
const deadline = Date.now() + waitBudgetMs;
|
|
20400
|
+
const backoffSchedule = [100, 200, 400, 800];
|
|
20401
|
+
let attempt = 0;
|
|
20402
|
+
while (true) {
|
|
20403
|
+
try {
|
|
20404
|
+
result = tryClaim();
|
|
20405
|
+
break;
|
|
20406
|
+
} catch (err2) {
|
|
20407
|
+
if (!(err2 instanceof NoIdleMemberError)) throw err2;
|
|
20408
|
+
const remaining = deadline - Date.now();
|
|
20409
|
+
if (remaining <= 0) {
|
|
20410
|
+
console.error(
|
|
20411
|
+
`Error: timed out waiting ${opts.wait} for an idle member of '${inventory}'`
|
|
20412
|
+
);
|
|
20413
|
+
process.exit(1);
|
|
20414
|
+
return;
|
|
20415
|
+
}
|
|
20416
|
+
const delay = Math.min(
|
|
20417
|
+
backoffSchedule[Math.min(attempt, backoffSchedule.length - 1)] ?? 1e3,
|
|
20418
|
+
1e3,
|
|
20419
|
+
remaining
|
|
20420
|
+
);
|
|
20421
|
+
attempt += 1;
|
|
20422
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
20423
|
+
}
|
|
20424
|
+
}
|
|
20425
|
+
}
|
|
20426
|
+
if (opts.json) {
|
|
20427
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20428
|
+
} else {
|
|
20429
|
+
console.log(
|
|
20430
|
+
`Claimed ${result.member_id} as ${result.lease_id} (expires ${result.expires_at})`
|
|
20431
|
+
);
|
|
20432
|
+
}
|
|
20433
|
+
} catch (error) {
|
|
20434
|
+
if (error instanceof NoIdleMemberError) {
|
|
20435
|
+
console.error(`Error: no idle members in '${error.inventorySlug}'`);
|
|
20436
|
+
process.exit(1);
|
|
20437
|
+
}
|
|
20438
|
+
if (error instanceof LeaseContentionError) {
|
|
20439
|
+
console.error(`Error: contention timeout on '${error.inventorySlug}'; retry`);
|
|
20440
|
+
process.exit(1);
|
|
20441
|
+
}
|
|
20442
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20443
|
+
console.error(`Error: ${message}`);
|
|
20103
20444
|
process.exit(1);
|
|
20104
20445
|
}
|
|
20105
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
20106
|
-
console.error(`Error: ${message}`);
|
|
20107
|
-
process.exit(1);
|
|
20108
20446
|
}
|
|
20109
|
-
|
|
20447
|
+
);
|
|
20110
20448
|
leaseCommand.command("release").description("Release a lease by its lease_id").argument("<lease-id>", "Opaque lease id returned by `claim`").action(async (leaseId) => {
|
|
20111
20449
|
try {
|
|
20112
20450
|
initLeasesDb();
|
|
@@ -20239,6 +20577,186 @@ leaseCommand.command("gc").description("Sweep expired leases across all inventor
|
|
|
20239
20577
|
process.exit(1);
|
|
20240
20578
|
}
|
|
20241
20579
|
});
|
|
20580
|
+
leaseCommand.command("revoke").description(
|
|
20581
|
+
"Force-release a lease (administrative). Idempotent \u2014 already-revoked exits 0."
|
|
20582
|
+
).argument("<lease-id>", "Lease id to revoke").action(async (leaseId) => {
|
|
20583
|
+
try {
|
|
20584
|
+
initLeasesDb();
|
|
20585
|
+
const existing = getLease(leaseId);
|
|
20586
|
+
if (!existing) {
|
|
20587
|
+
console.error(`Error: lease ${leaseId} not found`);
|
|
20588
|
+
process.exit(1);
|
|
20589
|
+
return;
|
|
20590
|
+
}
|
|
20591
|
+
if (existing.state === "revoked") {
|
|
20592
|
+
console.log(`already revoked ${leaseId}`);
|
|
20593
|
+
return;
|
|
20594
|
+
}
|
|
20595
|
+
const res = forceReleaseLease(leaseId);
|
|
20596
|
+
console.log(`revoked ${leaseId} (member_freed=${res.member_freed})`);
|
|
20597
|
+
} catch (error) {
|
|
20598
|
+
if (error instanceof NotFoundError) {
|
|
20599
|
+
console.error(`Error: lease ${error.id} not found`);
|
|
20600
|
+
process.exit(1);
|
|
20601
|
+
}
|
|
20602
|
+
if (error instanceof LeaseContentionError) {
|
|
20603
|
+
console.error(`Error: contention timeout; retry`);
|
|
20604
|
+
process.exit(1);
|
|
20605
|
+
}
|
|
20606
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20607
|
+
console.error(`Error: ${message}`);
|
|
20608
|
+
process.exit(1);
|
|
20609
|
+
}
|
|
20610
|
+
});
|
|
20611
|
+
leaseCommand.command("release-all").description("Release every active lease matching --for <tag>.").requiredOption("--for <tag>", "Requester tag to match against `requested_for`").option("--json", "Output JSON {released, stale} as id arrays").action(async (opts) => {
|
|
20612
|
+
try {
|
|
20613
|
+
initLeasesDb();
|
|
20614
|
+
const res = releaseLeasesByRequestedFor(opts.for);
|
|
20615
|
+
if (opts.json) {
|
|
20616
|
+
console.log(JSON.stringify(res, null, 2));
|
|
20617
|
+
return;
|
|
20618
|
+
}
|
|
20619
|
+
for (const lid of res.released) {
|
|
20620
|
+
console.log(`released ${lid}`);
|
|
20621
|
+
}
|
|
20622
|
+
for (const lid of res.stale) {
|
|
20623
|
+
console.log(`skipped ${lid} (stale)`);
|
|
20624
|
+
}
|
|
20625
|
+
console.log(
|
|
20626
|
+
`released ${res.released.length} lease(s) for tag "${opts.for}" (${res.stale.length} stale skipped)`
|
|
20627
|
+
);
|
|
20628
|
+
} catch (error) {
|
|
20629
|
+
if (error instanceof LeaseContentionError) {
|
|
20630
|
+
console.error(`Error: contention timeout; retry`);
|
|
20631
|
+
process.exit(1);
|
|
20632
|
+
}
|
|
20633
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20634
|
+
console.error(`Error: ${message}`);
|
|
20635
|
+
process.exit(1);
|
|
20636
|
+
}
|
|
20637
|
+
});
|
|
20638
|
+
leaseCommand.command("history").description(
|
|
20639
|
+
"Show lease_events. With a lease-id, print that lease's timeline; without, print the last N events across all leases."
|
|
20640
|
+
).argument("[lease-id]", "Optional lease id to filter on").option("--limit <n>", "Max events to return (default 50)", "50").option("--json", "Output JSON rows").action(
|
|
20641
|
+
async (leaseId, opts) => {
|
|
20642
|
+
try {
|
|
20643
|
+
initLeasesDb();
|
|
20644
|
+
const limit = Number.parseInt(opts.limit, 10);
|
|
20645
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
20646
|
+
throw new Error("--limit must be a positive integer");
|
|
20647
|
+
}
|
|
20648
|
+
const rows = getLeaseEvents(leaseId, limit);
|
|
20649
|
+
if (opts.json) {
|
|
20650
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
20651
|
+
return;
|
|
20652
|
+
}
|
|
20653
|
+
if (rows.length === 0) {
|
|
20654
|
+
console.log("(no events)");
|
|
20655
|
+
return;
|
|
20656
|
+
}
|
|
20657
|
+
for (const r of rows) {
|
|
20658
|
+
const detail = r.detail_json ? ` ${r.detail_json}` : "";
|
|
20659
|
+
console.log(`${r.at} ${r.event.padEnd(16)} ${r.lease_id}${detail}`);
|
|
20660
|
+
}
|
|
20661
|
+
} catch (error) {
|
|
20662
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20663
|
+
console.error(`Error: ${message}`);
|
|
20664
|
+
process.exit(1);
|
|
20665
|
+
}
|
|
20666
|
+
}
|
|
20667
|
+
);
|
|
20668
|
+
var inventoryCommand = leaseCommand.command("inventory").description("Manage existing inventories (update, delete)");
|
|
20669
|
+
inventoryCommand.command("update").description(
|
|
20670
|
+
"Update mutable inventory fields. `kind` is immutable in v1."
|
|
20671
|
+
).argument("<slug>", "Inventory slug").option("--default-ttl <duration>", "New default TTL (e.g. 30m, 2h)").option("--display-name <text>", "New display name").action(
|
|
20672
|
+
async (slug, opts) => {
|
|
20673
|
+
try {
|
|
20674
|
+
initLeasesDb();
|
|
20675
|
+
if (opts.defaultTtl === void 0 && opts.displayName === void 0) {
|
|
20676
|
+
throw new Error(
|
|
20677
|
+
"nothing to update \u2014 pass --default-ttl or --display-name"
|
|
20678
|
+
);
|
|
20679
|
+
}
|
|
20680
|
+
const patch = {};
|
|
20681
|
+
if (opts.defaultTtl !== void 0) {
|
|
20682
|
+
patch.default_ttl_s = parseDuration(opts.defaultTtl, 0);
|
|
20683
|
+
}
|
|
20684
|
+
if (opts.displayName !== void 0) {
|
|
20685
|
+
patch.display_name = opts.displayName;
|
|
20686
|
+
}
|
|
20687
|
+
const row = updateInventory(slug, patch);
|
|
20688
|
+
console.log(
|
|
20689
|
+
`Updated inventory '${row.slug}' (kind=${row.kind}, default_ttl=${row.default_ttl_s}s, display_name=${row.display_name ?? "(none)"}).`
|
|
20690
|
+
);
|
|
20691
|
+
} catch (error) {
|
|
20692
|
+
if (error instanceof NotFoundError) {
|
|
20693
|
+
console.error(`Error: inventory '${slug}' not found`);
|
|
20694
|
+
process.exit(1);
|
|
20695
|
+
}
|
|
20696
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20697
|
+
console.error(`Error: ${message}`);
|
|
20698
|
+
process.exit(1);
|
|
20699
|
+
}
|
|
20700
|
+
}
|
|
20701
|
+
);
|
|
20702
|
+
inventoryCommand.command("delete").description(
|
|
20703
|
+
"Delete an inventory. Refuses if any lease is active unless --force is set."
|
|
20704
|
+
).argument("<slug>", "Inventory slug").option("--force", "Revoke all active leases first, then cascade delete").action(async (slug, opts) => {
|
|
20705
|
+
try {
|
|
20706
|
+
initLeasesDb();
|
|
20707
|
+
const res = deleteInventory(slug, { force: opts.force });
|
|
20708
|
+
console.log(
|
|
20709
|
+
`deleted "${slug}" (revoked ${res.revoked} active lease(s))`
|
|
20710
|
+
);
|
|
20711
|
+
} catch (error) {
|
|
20712
|
+
if (error instanceof MemberInUseError) {
|
|
20713
|
+
console.error(
|
|
20714
|
+
`Error: inventory "${slug}" has active leases \u2014 use --force to revoke and delete`
|
|
20715
|
+
);
|
|
20716
|
+
process.exit(1);
|
|
20717
|
+
}
|
|
20718
|
+
if (error instanceof NotFoundError) {
|
|
20719
|
+
console.error(`Error: inventory '${slug}' not found`);
|
|
20720
|
+
process.exit(1);
|
|
20721
|
+
}
|
|
20722
|
+
if (error instanceof LeaseContentionError) {
|
|
20723
|
+
console.error(`Error: contention timeout; retry`);
|
|
20724
|
+
process.exit(1);
|
|
20725
|
+
}
|
|
20726
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20727
|
+
console.error(`Error: ${message}`);
|
|
20728
|
+
process.exit(1);
|
|
20729
|
+
}
|
|
20730
|
+
});
|
|
20731
|
+
memberCommand.command("list").description("List all members of an inventory (pure roster \u2014 no lease state).").argument("<inventory>", "Inventory slug").option("--json", "Output JSON rows").action(async (inventory, opts) => {
|
|
20732
|
+
try {
|
|
20733
|
+
initLeasesDb();
|
|
20734
|
+
const rows = listMembers(inventory);
|
|
20735
|
+
if (opts.json) {
|
|
20736
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
20737
|
+
return;
|
|
20738
|
+
}
|
|
20739
|
+
if (rows.length === 0) {
|
|
20740
|
+
console.log("(no members)");
|
|
20741
|
+
return;
|
|
20742
|
+
}
|
|
20743
|
+
for (const r of rows) {
|
|
20744
|
+
const meta = r.metadata_json ? ` ${r.metadata_json}` : "";
|
|
20745
|
+
const last = r.last_used_at ? ` last_used=${r.last_used_at}` : "";
|
|
20746
|
+
console.log(
|
|
20747
|
+
`${r.member_id.padEnd(24)} ${r.status.padEnd(8)} gen=${r.generation}${last}${meta}`
|
|
20748
|
+
);
|
|
20749
|
+
}
|
|
20750
|
+
} catch (error) {
|
|
20751
|
+
if (error instanceof NotFoundError) {
|
|
20752
|
+
console.error(`Error: inventory '${inventory}' not found`);
|
|
20753
|
+
process.exit(1);
|
|
20754
|
+
}
|
|
20755
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20756
|
+
console.error(`Error: ${message}`);
|
|
20757
|
+
process.exit(1);
|
|
20758
|
+
}
|
|
20759
|
+
});
|
|
20242
20760
|
|
|
20243
20761
|
// src/commands/request.ts
|
|
20244
20762
|
init_paths();
|
|
@@ -20515,7 +21033,7 @@ planCommand.command("version").description(
|
|
|
20515
21033
|
// src/commands/session.ts
|
|
20516
21034
|
init_fs();
|
|
20517
21035
|
import { Command as Command8 } from "commander";
|
|
20518
|
-
import { readFile as readFile37, readdir as readdir19, stat as
|
|
21036
|
+
import { readFile as readFile37, readdir as readdir19, stat as stat9 } from "fs/promises";
|
|
20519
21037
|
import { resolve as resolve56 } from "path";
|
|
20520
21038
|
async function readContext(cwd) {
|
|
20521
21039
|
const path = resolve56(cwd, ".syntaur", "context.json");
|
|
@@ -20536,7 +21054,7 @@ async function findLatestSessionSummary(assignmentDir) {
|
|
|
20536
21054
|
if (!entry.isDirectory()) continue;
|
|
20537
21055
|
const summaryPath = resolve56(sessionsRoot, entry.name, "summary.md");
|
|
20538
21056
|
if (!await fileExists(summaryPath)) continue;
|
|
20539
|
-
const st = await
|
|
21057
|
+
const st = await stat9(summaryPath);
|
|
20540
21058
|
if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
|
|
20541
21059
|
best = { sessionId: entry.name, path: summaryPath, mtime: st.mtime };
|
|
20542
21060
|
}
|
|
@@ -21127,19 +21645,19 @@ init_paths();
|
|
|
21127
21645
|
init_fs();
|
|
21128
21646
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
21129
21647
|
import { readFile as readFile42 } from "fs/promises";
|
|
21130
|
-
import { dirname as dirname16, join as
|
|
21131
|
-
import { spawn as
|
|
21648
|
+
import { dirname as dirname16, join as join9, resolve as resolve62 } from "path";
|
|
21649
|
+
import { spawn as spawn5 } from "child_process";
|
|
21132
21650
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
21133
21651
|
|
|
21134
21652
|
// src/utils/version.ts
|
|
21135
21653
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
21136
21654
|
import { readFile as readFile41 } from "fs/promises";
|
|
21137
|
-
import { dirname as dirname15, join as
|
|
21655
|
+
import { dirname as dirname15, join as join8 } from "path";
|
|
21138
21656
|
async function readPackageVersion(scriptUrl) {
|
|
21139
21657
|
try {
|
|
21140
21658
|
const scriptPath = fileURLToPath7(scriptUrl);
|
|
21141
21659
|
const pkgRoot = dirname15(dirname15(scriptPath));
|
|
21142
|
-
const raw = await readFile41(
|
|
21660
|
+
const raw = await readFile41(join8(pkgRoot, "package.json"), "utf-8");
|
|
21143
21661
|
const parsed = JSON.parse(raw);
|
|
21144
21662
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
21145
21663
|
} catch {
|
|
@@ -21183,7 +21701,7 @@ async function resolveNpmBin() {
|
|
|
21183
21701
|
const nodeDir = dirname16(process.execPath);
|
|
21184
21702
|
const isWin = process.platform === "win32";
|
|
21185
21703
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
21186
|
-
const nearNode =
|
|
21704
|
+
const nearNode = join9(nodeDir, npmName);
|
|
21187
21705
|
if (await fileExists(nearNode)) {
|
|
21188
21706
|
return { cmd: nearNode, shell: false };
|
|
21189
21707
|
}
|
|
@@ -21192,7 +21710,7 @@ async function resolveNpmBin() {
|
|
|
21192
21710
|
async function installGlobally() {
|
|
21193
21711
|
const { cmd, shell } = await resolveNpmBin();
|
|
21194
21712
|
return new Promise((resolvePromise) => {
|
|
21195
|
-
const child =
|
|
21713
|
+
const child = spawn5(cmd, ["install", "-g", "syntaur"], {
|
|
21196
21714
|
stdio: "inherit",
|
|
21197
21715
|
shell
|
|
21198
21716
|
});
|
|
@@ -21203,7 +21721,7 @@ async function installGlobally() {
|
|
|
21203
21721
|
async function readGlobalVersion() {
|
|
21204
21722
|
const { cmd, shell } = await resolveNpmBin();
|
|
21205
21723
|
const rootPath = await new Promise((resolvePromise) => {
|
|
21206
|
-
const child =
|
|
21724
|
+
const child = spawn5(cmd, ["root", "-g"], {
|
|
21207
21725
|
shell,
|
|
21208
21726
|
stdio: ["ignore", "pipe", "ignore"]
|
|
21209
21727
|
});
|
|
@@ -21226,7 +21744,7 @@ async function readGlobalVersion() {
|
|
|
21226
21744
|
});
|
|
21227
21745
|
if (!rootPath) return null;
|
|
21228
21746
|
try {
|
|
21229
|
-
const manifestPath =
|
|
21747
|
+
const manifestPath = join9(rootPath, "syntaur", "package.json");
|
|
21230
21748
|
if (!await fileExists(manifestPath)) return null;
|
|
21231
21749
|
const raw = await readFile42(manifestPath, "utf-8");
|
|
21232
21750
|
const parsed = JSON.parse(raw);
|
|
@@ -21398,7 +21916,7 @@ program.command("comment").description("Add a comment to an assignment (CLI-medi
|
|
|
21398
21916
|
process.exit(1);
|
|
21399
21917
|
}
|
|
21400
21918
|
});
|
|
21401
|
-
program.command("capture").description("Capture a typed proof artifact for an assignment").argument("[target]", "Assignment slug (with --project) or UUID; falls back to .syntaur/context.json").option("--kind <type>", "Artifact kind: screenshot | video | asciinema | http | text").option("--file <path>", "Source file to ingest (forbidden for --kind=text)").option("--criterion <index>", "Optional 0-based criterion index to tag").option("--note <text>", "Optional note (required for --kind=text)").option("--project <slug>", "Project slug if the target is project-nested").option("--dir <path>", "Override default project directory").action(async (target, options) => {
|
|
21919
|
+
program.command("capture").description("Capture a typed proof artifact for an assignment").argument("[target]", "Assignment slug (with --project) or UUID; falls back to .syntaur/context.json").option("--kind <type>", "Artifact kind: screenshot | video | asciinema | http | text").option("--file <path>", "Source file to ingest (forbidden for --kind=text)").option("--criterion <index>", "Optional 0-based criterion index to tag").option("--note <text>", "Optional note (required for --kind=text)").option("--project <slug>", "Project slug if the target is project-nested").option("--dir <path>", "Override default project directory").option("--interactive", "Screenshot mode: drag a region (macOS only)").option("--window", "Screenshot mode: window picker (macOS only)").option("--fullscreen", "Screenshot mode: silent full-screen capture (macOS only)").action(async (target, options) => {
|
|
21402
21920
|
try {
|
|
21403
21921
|
await captureCommand(target, options);
|
|
21404
21922
|
} catch (error) {
|