syntaur 0.12.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 +5 -1
- package/README.md +23 -0
- package/dashboard/dist/assets/{_basePickBy-XOV2F7dX.js → _basePickBy-qEDLAY_J.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-Dccfk3fK.js → _baseUniq-7tDs18Gn.js} +1 -1
- package/dashboard/dist/assets/{arc-TwJ5RF-H.js → arc-Brdw0dYW.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CpkVJudw.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-Ck6VQ4Fh.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-B5V7YG03.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
- package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-rBdyOxAe.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-DKlNXjYt.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-Cqm0PtUI.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-CQyXQcG3.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-DmqjHPpJ.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-CHXoBcYa.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-Dd4sChBS.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-D-QLnkY8.js → chunk-WL4C6EOR-D1pQmHqX.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-HFOb_XEt.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HFOb_XEt.js +1 -0
- package/dashboard/dist/assets/clone-BrYOwgIq.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-VZi6uV56.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CtT97Qhx.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BjYTNNHb.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-qj8_r6fb.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DlfWo7Vt.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Ch_nApTv.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-EOVsqNyi.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CS-lx0B-.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Ba6RDzPy.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
- package/dashboard/dist/assets/{graph-BKaAyLk-.js → graph-AdliIYqX.js} +1 -1
- package/dashboard/dist/assets/{index-BTWPZaIA.css → index-BoR_oyUZ.css} +1 -1
- package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Cr20xEsd.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CuILV9ph.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CO-l7Mpm.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CcirPw_S.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
- package/dashboard/dist/assets/{layout-BXvr8dMw.js → layout-B9r-gpD9.js} +1 -1
- package/dashboard/dist/assets/{linear-B85hgYnX.js → linear-CaaIRxVZ.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-DcZN9Xp2.js → mermaid.core-C8wCWYw-.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Bxi6p5_3.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DOSvU0UD.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Cmo-5tW9.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dw8alwuI.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-ZPB-rzOg.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-Ct8bkK2h.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DnGOXlmx.js → stateDiagram-RAJIS63D-x9gC6zy6.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CN5Dcv7u.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-AUFmbJjr.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-Dw5FDFAV.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DfxicU4N.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW--9N0V1r_.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +419 -84
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.d.ts +227 -0
- package/dist/db/leases-db.js +679 -0
- package/dist/db/leases-db.js.map +1 -0
- package/dist/index.js +1825 -445
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +5 -1
- package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
- package/platforms/claude-code/skills/extend-resource/SKILL.md +83 -0
- package/platforms/claude-code/skills/list-resources/SKILL.md +74 -0
- package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
- package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
- package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
- package/platforms/codex/skills/extend-resource/SKILL.md +83 -0
- package/platforms/codex/skills/list-resources/SKILL.md +74 -0
- package/platforms/codex/skills/release-resource/SKILL.md +65 -0
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
- package/skills/claim-resource/SKILL.md +89 -0
- package/skills/extend-resource/SKILL.md +83 -0
- package/skills/list-resources/SKILL.md +74 -0
- package/skills/release-resource/SKILL.md +65 -0
- package/skills/syntaur-protocol/SKILL.md +1 -0
- package/dashboard/dist/assets/channel-AEMTX70R.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-LzuOAGzu.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-LzuOAGzu.js +0 -1
- package/dashboard/dist/assets/clone-Cw63QGUJ.js +0 -1
- package/dashboard/dist/assets/index-Dk9PBm5B.js +0 -510
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-COGzlZxn.js +0 -1
package/dist/index.js
CHANGED
|
@@ -5961,7 +5961,7 @@ __export(parser_exports, {
|
|
|
5961
5961
|
});
|
|
5962
5962
|
import { randomBytes } from "crypto";
|
|
5963
5963
|
import { readFile as readFile13 } from "fs/promises";
|
|
5964
|
-
import { resolve as
|
|
5964
|
+
import { resolve as resolve19 } from "path";
|
|
5965
5965
|
function generateShortId() {
|
|
5966
5966
|
return randomBytes(2).toString("hex");
|
|
5967
5967
|
}
|
|
@@ -6199,10 +6199,10 @@ function serializeLogEntry(entry) {
|
|
|
6199
6199
|
return lines.join("\n");
|
|
6200
6200
|
}
|
|
6201
6201
|
function checklistPath(todosDir2, workspace) {
|
|
6202
|
-
return
|
|
6202
|
+
return resolve19(todosDir2, `${workspace}.md`);
|
|
6203
6203
|
}
|
|
6204
6204
|
function logPath(todosDir2, workspace) {
|
|
6205
|
-
return
|
|
6205
|
+
return resolve19(todosDir2, `${workspace}-log.md`);
|
|
6206
6206
|
}
|
|
6207
6207
|
function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
|
|
6208
6208
|
const year = now.getFullYear();
|
|
@@ -6226,7 +6226,7 @@ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new D
|
|
|
6226
6226
|
default:
|
|
6227
6227
|
suffix = `${year}-${month}-${day}`;
|
|
6228
6228
|
}
|
|
6229
|
-
return
|
|
6229
|
+
return resolve19(todosDir2, "archive", `${workspace}-${suffix}.md`);
|
|
6230
6230
|
}
|
|
6231
6231
|
async function readChecklist(todosDir2, workspace) {
|
|
6232
6232
|
const path = checklistPath(todosDir2, workspace);
|
|
@@ -6855,7 +6855,7 @@ __export(launch_exports, {
|
|
|
6855
6855
|
});
|
|
6856
6856
|
import { spawn as spawn2 } from "child_process";
|
|
6857
6857
|
import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
|
|
6858
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
6858
|
+
import { isAbsolute as isAbsolute3, resolve as resolve34 } from "path";
|
|
6859
6859
|
function formatFallbackCwdWarning(opts) {
|
|
6860
6860
|
const missing = [];
|
|
6861
6861
|
if (!opts.worktreePath) missing.push("worktreePath");
|
|
@@ -6899,8 +6899,8 @@ async function launchAgent(options) {
|
|
|
6899
6899
|
console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
|
|
6900
6900
|
process.exit(1);
|
|
6901
6901
|
}
|
|
6902
|
-
const projectDir =
|
|
6903
|
-
const assignmentDir =
|
|
6902
|
+
const projectDir = resolve34(projectsDir2, projectSlug);
|
|
6903
|
+
const assignmentDir = resolve34(projectDir, "assignments", assignmentSlug);
|
|
6904
6904
|
const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
|
|
6905
6905
|
const workspaceDir = resolvedFromWorkspace ?? process.cwd();
|
|
6906
6906
|
if (!cwdOverride) {
|
|
@@ -6912,7 +6912,7 @@ async function launchAgent(options) {
|
|
|
6912
6912
|
});
|
|
6913
6913
|
if (warning) console.warn(warning);
|
|
6914
6914
|
}
|
|
6915
|
-
const contextDir =
|
|
6915
|
+
const contextDir = resolve34(workspaceDir, ".syntaur");
|
|
6916
6916
|
await mkdir6(contextDir, { recursive: true });
|
|
6917
6917
|
const context = {
|
|
6918
6918
|
projectSlug,
|
|
@@ -6925,7 +6925,7 @@ async function launchAgent(options) {
|
|
|
6925
6925
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6926
6926
|
};
|
|
6927
6927
|
await writeFile9(
|
|
6928
|
-
|
|
6928
|
+
resolve34(contextDir, "context.json"),
|
|
6929
6929
|
JSON.stringify(context, null, 2) + "\n"
|
|
6930
6930
|
);
|
|
6931
6931
|
const { argv, shellFallbackWarning } = buildAgentArgv(
|
|
@@ -7082,7 +7082,7 @@ var init_git_worktree = __esm({
|
|
|
7082
7082
|
});
|
|
7083
7083
|
|
|
7084
7084
|
// src/index.ts
|
|
7085
|
-
import { Command as
|
|
7085
|
+
import { Command as Command13 } from "commander";
|
|
7086
7086
|
|
|
7087
7087
|
// src/commands/init.ts
|
|
7088
7088
|
init_paths();
|
|
@@ -7240,7 +7240,7 @@ init_create_assignment();
|
|
|
7240
7240
|
init_config2();
|
|
7241
7241
|
import { spawn } from "child_process";
|
|
7242
7242
|
import { createServer as createNetServer } from "net";
|
|
7243
|
-
import { resolve as
|
|
7243
|
+
import { resolve as resolve23, dirname as dirname7 } from "path";
|
|
7244
7244
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7245
7245
|
|
|
7246
7246
|
// src/dashboard/server.ts
|
|
@@ -7249,7 +7249,7 @@ init_api();
|
|
|
7249
7249
|
init_assignment_resolver();
|
|
7250
7250
|
import express from "express";
|
|
7251
7251
|
import { createServer } from "http";
|
|
7252
|
-
import { resolve as
|
|
7252
|
+
import { resolve as resolve22 } from "path";
|
|
7253
7253
|
import { writeFile as writeFile5, unlink as unlink4 } from "fs/promises";
|
|
7254
7254
|
import { WebSocketServer, WebSocket } from "ws";
|
|
7255
7255
|
|
|
@@ -7464,8 +7464,8 @@ function rowToSession(row) {
|
|
|
7464
7464
|
};
|
|
7465
7465
|
}
|
|
7466
7466
|
async function appendSession(_projectDir, session) {
|
|
7467
|
-
const
|
|
7468
|
-
|
|
7467
|
+
const db4 = getSessionDb();
|
|
7468
|
+
db4.prepare(`
|
|
7469
7469
|
INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path)
|
|
7470
7470
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7471
7471
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
@@ -7490,36 +7490,36 @@ async function appendSession(_projectDir, session) {
|
|
|
7490
7490
|
);
|
|
7491
7491
|
}
|
|
7492
7492
|
async function updateSessionStatus(_projectDir, sessionId, status) {
|
|
7493
|
-
const
|
|
7493
|
+
const db4 = getSessionDb();
|
|
7494
7494
|
const isTerminal = status === "completed" || status === "stopped";
|
|
7495
|
-
const result = isTerminal ?
|
|
7495
|
+
const result = isTerminal ? db4.prepare(
|
|
7496
7496
|
"UPDATE sessions SET status = ?, ended = datetime('now'), updated_at = datetime('now') WHERE session_id = ?"
|
|
7497
|
-
).run(status, sessionId) :
|
|
7497
|
+
).run(status, sessionId) : db4.prepare(
|
|
7498
7498
|
"UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE session_id = ?"
|
|
7499
7499
|
).run(status, sessionId);
|
|
7500
7500
|
return result.changes > 0;
|
|
7501
7501
|
}
|
|
7502
7502
|
async function listAllSessions(_projectsDir) {
|
|
7503
|
-
const
|
|
7504
|
-
const rows =
|
|
7503
|
+
const db4 = getSessionDb();
|
|
7504
|
+
const rows = db4.prepare("SELECT * FROM sessions ORDER BY started DESC").all();
|
|
7505
7505
|
return rows.map(rowToSession);
|
|
7506
7506
|
}
|
|
7507
7507
|
async function listProjectSessions(_projectsDir, projectSlug, assignmentSlug) {
|
|
7508
|
-
const
|
|
7508
|
+
const db4 = getSessionDb();
|
|
7509
7509
|
if (assignmentSlug) {
|
|
7510
|
-
const rows2 =
|
|
7510
|
+
const rows2 = db4.prepare(
|
|
7511
7511
|
"SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
|
|
7512
7512
|
).all(projectSlug, assignmentSlug);
|
|
7513
7513
|
return rows2.map(rowToSession);
|
|
7514
7514
|
}
|
|
7515
|
-
const rows =
|
|
7515
|
+
const rows = db4.prepare("SELECT * FROM sessions WHERE project_slug = ? ORDER BY started DESC").all(projectSlug);
|
|
7516
7516
|
return rows.map(rowToSession);
|
|
7517
7517
|
}
|
|
7518
7518
|
async function deleteSessions(sessionIds) {
|
|
7519
7519
|
if (sessionIds.length === 0) return 0;
|
|
7520
|
-
const
|
|
7520
|
+
const db4 = getSessionDb();
|
|
7521
7521
|
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
7522
|
-
const result =
|
|
7522
|
+
const result = db4.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`).run(...sessionIds);
|
|
7523
7523
|
return result.changes;
|
|
7524
7524
|
}
|
|
7525
7525
|
var DONE_ASSIGNMENT_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "review"]);
|
|
@@ -7535,8 +7535,8 @@ async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
|
7535
7535
|
);
|
|
7536
7536
|
}
|
|
7537
7537
|
async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
|
|
7538
|
-
const
|
|
7539
|
-
const activeSessions =
|
|
7538
|
+
const db4 = getSessionDb();
|
|
7539
|
+
const activeSessions = db4.prepare("SELECT * FROM sessions WHERE status = 'active' AND assignment_slug IS NOT NULL").all();
|
|
7540
7540
|
if (activeSessions.length === 0) return 0;
|
|
7541
7541
|
const assignmentStatuses = /* @__PURE__ */ new Map();
|
|
7542
7542
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7573,10 +7573,10 @@ async function reconcileActiveSessions(projectsDir2, assignmentsDir2) {
|
|
|
7573
7573
|
return totalUpdated;
|
|
7574
7574
|
}
|
|
7575
7575
|
async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
7576
|
-
const
|
|
7577
|
-
const rows = projectSlug === null ?
|
|
7576
|
+
const db4 = getSessionDb();
|
|
7577
|
+
const rows = projectSlug === null ? db4.prepare(
|
|
7578
7578
|
"SELECT * FROM sessions WHERE assignment_slug = ? AND project_slug IS NULL ORDER BY started DESC"
|
|
7579
|
-
).all(assignmentSlug) :
|
|
7579
|
+
).all(assignmentSlug) : db4.prepare(
|
|
7580
7580
|
"SELECT * FROM sessions WHERE project_slug = ? AND assignment_slug = ? ORDER BY started DESC"
|
|
7581
7581
|
).all(projectSlug, assignmentSlug);
|
|
7582
7582
|
return rows.map(rowToSession);
|
|
@@ -7584,9 +7584,9 @@ async function listSessionsByAssignment(projectSlug, assignmentSlug) {
|
|
|
7584
7584
|
|
|
7585
7585
|
// src/dashboard/watcher.ts
|
|
7586
7586
|
import { watch } from "chokidar";
|
|
7587
|
-
import { relative, sep } from "path";
|
|
7587
|
+
import { basename as basename2, dirname as dirname4, relative, sep } from "path";
|
|
7588
7588
|
function createWatcher(options) {
|
|
7589
|
-
const { projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir3, todosDir: todosDir2, onMessage, debounceMs = 300 } = options;
|
|
7589
|
+
const { projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, serversDir: serversDir2, playbooksDir: playbooksDir3, todosDir: todosDir2, dbPath, onMessage, debounceMs = 300 } = options;
|
|
7590
7590
|
const pendingEvents = /* @__PURE__ */ new Map();
|
|
7591
7591
|
const projectsWatcher = watch(projectsDir2, {
|
|
7592
7592
|
ignoreInitial: true,
|
|
@@ -7754,6 +7754,38 @@ function createWatcher(options) {
|
|
|
7754
7754
|
todosWatcher.on("add", handleTodoChange2);
|
|
7755
7755
|
todosWatcher.on("unlink", handleTodoChange2);
|
|
7756
7756
|
}
|
|
7757
|
+
let leasesDbWatcher = null;
|
|
7758
|
+
if (dbPath) {
|
|
7759
|
+
let handleDbChange2 = function(filePath) {
|
|
7760
|
+
if (!basename2(filePath).startsWith(dbBase)) return;
|
|
7761
|
+
const debounceKey = "__leases-db__";
|
|
7762
|
+
const existing = pendingEvents.get(debounceKey);
|
|
7763
|
+
if (existing) clearTimeout(existing);
|
|
7764
|
+
pendingEvents.set(
|
|
7765
|
+
debounceKey,
|
|
7766
|
+
setTimeout(() => {
|
|
7767
|
+
pendingEvents.delete(debounceKey);
|
|
7768
|
+
const message = {
|
|
7769
|
+
type: "leases-updated",
|
|
7770
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7771
|
+
};
|
|
7772
|
+
onMessage(message);
|
|
7773
|
+
}, debounceMs)
|
|
7774
|
+
);
|
|
7775
|
+
};
|
|
7776
|
+
var handleDbChange = handleDbChange2;
|
|
7777
|
+
const dbDir = dirname4(dbPath);
|
|
7778
|
+
const dbBase = basename2(dbPath);
|
|
7779
|
+
leasesDbWatcher = watch(dbDir, {
|
|
7780
|
+
ignoreInitial: true,
|
|
7781
|
+
persistent: true,
|
|
7782
|
+
depth: 0,
|
|
7783
|
+
ignored: /(^|[\/\\])\../
|
|
7784
|
+
});
|
|
7785
|
+
leasesDbWatcher.on("change", handleDbChange2);
|
|
7786
|
+
leasesDbWatcher.on("add", handleDbChange2);
|
|
7787
|
+
leasesDbWatcher.on("unlink", handleDbChange2);
|
|
7788
|
+
}
|
|
7757
7789
|
return {
|
|
7758
7790
|
close: async () => {
|
|
7759
7791
|
pendingEvents.forEach((timeout) => {
|
|
@@ -7765,6 +7797,7 @@ function createWatcher(options) {
|
|
|
7765
7797
|
if (serversWatcher) await serversWatcher.close();
|
|
7766
7798
|
if (playbooksWatcher) await playbooksWatcher.close();
|
|
7767
7799
|
if (todosWatcher) await todosWatcher.close();
|
|
7800
|
+
if (leasesDbWatcher) await leasesDbWatcher.close();
|
|
7768
7801
|
}
|
|
7769
7802
|
};
|
|
7770
7803
|
}
|
|
@@ -7782,7 +7815,7 @@ init_timestamp();
|
|
|
7782
7815
|
init_fs();
|
|
7783
7816
|
init_parser();
|
|
7784
7817
|
import { Router } from "express";
|
|
7785
|
-
import { resolve as resolve15, basename as
|
|
7818
|
+
import { resolve as resolve15, basename as basename3 } from "path";
|
|
7786
7819
|
import { rm, readFile as readFile11, open as fsOpen } from "fs/promises";
|
|
7787
7820
|
|
|
7788
7821
|
// src/dashboard/acceptance-criteria.ts
|
|
@@ -8067,7 +8100,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
|
|
|
8067
8100
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
8068
8101
|
return;
|
|
8069
8102
|
}
|
|
8070
|
-
const document = await getEditableDocument(projectsDir2, "memory",
|
|
8103
|
+
const document = await getEditableDocument(projectsDir2, "memory", basename3(projectDir), itemSlug);
|
|
8071
8104
|
if (!document) {
|
|
8072
8105
|
res.status(404).json({ error: "Memory not found" });
|
|
8073
8106
|
return;
|
|
@@ -8086,7 +8119,7 @@ function createWriteRouter(projectsDir2, assignmentsDir2) {
|
|
|
8086
8119
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
8087
8120
|
return;
|
|
8088
8121
|
}
|
|
8089
|
-
const document = await getEditableDocument(projectsDir2, "resource",
|
|
8122
|
+
const document = await getEditableDocument(projectsDir2, "resource", basename3(projectDir), itemSlug);
|
|
8090
8123
|
if (!document) {
|
|
8091
8124
|
res.status(404).json({ error: "Resource not found" });
|
|
8092
8125
|
return;
|
|
@@ -8171,7 +8204,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
8171
8204
|
let content = renderItemStub(kind, {
|
|
8172
8205
|
slug: requestedSlug,
|
|
8173
8206
|
name,
|
|
8174
|
-
projectSlug:
|
|
8207
|
+
projectSlug: basename3(projectDir),
|
|
8175
8208
|
timestamp
|
|
8176
8209
|
});
|
|
8177
8210
|
const customBody = typeof body.body === "string" ? body.body : "";
|
|
@@ -8188,13 +8221,13 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
8188
8221
|
} catch (err2) {
|
|
8189
8222
|
if (err2.code === "EEXIST") {
|
|
8190
8223
|
res.status(409).json({
|
|
8191
|
-
error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${
|
|
8224
|
+
error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename3(projectDir)}".`
|
|
8192
8225
|
});
|
|
8193
8226
|
return;
|
|
8194
8227
|
}
|
|
8195
8228
|
throw err2;
|
|
8196
8229
|
}
|
|
8197
|
-
res.status(201).json({ slug: requestedSlug, projectSlug:
|
|
8230
|
+
res.status(201).json({ slug: requestedSlug, projectSlug: basename3(projectDir), content });
|
|
8198
8231
|
} catch (error) {
|
|
8199
8232
|
console.error(`Error creating ${kind}:`, error);
|
|
8200
8233
|
res.status(500).json({ error: `Failed to create ${kind}: ${error.message}` });
|
|
@@ -8229,7 +8262,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
8229
8262
|
${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
8230
8263
|
merged = setTopLevelField(merged, "updated", nowTimestamp());
|
|
8231
8264
|
await writeFileForce(filePath, merged);
|
|
8232
|
-
const detail = await getItemDetail(kind,
|
|
8265
|
+
const detail = await getItemDetail(kind, basename3(projectDir), itemSlug);
|
|
8233
8266
|
res.json({ [kind]: detail, content: merged });
|
|
8234
8267
|
} catch (error) {
|
|
8235
8268
|
console.error(`Error updating ${kind}:`, error);
|
|
@@ -9639,6 +9672,65 @@ function createServersRouter(serversDir2, projectsDir2, assignmentsDir2) {
|
|
|
9639
9672
|
import { Router as Router3 } from "express";
|
|
9640
9673
|
import { resolve as resolve16 } from "path";
|
|
9641
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
|
|
9642
9734
|
function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
9643
9735
|
const router = Router3();
|
|
9644
9736
|
router.get("/", async (_req, res) => {
|
|
@@ -9686,6 +9778,8 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
9686
9778
|
return;
|
|
9687
9779
|
}
|
|
9688
9780
|
}
|
|
9781
|
+
const derivedPath = await derivePathFromTranscript(transcriptPath);
|
|
9782
|
+
const recordedPath = derivedPath ?? path ?? "";
|
|
9689
9783
|
const session = {
|
|
9690
9784
|
projectSlug: projectSlug || null,
|
|
9691
9785
|
assignmentSlug: assignmentSlug || null,
|
|
@@ -9693,7 +9787,7 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
9693
9787
|
sessionId,
|
|
9694
9788
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9695
9789
|
status: "active",
|
|
9696
|
-
path:
|
|
9790
|
+
path: recordedPath,
|
|
9697
9791
|
description: description || null,
|
|
9698
9792
|
transcriptPath: transcriptPath || null
|
|
9699
9793
|
};
|
|
@@ -9744,6 +9838,692 @@ function createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2) {
|
|
|
9744
9838
|
return router;
|
|
9745
9839
|
}
|
|
9746
9840
|
|
|
9841
|
+
// src/dashboard/api-leases.ts
|
|
9842
|
+
import { Router as Router4 } from "express";
|
|
9843
|
+
|
|
9844
|
+
// src/db/leases-db.ts
|
|
9845
|
+
init_paths();
|
|
9846
|
+
import Database2 from "better-sqlite3";
|
|
9847
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
9848
|
+
import { resolve as resolve17 } from "path";
|
|
9849
|
+
var db2 = null;
|
|
9850
|
+
var LEASE_SCHEMA_VERSION = "1";
|
|
9851
|
+
var SCHEMA_SQL2 = `
|
|
9852
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
9853
|
+
key TEXT PRIMARY KEY,
|
|
9854
|
+
value TEXT
|
|
9855
|
+
);
|
|
9856
|
+
|
|
9857
|
+
CREATE TABLE IF NOT EXISTS inventories (
|
|
9858
|
+
slug TEXT PRIMARY KEY,
|
|
9859
|
+
kind TEXT NOT NULL,
|
|
9860
|
+
display_name TEXT,
|
|
9861
|
+
default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),
|
|
9862
|
+
created_at TEXT NOT NULL
|
|
9863
|
+
);
|
|
9864
|
+
|
|
9865
|
+
CREATE TABLE IF NOT EXISTS inventory_members (
|
|
9866
|
+
inventory_slug TEXT NOT NULL,
|
|
9867
|
+
member_id TEXT NOT NULL,
|
|
9868
|
+
status TEXT NOT NULL DEFAULT 'idle'
|
|
9869
|
+
CHECK (status IN ('idle','leased','retired')),
|
|
9870
|
+
generation INTEGER NOT NULL DEFAULT 0,
|
|
9871
|
+
metadata_json TEXT,
|
|
9872
|
+
last_used_at TEXT,
|
|
9873
|
+
retired_at TEXT,
|
|
9874
|
+
PRIMARY KEY (inventory_slug, member_id),
|
|
9875
|
+
FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)
|
|
9876
|
+
);
|
|
9877
|
+
|
|
9878
|
+
CREATE INDEX IF NOT EXISTS idx_members_idle
|
|
9879
|
+
ON inventory_members (inventory_slug, status, last_used_at);
|
|
9880
|
+
|
|
9881
|
+
CREATE TABLE IF NOT EXISTS leases (
|
|
9882
|
+
lease_id TEXT PRIMARY KEY,
|
|
9883
|
+
inventory_slug TEXT NOT NULL,
|
|
9884
|
+
member_id TEXT NOT NULL,
|
|
9885
|
+
member_gen INTEGER NOT NULL,
|
|
9886
|
+
state TEXT NOT NULL
|
|
9887
|
+
CHECK (state IN ('active','released','expired','revoked')),
|
|
9888
|
+
granted_at TEXT NOT NULL,
|
|
9889
|
+
expires_at TEXT NOT NULL,
|
|
9890
|
+
released_at TEXT,
|
|
9891
|
+
requested_for TEXT,
|
|
9892
|
+
FOREIGN KEY (inventory_slug, member_id)
|
|
9893
|
+
REFERENCES inventory_members(inventory_slug, member_id)
|
|
9894
|
+
);
|
|
9895
|
+
|
|
9896
|
+
CREATE INDEX IF NOT EXISTS idx_leases_gc
|
|
9897
|
+
ON leases (state, expires_at);
|
|
9898
|
+
|
|
9899
|
+
CREATE TABLE IF NOT EXISTS lease_events (
|
|
9900
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
9901
|
+
lease_id TEXT NOT NULL,
|
|
9902
|
+
event TEXT NOT NULL,
|
|
9903
|
+
at TEXT NOT NULL,
|
|
9904
|
+
detail_json TEXT,
|
|
9905
|
+
FOREIGN KEY (lease_id) REFERENCES leases(lease_id)
|
|
9906
|
+
);
|
|
9907
|
+
|
|
9908
|
+
CREATE INDEX IF NOT EXISTS idx_events_lease
|
|
9909
|
+
ON lease_events (lease_id, at);
|
|
9910
|
+
`;
|
|
9911
|
+
var NoIdleMemberError = class extends Error {
|
|
9912
|
+
constructor(inventorySlug) {
|
|
9913
|
+
super(`no idle members in '${inventorySlug}'`);
|
|
9914
|
+
this.inventorySlug = inventorySlug;
|
|
9915
|
+
this.name = "NoIdleMemberError";
|
|
9916
|
+
}
|
|
9917
|
+
};
|
|
9918
|
+
var StaleLeaseError = class extends Error {
|
|
9919
|
+
constructor(leaseId) {
|
|
9920
|
+
super(`lease ${leaseId} is no longer active (expired or revoked)`);
|
|
9921
|
+
this.leaseId = leaseId;
|
|
9922
|
+
this.name = "StaleLeaseError";
|
|
9923
|
+
}
|
|
9924
|
+
};
|
|
9925
|
+
var NotFoundError = class extends Error {
|
|
9926
|
+
constructor(id) {
|
|
9927
|
+
super(`lease ${id} not found`);
|
|
9928
|
+
this.id = id;
|
|
9929
|
+
this.name = "NotFoundError";
|
|
9930
|
+
}
|
|
9931
|
+
};
|
|
9932
|
+
var LeaseContentionError = class extends Error {
|
|
9933
|
+
constructor(inventorySlug) {
|
|
9934
|
+
super(`contention timeout on '${inventorySlug}'; retry`);
|
|
9935
|
+
this.inventorySlug = inventorySlug;
|
|
9936
|
+
this.name = "LeaseContentionError";
|
|
9937
|
+
}
|
|
9938
|
+
};
|
|
9939
|
+
var MemberInUseError = class extends Error {
|
|
9940
|
+
constructor(inventorySlug, memberId) {
|
|
9941
|
+
super(`member ${inventorySlug}/${memberId} is currently leased; release before retiring`);
|
|
9942
|
+
this.inventorySlug = inventorySlug;
|
|
9943
|
+
this.memberId = memberId;
|
|
9944
|
+
this.name = "MemberInUseError";
|
|
9945
|
+
}
|
|
9946
|
+
};
|
|
9947
|
+
var DuplicateInventoryError = class extends Error {
|
|
9948
|
+
constructor(slug) {
|
|
9949
|
+
super(`inventory '${slug}' already exists`);
|
|
9950
|
+
this.slug = slug;
|
|
9951
|
+
this.name = "DuplicateInventoryError";
|
|
9952
|
+
}
|
|
9953
|
+
};
|
|
9954
|
+
var DuplicateMemberError = class extends Error {
|
|
9955
|
+
constructor(inventorySlug, memberId) {
|
|
9956
|
+
super(`member ${inventorySlug}/${memberId} already exists`);
|
|
9957
|
+
this.inventorySlug = inventorySlug;
|
|
9958
|
+
this.memberId = memberId;
|
|
9959
|
+
this.name = "DuplicateMemberError";
|
|
9960
|
+
}
|
|
9961
|
+
};
|
|
9962
|
+
function nowIso() {
|
|
9963
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9964
|
+
}
|
|
9965
|
+
function isBusyError(err2) {
|
|
9966
|
+
if (!err2 || typeof err2 !== "object") return false;
|
|
9967
|
+
const e = err2;
|
|
9968
|
+
return e.code === "SQLITE_BUSY" || e.code === "SQLITE_BUSY_SNAPSHOT";
|
|
9969
|
+
}
|
|
9970
|
+
function initLeasesDb(dbPath) {
|
|
9971
|
+
if (db2) return db2;
|
|
9972
|
+
const finalPath = dbPath ?? resolve17(syntaurRoot(), "syntaur.db");
|
|
9973
|
+
db2 = new Database2(finalPath);
|
|
9974
|
+
db2.pragma("journal_mode = WAL");
|
|
9975
|
+
db2.pragma("busy_timeout = 5000");
|
|
9976
|
+
db2.pragma("foreign_keys = ON");
|
|
9977
|
+
const database = db2;
|
|
9978
|
+
const runMigrations = database.transaction(() => {
|
|
9979
|
+
database.exec(SCHEMA_SQL2);
|
|
9980
|
+
database.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run("lease_schema_version", LEASE_SCHEMA_VERSION);
|
|
9981
|
+
});
|
|
9982
|
+
runMigrations.exclusive();
|
|
9983
|
+
return db2;
|
|
9984
|
+
}
|
|
9985
|
+
function getLeasesDb() {
|
|
9986
|
+
if (!db2) {
|
|
9987
|
+
throw new Error("Leases database not initialized. Call initLeasesDb() first.");
|
|
9988
|
+
}
|
|
9989
|
+
return db2;
|
|
9990
|
+
}
|
|
9991
|
+
function closeLeasesDb() {
|
|
9992
|
+
if (db2) {
|
|
9993
|
+
db2.close();
|
|
9994
|
+
db2 = null;
|
|
9995
|
+
}
|
|
9996
|
+
}
|
|
9997
|
+
function createInventory(input4) {
|
|
9998
|
+
const database = getLeasesDb();
|
|
9999
|
+
const row = {
|
|
10000
|
+
slug: input4.slug,
|
|
10001
|
+
kind: input4.kind,
|
|
10002
|
+
display_name: input4.display_name ?? null,
|
|
10003
|
+
default_ttl_s: input4.default_ttl_s,
|
|
10004
|
+
created_at: nowIso()
|
|
10005
|
+
};
|
|
10006
|
+
try {
|
|
10007
|
+
database.prepare(
|
|
10008
|
+
`INSERT INTO inventories (slug, kind, display_name, default_ttl_s, created_at)
|
|
10009
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
10010
|
+
).run(row.slug, row.kind, row.display_name, row.default_ttl_s, row.created_at);
|
|
10011
|
+
} catch (err2) {
|
|
10012
|
+
if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
|
|
10013
|
+
throw new DuplicateInventoryError(input4.slug);
|
|
10014
|
+
}
|
|
10015
|
+
throw err2;
|
|
10016
|
+
}
|
|
10017
|
+
return row;
|
|
10018
|
+
}
|
|
10019
|
+
function listInventories() {
|
|
10020
|
+
const database = getLeasesDb();
|
|
10021
|
+
return database.prepare(
|
|
10022
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
10023
|
+
FROM inventories ORDER BY slug`
|
|
10024
|
+
).all();
|
|
10025
|
+
}
|
|
10026
|
+
function getInventoryDetail(slug) {
|
|
10027
|
+
const database = getLeasesDb();
|
|
10028
|
+
const inv = database.prepare(
|
|
10029
|
+
`SELECT slug, kind, display_name, default_ttl_s, created_at
|
|
10030
|
+
FROM inventories WHERE slug = ?`
|
|
10031
|
+
).get(slug);
|
|
10032
|
+
if (!inv) return null;
|
|
10033
|
+
const members = database.prepare(
|
|
10034
|
+
`SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at
|
|
10035
|
+
FROM inventory_members WHERE inventory_slug = ?
|
|
10036
|
+
ORDER BY member_id`
|
|
10037
|
+
).all(slug);
|
|
10038
|
+
const active_leases = database.prepare(
|
|
10039
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for
|
|
10040
|
+
FROM leases WHERE inventory_slug = ? AND state = 'active'
|
|
10041
|
+
ORDER BY granted_at`
|
|
10042
|
+
).all(slug);
|
|
10043
|
+
return { inventory: inv, members, active_leases };
|
|
10044
|
+
}
|
|
10045
|
+
function addMember(input4) {
|
|
10046
|
+
const database = getLeasesDb();
|
|
10047
|
+
const row = {
|
|
10048
|
+
inventory_slug: input4.inventory_slug,
|
|
10049
|
+
member_id: input4.member_id,
|
|
10050
|
+
status: "idle",
|
|
10051
|
+
generation: 0,
|
|
10052
|
+
metadata_json: input4.metadata ? JSON.stringify(input4.metadata) : null,
|
|
10053
|
+
last_used_at: null,
|
|
10054
|
+
retired_at: null
|
|
10055
|
+
};
|
|
10056
|
+
try {
|
|
10057
|
+
database.prepare(
|
|
10058
|
+
`INSERT INTO inventory_members
|
|
10059
|
+
(inventory_slug, member_id, status, generation, metadata_json)
|
|
10060
|
+
VALUES (?, ?, 'idle', 0, ?)`
|
|
10061
|
+
).run(row.inventory_slug, row.member_id, row.metadata_json);
|
|
10062
|
+
} catch (err2) {
|
|
10063
|
+
if (err2 instanceof Error && err2.message.includes("UNIQUE")) {
|
|
10064
|
+
throw new DuplicateMemberError(input4.inventory_slug, input4.member_id);
|
|
10065
|
+
}
|
|
10066
|
+
throw err2;
|
|
10067
|
+
}
|
|
10068
|
+
return row;
|
|
10069
|
+
}
|
|
10070
|
+
function retireMember(inventorySlug, memberId) {
|
|
10071
|
+
const database = getLeasesDb();
|
|
10072
|
+
const retire = database.transaction(() => {
|
|
10073
|
+
const row = database.prepare(
|
|
10074
|
+
`SELECT status FROM inventory_members
|
|
10075
|
+
WHERE inventory_slug = ? AND member_id = ?`
|
|
10076
|
+
).get(inventorySlug, memberId);
|
|
10077
|
+
if (!row) throw new NotFoundError(`${inventorySlug}/${memberId}`);
|
|
10078
|
+
if (row.status === "leased") {
|
|
10079
|
+
throw new MemberInUseError(inventorySlug, memberId);
|
|
10080
|
+
}
|
|
10081
|
+
database.prepare(
|
|
10082
|
+
`UPDATE inventory_members
|
|
10083
|
+
SET status = 'retired', generation = generation + 1, retired_at = ?
|
|
10084
|
+
WHERE inventory_slug = ? AND member_id = ?`
|
|
10085
|
+
).run(nowIso(), inventorySlug, memberId);
|
|
10086
|
+
});
|
|
10087
|
+
retire.immediate();
|
|
10088
|
+
}
|
|
10089
|
+
function claimLease(slug, ttl_s, requested_for) {
|
|
10090
|
+
const database = getLeasesDb();
|
|
10091
|
+
try {
|
|
10092
|
+
const fn = database.transaction(() => {
|
|
10093
|
+
const now = nowIso();
|
|
10094
|
+
database.prepare(
|
|
10095
|
+
`UPDATE leases
|
|
10096
|
+
SET state = 'expired'
|
|
10097
|
+
WHERE state = 'active'
|
|
10098
|
+
AND expires_at <= ?
|
|
10099
|
+
AND inventory_slug = ?`
|
|
10100
|
+
).run(now, slug);
|
|
10101
|
+
database.prepare(
|
|
10102
|
+
`UPDATE inventory_members
|
|
10103
|
+
SET status = 'idle', generation = generation + 1
|
|
10104
|
+
WHERE inventory_slug = ?
|
|
10105
|
+
AND status = 'leased'
|
|
10106
|
+
AND (inventory_slug, member_id) IN (
|
|
10107
|
+
SELECT inventory_slug, member_id
|
|
10108
|
+
FROM leases
|
|
10109
|
+
WHERE inventory_slug = ?
|
|
10110
|
+
AND state = 'expired'
|
|
10111
|
+
AND released_at IS NULL
|
|
10112
|
+
)
|
|
10113
|
+
AND generation = (
|
|
10114
|
+
SELECT member_gen FROM leases l
|
|
10115
|
+
WHERE l.inventory_slug = inventory_members.inventory_slug
|
|
10116
|
+
AND l.member_id = inventory_members.member_id
|
|
10117
|
+
AND l.state = 'expired'
|
|
10118
|
+
AND l.released_at IS NULL
|
|
10119
|
+
ORDER BY l.granted_at DESC LIMIT 1
|
|
10120
|
+
)`
|
|
10121
|
+
).run(slug, slug);
|
|
10122
|
+
database.prepare(
|
|
10123
|
+
`UPDATE leases
|
|
10124
|
+
SET released_at = ?
|
|
10125
|
+
WHERE state = 'expired'
|
|
10126
|
+
AND released_at IS NULL
|
|
10127
|
+
AND inventory_slug = ?`
|
|
10128
|
+
).run(now, slug);
|
|
10129
|
+
const picked = database.prepare(
|
|
10130
|
+
`UPDATE inventory_members
|
|
10131
|
+
SET status = 'leased',
|
|
10132
|
+
generation = generation + 1,
|
|
10133
|
+
last_used_at = ?
|
|
10134
|
+
WHERE (inventory_slug, member_id) = (
|
|
10135
|
+
SELECT inventory_slug, member_id
|
|
10136
|
+
FROM inventory_members
|
|
10137
|
+
WHERE inventory_slug = ? AND status = 'idle'
|
|
10138
|
+
ORDER BY last_used_at ASC NULLS FIRST
|
|
10139
|
+
LIMIT 1
|
|
10140
|
+
)
|
|
10141
|
+
RETURNING member_id, generation, metadata_json`
|
|
10142
|
+
).get(now, slug);
|
|
10143
|
+
if (!picked) {
|
|
10144
|
+
throw new NoIdleMemberError(slug);
|
|
10145
|
+
}
|
|
10146
|
+
const lease_id = randomUUID2();
|
|
10147
|
+
const expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
|
|
10148
|
+
database.prepare(
|
|
10149
|
+
`INSERT INTO leases
|
|
10150
|
+
(lease_id, inventory_slug, member_id, member_gen,
|
|
10151
|
+
state, granted_at, expires_at, requested_for)
|
|
10152
|
+
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`
|
|
10153
|
+
).run(
|
|
10154
|
+
lease_id,
|
|
10155
|
+
slug,
|
|
10156
|
+
picked.member_id,
|
|
10157
|
+
picked.generation,
|
|
10158
|
+
now,
|
|
10159
|
+
expires_at,
|
|
10160
|
+
requested_for ?? null
|
|
10161
|
+
);
|
|
10162
|
+
database.prepare(
|
|
10163
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
10164
|
+
VALUES (?, 'claimed', ?, ?)`
|
|
10165
|
+
).run(lease_id, now, JSON.stringify({ member_id: picked.member_id }));
|
|
10166
|
+
return {
|
|
10167
|
+
lease_id,
|
|
10168
|
+
inventory_slug: slug,
|
|
10169
|
+
member_id: picked.member_id,
|
|
10170
|
+
member_gen: picked.generation,
|
|
10171
|
+
granted_at: now,
|
|
10172
|
+
expires_at,
|
|
10173
|
+
metadata: picked.metadata_json ? JSON.parse(picked.metadata_json) : null
|
|
10174
|
+
};
|
|
10175
|
+
});
|
|
10176
|
+
return fn.immediate();
|
|
10177
|
+
} catch (err2) {
|
|
10178
|
+
if (isBusyError(err2)) throw new LeaseContentionError(slug);
|
|
10179
|
+
throw err2;
|
|
10180
|
+
}
|
|
10181
|
+
}
|
|
10182
|
+
function releaseLease(lease_id) {
|
|
10183
|
+
const database = getLeasesDb();
|
|
10184
|
+
try {
|
|
10185
|
+
const fn = database.transaction(() => {
|
|
10186
|
+
const now = nowIso();
|
|
10187
|
+
const memberRes = database.prepare(
|
|
10188
|
+
`UPDATE inventory_members
|
|
10189
|
+
SET status = 'idle', generation = generation + 1
|
|
10190
|
+
WHERE (inventory_slug, member_id) = (
|
|
10191
|
+
SELECT inventory_slug, member_id FROM leases
|
|
10192
|
+
WHERE lease_id = ? AND state = 'active'
|
|
10193
|
+
)
|
|
10194
|
+
AND generation = (
|
|
10195
|
+
SELECT member_gen FROM leases
|
|
10196
|
+
WHERE lease_id = ? AND state = 'active'
|
|
10197
|
+
)`
|
|
10198
|
+
).run(lease_id, lease_id);
|
|
10199
|
+
if (memberRes.changes === 0) {
|
|
10200
|
+
throw new StaleLeaseError(lease_id);
|
|
10201
|
+
}
|
|
10202
|
+
database.prepare(
|
|
10203
|
+
`UPDATE leases
|
|
10204
|
+
SET state = 'released', released_at = ?
|
|
10205
|
+
WHERE lease_id = ? AND state = 'active'`
|
|
10206
|
+
).run(now, lease_id);
|
|
10207
|
+
database.prepare(
|
|
10208
|
+
`INSERT INTO lease_events (lease_id, event, at)
|
|
10209
|
+
VALUES (?, 'released', ?)`
|
|
10210
|
+
).run(lease_id, now);
|
|
10211
|
+
});
|
|
10212
|
+
fn.immediate();
|
|
10213
|
+
} catch (err2) {
|
|
10214
|
+
if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
|
|
10215
|
+
throw err2;
|
|
10216
|
+
}
|
|
10217
|
+
}
|
|
10218
|
+
function extendLease(lease_id, ttl_s) {
|
|
10219
|
+
const database = getLeasesDb();
|
|
10220
|
+
try {
|
|
10221
|
+
const fn = database.transaction(() => {
|
|
10222
|
+
const now = nowIso();
|
|
10223
|
+
const new_expires_at = new Date(Date.now() + ttl_s * 1e3).toISOString();
|
|
10224
|
+
const res = database.prepare(
|
|
10225
|
+
`UPDATE leases
|
|
10226
|
+
SET expires_at = ?
|
|
10227
|
+
WHERE lease_id = ?
|
|
10228
|
+
AND state = 'active'
|
|
10229
|
+
AND expires_at > ?
|
|
10230
|
+
AND member_gen = (
|
|
10231
|
+
SELECT generation FROM inventory_members
|
|
10232
|
+
WHERE inventory_slug = leases.inventory_slug
|
|
10233
|
+
AND member_id = leases.member_id
|
|
10234
|
+
)`
|
|
10235
|
+
).run(new_expires_at, lease_id, now);
|
|
10236
|
+
if (res.changes === 0) {
|
|
10237
|
+
throw new StaleLeaseError(lease_id);
|
|
10238
|
+
}
|
|
10239
|
+
database.prepare(
|
|
10240
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
10241
|
+
VALUES (?, 'extended', ?, ?)`
|
|
10242
|
+
).run(lease_id, now, JSON.stringify({ new_expires_at }));
|
|
10243
|
+
return { new_expires_at };
|
|
10244
|
+
});
|
|
10245
|
+
return fn.immediate();
|
|
10246
|
+
} catch (err2) {
|
|
10247
|
+
if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
|
|
10248
|
+
throw err2;
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
function getLease(lease_id) {
|
|
10252
|
+
const database = getLeasesDb();
|
|
10253
|
+
const row = database.prepare(
|
|
10254
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state,
|
|
10255
|
+
granted_at, expires_at, released_at, requested_for
|
|
10256
|
+
FROM leases WHERE lease_id = ?`
|
|
10257
|
+
).get(lease_id);
|
|
10258
|
+
return row ?? null;
|
|
10259
|
+
}
|
|
10260
|
+
function listLeases(filter) {
|
|
10261
|
+
const database = getLeasesDb();
|
|
10262
|
+
const where = [];
|
|
10263
|
+
const params2 = [];
|
|
10264
|
+
if (filter?.inventory) {
|
|
10265
|
+
where.push("inventory_slug = ?");
|
|
10266
|
+
params2.push(filter.inventory);
|
|
10267
|
+
}
|
|
10268
|
+
if (filter?.state) {
|
|
10269
|
+
where.push("state = ?");
|
|
10270
|
+
params2.push(filter.state);
|
|
10271
|
+
}
|
|
10272
|
+
const whereSql = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
10273
|
+
return database.prepare(
|
|
10274
|
+
`SELECT lease_id, inventory_slug, member_id, member_gen, state,
|
|
10275
|
+
granted_at, expires_at, released_at, requested_for
|
|
10276
|
+
FROM leases ${whereSql}
|
|
10277
|
+
ORDER BY granted_at DESC`
|
|
10278
|
+
).all(...params2);
|
|
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
|
+
}
|
|
10307
|
+
function gcExpiredLeases() {
|
|
10308
|
+
const database = getLeasesDb();
|
|
10309
|
+
try {
|
|
10310
|
+
const fn = database.transaction(() => {
|
|
10311
|
+
const now = nowIso();
|
|
10312
|
+
const expRes = database.prepare(
|
|
10313
|
+
`UPDATE leases SET state = 'expired'
|
|
10314
|
+
WHERE state = 'active' AND expires_at <= ?`
|
|
10315
|
+
).run(now);
|
|
10316
|
+
database.prepare(
|
|
10317
|
+
`UPDATE inventory_members
|
|
10318
|
+
SET status = 'idle', generation = generation + 1
|
|
10319
|
+
WHERE status = 'leased'
|
|
10320
|
+
AND (inventory_slug, member_id) IN (
|
|
10321
|
+
SELECT inventory_slug, member_id FROM leases
|
|
10322
|
+
WHERE state = 'expired' AND released_at IS NULL
|
|
10323
|
+
)
|
|
10324
|
+
AND generation = (
|
|
10325
|
+
SELECT member_gen FROM leases l
|
|
10326
|
+
WHERE l.inventory_slug = inventory_members.inventory_slug
|
|
10327
|
+
AND l.member_id = inventory_members.member_id
|
|
10328
|
+
AND l.state = 'expired'
|
|
10329
|
+
AND l.released_at IS NULL
|
|
10330
|
+
ORDER BY l.granted_at DESC LIMIT 1
|
|
10331
|
+
)`
|
|
10332
|
+
).run();
|
|
10333
|
+
database.prepare(
|
|
10334
|
+
`UPDATE leases SET released_at = ?
|
|
10335
|
+
WHERE state = 'expired' AND released_at IS NULL AND expires_at <= ?`
|
|
10336
|
+
).run(now, now);
|
|
10337
|
+
return expRes.changes;
|
|
10338
|
+
});
|
|
10339
|
+
return fn.immediate();
|
|
10340
|
+
} catch (err2) {
|
|
10341
|
+
if (isBusyError(err2)) throw new LeaseContentionError("gc");
|
|
10342
|
+
throw err2;
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
function forceReleaseLease(lease_id) {
|
|
10346
|
+
const database = getLeasesDb();
|
|
10347
|
+
try {
|
|
10348
|
+
const fn = database.transaction(() => {
|
|
10349
|
+
const now = nowIso();
|
|
10350
|
+
const lookup = database.prepare(
|
|
10351
|
+
`SELECT inventory_slug, member_id, member_gen, state
|
|
10352
|
+
FROM leases WHERE lease_id = ?`
|
|
10353
|
+
).get(lease_id);
|
|
10354
|
+
if (!lookup) throw new NotFoundError(lease_id);
|
|
10355
|
+
if (lookup.state === "revoked") return { member_freed: false };
|
|
10356
|
+
const memberRes = database.prepare(
|
|
10357
|
+
`UPDATE inventory_members
|
|
10358
|
+
SET status = 'idle', generation = generation + 1
|
|
10359
|
+
WHERE inventory_slug = ? AND member_id = ?
|
|
10360
|
+
AND generation = ?`
|
|
10361
|
+
).run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);
|
|
10362
|
+
database.prepare(
|
|
10363
|
+
`UPDATE leases
|
|
10364
|
+
SET state = 'revoked', released_at = ?
|
|
10365
|
+
WHERE lease_id = ? AND state != 'revoked'`
|
|
10366
|
+
).run(now, lease_id);
|
|
10367
|
+
const member_freed = memberRes.changes > 0;
|
|
10368
|
+
database.prepare(
|
|
10369
|
+
`INSERT INTO lease_events (lease_id, event, at, detail_json)
|
|
10370
|
+
VALUES (?, 'force_released', ?, ?)`
|
|
10371
|
+
).run(lease_id, now, JSON.stringify({ member_freed }));
|
|
10372
|
+
return { member_freed };
|
|
10373
|
+
});
|
|
10374
|
+
return fn.immediate();
|
|
10375
|
+
} catch (err2) {
|
|
10376
|
+
if (isBusyError(err2)) throw new LeaseContentionError(lease_id);
|
|
10377
|
+
throw err2;
|
|
10378
|
+
}
|
|
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
|
+
}
|
|
10470
|
+
|
|
10471
|
+
// src/dashboard/api-leases.ts
|
|
10472
|
+
function createLeasesRouter(broadcast) {
|
|
10473
|
+
const router = Router4();
|
|
10474
|
+
router.get("/", (_req, res) => {
|
|
10475
|
+
try {
|
|
10476
|
+
initLeasesDb();
|
|
10477
|
+
const inventories = listInventories();
|
|
10478
|
+
const details = inventories.map((inv) => getInventoryDetail(inv.slug)).filter((d) => d !== null);
|
|
10479
|
+
res.json({ inventories: details });
|
|
10480
|
+
} catch (error) {
|
|
10481
|
+
res.status(500).json({
|
|
10482
|
+
error: error instanceof Error ? error.message : "List failed"
|
|
10483
|
+
});
|
|
10484
|
+
}
|
|
10485
|
+
});
|
|
10486
|
+
router.get("/:slug", (req, res) => {
|
|
10487
|
+
try {
|
|
10488
|
+
initLeasesDb();
|
|
10489
|
+
const detail = getInventoryDetail(req.params.slug);
|
|
10490
|
+
if (!detail) {
|
|
10491
|
+
res.status(404).json({ error: "Inventory not found" });
|
|
10492
|
+
return;
|
|
10493
|
+
}
|
|
10494
|
+
res.json(detail);
|
|
10495
|
+
} catch (error) {
|
|
10496
|
+
res.status(500).json({
|
|
10497
|
+
error: error instanceof Error ? error.message : "Read failed"
|
|
10498
|
+
});
|
|
10499
|
+
}
|
|
10500
|
+
});
|
|
10501
|
+
router.post("/:slug/force-release/:lease_id", (req, res) => {
|
|
10502
|
+
try {
|
|
10503
|
+
initLeasesDb();
|
|
10504
|
+
const result = forceReleaseLease(req.params.lease_id);
|
|
10505
|
+
broadcast?.({
|
|
10506
|
+
type: "leases-updated",
|
|
10507
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10508
|
+
});
|
|
10509
|
+
res.json({ ...result, lease_id: req.params.lease_id });
|
|
10510
|
+
} catch (error) {
|
|
10511
|
+
if (error instanceof NotFoundError) {
|
|
10512
|
+
res.status(404).json({ error: "Lease not found" });
|
|
10513
|
+
return;
|
|
10514
|
+
}
|
|
10515
|
+
if (error instanceof LeaseContentionError) {
|
|
10516
|
+
res.status(503).json({ error: "Contention timeout; retry" });
|
|
10517
|
+
return;
|
|
10518
|
+
}
|
|
10519
|
+
res.status(500).json({
|
|
10520
|
+
error: error instanceof Error ? error.message : "Force release failed"
|
|
10521
|
+
});
|
|
10522
|
+
}
|
|
10523
|
+
});
|
|
10524
|
+
return router;
|
|
10525
|
+
}
|
|
10526
|
+
|
|
9747
10527
|
// src/dashboard/api-playbooks.ts
|
|
9748
10528
|
init_api();
|
|
9749
10529
|
init_parser();
|
|
@@ -9752,11 +10532,11 @@ init_timestamp();
|
|
|
9752
10532
|
init_fs();
|
|
9753
10533
|
init_playbook();
|
|
9754
10534
|
init_playbooks();
|
|
9755
|
-
import { Router as
|
|
9756
|
-
import { resolve as
|
|
10535
|
+
import { Router as Router5 } from "express";
|
|
10536
|
+
import { resolve as resolve18 } from "path";
|
|
9757
10537
|
import { readFile as readFile12, unlink as unlink2 } from "fs/promises";
|
|
9758
10538
|
function createPlaybooksRouter(playbooksDir3) {
|
|
9759
|
-
const router =
|
|
10539
|
+
const router = Router5();
|
|
9760
10540
|
router.get("/", async (_req, res) => {
|
|
9761
10541
|
try {
|
|
9762
10542
|
const playbooks = await listPlaybooks(playbooksDir3);
|
|
@@ -9823,7 +10603,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9823
10603
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9824
10604
|
return;
|
|
9825
10605
|
}
|
|
9826
|
-
const filePath =
|
|
10606
|
+
const filePath = resolve18(playbooksDir3, resolved.filename);
|
|
9827
10607
|
const content = await readFile12(filePath, "utf-8");
|
|
9828
10608
|
res.json({
|
|
9829
10609
|
documentType: "playbook",
|
|
@@ -9849,7 +10629,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9849
10629
|
return;
|
|
9850
10630
|
}
|
|
9851
10631
|
await ensureDir(playbooksDir3);
|
|
9852
|
-
const filePath =
|
|
10632
|
+
const filePath = resolve18(playbooksDir3, `${slug}.md`);
|
|
9853
10633
|
if (await fileExists(filePath)) {
|
|
9854
10634
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
9855
10635
|
return;
|
|
@@ -9873,7 +10653,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9873
10653
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9874
10654
|
return;
|
|
9875
10655
|
}
|
|
9876
|
-
const filePath =
|
|
10656
|
+
const filePath = resolve18(playbooksDir3, resolved.filename);
|
|
9877
10657
|
await writeFileForce(filePath, content);
|
|
9878
10658
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
9879
10659
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -9892,7 +10672,7 @@ function createPlaybooksRouter(playbooksDir3) {
|
|
|
9892
10672
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9893
10673
|
return;
|
|
9894
10674
|
}
|
|
9895
|
-
const filePath =
|
|
10675
|
+
const filePath = resolve18(playbooksDir3, resolved.filename);
|
|
9896
10676
|
await unlink2(filePath);
|
|
9897
10677
|
await removeFromDisabledList(resolved.slug);
|
|
9898
10678
|
await rebuildPlaybookManifest(playbooksDir3);
|
|
@@ -9911,9 +10691,9 @@ init_fs_migration();
|
|
|
9911
10691
|
init_parser2();
|
|
9912
10692
|
init_fs();
|
|
9913
10693
|
init_paths();
|
|
9914
|
-
import { Router as
|
|
10694
|
+
import { Router as Router6 } from "express";
|
|
9915
10695
|
import { readdir as readdir9 } from "fs/promises";
|
|
9916
|
-
import { resolve as resolvePath, dirname as
|
|
10696
|
+
import { resolve as resolvePath, dirname as dirname5 } from "path";
|
|
9917
10697
|
import { rename as rename3, mkdir as mkdir2 } from "fs/promises";
|
|
9918
10698
|
|
|
9919
10699
|
// src/dashboard/todos-locks.ts
|
|
@@ -9959,7 +10739,7 @@ function touchItem(item) {
|
|
|
9959
10739
|
item.updatedAt = now;
|
|
9960
10740
|
}
|
|
9961
10741
|
function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
9962
|
-
const router =
|
|
10742
|
+
const router = Router6();
|
|
9963
10743
|
function broadcastUpdate() {
|
|
9964
10744
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
9965
10745
|
}
|
|
@@ -10089,7 +10869,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
10089
10869
|
router.post("/:workspace/archive", async (req, res) => {
|
|
10090
10870
|
try {
|
|
10091
10871
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
10092
|
-
const { resolve:
|
|
10872
|
+
const { resolve: resolve63 } = await import("path");
|
|
10093
10873
|
const { readFile: readFile43 } = await import("fs/promises");
|
|
10094
10874
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
10095
10875
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
@@ -10106,7 +10886,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
10106
10886
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
10107
10887
|
);
|
|
10108
10888
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
10109
|
-
await ensureDir(
|
|
10889
|
+
await ensureDir(resolve63(todosDir2, "archive"));
|
|
10110
10890
|
let archContent = "";
|
|
10111
10891
|
if (await fileExists(archFile)) {
|
|
10112
10892
|
archContent = await readFile43(archFile, "utf-8");
|
|
@@ -10513,7 +11293,7 @@ workspace: ${workspace}
|
|
|
10513
11293
|
if (await fileExists(newPlanDir)) {
|
|
10514
11294
|
return { status: 409, error: "plan dir already exists in target" };
|
|
10515
11295
|
}
|
|
10516
|
-
await mkdir2(
|
|
11296
|
+
await mkdir2(dirname5(newPlanDir), { recursive: true });
|
|
10517
11297
|
await rename3(item.planDir, newPlanDir);
|
|
10518
11298
|
item.planDir = newPlanDir;
|
|
10519
11299
|
}
|
|
@@ -10589,9 +11369,9 @@ init_parser2();
|
|
|
10589
11369
|
init_fs();
|
|
10590
11370
|
init_paths();
|
|
10591
11371
|
init_slug();
|
|
10592
|
-
import { Router as
|
|
11372
|
+
import { Router as Router7 } from "express";
|
|
10593
11373
|
import { mkdir as mkdir3, readFile as readFile14, rename as rename4 } from "fs/promises";
|
|
10594
|
-
import { resolve as
|
|
11374
|
+
import { resolve as resolve20, dirname as dirname6 } from "path";
|
|
10595
11375
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
10596
11376
|
function touchItem2(item) {
|
|
10597
11377
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -10606,7 +11386,7 @@ function params(req) {
|
|
|
10606
11386
|
return req.params;
|
|
10607
11387
|
}
|
|
10608
11388
|
async function projectExists(projectsDir2, slug) {
|
|
10609
|
-
return fileExists(
|
|
11389
|
+
return fileExists(resolve20(projectsDir2, slug, "project.md"));
|
|
10610
11390
|
}
|
|
10611
11391
|
async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
10612
11392
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
@@ -10623,7 +11403,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
|
10623
11403
|
throw err2;
|
|
10624
11404
|
}
|
|
10625
11405
|
try {
|
|
10626
|
-
await mkdir3(
|
|
11406
|
+
await mkdir3(resolve20(todosDir2, "archive"), { recursive: false });
|
|
10627
11407
|
} catch (err2) {
|
|
10628
11408
|
const code = err2.code;
|
|
10629
11409
|
if (code === "EEXIST") return;
|
|
@@ -10639,7 +11419,7 @@ function notFound(res, slug) {
|
|
|
10639
11419
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
10640
11420
|
}
|
|
10641
11421
|
function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
10642
|
-
const router =
|
|
11422
|
+
const router = Router7({ mergeParams: true });
|
|
10643
11423
|
function broadcastUpdate(projectSlug) {
|
|
10644
11424
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10645
11425
|
}
|
|
@@ -11247,18 +12027,18 @@ workspace: ${slug}
|
|
|
11247
12027
|
if (tg.includes("/")) {
|
|
11248
12028
|
const parts = tg.split("/");
|
|
11249
12029
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
11250
|
-
assignmentDir =
|
|
12030
|
+
assignmentDir = resolve20(projectsDir2, parts[0], "assignments", parts[1]);
|
|
11251
12031
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
11252
12032
|
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
11253
|
-
assignmentDir =
|
|
12033
|
+
assignmentDir = resolve20(assignmentsDirFn(), tg);
|
|
11254
12034
|
assignmentRef = tg;
|
|
11255
12035
|
} else {
|
|
11256
12036
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
11257
12037
|
}
|
|
11258
|
-
const assignmentMdPath3 =
|
|
12038
|
+
const assignmentMdPath3 = resolve20(assignmentDir, "assignment.md");
|
|
11259
12039
|
if (!await fileExists(assignmentMdPath3)) return { error: `Target assignment not found: ${assignmentMdPath3}` };
|
|
11260
12040
|
}
|
|
11261
|
-
const assignmentMdPath2 =
|
|
12041
|
+
const assignmentMdPath2 = resolve20(assignmentDir, "assignment.md");
|
|
11262
12042
|
let content = await readFile14(assignmentMdPath2, "utf-8");
|
|
11263
12043
|
content = appendTodosToAssignmentBody2(
|
|
11264
12044
|
content,
|
|
@@ -11388,7 +12168,7 @@ workspace: ${slug}
|
|
|
11388
12168
|
if (await fileExists(newPlanDir)) {
|
|
11389
12169
|
return { status: 409, error: "plan dir already exists in target" };
|
|
11390
12170
|
}
|
|
11391
|
-
await mkdir3(
|
|
12171
|
+
await mkdir3(dirname6(newPlanDir), { recursive: true });
|
|
11392
12172
|
await rename4(item.planDir, newPlanDir);
|
|
11393
12173
|
item.planDir = newPlanDir;
|
|
11394
12174
|
}
|
|
@@ -11451,7 +12231,7 @@ workspace: ${slug}
|
|
|
11451
12231
|
|
|
11452
12232
|
// src/dashboard/api-backup.ts
|
|
11453
12233
|
init_config2();
|
|
11454
|
-
import { Router as
|
|
12234
|
+
import { Router as Router8 } from "express";
|
|
11455
12235
|
|
|
11456
12236
|
// src/utils/github-backup.ts
|
|
11457
12237
|
init_paths();
|
|
@@ -11459,8 +12239,8 @@ init_fs();
|
|
|
11459
12239
|
init_config2();
|
|
11460
12240
|
import { execFile as execFile2 } from "child_process";
|
|
11461
12241
|
import { promisify as promisify2 } from "util";
|
|
11462
|
-
import { cp, mkdtemp, rm as rm2, readFile as readFile15, writeFile as writeFile4, unlink as unlink3, stat, open, rename as rename5 } from "fs/promises";
|
|
11463
|
-
import { resolve as
|
|
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";
|
|
12243
|
+
import { resolve as resolve21, join as join2 } from "path";
|
|
11464
12244
|
import { tmpdir } from "os";
|
|
11465
12245
|
var exec2 = promisify2(execFile2);
|
|
11466
12246
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -11500,7 +12280,7 @@ async function resolveCategoryPath(category) {
|
|
|
11500
12280
|
case "servers":
|
|
11501
12281
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
11502
12282
|
case "config":
|
|
11503
|
-
return { sourcePath:
|
|
12283
|
+
return { sourcePath: resolve21(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
11504
12284
|
}
|
|
11505
12285
|
}
|
|
11506
12286
|
async function checkGitInstalled() {
|
|
@@ -11511,10 +12291,10 @@ async function checkGitInstalled() {
|
|
|
11511
12291
|
}
|
|
11512
12292
|
}
|
|
11513
12293
|
async function acquireLock() {
|
|
11514
|
-
const lockPath =
|
|
12294
|
+
const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
|
|
11515
12295
|
await ensureDir(syntaurRoot());
|
|
11516
12296
|
try {
|
|
11517
|
-
const handle = await
|
|
12297
|
+
const handle = await open2(lockPath, "wx");
|
|
11518
12298
|
await handle.write(String(process.pid));
|
|
11519
12299
|
await handle.close();
|
|
11520
12300
|
return lockPath;
|
|
@@ -11558,7 +12338,7 @@ async function copyRecursive(src, dest) {
|
|
|
11558
12338
|
await ensureDir(dest);
|
|
11559
12339
|
await cp(src, dest, { recursive: true, force: true });
|
|
11560
12340
|
} else {
|
|
11561
|
-
await ensureDir(
|
|
12341
|
+
await ensureDir(resolve21(dest, ".."));
|
|
11562
12342
|
await cp(src, dest, { force: true });
|
|
11563
12343
|
}
|
|
11564
12344
|
}
|
|
@@ -11606,7 +12386,7 @@ async function backupToGithub(overrides) {
|
|
|
11606
12386
|
}
|
|
11607
12387
|
if (category === "config") {
|
|
11608
12388
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
11609
|
-
await ensureDir(
|
|
12389
|
+
await ensureDir(resolve21(destPath, ".."));
|
|
11610
12390
|
await writeFile4(destPath, sanitized, "utf-8");
|
|
11611
12391
|
} else {
|
|
11612
12392
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -11660,7 +12440,7 @@ async function backupToGithub(overrides) {
|
|
|
11660
12440
|
}
|
|
11661
12441
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
11662
12442
|
if (isFile) {
|
|
11663
|
-
await ensureDir(
|
|
12443
|
+
await ensureDir(resolve21(localPath, ".."));
|
|
11664
12444
|
await cp(repoSrcPath, localPath, { force: true });
|
|
11665
12445
|
return;
|
|
11666
12446
|
}
|
|
@@ -11761,7 +12541,7 @@ async function restoreFromGithub(overrides) {
|
|
|
11761
12541
|
}
|
|
11762
12542
|
async function getBackupStatus() {
|
|
11763
12543
|
const config = await readConfig();
|
|
11764
|
-
const lockPath =
|
|
12544
|
+
const lockPath = resolve21(syntaurRoot(), LOCK_FILE_NAME);
|
|
11765
12545
|
const locked = await fileExists(lockPath);
|
|
11766
12546
|
return {
|
|
11767
12547
|
repo: config.backup?.repo ?? null,
|
|
@@ -11774,7 +12554,7 @@ async function getBackupStatus() {
|
|
|
11774
12554
|
|
|
11775
12555
|
// src/dashboard/api-backup.ts
|
|
11776
12556
|
function createBackupRouter() {
|
|
11777
|
-
const router =
|
|
12557
|
+
const router = Router8();
|
|
11778
12558
|
router.get("/", async (_req, res) => {
|
|
11779
12559
|
try {
|
|
11780
12560
|
const status = await getBackupStatus();
|
|
@@ -12095,10 +12875,11 @@ function createDashboardServer(options) {
|
|
|
12095
12875
|
migrateFromMarkdown(projectsDir2).catch((err2) => {
|
|
12096
12876
|
console.error("Session migration from markdown failed:", err2);
|
|
12097
12877
|
});
|
|
12878
|
+
initLeasesDb();
|
|
12098
12879
|
(async () => {
|
|
12099
12880
|
try {
|
|
12100
12881
|
const configResult = await migrateLegacyConfig(
|
|
12101
|
-
|
|
12882
|
+
resolve22(syntaurRoot(), "config.md")
|
|
12102
12883
|
);
|
|
12103
12884
|
const projectResult = await migrateLegacyProjectFiles(projectsDir2);
|
|
12104
12885
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -12428,6 +13209,7 @@ function createDashboardServer(options) {
|
|
|
12428
13209
|
});
|
|
12429
13210
|
app.use(createWriteRouter(projectsDir2, assignmentsDir2));
|
|
12430
13211
|
app.use("/api/servers", createServersRouter(serversDir2, projectsDir2, assignmentsDir2));
|
|
13212
|
+
app.use("/api/leases", createLeasesRouter(broadcast));
|
|
12431
13213
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir2, broadcast, assignmentsDir2));
|
|
12432
13214
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir3));
|
|
12433
13215
|
app.get("/api/memories", async (_req, res) => {
|
|
@@ -12453,14 +13235,14 @@ function createDashboardServer(options) {
|
|
|
12453
13235
|
app.use("/api/backup", createBackupRouter());
|
|
12454
13236
|
if (serveStaticUi && dashboardDistPath) {
|
|
12455
13237
|
const sendOpts = { dotfiles: "allow" };
|
|
12456
|
-
app.use("/assets", express.static(
|
|
13238
|
+
app.use("/assets", express.static(resolve22(dashboardDistPath, "assets"), sendOpts));
|
|
12457
13239
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
12458
13240
|
app.get("{*path}", async (req, res) => {
|
|
12459
13241
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
12460
13242
|
res.status(404).json({ error: "Not Found" });
|
|
12461
13243
|
return;
|
|
12462
13244
|
}
|
|
12463
|
-
const indexPath =
|
|
13245
|
+
const indexPath = resolve22(dashboardDistPath, "index.html");
|
|
12464
13246
|
if (!await fileExists(indexPath)) {
|
|
12465
13247
|
res.status(503).send(
|
|
12466
13248
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -12484,6 +13266,7 @@ function createDashboardServer(options) {
|
|
|
12484
13266
|
serversDir: serversDir2,
|
|
12485
13267
|
playbooksDir: playbooksDir3,
|
|
12486
13268
|
todosDir: todosDir2,
|
|
13269
|
+
dbPath: resolve22(syntaurRoot(), "syntaur.db"),
|
|
12487
13270
|
onMessage: broadcast
|
|
12488
13271
|
});
|
|
12489
13272
|
startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -12498,7 +13281,7 @@ function createDashboardServer(options) {
|
|
|
12498
13281
|
}
|
|
12499
13282
|
});
|
|
12500
13283
|
server.listen(port, () => {
|
|
12501
|
-
const portFile =
|
|
13284
|
+
const portFile = resolve22(syntaurRoot(), "dashboard-port");
|
|
12502
13285
|
writeFile5(portFile, String(port), "utf-8").catch(() => {
|
|
12503
13286
|
});
|
|
12504
13287
|
resolvePromise();
|
|
@@ -12511,11 +13294,12 @@ function createDashboardServer(options) {
|
|
|
12511
13294
|
await watcherHandle.close();
|
|
12512
13295
|
}
|
|
12513
13296
|
closeSessionDb();
|
|
13297
|
+
closeLeasesDb();
|
|
12514
13298
|
for (const client of clients) {
|
|
12515
13299
|
client.terminate();
|
|
12516
13300
|
}
|
|
12517
13301
|
clients.clear();
|
|
12518
|
-
const portFile =
|
|
13302
|
+
const portFile = resolve22(syntaurRoot(), "dashboard-port");
|
|
12519
13303
|
await unlink4(portFile).catch(() => {
|
|
12520
13304
|
});
|
|
12521
13305
|
server.closeAllConnections?.();
|
|
@@ -12595,8 +13379,8 @@ async function dashboardCommand(options) {
|
|
|
12595
13379
|
port = availablePort;
|
|
12596
13380
|
}
|
|
12597
13381
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
12598
|
-
const packageRoot =
|
|
12599
|
-
const dashboardDist =
|
|
13382
|
+
const packageRoot = resolve23(dirname7(thisFile), "..");
|
|
13383
|
+
const dashboardDist = resolve23(packageRoot, "dashboard", "dist");
|
|
12600
13384
|
const server = createDashboardServer({
|
|
12601
13385
|
port,
|
|
12602
13386
|
projectsDir: projectsDir2,
|
|
@@ -12610,8 +13394,8 @@ async function dashboardCommand(options) {
|
|
|
12610
13394
|
await server.start();
|
|
12611
13395
|
let viteProcess = null;
|
|
12612
13396
|
if (mode === "dev") {
|
|
12613
|
-
const dashboardDir =
|
|
12614
|
-
const viteBin =
|
|
13397
|
+
const dashboardDir = resolve23(packageRoot, "dashboard");
|
|
13398
|
+
const viteBin = resolve23(dashboardDir, "node_modules", ".bin", "vite");
|
|
12615
13399
|
if (!await fileExists(viteBin)) {
|
|
12616
13400
|
console.error(
|
|
12617
13401
|
'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
|
|
@@ -12686,7 +13470,7 @@ init_config2();
|
|
|
12686
13470
|
init_slug();
|
|
12687
13471
|
init_lifecycle();
|
|
12688
13472
|
init_assignment_resolver();
|
|
12689
|
-
import { resolve as
|
|
13473
|
+
import { resolve as resolve24 } from "path";
|
|
12690
13474
|
async function runTransition(assignment, command, options = {}) {
|
|
12691
13475
|
const config = await readConfig();
|
|
12692
13476
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
@@ -12697,8 +13481,8 @@ async function runTransition(assignment, command, options = {}) {
|
|
|
12697
13481
|
if (!isValidSlug(assignment)) {
|
|
12698
13482
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
12699
13483
|
}
|
|
12700
|
-
const projectDir =
|
|
12701
|
-
const projectMdPath =
|
|
13484
|
+
const projectDir = resolve24(baseDir, options.project);
|
|
13485
|
+
const projectMdPath = resolve24(projectDir, "project.md");
|
|
12702
13486
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
12703
13487
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
12704
13488
|
}
|
|
@@ -12729,8 +13513,8 @@ async function runAssign(assignment, agent, options = {}) {
|
|
|
12729
13513
|
if (!isValidSlug(assignment)) {
|
|
12730
13514
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
12731
13515
|
}
|
|
12732
|
-
const projectDir =
|
|
12733
|
-
const projectMdPath =
|
|
13516
|
+
const projectDir = resolve24(baseDir, options.project);
|
|
13517
|
+
const projectMdPath = resolve24(projectDir, "project.md");
|
|
12734
13518
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
12735
13519
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
12736
13520
|
}
|
|
@@ -12825,20 +13609,20 @@ import {
|
|
|
12825
13609
|
} from "fs/promises";
|
|
12826
13610
|
import { existsSync } from "fs";
|
|
12827
13611
|
import { homedir as homedir2 } from "os";
|
|
12828
|
-
import { basename as
|
|
13612
|
+
import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve26 } from "path";
|
|
12829
13613
|
|
|
12830
13614
|
// src/utils/package-root.ts
|
|
12831
13615
|
init_fs();
|
|
12832
|
-
import { dirname as
|
|
13616
|
+
import { dirname as dirname8, resolve as resolve25 } from "path";
|
|
12833
13617
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12834
13618
|
async function findPackageRoot(expectedRelativePath) {
|
|
12835
|
-
let currentDir =
|
|
13619
|
+
let currentDir = dirname8(fileURLToPath3(import.meta.url));
|
|
12836
13620
|
while (true) {
|
|
12837
|
-
const candidate =
|
|
13621
|
+
const candidate = resolve25(currentDir, expectedRelativePath);
|
|
12838
13622
|
if (await fileExists(candidate)) {
|
|
12839
13623
|
return currentDir;
|
|
12840
13624
|
}
|
|
12841
|
-
const parentDir =
|
|
13625
|
+
const parentDir = resolve25(currentDir, "..");
|
|
12842
13626
|
if (parentDir === currentDir) {
|
|
12843
13627
|
throw new Error(
|
|
12844
13628
|
`Could not locate package root containing ${expectedRelativePath}.`
|
|
@@ -12859,25 +13643,25 @@ function getPluginManifestRelativePath(pluginKind) {
|
|
|
12859
13643
|
}
|
|
12860
13644
|
function getDefaultPluginTargetDir(pluginKind) {
|
|
12861
13645
|
const home = homedir2();
|
|
12862
|
-
return pluginKind === "claude" ?
|
|
13646
|
+
return pluginKind === "claude" ? resolve26(home, ".claude", "plugins", "syntaur") : resolve26(home, "plugins", "syntaur");
|
|
12863
13647
|
}
|
|
12864
13648
|
function getDefaultMarketplacePath() {
|
|
12865
|
-
return
|
|
13649
|
+
return resolve26(homedir2(), ".agents", "plugins", "marketplace.json");
|
|
12866
13650
|
}
|
|
12867
13651
|
function getClaudeMarketplacesRoot() {
|
|
12868
|
-
return
|
|
13652
|
+
return resolve26(homedir2(), ".claude", "plugins", "marketplaces");
|
|
12869
13653
|
}
|
|
12870
13654
|
function getClaudeKnownMarketplacesPath() {
|
|
12871
|
-
return
|
|
13655
|
+
return resolve26(homedir2(), ".claude", "plugins", "known_marketplaces.json");
|
|
12872
13656
|
}
|
|
12873
13657
|
function getClaudeInstalledPluginsPath() {
|
|
12874
|
-
return
|
|
13658
|
+
return resolve26(homedir2(), ".claude", "plugins", "installed_plugins.json");
|
|
12875
13659
|
}
|
|
12876
13660
|
function getInstallMarkerPath(targetDir) {
|
|
12877
|
-
return
|
|
13661
|
+
return resolve26(targetDir, INSTALL_MARKER_FILENAME);
|
|
12878
13662
|
}
|
|
12879
13663
|
async function readPackageManifest(packageRoot) {
|
|
12880
|
-
const raw = await readFile16(
|
|
13664
|
+
const raw = await readFile16(resolve26(packageRoot, "package.json"), "utf-8");
|
|
12881
13665
|
return JSON.parse(raw);
|
|
12882
13666
|
}
|
|
12883
13667
|
async function readJsonFileIfExists(pathValue) {
|
|
@@ -12893,11 +13677,11 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
12893
13677
|
}
|
|
12894
13678
|
async function readClaudePluginManifest(pluginDir) {
|
|
12895
13679
|
return await readJsonFileIfExists(
|
|
12896
|
-
|
|
13680
|
+
resolve26(pluginDir, ".claude-plugin", "plugin.json")
|
|
12897
13681
|
) ?? {};
|
|
12898
13682
|
}
|
|
12899
13683
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
12900
|
-
const manifestPath =
|
|
13684
|
+
const manifestPath = resolve26(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
12901
13685
|
if (!await fileExists(manifestPath)) {
|
|
12902
13686
|
return void 0;
|
|
12903
13687
|
}
|
|
@@ -12924,7 +13708,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
12924
13708
|
const info = await lstat(targetDir);
|
|
12925
13709
|
if (info.isSymbolicLink()) {
|
|
12926
13710
|
const symlinkTarget = await readlink(targetDir);
|
|
12927
|
-
const resolvedTarget =
|
|
13711
|
+
const resolvedTarget = resolve26(dirname9(targetDir), symlinkTarget);
|
|
12928
13712
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
12929
13713
|
return {
|
|
12930
13714
|
exists: true,
|
|
@@ -12961,16 +13745,16 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
12961
13745
|
);
|
|
12962
13746
|
}
|
|
12963
13747
|
async function installCopy(paths, pluginKind) {
|
|
12964
|
-
await ensureDir(
|
|
13748
|
+
await ensureDir(dirname9(paths.targetDir));
|
|
12965
13749
|
await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
|
|
12966
13750
|
const packageManifest = await readPackageManifest(paths.packageRoot);
|
|
12967
13751
|
await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
|
|
12968
13752
|
}
|
|
12969
13753
|
async function installLink(paths) {
|
|
12970
|
-
await ensureDir(
|
|
13754
|
+
await ensureDir(dirname9(paths.targetDir));
|
|
12971
13755
|
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
12972
|
-
await ensureDir(
|
|
12973
|
-
await symlink(
|
|
13756
|
+
await ensureDir(dirname9(paths.targetDir));
|
|
13757
|
+
await symlink(resolve26(paths.sourceDir), paths.targetDir, "dir");
|
|
12974
13758
|
}
|
|
12975
13759
|
async function removeInstallMarker(targetDir) {
|
|
12976
13760
|
const markerPath = getInstallMarkerPath(targetDir);
|
|
@@ -12984,13 +13768,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
|
12984
13768
|
if (!isAbsolute2(expanded)) {
|
|
12985
13769
|
throw new Error(`${label} must be an absolute path.`);
|
|
12986
13770
|
}
|
|
12987
|
-
return
|
|
13771
|
+
return resolve26(expanded);
|
|
12988
13772
|
}
|
|
12989
13773
|
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
12990
13774
|
const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
12991
13775
|
return {
|
|
12992
13776
|
packageRoot,
|
|
12993
|
-
sourceDir:
|
|
13777
|
+
sourceDir: resolve26(packageRoot, getPluginRelativePath(pluginKind)),
|
|
12994
13778
|
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
12995
13779
|
};
|
|
12996
13780
|
}
|
|
@@ -13024,7 +13808,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
|
|
|
13024
13808
|
};
|
|
13025
13809
|
}
|
|
13026
13810
|
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
13027
|
-
await ensureDir(
|
|
13811
|
+
await ensureDir(dirname9(manifestPath));
|
|
13028
13812
|
if (Array.isArray(marketplace.plugins)) {
|
|
13029
13813
|
marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
|
|
13030
13814
|
const an = a?.name ?? "";
|
|
@@ -13115,8 +13899,8 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
13115
13899
|
if (!entry.isDirectory()) {
|
|
13116
13900
|
continue;
|
|
13117
13901
|
}
|
|
13118
|
-
const candidateRoot =
|
|
13119
|
-
const manifestPath =
|
|
13902
|
+
const candidateRoot = resolve26(rootDir, entry.name);
|
|
13903
|
+
const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
13120
13904
|
if (!await fileExists(manifestPath)) {
|
|
13121
13905
|
continue;
|
|
13122
13906
|
}
|
|
@@ -13143,11 +13927,11 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
13143
13927
|
if (!installLocation) {
|
|
13144
13928
|
continue;
|
|
13145
13929
|
}
|
|
13146
|
-
const candidateRoot =
|
|
13930
|
+
const candidateRoot = resolve26(expandHome(installLocation));
|
|
13147
13931
|
if (seen.has(candidateRoot)) {
|
|
13148
13932
|
continue;
|
|
13149
13933
|
}
|
|
13150
|
-
const manifestPath =
|
|
13934
|
+
const manifestPath = resolve26(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
13151
13935
|
if (!await fileExists(manifestPath)) {
|
|
13152
13936
|
continue;
|
|
13153
13937
|
}
|
|
@@ -13175,7 +13959,7 @@ async function getPreferredClaudeMarketplace() {
|
|
|
13175
13959
|
name: candidate.name,
|
|
13176
13960
|
rootDir: candidate.rootDir,
|
|
13177
13961
|
manifestPath: candidate.manifestPath,
|
|
13178
|
-
targetDir:
|
|
13962
|
+
targetDir: resolve26(candidate.rootDir, "plugins", "syntaur")
|
|
13179
13963
|
};
|
|
13180
13964
|
}
|
|
13181
13965
|
async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
@@ -13207,7 +13991,7 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
|
13207
13991
|
};
|
|
13208
13992
|
existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
13209
13993
|
existing[name].autoUpdate = true;
|
|
13210
|
-
await ensureDir(
|
|
13994
|
+
await ensureDir(dirname9(manifestPath));
|
|
13211
13995
|
if (await fileExists(manifestPath)) {
|
|
13212
13996
|
const prev = await readFile16(manifestPath, "utf-8");
|
|
13213
13997
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13223,7 +14007,7 @@ async function ensureKnownClaudeMarketplaceForRoot(options) {
|
|
|
13223
14007
|
return registerKnownClaudeMarketplace(options.name, options.rootDir);
|
|
13224
14008
|
}
|
|
13225
14009
|
async function setSyntaurPluginEnabled(options) {
|
|
13226
|
-
const settingsPath =
|
|
14010
|
+
const settingsPath = resolve26(homedir2(), ".claude", "settings.json");
|
|
13227
14011
|
const key = `syntaur@${options.marketplaceName}`;
|
|
13228
14012
|
let parsed = {};
|
|
13229
14013
|
if (await fileExists(settingsPath)) {
|
|
@@ -13243,7 +14027,7 @@ async function setSyntaurPluginEnabled(options) {
|
|
|
13243
14027
|
}
|
|
13244
14028
|
enabledPlugins[key] = options.enabled;
|
|
13245
14029
|
parsed.enabledPlugins = enabledPlugins;
|
|
13246
|
-
await ensureDir(
|
|
14030
|
+
await ensureDir(dirname9(settingsPath));
|
|
13247
14031
|
if (await fileExists(settingsPath)) {
|
|
13248
14032
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13249
14033
|
const prev = await readFile16(settingsPath, "utf-8");
|
|
@@ -13260,9 +14044,9 @@ async function ensureClaudeUserMarketplace() {
|
|
|
13260
14044
|
if (existing) {
|
|
13261
14045
|
return existing;
|
|
13262
14046
|
}
|
|
13263
|
-
const rootDir =
|
|
13264
|
-
const manifestPath =
|
|
13265
|
-
await ensureDir(
|
|
14047
|
+
const rootDir = resolve26(getClaudeMarketplacesRoot(), "user-plugins");
|
|
14048
|
+
const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
|
|
14049
|
+
await ensureDir(resolve26(rootDir, "plugins"));
|
|
13266
14050
|
if (!await fileExists(manifestPath)) {
|
|
13267
14051
|
const scaffold = {
|
|
13268
14052
|
plugins: []
|
|
@@ -13281,22 +14065,22 @@ async function ensureClaudeUserMarketplace() {
|
|
|
13281
14065
|
name: "user-plugins",
|
|
13282
14066
|
rootDir,
|
|
13283
14067
|
manifestPath,
|
|
13284
|
-
targetDir:
|
|
14068
|
+
targetDir: resolve26(rootDir, "plugins", "syntaur")
|
|
13285
14069
|
};
|
|
13286
14070
|
}
|
|
13287
14071
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
13288
14072
|
const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
|
|
13289
|
-
const pluginsDir =
|
|
13290
|
-
if (
|
|
14073
|
+
const pluginsDir = dirname9(normalizedTargetDir);
|
|
14074
|
+
if (basename4(pluginsDir) !== "plugins") {
|
|
13291
14075
|
return null;
|
|
13292
14076
|
}
|
|
13293
|
-
const rootDir =
|
|
13294
|
-
const manifestPath =
|
|
14077
|
+
const rootDir = dirname9(pluginsDir);
|
|
14078
|
+
const manifestPath = resolve26(rootDir, ".claude-plugin", "marketplace.json");
|
|
13295
14079
|
if (!await fileExists(manifestPath)) {
|
|
13296
14080
|
return null;
|
|
13297
14081
|
}
|
|
13298
14082
|
const marketplace = await readClaudeMarketplaceFile(manifestPath);
|
|
13299
|
-
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name :
|
|
14083
|
+
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename4(rootDir);
|
|
13300
14084
|
return {
|
|
13301
14085
|
name,
|
|
13302
14086
|
rootDir,
|
|
@@ -13307,7 +14091,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
13307
14091
|
async function findManagedClaudeMarketplacePluginDir() {
|
|
13308
14092
|
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
13309
14093
|
for (const marketplace of marketplaces) {
|
|
13310
|
-
const targetDir =
|
|
14094
|
+
const targetDir = resolve26(marketplace.rootDir, "plugins", "syntaur");
|
|
13311
14095
|
const status = await getInstallStatus(targetDir, "claude");
|
|
13312
14096
|
if (status.exists && status.managed) {
|
|
13313
14097
|
return targetDir;
|
|
@@ -13432,7 +14216,7 @@ async function installManagedPlugin(options) {
|
|
|
13432
14216
|
`${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
|
|
13433
14217
|
);
|
|
13434
14218
|
}
|
|
13435
|
-
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget ===
|
|
14219
|
+
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve26(paths.sourceDir) && !force) {
|
|
13436
14220
|
return {
|
|
13437
14221
|
targetDir: paths.targetDir,
|
|
13438
14222
|
sourceDir: paths.sourceDir,
|
|
@@ -13456,7 +14240,7 @@ async function installManagedPlugin(options) {
|
|
|
13456
14240
|
};
|
|
13457
14241
|
}
|
|
13458
14242
|
function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
|
|
13459
|
-
const relPath = relative2(
|
|
14243
|
+
const relPath = relative2(dirname9(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
|
|
13460
14244
|
if (relPath === "") {
|
|
13461
14245
|
return ".";
|
|
13462
14246
|
}
|
|
@@ -13493,7 +14277,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
13493
14277
|
};
|
|
13494
14278
|
}
|
|
13495
14279
|
async function writeMarketplaceFile(marketplacePath, marketplace) {
|
|
13496
|
-
await ensureDir(
|
|
14280
|
+
await ensureDir(dirname9(marketplacePath));
|
|
13497
14281
|
await writeFile6(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
|
|
13498
14282
|
`, "utf-8");
|
|
13499
14283
|
}
|
|
@@ -13645,13 +14429,13 @@ async function recommendMarketplacePath() {
|
|
|
13645
14429
|
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
13646
14430
|
}
|
|
13647
14431
|
async function isSyntaurDataInstalled() {
|
|
13648
|
-
return fileExists(
|
|
14432
|
+
return fileExists(resolve26(syntaurRoot(), "config.md"));
|
|
13649
14433
|
}
|
|
13650
14434
|
async function removeSyntaurData() {
|
|
13651
14435
|
await rm3(syntaurRoot(), { recursive: true, force: true });
|
|
13652
14436
|
}
|
|
13653
14437
|
async function getConfiguredProjectDir() {
|
|
13654
|
-
if (!await fileExists(
|
|
14438
|
+
if (!await fileExists(resolve26(syntaurRoot(), "config.md"))) {
|
|
13655
14439
|
return null;
|
|
13656
14440
|
}
|
|
13657
14441
|
return (await readConfig()).defaultProjectDir;
|
|
@@ -13721,16 +14505,16 @@ async function textPrompt(question, defaultValue) {
|
|
|
13721
14505
|
// src/utils/install-skills.ts
|
|
13722
14506
|
init_fs();
|
|
13723
14507
|
import { readFile as readFile18, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
|
|
13724
|
-
import { resolve as
|
|
14508
|
+
import { resolve as resolve28, relative as relative3, join as join3 } from "path";
|
|
13725
14509
|
import { homedir as homedir4 } from "os";
|
|
13726
14510
|
|
|
13727
14511
|
// src/utils/plugin-state.ts
|
|
13728
14512
|
init_fs();
|
|
13729
14513
|
import { readFile as readFile17 } from "fs/promises";
|
|
13730
|
-
import { resolve as
|
|
14514
|
+
import { resolve as resolve27 } from "path";
|
|
13731
14515
|
import { homedir as homedir3 } from "os";
|
|
13732
14516
|
function settingsPathFor(agent) {
|
|
13733
|
-
if (agent === "claude") return
|
|
14517
|
+
if (agent === "claude") return resolve27(homedir3(), ".claude", "settings.json");
|
|
13734
14518
|
return null;
|
|
13735
14519
|
}
|
|
13736
14520
|
async function readJsonOrNull(path) {
|
|
@@ -13776,16 +14560,20 @@ var KNOWN_SKILL_NAMES = [
|
|
|
13776
14560
|
"add-memory",
|
|
13777
14561
|
"list-assignments",
|
|
13778
14562
|
"log-progress",
|
|
13779
|
-
"set-workspace"
|
|
14563
|
+
"set-workspace",
|
|
14564
|
+
"claim-resource",
|
|
14565
|
+
"release-resource",
|
|
14566
|
+
"extend-resource",
|
|
14567
|
+
"list-resources"
|
|
13780
14568
|
];
|
|
13781
14569
|
var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
|
|
13782
14570
|
async function getSkillsDir() {
|
|
13783
14571
|
const packageRoot = await findPackageRoot("skills");
|
|
13784
|
-
return
|
|
14572
|
+
return resolve28(packageRoot, "skills");
|
|
13785
14573
|
}
|
|
13786
14574
|
function defaultSkillTargetDir(target) {
|
|
13787
|
-
if (target === "claude") return
|
|
13788
|
-
return
|
|
14575
|
+
if (target === "claude") return resolve28(homedir4(), ".claude", "skills");
|
|
14576
|
+
return resolve28(homedir4(), ".codex", "skills");
|
|
13789
14577
|
}
|
|
13790
14578
|
async function walkFiles(root) {
|
|
13791
14579
|
const out = [];
|
|
@@ -13839,8 +14627,8 @@ async function skillMatches(srcDir, destDir) {
|
|
|
13839
14627
|
}
|
|
13840
14628
|
async function isSymlink(path) {
|
|
13841
14629
|
try {
|
|
13842
|
-
const
|
|
13843
|
-
return
|
|
14630
|
+
const stat10 = await lstat2(path);
|
|
14631
|
+
return stat10.isSymbolicLink();
|
|
13844
14632
|
} catch {
|
|
13845
14633
|
return false;
|
|
13846
14634
|
}
|
|
@@ -14117,7 +14905,7 @@ async function installPluginCommand(options) {
|
|
|
14117
14905
|
init_paths();
|
|
14118
14906
|
init_fs();
|
|
14119
14907
|
import { readFile as readFile20, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink6, lstat as lstat3 } from "fs/promises";
|
|
14120
|
-
import { resolve as
|
|
14908
|
+
import { resolve as resolve30, dirname as dirname11 } from "path";
|
|
14121
14909
|
import { homedir as homedir5 } from "os";
|
|
14122
14910
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14123
14911
|
|
|
@@ -14125,7 +14913,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
|
|
|
14125
14913
|
init_paths();
|
|
14126
14914
|
init_fs();
|
|
14127
14915
|
import { readFile as readFile19, writeFile as writeFile7 } from "fs/promises";
|
|
14128
|
-
import { resolve as
|
|
14916
|
+
import { resolve as resolve29, dirname as dirname10 } from "path";
|
|
14129
14917
|
import { spawnSync } from "child_process";
|
|
14130
14918
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
14131
14919
|
var AVAILABLE_SEGMENTS = [
|
|
@@ -14146,7 +14934,7 @@ var PRESETS = {
|
|
|
14146
14934
|
tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
|
|
14147
14935
|
};
|
|
14148
14936
|
function getConfigPath(installRoot) {
|
|
14149
|
-
return
|
|
14937
|
+
return resolve29(installRoot, "statusline.config.json");
|
|
14150
14938
|
}
|
|
14151
14939
|
async function readConfig2(path) {
|
|
14152
14940
|
if (!await fileExists(path)) return null;
|
|
@@ -14246,7 +15034,7 @@ function renderPreview(config, statuslineScript, cwd) {
|
|
|
14246
15034
|
env: {
|
|
14247
15035
|
...process.env,
|
|
14248
15036
|
// Force the child to pick up the freshly-written config from install root.
|
|
14249
|
-
HOME:
|
|
15037
|
+
HOME: dirname10(dirname10(statuslineScript))
|
|
14250
15038
|
}
|
|
14251
15039
|
});
|
|
14252
15040
|
if (res.status !== 0) return null;
|
|
@@ -14297,14 +15085,14 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
14297
15085
|
console.log("(preview mode \u2014 config NOT written)");
|
|
14298
15086
|
return;
|
|
14299
15087
|
}
|
|
14300
|
-
await ensureDir(
|
|
15088
|
+
await ensureDir(dirname10(configPath));
|
|
14301
15089
|
await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
14302
15090
|
console.log("Wrote statusline config:");
|
|
14303
15091
|
console.log(` path: ${configPath}`);
|
|
14304
15092
|
console.log(` segments: ${config.segments.join(", ")}`);
|
|
14305
15093
|
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
14306
15094
|
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
14307
|
-
const script = options.statuslineScript ??
|
|
15095
|
+
const script = options.statuslineScript ?? resolve29(installRoot, "statusline.sh");
|
|
14308
15096
|
if (await fileExists(script)) {
|
|
14309
15097
|
console.log("");
|
|
14310
15098
|
console.log("Live preview:");
|
|
@@ -14324,7 +15112,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
14324
15112
|
async function writeDefaultConfigIfMissing(installRoot) {
|
|
14325
15113
|
const path = getConfigPath(installRoot);
|
|
14326
15114
|
if (await fileExists(path)) return;
|
|
14327
|
-
await ensureDir(
|
|
15115
|
+
await ensureDir(dirname10(path));
|
|
14328
15116
|
const defaultConfig = {
|
|
14329
15117
|
segments: ["git", "assignment", "session"],
|
|
14330
15118
|
separator: " \xB7 "
|
|
@@ -14334,8 +15122,8 @@ async function writeDefaultConfigIfMissing(installRoot) {
|
|
|
14334
15122
|
|
|
14335
15123
|
// src/commands/install-statusline.ts
|
|
14336
15124
|
function getPackageStatuslineSource() {
|
|
14337
|
-
const here =
|
|
14338
|
-
return
|
|
15125
|
+
const here = dirname11(fileURLToPath4(import.meta.url));
|
|
15126
|
+
return resolve30(here, "..", "statusline", "statusline.sh");
|
|
14339
15127
|
}
|
|
14340
15128
|
async function readSettingsJson(settingsPath) {
|
|
14341
15129
|
if (!await fileExists(settingsPath)) return {};
|
|
@@ -14351,7 +15139,7 @@ async function readSettingsJson(settingsPath) {
|
|
|
14351
15139
|
}
|
|
14352
15140
|
}
|
|
14353
15141
|
async function writeSettingsJson(settingsPath, data) {
|
|
14354
|
-
await ensureDir(
|
|
15142
|
+
await ensureDir(dirname11(settingsPath));
|
|
14355
15143
|
await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
14356
15144
|
}
|
|
14357
15145
|
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
@@ -14386,7 +15174,7 @@ function extractExistingCommand(settings) {
|
|
|
14386
15174
|
};
|
|
14387
15175
|
}
|
|
14388
15176
|
async function backupSettings(settingsSnapshot, backupPath) {
|
|
14389
|
-
await ensureDir(
|
|
15177
|
+
await ensureDir(dirname11(backupPath));
|
|
14390
15178
|
await writeFile8(
|
|
14391
15179
|
backupPath,
|
|
14392
15180
|
JSON.stringify(
|
|
@@ -14403,7 +15191,7 @@ async function backupSettings(settingsSnapshot, backupPath) {
|
|
|
14403
15191
|
);
|
|
14404
15192
|
}
|
|
14405
15193
|
async function installScript(sourceScript, destScript, link) {
|
|
14406
|
-
await ensureDir(
|
|
15194
|
+
await ensureDir(dirname11(destScript));
|
|
14407
15195
|
try {
|
|
14408
15196
|
const s = await lstat3(destScript);
|
|
14409
15197
|
if (s.isSymbolicLink() || s.isFile()) {
|
|
@@ -14419,12 +15207,12 @@ async function installScript(sourceScript, destScript, link) {
|
|
|
14419
15207
|
}
|
|
14420
15208
|
async function installStatuslineCommand(options = {}) {
|
|
14421
15209
|
const mode = options.mode ?? "ask";
|
|
14422
|
-
const settingsPath = options.settingsPath ??
|
|
15210
|
+
const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
|
|
14423
15211
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
14424
15212
|
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
14425
|
-
const destScript =
|
|
14426
|
-
const confPath =
|
|
14427
|
-
const backupPath =
|
|
15213
|
+
const destScript = resolve30(installRoot, "statusline.sh");
|
|
15214
|
+
const confPath = resolve30(installRoot, "statusline.conf");
|
|
15215
|
+
const backupPath = resolve30(installRoot, "statusline.backup.json");
|
|
14428
15216
|
if (!await fileExists(sourceScript)) {
|
|
14429
15217
|
throw new Error(
|
|
14430
15218
|
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
@@ -14460,7 +15248,7 @@ async function installStatuslineCommand(options = {}) {
|
|
|
14460
15248
|
if (parsed) {
|
|
14461
15249
|
wrapTarget = parsed;
|
|
14462
15250
|
} else {
|
|
14463
|
-
const wrapperPath =
|
|
15251
|
+
const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
|
|
14464
15252
|
const wrapperBody = `#!/usr/bin/env bash
|
|
14465
15253
|
# Auto-generated by syntaur install-statusline.
|
|
14466
15254
|
# Executes the previously configured statusLine command.
|
|
@@ -14471,7 +15259,7 @@ exec ${existingCommand}
|
|
|
14471
15259
|
wrapTarget = wrapperPath;
|
|
14472
15260
|
}
|
|
14473
15261
|
}
|
|
14474
|
-
await ensureDir(
|
|
15262
|
+
await ensureDir(dirname11(confPath));
|
|
14475
15263
|
await writeFile8(
|
|
14476
15264
|
confPath,
|
|
14477
15265
|
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
@@ -14515,12 +15303,12 @@ async function chmodExec(path) {
|
|
|
14515
15303
|
}
|
|
14516
15304
|
}
|
|
14517
15305
|
async function uninstallStatuslineCommand(options = {}) {
|
|
14518
|
-
const settingsPath = options.settingsPath ??
|
|
15306
|
+
const settingsPath = options.settingsPath ?? resolve30(homedir5(), ".claude", "settings.json");
|
|
14519
15307
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
14520
|
-
const destScript =
|
|
14521
|
-
const confPath =
|
|
14522
|
-
const backupPath =
|
|
14523
|
-
const wrapperPath =
|
|
15308
|
+
const destScript = resolve30(installRoot, "statusline.sh");
|
|
15309
|
+
const confPath = resolve30(installRoot, "statusline.conf");
|
|
15310
|
+
const backupPath = resolve30(installRoot, "statusline.backup.json");
|
|
15311
|
+
const wrapperPath = resolve30(installRoot, "statusline-wrapped.sh");
|
|
14524
15312
|
const settings = await readSettingsJson(settingsPath);
|
|
14525
15313
|
const existing = extractExistingCommand(settings);
|
|
14526
15314
|
const ourCommand = `bash ${destScript}`;
|
|
@@ -14548,7 +15336,7 @@ async function uninstallStatuslineCommand(options = {}) {
|
|
|
14548
15336
|
await writeSettingsJson(settingsPath, settings);
|
|
14549
15337
|
}
|
|
14550
15338
|
if (!options.keepScript) {
|
|
14551
|
-
const configPath =
|
|
15339
|
+
const configPath = resolve30(installRoot, "statusline.config.json");
|
|
14552
15340
|
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
14553
15341
|
try {
|
|
14554
15342
|
await rm5(path, { force: true });
|
|
@@ -14805,7 +15593,7 @@ async function setupCommand(options) {
|
|
|
14805
15593
|
}
|
|
14806
15594
|
|
|
14807
15595
|
// src/commands/uninstall.ts
|
|
14808
|
-
import { resolve as
|
|
15596
|
+
import { resolve as resolve31 } from "path";
|
|
14809
15597
|
init_paths();
|
|
14810
15598
|
function expandTargets(options) {
|
|
14811
15599
|
if (options.all) {
|
|
@@ -14885,7 +15673,7 @@ async function uninstallCommand(options) {
|
|
|
14885
15673
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
14886
15674
|
await removeSyntaurData();
|
|
14887
15675
|
console.log(`Removed ${syntaurRoot()}`);
|
|
14888
|
-
if (configuredProjectDir &&
|
|
15676
|
+
if (configuredProjectDir && resolve31(configuredProjectDir) !== resolve31(syntaurRoot(), "projects")) {
|
|
14889
15677
|
console.warn(
|
|
14890
15678
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
14891
15679
|
);
|
|
@@ -14904,7 +15692,7 @@ init_slug();
|
|
|
14904
15692
|
init_cursor_rules();
|
|
14905
15693
|
init_codex_agents();
|
|
14906
15694
|
init_opencode_config();
|
|
14907
|
-
import { resolve as
|
|
15695
|
+
import { resolve as resolve32 } from "path";
|
|
14908
15696
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
14909
15697
|
async function setupAdapterCommand(framework, options) {
|
|
14910
15698
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -14930,19 +15718,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14930
15718
|
}
|
|
14931
15719
|
const config = await readConfig();
|
|
14932
15720
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
14933
|
-
const projectDir =
|
|
14934
|
-
const assignmentDir =
|
|
15721
|
+
const projectDir = resolve32(baseDir, options.project);
|
|
15722
|
+
const assignmentDir = resolve32(
|
|
14935
15723
|
projectDir,
|
|
14936
15724
|
"assignments",
|
|
14937
15725
|
options.assignment
|
|
14938
15726
|
);
|
|
14939
|
-
const projectMdPath =
|
|
15727
|
+
const projectMdPath = resolve32(projectDir, "project.md");
|
|
14940
15728
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
14941
15729
|
throw new Error(
|
|
14942
15730
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
14943
15731
|
);
|
|
14944
15732
|
}
|
|
14945
|
-
const assignmentMdPath2 =
|
|
15733
|
+
const assignmentMdPath2 = resolve32(assignmentDir, "assignment.md");
|
|
14946
15734
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
|
|
14947
15735
|
throw new Error(
|
|
14948
15736
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -14970,15 +15758,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14970
15758
|
}
|
|
14971
15759
|
}
|
|
14972
15760
|
if (framework === "cursor") {
|
|
14973
|
-
const protocolPath =
|
|
14974
|
-
const assignmentPath =
|
|
15761
|
+
const protocolPath = resolve32(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
15762
|
+
const assignmentPath = resolve32(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
14975
15763
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
14976
15764
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
14977
15765
|
} else if (framework === "codex" || framework === "opencode") {
|
|
14978
|
-
const agentsPath =
|
|
15766
|
+
const agentsPath = resolve32(cwd, "AGENTS.md");
|
|
14979
15767
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
14980
15768
|
if (framework === "opencode") {
|
|
14981
|
-
const configPath =
|
|
15769
|
+
const configPath = resolve32(cwd, "opencode.json");
|
|
14982
15770
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
14983
15771
|
}
|
|
14984
15772
|
}
|
|
@@ -15003,7 +15791,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
15003
15791
|
init_paths();
|
|
15004
15792
|
init_fs();
|
|
15005
15793
|
init_config2();
|
|
15006
|
-
import { resolve as
|
|
15794
|
+
import { resolve as resolve33 } from "path";
|
|
15007
15795
|
async function trackSessionCommand(options) {
|
|
15008
15796
|
if (!options.agent) {
|
|
15009
15797
|
throw new Error("--agent <name> is required.");
|
|
@@ -15016,7 +15804,7 @@ async function trackSessionCommand(options) {
|
|
|
15016
15804
|
if (options.project) {
|
|
15017
15805
|
const config = await readConfig();
|
|
15018
15806
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
15019
|
-
const projectDir =
|
|
15807
|
+
const projectDir = resolve33(baseDir, options.project);
|
|
15020
15808
|
if (!await fileExists(projectDir)) {
|
|
15021
15809
|
throw new Error(
|
|
15022
15810
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -15025,6 +15813,8 @@ async function trackSessionCommand(options) {
|
|
|
15025
15813
|
}
|
|
15026
15814
|
initSessionDb();
|
|
15027
15815
|
const { sessionId } = options;
|
|
15816
|
+
const derivedPath = await derivePathFromTranscript(options.transcriptPath);
|
|
15817
|
+
const recordedPath = derivedPath ?? options.path ?? process.cwd();
|
|
15028
15818
|
await appendSession("", {
|
|
15029
15819
|
projectSlug: options.project || null,
|
|
15030
15820
|
assignmentSlug: options.assignment || null,
|
|
@@ -15032,7 +15822,7 @@ async function trackSessionCommand(options) {
|
|
|
15032
15822
|
sessionId,
|
|
15033
15823
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15034
15824
|
status: "active",
|
|
15035
|
-
path:
|
|
15825
|
+
path: recordedPath,
|
|
15036
15826
|
description: options.description || null,
|
|
15037
15827
|
transcriptPath: options.transcriptPath ?? null
|
|
15038
15828
|
});
|
|
@@ -15050,7 +15840,7 @@ init_config2();
|
|
|
15050
15840
|
init_paths();
|
|
15051
15841
|
init_fs();
|
|
15052
15842
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15053
|
-
import { resolve as
|
|
15843
|
+
import { resolve as resolve35, isAbsolute as isAbsolute4 } from "path";
|
|
15054
15844
|
import { readFile as readFile22 } from "fs/promises";
|
|
15055
15845
|
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
15056
15846
|
async function browseCommand(options) {
|
|
@@ -15120,7 +15910,7 @@ async function pickAgent(agents) {
|
|
|
15120
15910
|
return picked;
|
|
15121
15911
|
}
|
|
15122
15912
|
async function ensureWorktree(opts) {
|
|
15123
|
-
const assignmentPath =
|
|
15913
|
+
const assignmentPath = resolve35(
|
|
15124
15914
|
opts.projectsDir,
|
|
15125
15915
|
opts.projectSlug,
|
|
15126
15916
|
"assignments",
|
|
@@ -15201,7 +15991,7 @@ function computeWorktreeDefaults(opts) {
|
|
|
15201
15991
|
const repository = opts.existing.repository ?? detectCurrentGitRoot();
|
|
15202
15992
|
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
15203
15993
|
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
|
|
15204
|
-
const worktreeBase = repository ?
|
|
15994
|
+
const worktreeBase = repository ? resolve35(repository, ".worktrees", branch) : resolve35(
|
|
15205
15995
|
syntaurRoot(),
|
|
15206
15996
|
"worktrees",
|
|
15207
15997
|
opts.projectSlug || "standalone",
|
|
@@ -15234,7 +16024,7 @@ function detectCurrentBranch() {
|
|
|
15234
16024
|
async function runCreate(opts) {
|
|
15235
16025
|
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
15236
16026
|
const expandedWorktree = expandHome(opts.worktreePath);
|
|
15237
|
-
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree :
|
|
16027
|
+
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve35(expandedWorktree);
|
|
15238
16028
|
try {
|
|
15239
16029
|
await createWorktreeAndRecord2({
|
|
15240
16030
|
assignmentPath: opts.assignmentPath,
|
|
@@ -15263,7 +16053,7 @@ init_paths();
|
|
|
15263
16053
|
init_fs();
|
|
15264
16054
|
init_playbook();
|
|
15265
16055
|
init_playbooks();
|
|
15266
|
-
import { resolve as
|
|
16056
|
+
import { resolve as resolve36 } from "path";
|
|
15267
16057
|
async function createPlaybookCommand(name, options) {
|
|
15268
16058
|
if (!name.trim()) {
|
|
15269
16059
|
throw new Error("Playbook name cannot be empty.");
|
|
@@ -15276,7 +16066,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
15276
16066
|
}
|
|
15277
16067
|
const dir = playbooksDir();
|
|
15278
16068
|
await ensureDir(dir);
|
|
15279
|
-
const filePath =
|
|
16069
|
+
const filePath = resolve36(dir, `${slug}.md`);
|
|
15280
16070
|
if (await fileExists(filePath)) {
|
|
15281
16071
|
throw new Error(
|
|
15282
16072
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -15299,7 +16089,7 @@ init_fs();
|
|
|
15299
16089
|
init_parser();
|
|
15300
16090
|
init_config2();
|
|
15301
16091
|
import { readdir as readdir12, readFile as readFile23 } from "fs/promises";
|
|
15302
|
-
import { resolve as
|
|
16092
|
+
import { resolve as resolve37 } from "path";
|
|
15303
16093
|
async function listPlaybooksCommand(options = {}) {
|
|
15304
16094
|
const dir = playbooksDir();
|
|
15305
16095
|
if (!await fileExists(dir)) {
|
|
@@ -15314,7 +16104,7 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
15314
16104
|
);
|
|
15315
16105
|
const rows = [];
|
|
15316
16106
|
for (const entry of mdFiles) {
|
|
15317
|
-
const filePath =
|
|
16107
|
+
const filePath = resolve37(dir, entry.name);
|
|
15318
16108
|
const raw = await readFile23(filePath, "utf-8");
|
|
15319
16109
|
const parsed = parsePlaybook(raw);
|
|
15320
16110
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
@@ -15414,7 +16204,7 @@ init_config2();
|
|
|
15414
16204
|
init_slug();
|
|
15415
16205
|
import { Command } from "commander";
|
|
15416
16206
|
import { readFile as readFile24 } from "fs/promises";
|
|
15417
|
-
import { resolve as
|
|
16207
|
+
import { resolve as resolve38 } from "path";
|
|
15418
16208
|
var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
15419
16209
|
async function resolveScope(options) {
|
|
15420
16210
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
@@ -15426,7 +16216,7 @@ async function resolveScope(options) {
|
|
|
15426
16216
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
15427
16217
|
}
|
|
15428
16218
|
const config = await readConfig();
|
|
15429
|
-
const projectMd =
|
|
16219
|
+
const projectMd = resolve38(config.defaultProjectDir, options.project, "project.md");
|
|
15430
16220
|
if (!await fileExists(projectMd)) {
|
|
15431
16221
|
throw new Error(`Project "${options.project}" not found.`);
|
|
15432
16222
|
}
|
|
@@ -15745,7 +16535,7 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
15745
16535
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
15746
16536
|
);
|
|
15747
16537
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
15748
|
-
await ensureDir(
|
|
16538
|
+
await ensureDir(resolve38(todosPath, "archive"));
|
|
15749
16539
|
let archContent = "";
|
|
15750
16540
|
if (await fileExists(archFile)) {
|
|
15751
16541
|
archContent = await readFile24(archFile, "utf-8");
|
|
@@ -15977,7 +16767,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
15977
16767
|
);
|
|
15978
16768
|
let target;
|
|
15979
16769
|
if (existingFiles.length === 0) {
|
|
15980
|
-
target =
|
|
16770
|
+
target = resolve38(planDir, "plan.md");
|
|
15981
16771
|
} else {
|
|
15982
16772
|
const versions = /* @__PURE__ */ new Set();
|
|
15983
16773
|
for (const f of existingFiles) {
|
|
@@ -15987,7 +16777,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
15987
16777
|
}
|
|
15988
16778
|
let n = 2;
|
|
15989
16779
|
while (versions.has(n)) n++;
|
|
15990
|
-
target =
|
|
16780
|
+
target = resolve38(planDir, `plan-v${n}.md`);
|
|
15991
16781
|
}
|
|
15992
16782
|
if (!await fileExists(target)) {
|
|
15993
16783
|
const stub = `---
|
|
@@ -16058,8 +16848,8 @@ async function moveTodo(id, options) {
|
|
|
16058
16848
|
throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
|
|
16059
16849
|
}
|
|
16060
16850
|
const { rename: rename8, mkdir: mkdir8 } = await import("fs/promises");
|
|
16061
|
-
const { dirname:
|
|
16062
|
-
await mkdir8(
|
|
16851
|
+
const { dirname: dirname17 } = await import("path");
|
|
16852
|
+
await mkdir8(dirname17(newPlanDir), { recursive: true });
|
|
16063
16853
|
await rename8(item.planDir, newPlanDir);
|
|
16064
16854
|
item.planDir = newPlanDir;
|
|
16065
16855
|
}
|
|
@@ -16177,31 +16967,31 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
16177
16967
|
// src/commands/doctor.ts
|
|
16178
16968
|
import { Command as Command3 } from "commander";
|
|
16179
16969
|
import { readFile as readFile31 } from "fs/promises";
|
|
16180
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
16970
|
+
import { isAbsolute as isAbsolute7, resolve as resolve48 } from "path";
|
|
16181
16971
|
|
|
16182
16972
|
// src/utils/doctor/index.ts
|
|
16183
16973
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
16184
16974
|
import { readFile as readFile30 } from "fs/promises";
|
|
16185
|
-
import { dirname as
|
|
16975
|
+
import { dirname as dirname14, join as join6 } from "path";
|
|
16186
16976
|
|
|
16187
16977
|
// src/utils/doctor/context.ts
|
|
16188
16978
|
init_config2();
|
|
16189
16979
|
init_paths();
|
|
16190
16980
|
init_fs();
|
|
16191
|
-
import
|
|
16192
|
-
import { resolve as
|
|
16981
|
+
import Database3 from "better-sqlite3";
|
|
16982
|
+
import { resolve as resolve39 } from "path";
|
|
16193
16983
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
16194
16984
|
const config = await readConfig();
|
|
16195
16985
|
const root = syntaurRoot();
|
|
16196
|
-
const dbPath =
|
|
16197
|
-
let
|
|
16986
|
+
const dbPath = resolve39(root, "syntaur.db");
|
|
16987
|
+
let db4 = null;
|
|
16198
16988
|
let dbError = null;
|
|
16199
16989
|
if (await fileExists(dbPath)) {
|
|
16200
16990
|
try {
|
|
16201
|
-
|
|
16991
|
+
db4 = new Database3(dbPath, { readonly: true, fileMustExist: true });
|
|
16202
16992
|
} catch (err2) {
|
|
16203
16993
|
dbError = err2 instanceof Error ? err2.message : String(err2);
|
|
16204
|
-
|
|
16994
|
+
db4 = null;
|
|
16205
16995
|
}
|
|
16206
16996
|
} else {
|
|
16207
16997
|
dbError = `database file not found at ${dbPath}`;
|
|
@@ -16209,7 +16999,7 @@ async function buildCheckContext(cwd = process.cwd()) {
|
|
|
16209
16999
|
return {
|
|
16210
17000
|
config,
|
|
16211
17001
|
syntaurRoot: root,
|
|
16212
|
-
db:
|
|
17002
|
+
db: db4,
|
|
16213
17003
|
dbError,
|
|
16214
17004
|
cwd,
|
|
16215
17005
|
now: /* @__PURE__ */ new Date()
|
|
@@ -16227,10 +17017,10 @@ function closeCheckContext(ctx) {
|
|
|
16227
17017
|
// src/utils/doctor/checks/env.ts
|
|
16228
17018
|
init_fs();
|
|
16229
17019
|
init_paths();
|
|
16230
|
-
import { resolve as
|
|
17020
|
+
import { resolve as resolve40, isAbsolute as isAbsolute5 } from "path";
|
|
16231
17021
|
import { readFile as readFile25, stat as stat3 } from "fs/promises";
|
|
16232
17022
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
16233
|
-
import { dirname as
|
|
17023
|
+
import { dirname as dirname12, join as join4 } from "path";
|
|
16234
17024
|
var CATEGORY = "env";
|
|
16235
17025
|
var syntaurRootExists = {
|
|
16236
17026
|
id: "env.syntaur-root-exists",
|
|
@@ -16268,7 +17058,7 @@ var configValid = {
|
|
|
16268
17058
|
category: CATEGORY,
|
|
16269
17059
|
title: "~/.syntaur/config.md is valid",
|
|
16270
17060
|
async run(ctx) {
|
|
16271
|
-
const configPath =
|
|
17061
|
+
const configPath = resolve40(ctx.syntaurRoot, "config.md");
|
|
16272
17062
|
if (!await fileExists(configPath)) {
|
|
16273
17063
|
return {
|
|
16274
17064
|
id: this.id,
|
|
@@ -16561,14 +17351,14 @@ async function readLocalVersion() {
|
|
|
16561
17351
|
async function readLocalPkg() {
|
|
16562
17352
|
try {
|
|
16563
17353
|
const here = fileURLToPath5(import.meta.url);
|
|
16564
|
-
let dir =
|
|
17354
|
+
let dir = dirname12(here);
|
|
16565
17355
|
for (let i = 0; i < 6; i++) {
|
|
16566
17356
|
const candidate = join4(dir, "package.json");
|
|
16567
17357
|
try {
|
|
16568
17358
|
const text = await readFile25(candidate, "utf-8");
|
|
16569
17359
|
return JSON.parse(text);
|
|
16570
17360
|
} catch {
|
|
16571
|
-
dir =
|
|
17361
|
+
dir = dirname12(dir);
|
|
16572
17362
|
}
|
|
16573
17363
|
}
|
|
16574
17364
|
return null;
|
|
@@ -16617,7 +17407,7 @@ function versionGte(a, b) {
|
|
|
16617
17407
|
|
|
16618
17408
|
// src/utils/doctor/checks/structure.ts
|
|
16619
17409
|
init_fs();
|
|
16620
|
-
import { resolve as
|
|
17410
|
+
import { resolve as resolve41 } from "path";
|
|
16621
17411
|
import { readdir as readdir13, stat as stat4 } from "fs/promises";
|
|
16622
17412
|
var CATEGORY2 = "structure";
|
|
16623
17413
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
@@ -16637,7 +17427,7 @@ var projectsDir = {
|
|
|
16637
17427
|
category: CATEGORY2,
|
|
16638
17428
|
title: "projects/ directory exists",
|
|
16639
17429
|
async run(ctx) {
|
|
16640
|
-
const p =
|
|
17430
|
+
const p = resolve41(ctx.syntaurRoot, "projects");
|
|
16641
17431
|
if (!await fileExists(p)) {
|
|
16642
17432
|
return {
|
|
16643
17433
|
id: this.id,
|
|
@@ -16662,7 +17452,7 @@ var playbooksDir2 = {
|
|
|
16662
17452
|
category: CATEGORY2,
|
|
16663
17453
|
title: "playbooks/ directory exists",
|
|
16664
17454
|
async run(ctx) {
|
|
16665
|
-
const p =
|
|
17455
|
+
const p = resolve41(ctx.syntaurRoot, "playbooks");
|
|
16666
17456
|
if (!await fileExists(p)) {
|
|
16667
17457
|
return {
|
|
16668
17458
|
id: this.id,
|
|
@@ -16687,7 +17477,7 @@ var todosDirValid = {
|
|
|
16687
17477
|
category: CATEGORY2,
|
|
16688
17478
|
title: "todos/ directory is readable (if present)",
|
|
16689
17479
|
async run(ctx) {
|
|
16690
|
-
const p =
|
|
17480
|
+
const p = resolve41(ctx.syntaurRoot, "todos");
|
|
16691
17481
|
if (!await fileExists(p)) {
|
|
16692
17482
|
return {
|
|
16693
17483
|
id: this.id,
|
|
@@ -16718,7 +17508,7 @@ var serversDirValid = {
|
|
|
16718
17508
|
category: CATEGORY2,
|
|
16719
17509
|
title: "servers/ directory is readable (if present)",
|
|
16720
17510
|
async run(ctx) {
|
|
16721
|
-
const p =
|
|
17511
|
+
const p = resolve41(ctx.syntaurRoot, "servers");
|
|
16722
17512
|
if (!await fileExists(p)) {
|
|
16723
17513
|
return {
|
|
16724
17514
|
id: this.id,
|
|
@@ -16763,7 +17553,7 @@ var knownFilesRecognized = {
|
|
|
16763
17553
|
title: this.title,
|
|
16764
17554
|
status: "warn",
|
|
16765
17555
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
16766
|
-
affected: unexpected.map((n) =>
|
|
17556
|
+
affected: unexpected.map((n) => resolve41(ctx.syntaurRoot, n)),
|
|
16767
17557
|
remediation: {
|
|
16768
17558
|
kind: "manual",
|
|
16769
17559
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -16792,7 +17582,7 @@ function pass2(check) {
|
|
|
16792
17582
|
|
|
16793
17583
|
// src/utils/doctor/checks/project.ts
|
|
16794
17584
|
init_fs();
|
|
16795
|
-
import { resolve as
|
|
17585
|
+
import { resolve as resolve42 } from "path";
|
|
16796
17586
|
import { readdir as readdir14, stat as stat5 } from "fs/promises";
|
|
16797
17587
|
var CATEGORY3 = "project";
|
|
16798
17588
|
var REQUIRED_PROJECT_FILES = [
|
|
@@ -16822,10 +17612,10 @@ async function listProjects2(ctx) {
|
|
|
16822
17612
|
for (const e of entries) {
|
|
16823
17613
|
if (!e.isDirectory()) continue;
|
|
16824
17614
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
16825
|
-
const projectDir =
|
|
17615
|
+
const projectDir = resolve42(dir, e.name);
|
|
16826
17616
|
let looksLikeProject = false;
|
|
16827
17617
|
for (const marker of PROJECT_MARKERS) {
|
|
16828
|
-
if (await fileExists(
|
|
17618
|
+
if (await fileExists(resolve42(projectDir, marker))) {
|
|
16829
17619
|
looksLikeProject = true;
|
|
16830
17620
|
break;
|
|
16831
17621
|
}
|
|
@@ -16844,7 +17634,7 @@ var requiredFiles = {
|
|
|
16844
17634
|
for (const projectDir of projects) {
|
|
16845
17635
|
const missing = [];
|
|
16846
17636
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
16847
|
-
const p =
|
|
17637
|
+
const p = resolve42(projectDir, rel);
|
|
16848
17638
|
if (!await fileExists(p)) missing.push(rel);
|
|
16849
17639
|
}
|
|
16850
17640
|
if (missing.length === 0) continue;
|
|
@@ -16854,7 +17644,7 @@ var requiredFiles = {
|
|
|
16854
17644
|
title: this.title,
|
|
16855
17645
|
status: "error",
|
|
16856
17646
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
16857
|
-
affected: missing.map((m) =>
|
|
17647
|
+
affected: missing.map((m) => resolve42(projectDir, m)),
|
|
16858
17648
|
remediation: {
|
|
16859
17649
|
kind: "manual",
|
|
16860
17650
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -16877,7 +17667,7 @@ var manifestStale = {
|
|
|
16877
17667
|
const projects = await listProjects2(ctx);
|
|
16878
17668
|
const results = [];
|
|
16879
17669
|
for (const projectDir of projects) {
|
|
16880
|
-
const manifestPath =
|
|
17670
|
+
const manifestPath = resolve42(projectDir, "manifest.md");
|
|
16881
17671
|
if (!await fileExists(manifestPath)) continue;
|
|
16882
17672
|
const manifestMtime = (await stat5(manifestPath)).mtimeMs;
|
|
16883
17673
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -16926,7 +17716,7 @@ var orphanFiles = {
|
|
|
16926
17716
|
title: this.title,
|
|
16927
17717
|
status: "warn",
|
|
16928
17718
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
16929
|
-
affected: orphans.map((o) =>
|
|
17719
|
+
affected: orphans.map((o) => resolve42(projectDir, o)),
|
|
16930
17720
|
autoFixable: false
|
|
16931
17721
|
});
|
|
16932
17722
|
}
|
|
@@ -16936,7 +17726,7 @@ var orphanFiles = {
|
|
|
16936
17726
|
};
|
|
16937
17727
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
16938
17728
|
async function newestAssignmentMtime(projectDir) {
|
|
16939
|
-
const assignmentsRoot =
|
|
17729
|
+
const assignmentsRoot = resolve42(projectDir, "assignments");
|
|
16940
17730
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
16941
17731
|
let newest = 0;
|
|
16942
17732
|
let entries;
|
|
@@ -16947,7 +17737,7 @@ async function newestAssignmentMtime(projectDir) {
|
|
|
16947
17737
|
}
|
|
16948
17738
|
for (const e of entries) {
|
|
16949
17739
|
if (!e.isDirectory()) continue;
|
|
16950
|
-
const assignmentMd =
|
|
17740
|
+
const assignmentMd = resolve42(assignmentsRoot, e.name, "assignment.md");
|
|
16951
17741
|
try {
|
|
16952
17742
|
const s = await stat5(assignmentMd);
|
|
16953
17743
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -16971,7 +17761,7 @@ init_fs();
|
|
|
16971
17761
|
init_parser();
|
|
16972
17762
|
init_types();
|
|
16973
17763
|
init_paths();
|
|
16974
|
-
import { resolve as
|
|
17764
|
+
import { resolve as resolve43 } from "path";
|
|
16975
17765
|
import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
|
|
16976
17766
|
var CATEGORY4 = "assignment";
|
|
16977
17767
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
@@ -16983,16 +17773,16 @@ async function listAssignments(ctx) {
|
|
|
16983
17773
|
for (const m of projects) {
|
|
16984
17774
|
if (!m.isDirectory()) continue;
|
|
16985
17775
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
16986
|
-
const assignmentsDir2 =
|
|
17776
|
+
const assignmentsDir2 = resolve43(projectsDir2, m.name, "assignments");
|
|
16987
17777
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
16988
17778
|
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
16989
17779
|
for (const a of entries) {
|
|
16990
17780
|
if (!a.isDirectory()) continue;
|
|
16991
17781
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
16992
|
-
const assignmentDir =
|
|
16993
|
-
const assignmentMd =
|
|
17782
|
+
const assignmentDir = resolve43(assignmentsDir2, a.name);
|
|
17783
|
+
const assignmentMd = resolve43(assignmentDir, "assignment.md");
|
|
16994
17784
|
const entry = {
|
|
16995
|
-
projectDir:
|
|
17785
|
+
projectDir: resolve43(projectsDir2, m.name),
|
|
16996
17786
|
projectSlug: m.name,
|
|
16997
17787
|
assignmentDir,
|
|
16998
17788
|
assignmentSlug: a.name,
|
|
@@ -17012,8 +17802,8 @@ async function listAssignments(ctx) {
|
|
|
17012
17802
|
for (const a of entries) {
|
|
17013
17803
|
if (!a.isDirectory()) continue;
|
|
17014
17804
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
17015
|
-
const assignmentDir =
|
|
17016
|
-
const assignmentMd =
|
|
17805
|
+
const assignmentDir = resolve43(standaloneRoot, a.name);
|
|
17806
|
+
const assignmentMd = resolve43(assignmentDir, "assignment.md");
|
|
17017
17807
|
const entry = {
|
|
17018
17808
|
projectDir: standaloneRoot,
|
|
17019
17809
|
projectSlug: null,
|
|
@@ -17091,7 +17881,7 @@ var invalidStatus = {
|
|
|
17091
17881
|
const allowed = configuredStatuses(ctx);
|
|
17092
17882
|
const results = [];
|
|
17093
17883
|
for (const a of withAssignmentMd) {
|
|
17094
|
-
const path =
|
|
17884
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17095
17885
|
const parsed = await parseSafe(path);
|
|
17096
17886
|
if (!parsed) continue;
|
|
17097
17887
|
if (!allowed.has(parsed.status)) {
|
|
@@ -17124,7 +17914,7 @@ var workspaceMissing = {
|
|
|
17124
17914
|
const terminal = terminalStatuses(ctx);
|
|
17125
17915
|
const results = [];
|
|
17126
17916
|
for (const a of withAssignmentMd) {
|
|
17127
|
-
const path =
|
|
17917
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17128
17918
|
const parsed = await parseSafe(path);
|
|
17129
17919
|
if (!parsed) continue;
|
|
17130
17920
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -17171,12 +17961,12 @@ var requiredFilesByStatus = {
|
|
|
17171
17961
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
17172
17962
|
const results = [];
|
|
17173
17963
|
for (const a of withAssignmentMd) {
|
|
17174
|
-
const assignmentPath =
|
|
17964
|
+
const assignmentPath = resolve43(a.assignmentDir, "assignment.md");
|
|
17175
17965
|
const parsed = await parseSafe(assignmentPath);
|
|
17176
17966
|
if (!parsed) continue;
|
|
17177
17967
|
const missing = [];
|
|
17178
17968
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
17179
|
-
const handoffPath =
|
|
17969
|
+
const handoffPath = resolve43(a.assignmentDir, "handoff.md");
|
|
17180
17970
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
17181
17971
|
}
|
|
17182
17972
|
if (missing.length === 0) continue;
|
|
@@ -17186,7 +17976,7 @@ var requiredFilesByStatus = {
|
|
|
17186
17976
|
title: this.title,
|
|
17187
17977
|
status: "warn",
|
|
17188
17978
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
17189
|
-
affected: missing.map((m) =>
|
|
17979
|
+
affected: missing.map((m) => resolve43(a.assignmentDir, m)),
|
|
17190
17980
|
remediation: {
|
|
17191
17981
|
kind: "manual",
|
|
17192
17982
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -17209,7 +17999,7 @@ var companionFilesScaffolded = {
|
|
|
17209
17999
|
for (const a of withAssignmentMd) {
|
|
17210
18000
|
const missing = [];
|
|
17211
18001
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
17212
|
-
if (!await fileExists(
|
|
18002
|
+
if (!await fileExists(resolve43(a.assignmentDir, filename))) {
|
|
17213
18003
|
missing.push(filename);
|
|
17214
18004
|
}
|
|
17215
18005
|
}
|
|
@@ -17221,7 +18011,7 @@ var companionFilesScaffolded = {
|
|
|
17221
18011
|
title: this.title,
|
|
17222
18012
|
status: "warn",
|
|
17223
18013
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
17224
|
-
affected: missing.map((m) =>
|
|
18014
|
+
affected: missing.map((m) => resolve43(a.assignmentDir, m)),
|
|
17225
18015
|
remediation: {
|
|
17226
18016
|
kind: "manual",
|
|
17227
18017
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -17254,7 +18044,7 @@ var typeDefinition = {
|
|
|
17254
18044
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
17255
18045
|
const results = [];
|
|
17256
18046
|
for (const a of withAssignmentMd) {
|
|
17257
|
-
const path =
|
|
18047
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17258
18048
|
const parsed = await parseSafe(path);
|
|
17259
18049
|
if (!parsed) continue;
|
|
17260
18050
|
if (!parsed.type) continue;
|
|
@@ -17288,7 +18078,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
17288
18078
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
17289
18079
|
const results = [];
|
|
17290
18080
|
for (const a of withAssignmentMd) {
|
|
17291
|
-
const path =
|
|
18081
|
+
const path = resolve43(a.assignmentDir, "assignment.md");
|
|
17292
18082
|
const parsed = await parseSafe(path);
|
|
17293
18083
|
if (!parsed) continue;
|
|
17294
18084
|
if (a.standalone) {
|
|
@@ -17362,7 +18152,7 @@ function pass4(check, detail) {
|
|
|
17362
18152
|
|
|
17363
18153
|
// src/utils/doctor/checks/dashboard.ts
|
|
17364
18154
|
init_fs();
|
|
17365
|
-
import { resolve as
|
|
18155
|
+
import { resolve as resolve44 } from "path";
|
|
17366
18156
|
var CATEGORY5 = "dashboard";
|
|
17367
18157
|
var dbReachable = {
|
|
17368
18158
|
id: "dashboard.db-reachable",
|
|
@@ -17376,7 +18166,7 @@ var dbReachable = {
|
|
|
17376
18166
|
title: this.title,
|
|
17377
18167
|
status: "error",
|
|
17378
18168
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
17379
|
-
affected: [
|
|
18169
|
+
affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
|
|
17380
18170
|
remediation: {
|
|
17381
18171
|
kind: "manual",
|
|
17382
18172
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -17394,7 +18184,7 @@ var dbReachable = {
|
|
|
17394
18184
|
title: this.title,
|
|
17395
18185
|
status: "error",
|
|
17396
18186
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
17397
|
-
affected: [
|
|
18187
|
+
affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
|
|
17398
18188
|
autoFixable: false
|
|
17399
18189
|
};
|
|
17400
18190
|
}
|
|
@@ -17406,7 +18196,7 @@ var dbReachable = {
|
|
|
17406
18196
|
title: this.title,
|
|
17407
18197
|
status: "error",
|
|
17408
18198
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
17409
|
-
affected: [
|
|
18199
|
+
affected: [resolve44(ctx.syntaurRoot, "syntaur.db")],
|
|
17410
18200
|
autoFixable: false
|
|
17411
18201
|
};
|
|
17412
18202
|
}
|
|
@@ -17432,7 +18222,7 @@ var ghostSessions = {
|
|
|
17432
18222
|
const results = [];
|
|
17433
18223
|
for (const row of rows) {
|
|
17434
18224
|
if (!row.project_slug) continue;
|
|
17435
|
-
const projectPath =
|
|
18225
|
+
const projectPath = resolve44(projectsDir2, row.project_slug, "project.md");
|
|
17436
18226
|
if (!await fileExists(projectPath)) {
|
|
17437
18227
|
results.push({
|
|
17438
18228
|
id: this.id,
|
|
@@ -17451,7 +18241,7 @@ var ghostSessions = {
|
|
|
17451
18241
|
continue;
|
|
17452
18242
|
}
|
|
17453
18243
|
if (row.assignment_slug) {
|
|
17454
|
-
const assignmentPath =
|
|
18244
|
+
const assignmentPath = resolve44(
|
|
17455
18245
|
projectsDir2,
|
|
17456
18246
|
row.project_slug,
|
|
17457
18247
|
"assignments",
|
|
@@ -17503,7 +18293,7 @@ function skipped(check, reason) {
|
|
|
17503
18293
|
|
|
17504
18294
|
// src/utils/doctor/checks/integrations.ts
|
|
17505
18295
|
init_fs();
|
|
17506
|
-
import { resolve as
|
|
18296
|
+
import { resolve as resolve45, dirname as dirname13, basename as basename5 } from "path";
|
|
17507
18297
|
import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
|
|
17508
18298
|
import { homedir as homedir6 } from "os";
|
|
17509
18299
|
var CATEGORY6 = "integrations";
|
|
@@ -17586,7 +18376,7 @@ var backupConfigured = {
|
|
|
17586
18376
|
}
|
|
17587
18377
|
};
|
|
17588
18378
|
async function readKnownMarketplaces() {
|
|
17589
|
-
const path =
|
|
18379
|
+
const path = resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json");
|
|
17590
18380
|
if (!await fileExists(path)) return {};
|
|
17591
18381
|
try {
|
|
17592
18382
|
const raw = await readFile27(path, "utf-8");
|
|
@@ -17605,8 +18395,8 @@ var claudeMarketplaceRegistered = {
|
|
|
17605
18395
|
if (!await fileExists(dir)) {
|
|
17606
18396
|
return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
|
|
17607
18397
|
}
|
|
17608
|
-
const pluginsParent =
|
|
17609
|
-
if (
|
|
18398
|
+
const pluginsParent = dirname13(dir);
|
|
18399
|
+
if (basename5(pluginsParent) !== "plugins") {
|
|
17610
18400
|
return {
|
|
17611
18401
|
id: this.id,
|
|
17612
18402
|
category: this.category,
|
|
@@ -17622,8 +18412,8 @@ var claudeMarketplaceRegistered = {
|
|
|
17622
18412
|
autoFixable: false
|
|
17623
18413
|
};
|
|
17624
18414
|
}
|
|
17625
|
-
const marketplaceRoot =
|
|
17626
|
-
const marketplaceManifest =
|
|
18415
|
+
const marketplaceRoot = dirname13(pluginsParent);
|
|
18416
|
+
const marketplaceManifest = resolve45(marketplaceRoot, ".claude-plugin", "marketplace.json");
|
|
17627
18417
|
if (!await fileExists(marketplaceManifest)) {
|
|
17628
18418
|
return {
|
|
17629
18419
|
id: this.id,
|
|
@@ -17654,7 +18444,7 @@ var claudeMarketplaceRegistered = {
|
|
|
17654
18444
|
autoFixable: false
|
|
17655
18445
|
};
|
|
17656
18446
|
}
|
|
17657
|
-
const marketplaceName = parsed.name ??
|
|
18447
|
+
const marketplaceName = parsed.name ?? basename5(marketplaceRoot);
|
|
17658
18448
|
const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
|
|
17659
18449
|
const known = await readKnownMarketplaces();
|
|
17660
18450
|
const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
|
|
@@ -17674,7 +18464,7 @@ var claudeMarketplaceRegistered = {
|
|
|
17674
18464
|
title: this.title,
|
|
17675
18465
|
status: "error",
|
|
17676
18466
|
detail: issues.join("; "),
|
|
17677
|
-
affected: [marketplaceManifest,
|
|
18467
|
+
affected: [marketplaceManifest, resolve45(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
|
|
17678
18468
|
remediation: {
|
|
17679
18469
|
kind: "manual",
|
|
17680
18470
|
suggestion: "Re-run install-plugin to ensure both files are in sync.",
|
|
@@ -17714,7 +18504,7 @@ function skipped2(check, reason) {
|
|
|
17714
18504
|
init_fs();
|
|
17715
18505
|
init_parser();
|
|
17716
18506
|
init_types();
|
|
17717
|
-
import { resolve as
|
|
18507
|
+
import { resolve as resolve46 } from "path";
|
|
17718
18508
|
import { readFile as readFile28 } from "fs/promises";
|
|
17719
18509
|
var CATEGORY7 = "workspace";
|
|
17720
18510
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
@@ -17727,7 +18517,7 @@ function isStandaloneSession(ctx) {
|
|
|
17727
18517
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
17728
18518
|
}
|
|
17729
18519
|
async function loadContext(ctx) {
|
|
17730
|
-
const path =
|
|
18520
|
+
const path = resolve46(ctx.cwd, ".syntaur", "context.json");
|
|
17731
18521
|
if (!await fileExists(path)) {
|
|
17732
18522
|
return { data: null, path, exists: false, parseError: null };
|
|
17733
18523
|
}
|
|
@@ -17807,7 +18597,7 @@ var contextAssignmentResolves = {
|
|
|
17807
18597
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
17808
18598
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
17809
18599
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
17810
|
-
const assignmentMd =
|
|
18600
|
+
const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
|
|
17811
18601
|
if (!await fileExists(assignmentMd)) {
|
|
17812
18602
|
return {
|
|
17813
18603
|
id: this.id,
|
|
@@ -17836,7 +18626,7 @@ var contextTerminal = {
|
|
|
17836
18626
|
if (!exists) return skipped3(this, "no context to check");
|
|
17837
18627
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
17838
18628
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
17839
|
-
const assignmentMd =
|
|
18629
|
+
const assignmentMd = resolve46(data.assignmentDir, "assignment.md");
|
|
17840
18630
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
17841
18631
|
try {
|
|
17842
18632
|
const content = await readFile28(assignmentMd, "utf-8");
|
|
@@ -17984,13 +18774,13 @@ var agentChecks = [agentsResolvable];
|
|
|
17984
18774
|
|
|
17985
18775
|
// src/utils/doctor/checks/skills.ts
|
|
17986
18776
|
init_fs();
|
|
17987
|
-
import { resolve as
|
|
18777
|
+
import { resolve as resolve47, join as join5 } from "path";
|
|
17988
18778
|
import { readdir as readdir17, readFile as readFile29, lstat as lstat4 } from "fs/promises";
|
|
17989
18779
|
import { homedir as homedir7 } from "os";
|
|
17990
18780
|
var CATEGORY9 = "skills";
|
|
17991
18781
|
var skillTargets = [
|
|
17992
|
-
{ agent: "claude", dir:
|
|
17993
|
-
{ agent: "codex", dir:
|
|
18782
|
+
{ agent: "claude", dir: resolve47(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
|
|
18783
|
+
{ agent: "codex", dir: resolve47(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
|
|
17994
18784
|
];
|
|
17995
18785
|
var skillsDedupCheck = {
|
|
17996
18786
|
id: "skills.dedup",
|
|
@@ -18156,14 +18946,14 @@ async function finalize(checks) {
|
|
|
18156
18946
|
async function readVersion() {
|
|
18157
18947
|
try {
|
|
18158
18948
|
const here = fileURLToPath6(import.meta.url);
|
|
18159
|
-
let dir =
|
|
18949
|
+
let dir = dirname14(here);
|
|
18160
18950
|
for (let i = 0; i < 6; i++) {
|
|
18161
18951
|
try {
|
|
18162
18952
|
const raw = await readFile30(join6(dir, "package.json"), "utf-8");
|
|
18163
18953
|
const parsed = JSON.parse(raw);
|
|
18164
18954
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
18165
18955
|
} catch {
|
|
18166
|
-
dir =
|
|
18956
|
+
dir = dirname14(dir);
|
|
18167
18957
|
}
|
|
18168
18958
|
}
|
|
18169
18959
|
return null;
|
|
@@ -18256,7 +19046,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
|
|
|
18256
19046
|
];
|
|
18257
19047
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
18258
19048
|
async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
|
|
18259
|
-
const absolute = isAbsolute7(inputPath) ? inputPath :
|
|
19049
|
+
const absolute = isAbsolute7(inputPath) ? inputPath : resolve48(cwd, inputPath);
|
|
18260
19050
|
const errors = [];
|
|
18261
19051
|
const warnings = [];
|
|
18262
19052
|
if (!await fileExists(absolute)) {
|
|
@@ -18593,7 +19383,7 @@ init_uuid();
|
|
|
18593
19383
|
init_timestamp();
|
|
18594
19384
|
init_assignment_resolver();
|
|
18595
19385
|
init_templates();
|
|
18596
|
-
import { resolve as
|
|
19386
|
+
import { resolve as resolve49 } from "path";
|
|
18597
19387
|
import { readFile as readFile32 } from "fs/promises";
|
|
18598
19388
|
function shortId() {
|
|
18599
19389
|
return generateId().split("-")[0];
|
|
@@ -18624,7 +19414,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
18624
19414
|
if (!isValidSlug(target)) {
|
|
18625
19415
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
18626
19416
|
}
|
|
18627
|
-
assignmentDir =
|
|
19417
|
+
assignmentDir = resolve49(baseDir, options.project, "assignments", target);
|
|
18628
19418
|
assignmentRef = target;
|
|
18629
19419
|
} else {
|
|
18630
19420
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -18634,7 +19424,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
18634
19424
|
assignmentDir = resolved.assignmentDir;
|
|
18635
19425
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
18636
19426
|
}
|
|
18637
|
-
const commentsPath =
|
|
19427
|
+
const commentsPath = resolve49(assignmentDir, "comments.md");
|
|
18638
19428
|
const timestamp = nowTimestamp();
|
|
18639
19429
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
18640
19430
|
let currentContent;
|
|
@@ -18674,8 +19464,8 @@ ${entry}`;
|
|
|
18674
19464
|
}
|
|
18675
19465
|
|
|
18676
19466
|
// src/commands/capture.ts
|
|
18677
|
-
import { resolve as
|
|
18678
|
-
import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as
|
|
19467
|
+
import { resolve as resolve52, relative as relative4 } from "path";
|
|
19468
|
+
import { copyFile as copyFile3, mkdir as mkdir7, realpath as realpath2, stat as stat7 } from "fs/promises";
|
|
18679
19469
|
|
|
18680
19470
|
// src/utils/assignment-target.ts
|
|
18681
19471
|
init_paths();
|
|
@@ -18684,12 +19474,12 @@ init_config2();
|
|
|
18684
19474
|
init_slug();
|
|
18685
19475
|
init_assignment_resolver();
|
|
18686
19476
|
init_parser();
|
|
18687
|
-
import { resolve as
|
|
19477
|
+
import { resolve as resolve50 } from "path";
|
|
18688
19478
|
import { readFile as readFile33 } from "fs/promises";
|
|
18689
19479
|
var AssignmentTargetError = class extends Error {
|
|
18690
19480
|
};
|
|
18691
19481
|
async function readAssignmentFrontmatterId(assignmentDir) {
|
|
18692
|
-
const path =
|
|
19482
|
+
const path = resolve50(assignmentDir, "assignment.md");
|
|
18693
19483
|
if (!await fileExists(path)) return null;
|
|
18694
19484
|
try {
|
|
18695
19485
|
const content = await readFile33(path, "utf-8");
|
|
@@ -18700,7 +19490,7 @@ async function readAssignmentFrontmatterId(assignmentDir) {
|
|
|
18700
19490
|
}
|
|
18701
19491
|
}
|
|
18702
19492
|
async function readContextJson(cwd) {
|
|
18703
|
-
const path =
|
|
19493
|
+
const path = resolve50(cwd, ".syntaur", "context.json");
|
|
18704
19494
|
if (!await fileExists(path)) return null;
|
|
18705
19495
|
try {
|
|
18706
19496
|
const raw = await readFile33(path, "utf-8");
|
|
@@ -18724,15 +19514,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
18724
19514
|
if (!isValidSlug(input4)) {
|
|
18725
19515
|
throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
|
|
18726
19516
|
}
|
|
18727
|
-
const projectDir =
|
|
18728
|
-
const projectMdPath =
|
|
19517
|
+
const projectDir = resolve50(baseDir, opts.project);
|
|
19518
|
+
const projectMdPath = resolve50(projectDir, "project.md");
|
|
18729
19519
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
18730
19520
|
throw new AssignmentTargetError(
|
|
18731
19521
|
`Project "${opts.project}" not found at ${projectDir}.`
|
|
18732
19522
|
);
|
|
18733
19523
|
}
|
|
18734
|
-
const assignmentDir =
|
|
18735
|
-
const assignmentMdPath2 =
|
|
19524
|
+
const assignmentDir = resolve50(projectDir, "assignments", input4);
|
|
19525
|
+
const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
|
|
18736
19526
|
if (!await fileExists(assignmentMdPath2)) {
|
|
18737
19527
|
throw new AssignmentTargetError(
|
|
18738
19528
|
`Assignment "${input4}" not found in project "${opts.project}".`
|
|
@@ -18766,7 +19556,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
18766
19556
|
}
|
|
18767
19557
|
if (ctx.assignmentDir) {
|
|
18768
19558
|
const dir = expandHome(ctx.assignmentDir);
|
|
18769
|
-
const assignmentMdPath2 =
|
|
19559
|
+
const assignmentMdPath2 = resolve50(dir, "assignment.md");
|
|
18770
19560
|
if (!await fileExists(assignmentMdPath2)) {
|
|
18771
19561
|
throw new AssignmentTargetError(
|
|
18772
19562
|
`.syntaur/context.json points to a missing assignment dir: ${dir}.`
|
|
@@ -18795,8 +19585,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
18795
19585
|
`.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
|
|
18796
19586
|
);
|
|
18797
19587
|
}
|
|
18798
|
-
const assignmentDir =
|
|
18799
|
-
const assignmentMdPath2 =
|
|
19588
|
+
const assignmentDir = resolve50(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
|
|
19589
|
+
const assignmentMdPath2 = resolve50(assignmentDir, "assignment.md");
|
|
18800
19590
|
if (!await fileExists(assignmentMdPath2)) {
|
|
18801
19591
|
throw new AssignmentTargetError(
|
|
18802
19592
|
`.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
|
|
@@ -18855,48 +19645,122 @@ function isArtifactKind(value) {
|
|
|
18855
19645
|
// src/commands/capture.ts
|
|
18856
19646
|
init_fs();
|
|
18857
19647
|
|
|
18858
|
-
// src/
|
|
18859
|
-
|
|
18860
|
-
import
|
|
18861
|
-
import {
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
|
|
18868
|
-
|
|
18869
|
-
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
|
|
18873
|
-
|
|
18874
|
-
)
|
|
18875
|
-
|
|
18876
|
-
|
|
18877
|
-
|
|
18878
|
-
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
18886
|
-
|
|
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
|
+
|
|
19722
|
+
// src/db/proof-db.ts
|
|
19723
|
+
init_paths();
|
|
19724
|
+
import Database4 from "better-sqlite3";
|
|
19725
|
+
import { resolve as resolve51 } from "path";
|
|
19726
|
+
var db3 = null;
|
|
19727
|
+
var PROOF_SCHEMA_VERSION = "1";
|
|
19728
|
+
var SCHEMA_SQL3 = `
|
|
19729
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
19730
|
+
id TEXT PRIMARY KEY,
|
|
19731
|
+
assignment_id TEXT NOT NULL,
|
|
19732
|
+
assignment_dir TEXT NOT NULL,
|
|
19733
|
+
criterion_index INTEGER,
|
|
19734
|
+
kind TEXT NOT NULL,
|
|
19735
|
+
file_path TEXT,
|
|
19736
|
+
note TEXT,
|
|
19737
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19738
|
+
);
|
|
19739
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_assignment ON artifacts(assignment_id);
|
|
19740
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_assignment_criterion ON artifacts(assignment_id, criterion_index);
|
|
19741
|
+
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
19742
|
+
`;
|
|
19743
|
+
function initProofDb(dbPath) {
|
|
19744
|
+
if (db3) return db3;
|
|
19745
|
+
const finalPath = dbPath ?? resolve51(syntaurRoot(), "syntaur.db");
|
|
19746
|
+
db3 = new Database4(finalPath);
|
|
19747
|
+
db3.pragma("journal_mode = WAL");
|
|
19748
|
+
db3.exec(SCHEMA_SQL3);
|
|
19749
|
+
db3.prepare("INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)").run(
|
|
19750
|
+
"proof_schema_version",
|
|
18887
19751
|
PROOF_SCHEMA_VERSION
|
|
18888
19752
|
);
|
|
18889
|
-
const database =
|
|
19753
|
+
const database = db3;
|
|
18890
19754
|
const runMigrations = database.transaction(() => {
|
|
18891
19755
|
});
|
|
18892
19756
|
runMigrations.exclusive();
|
|
18893
|
-
return
|
|
19757
|
+
return db3;
|
|
18894
19758
|
}
|
|
18895
19759
|
function getProofDb() {
|
|
18896
|
-
if (!
|
|
19760
|
+
if (!db3) {
|
|
18897
19761
|
throw new Error("Proof database not initialized. Call initProofDb() first.");
|
|
18898
19762
|
}
|
|
18899
|
-
return
|
|
19763
|
+
return db3;
|
|
18900
19764
|
}
|
|
18901
19765
|
function insertArtifact(input4) {
|
|
18902
19766
|
const database = getProofDb();
|
|
@@ -18959,6 +19823,27 @@ async function captureCommand(target, options = {}) {
|
|
|
18959
19823
|
);
|
|
18960
19824
|
}
|
|
18961
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
|
+
}
|
|
18962
19847
|
if (kind === "text") {
|
|
18963
19848
|
if (options.file) {
|
|
18964
19849
|
throw new Error("--kind=text forbids --file. Use --note for text payloads.");
|
|
@@ -18967,7 +19852,7 @@ async function captureCommand(target, options = {}) {
|
|
|
18967
19852
|
throw new Error("--kind=text requires --note.");
|
|
18968
19853
|
}
|
|
18969
19854
|
} else {
|
|
18970
|
-
if (kind !== "http" && !options.file) {
|
|
19855
|
+
if (kind !== "http" && !options.file && !shelloutMode) {
|
|
18971
19856
|
throw new Error(`--kind=${kind} requires --file.`);
|
|
18972
19857
|
}
|
|
18973
19858
|
if (kind === "http" && !options.file && (!options.note || options.note.trim() === "")) {
|
|
@@ -18981,8 +19866,9 @@ async function captureCommand(target, options = {}) {
|
|
|
18981
19866
|
cwd: options.cwd
|
|
18982
19867
|
});
|
|
18983
19868
|
let resolvedSource = null;
|
|
19869
|
+
let shelloutCleanup = null;
|
|
18984
19870
|
if (options.file) {
|
|
18985
|
-
const expanded = options.file.startsWith("~/") ?
|
|
19871
|
+
const expanded = options.file.startsWith("~/") ? resolve52(process.env.HOME ?? "", options.file.slice(2)) : resolve52(options.file);
|
|
18986
19872
|
if (!await fileExists(expanded)) {
|
|
18987
19873
|
throw new Error(`--file does not exist: ${options.file}`);
|
|
18988
19874
|
}
|
|
@@ -18994,72 +19880,93 @@ async function captureCommand(target, options = {}) {
|
|
|
18994
19880
|
`--file is unreadable: ${options.file} (${e instanceof Error ? e.message : String(e)})`
|
|
18995
19881
|
);
|
|
18996
19882
|
}
|
|
18997
|
-
const st = await
|
|
19883
|
+
const st = await stat7(real);
|
|
18998
19884
|
if (!st.isFile()) {
|
|
18999
19885
|
throw new Error(`--file is not a regular file: ${options.file}`);
|
|
19000
19886
|
}
|
|
19001
19887
|
resolvedSource = real;
|
|
19888
|
+
} else if (shelloutMode) {
|
|
19889
|
+
const { pngPath, cleanup } = await captureScreenshot(shelloutMode);
|
|
19890
|
+
resolvedSource = pngPath;
|
|
19891
|
+
shelloutCleanup = cleanup;
|
|
19002
19892
|
}
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
|
|
19013
|
-
|
|
19014
|
-
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
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;
|
|
19039
19932
|
}
|
|
19040
|
-
throw e;
|
|
19041
19933
|
}
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
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
|
+
}
|
|
19056
19963
|
}
|
|
19057
19964
|
}
|
|
19058
19965
|
|
|
19059
19966
|
// src/commands/proof.ts
|
|
19060
19967
|
import { Command as Command5 } from "commander";
|
|
19061
|
-
import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as
|
|
19062
|
-
import { resolve as
|
|
19968
|
+
import { readFile as readFile34, writeFile as writeFile10, rename as rename7, stat as stat8 } from "fs/promises";
|
|
19969
|
+
import { resolve as resolve53, relative as relative5, isAbsolute as isAbsolute8 } from "path";
|
|
19063
19970
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
19064
19971
|
|
|
19065
19972
|
// src/utils/acceptance-criteria-parse.ts
|
|
@@ -19262,7 +20169,7 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map()) {
|
|
|
19262
20169
|
|
|
19263
20170
|
// src/commands/proof.ts
|
|
19264
20171
|
async function readAssignmentMeta(assignmentDir) {
|
|
19265
|
-
const path =
|
|
20172
|
+
const path = resolve53(assignmentDir, "assignment.md");
|
|
19266
20173
|
if (!await fileExists(path)) {
|
|
19267
20174
|
return { title: "", body: "" };
|
|
19268
20175
|
}
|
|
@@ -19313,7 +20220,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
19313
20220
|
for (const r of rows) {
|
|
19314
20221
|
if (!r.file_path) continue;
|
|
19315
20222
|
if (r.kind !== "http" && r.kind !== "text") continue;
|
|
19316
|
-
const abs =
|
|
20223
|
+
const abs = resolve53(assignmentDir, r.file_path);
|
|
19317
20224
|
if (!isWithin(proofRoot, abs)) {
|
|
19318
20225
|
out.set(r.file_path, null);
|
|
19319
20226
|
continue;
|
|
@@ -19322,7 +20229,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
19322
20229
|
out.set(r.file_path, null);
|
|
19323
20230
|
continue;
|
|
19324
20231
|
}
|
|
19325
|
-
const st = await
|
|
20232
|
+
const st = await stat8(abs);
|
|
19326
20233
|
if (st.size > INLINE_TEXT_LIMIT_BYTES) {
|
|
19327
20234
|
out.set(r.file_path, null);
|
|
19328
20235
|
continue;
|
|
@@ -19361,8 +20268,8 @@ async function proofBuildCommand(target, options = {}) {
|
|
|
19361
20268
|
};
|
|
19362
20269
|
const md = renderProofMarkdown(renderParams);
|
|
19363
20270
|
const html = renderProofHtml(renderParams, inlineFiles);
|
|
19364
|
-
const mdPath =
|
|
19365
|
-
const htmlPath =
|
|
20271
|
+
const mdPath = resolve53(resolved.assignmentDir, "proof.md");
|
|
20272
|
+
const htmlPath = resolve53(resolved.assignmentDir, "proof.html");
|
|
19366
20273
|
await atomicWrite(mdPath, md);
|
|
19367
20274
|
await atomicWrite(htmlPath, html);
|
|
19368
20275
|
console.log(`Wrote ${htmlPath}`);
|
|
@@ -19379,6 +20286,478 @@ proofCommand.command("build").description("Render proof.html and proof.md for an
|
|
|
19379
20286
|
}
|
|
19380
20287
|
});
|
|
19381
20288
|
|
|
20289
|
+
// src/commands/lease.ts
|
|
20290
|
+
import { Command as Command6 } from "commander";
|
|
20291
|
+
var DURATION_REGEX = /^(\d+)\s*(s|m|h|d)?$/i;
|
|
20292
|
+
function parseDuration(input4, fallbackSeconds) {
|
|
20293
|
+
if (!input4) return fallbackSeconds;
|
|
20294
|
+
const match = DURATION_REGEX.exec(input4.trim());
|
|
20295
|
+
if (!match) {
|
|
20296
|
+
throw new Error(
|
|
20297
|
+
`invalid duration "${input4}" \u2014 use e.g. 30s, 5m, 2h, 1d`
|
|
20298
|
+
);
|
|
20299
|
+
}
|
|
20300
|
+
const n = Number.parseInt(match[1], 10);
|
|
20301
|
+
const unit = (match[2] ?? "s").toLowerCase();
|
|
20302
|
+
const mult = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
20303
|
+
return n * mult[unit];
|
|
20304
|
+
}
|
|
20305
|
+
function parseMetadataFlags(values) {
|
|
20306
|
+
if (!values || values.length === 0) return void 0;
|
|
20307
|
+
const out = {};
|
|
20308
|
+
for (const raw of values) {
|
|
20309
|
+
const eq = raw.indexOf("=");
|
|
20310
|
+
if (eq < 0) {
|
|
20311
|
+
throw new Error(`invalid -m flag "${raw}" \u2014 use key=value`);
|
|
20312
|
+
}
|
|
20313
|
+
out[raw.slice(0, eq)] = raw.slice(eq + 1);
|
|
20314
|
+
}
|
|
20315
|
+
return out;
|
|
20316
|
+
}
|
|
20317
|
+
var leaseCommand = new Command6("lease").description(
|
|
20318
|
+
"Resource leases \u2014 claim and release coordinated shared resources"
|
|
20319
|
+
);
|
|
20320
|
+
leaseCommand.command("create-inventory").description("Create a new inventory of leaseable resources").argument("<slug>", "Inventory slug (e.g. dev-envs, prod-migration-lock)").requiredOption("--kind <kind>", "Inventory kind (free-form label, e.g. dev-env, db-instance, lock)").option("--display-name <text>", "Human-readable display name").option("--default-ttl <duration>", "Default lease TTL (e.g. 30m, 2h). Default: 30m", "30m").action(async (slug, opts) => {
|
|
20321
|
+
try {
|
|
20322
|
+
initLeasesDb();
|
|
20323
|
+
const ttl_s = parseDuration(opts.defaultTtl, 30 * 60);
|
|
20324
|
+
const row = createInventory({
|
|
20325
|
+
slug,
|
|
20326
|
+
kind: opts.kind,
|
|
20327
|
+
display_name: opts.displayName,
|
|
20328
|
+
default_ttl_s: ttl_s
|
|
20329
|
+
});
|
|
20330
|
+
console.log(`Created inventory '${row.slug}' (kind=${row.kind}, default_ttl=${row.default_ttl_s}s).`);
|
|
20331
|
+
} catch (error) {
|
|
20332
|
+
if (error instanceof DuplicateInventoryError) {
|
|
20333
|
+
console.error(`Error: inventory '${error.slug}' already exists`);
|
|
20334
|
+
process.exit(1);
|
|
20335
|
+
}
|
|
20336
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20337
|
+
console.error(`Error: ${message}`);
|
|
20338
|
+
process.exit(1);
|
|
20339
|
+
}
|
|
20340
|
+
});
|
|
20341
|
+
var memberCommand = leaseCommand.command("member").description("Manage members of an inventory");
|
|
20342
|
+
memberCommand.command("add").description("Add a member to an inventory").argument("<inventory>", "Inventory slug").argument("<member-id>", "Member id (unique within the inventory)").option("-m, --metadata <key=value...>", "Metadata key=value pairs (repeatable)").action(async (inventory, memberId, opts) => {
|
|
20343
|
+
try {
|
|
20344
|
+
initLeasesDb();
|
|
20345
|
+
const metadata = parseMetadataFlags(opts.metadata);
|
|
20346
|
+
addMember({ inventory_slug: inventory, member_id: memberId, metadata });
|
|
20347
|
+
console.log(`Added member '${memberId}' to '${inventory}'.`);
|
|
20348
|
+
} catch (error) {
|
|
20349
|
+
if (error instanceof DuplicateMemberError) {
|
|
20350
|
+
console.error(`Error: member ${error.inventorySlug}/${error.memberId} already exists`);
|
|
20351
|
+
process.exit(1);
|
|
20352
|
+
}
|
|
20353
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20354
|
+
console.error(`Error: ${message}`);
|
|
20355
|
+
process.exit(1);
|
|
20356
|
+
}
|
|
20357
|
+
});
|
|
20358
|
+
memberCommand.command("retire").description("Retire a member (no longer claimable). Refuses if member is currently leased.").argument("<inventory>", "Inventory slug").argument("<member-id>", "Member id").action(async (inventory, memberId) => {
|
|
20359
|
+
try {
|
|
20360
|
+
initLeasesDb();
|
|
20361
|
+
retireMember(inventory, memberId);
|
|
20362
|
+
console.log(`Retired member '${inventory}/${memberId}'.`);
|
|
20363
|
+
} catch (error) {
|
|
20364
|
+
if (error instanceof MemberInUseError) {
|
|
20365
|
+
console.error(
|
|
20366
|
+
`Error: member ${error.inventorySlug}/${error.memberId} is currently leased; release before retiring`
|
|
20367
|
+
);
|
|
20368
|
+
process.exit(1);
|
|
20369
|
+
}
|
|
20370
|
+
if (error instanceof NotFoundError) {
|
|
20371
|
+
console.error(`Error: member not found`);
|
|
20372
|
+
process.exit(1);
|
|
20373
|
+
}
|
|
20374
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20375
|
+
console.error(`Error: ${message}`);
|
|
20376
|
+
process.exit(1);
|
|
20377
|
+
}
|
|
20378
|
+
});
|
|
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}`);
|
|
20444
|
+
process.exit(1);
|
|
20445
|
+
}
|
|
20446
|
+
}
|
|
20447
|
+
);
|
|
20448
|
+
leaseCommand.command("release").description("Release a lease by its lease_id").argument("<lease-id>", "Opaque lease id returned by `claim`").action(async (leaseId) => {
|
|
20449
|
+
try {
|
|
20450
|
+
initLeasesDb();
|
|
20451
|
+
releaseLease(leaseId);
|
|
20452
|
+
console.log(`Released lease ${leaseId}.`);
|
|
20453
|
+
} catch (error) {
|
|
20454
|
+
if (error instanceof StaleLeaseError) {
|
|
20455
|
+
console.error(
|
|
20456
|
+
`Error: lease ${error.leaseId} is no longer active (expired or revoked)`
|
|
20457
|
+
);
|
|
20458
|
+
process.exit(1);
|
|
20459
|
+
}
|
|
20460
|
+
if (error instanceof LeaseContentionError) {
|
|
20461
|
+
console.error(`Error: contention timeout; retry`);
|
|
20462
|
+
process.exit(1);
|
|
20463
|
+
}
|
|
20464
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20465
|
+
console.error(`Error: ${message}`);
|
|
20466
|
+
process.exit(1);
|
|
20467
|
+
}
|
|
20468
|
+
});
|
|
20469
|
+
leaseCommand.command("extend").description("Extend a lease \u2014 sets new expires_at = now + ttl. Refuses if expired or stale.").argument("<lease-id>", "Opaque lease id").requiredOption("--ttl <duration>", "New TTL from now (e.g. 15m)").action(async (leaseId, opts) => {
|
|
20470
|
+
try {
|
|
20471
|
+
initLeasesDb();
|
|
20472
|
+
const ttl_s = parseDuration(opts.ttl, 0);
|
|
20473
|
+
if (ttl_s <= 0) {
|
|
20474
|
+
throw new Error("--ttl must be positive");
|
|
20475
|
+
}
|
|
20476
|
+
const res = extendLease(leaseId, ttl_s);
|
|
20477
|
+
console.log(`Extended lease ${leaseId} (new expires_at=${res.new_expires_at}).`);
|
|
20478
|
+
} catch (error) {
|
|
20479
|
+
if (error instanceof StaleLeaseError) {
|
|
20480
|
+
console.error(
|
|
20481
|
+
`Error: lease ${error.leaseId} cannot be extended (expired, revoked, or member generation advanced)`
|
|
20482
|
+
);
|
|
20483
|
+
process.exit(1);
|
|
20484
|
+
}
|
|
20485
|
+
if (error instanceof LeaseContentionError) {
|
|
20486
|
+
console.error(`Error: contention timeout; retry`);
|
|
20487
|
+
process.exit(1);
|
|
20488
|
+
}
|
|
20489
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20490
|
+
console.error(`Error: ${message}`);
|
|
20491
|
+
process.exit(1);
|
|
20492
|
+
}
|
|
20493
|
+
});
|
|
20494
|
+
leaseCommand.command("show").description("Show a single lease as JSON").argument("<lease-id>", "Lease id").action(async (leaseId) => {
|
|
20495
|
+
try {
|
|
20496
|
+
initLeasesDb();
|
|
20497
|
+
const row = getLease(leaseId);
|
|
20498
|
+
if (!row) {
|
|
20499
|
+
console.error(`Error: lease ${leaseId} not found`);
|
|
20500
|
+
process.exit(1);
|
|
20501
|
+
return;
|
|
20502
|
+
}
|
|
20503
|
+
console.log(JSON.stringify(row, null, 2));
|
|
20504
|
+
} catch (error) {
|
|
20505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20506
|
+
console.error(`Error: ${message}`);
|
|
20507
|
+
process.exit(1);
|
|
20508
|
+
}
|
|
20509
|
+
});
|
|
20510
|
+
var VALID_STATES = /* @__PURE__ */ new Set([
|
|
20511
|
+
"active",
|
|
20512
|
+
"expired",
|
|
20513
|
+
"released",
|
|
20514
|
+
"revoked"
|
|
20515
|
+
]);
|
|
20516
|
+
leaseCommand.command("list").description("List leases (optionally filtered by inventory and/or state)").option("--inventory <slug>", "Filter by inventory").option("--state <state>", "Filter by state: active|expired|released|revoked").option("--json", "Output JSON instead of a table").action(async (opts) => {
|
|
20517
|
+
try {
|
|
20518
|
+
initLeasesDb();
|
|
20519
|
+
let state;
|
|
20520
|
+
if (opts.state) {
|
|
20521
|
+
if (!VALID_STATES.has(opts.state)) {
|
|
20522
|
+
throw new Error(
|
|
20523
|
+
`--state must be one of: ${[...VALID_STATES].join("|")}`
|
|
20524
|
+
);
|
|
20525
|
+
}
|
|
20526
|
+
state = opts.state;
|
|
20527
|
+
}
|
|
20528
|
+
if (!opts.inventory && !state && !opts.json) {
|
|
20529
|
+
const inventories = listInventories();
|
|
20530
|
+
if (inventories.length === 0) {
|
|
20531
|
+
console.log("(no inventories \u2014 create one with `syntaur lease create-inventory <slug> --kind <kind>`)");
|
|
20532
|
+
return;
|
|
20533
|
+
}
|
|
20534
|
+
for (const inv of inventories) {
|
|
20535
|
+
const detail = getInventoryDetail(inv.slug);
|
|
20536
|
+
if (!detail) continue;
|
|
20537
|
+
const idleCount = detail.members.filter((m) => m.status === "idle").length;
|
|
20538
|
+
const leasedCount = detail.members.filter((m) => m.status === "leased").length;
|
|
20539
|
+
console.log(
|
|
20540
|
+
`${inv.slug.padEnd(24)} kind=${inv.kind} idle=${idleCount} leased=${leasedCount} active_leases=${detail.active_leases.length}`
|
|
20541
|
+
);
|
|
20542
|
+
}
|
|
20543
|
+
return;
|
|
20544
|
+
}
|
|
20545
|
+
const rows = listLeases({ inventory: opts.inventory, state });
|
|
20546
|
+
if (opts.json) {
|
|
20547
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
20548
|
+
return;
|
|
20549
|
+
}
|
|
20550
|
+
if (rows.length === 0) {
|
|
20551
|
+
console.log("(no leases match)");
|
|
20552
|
+
return;
|
|
20553
|
+
}
|
|
20554
|
+
for (const r of rows) {
|
|
20555
|
+
console.log(
|
|
20556
|
+
`${r.lease_id} ${r.inventory_slug}/${r.member_id} ${r.state} expires=${r.expires_at}`
|
|
20557
|
+
);
|
|
20558
|
+
}
|
|
20559
|
+
} catch (error) {
|
|
20560
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20561
|
+
console.error(`Error: ${message}`);
|
|
20562
|
+
process.exit(1);
|
|
20563
|
+
}
|
|
20564
|
+
});
|
|
20565
|
+
leaseCommand.command("gc").description("Sweep expired leases across all inventories (idempotent, concurrency-safe)").action(async () => {
|
|
20566
|
+
try {
|
|
20567
|
+
initLeasesDb();
|
|
20568
|
+
const n = gcExpiredLeases();
|
|
20569
|
+
console.log(`Expired ${n} lease(s).`);
|
|
20570
|
+
} catch (error) {
|
|
20571
|
+
if (error instanceof LeaseContentionError) {
|
|
20572
|
+
console.error(`Error: contention timeout; retry`);
|
|
20573
|
+
process.exit(1);
|
|
20574
|
+
}
|
|
20575
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20576
|
+
console.error(`Error: ${message}`);
|
|
20577
|
+
process.exit(1);
|
|
20578
|
+
}
|
|
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
|
+
});
|
|
20760
|
+
|
|
19382
20761
|
// src/commands/request.ts
|
|
19383
20762
|
init_paths();
|
|
19384
20763
|
init_fs();
|
|
@@ -19387,7 +20766,7 @@ init_slug();
|
|
|
19387
20766
|
init_timestamp();
|
|
19388
20767
|
init_assignment_resolver();
|
|
19389
20768
|
init_assignment_todos();
|
|
19390
|
-
import { resolve as
|
|
20769
|
+
import { resolve as resolve54 } from "path";
|
|
19391
20770
|
import { readFile as readFile35 } from "fs/promises";
|
|
19392
20771
|
async function requestCommand(target, text, options = {}) {
|
|
19393
20772
|
if (!text || !text.trim()) {
|
|
@@ -19404,7 +20783,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
19404
20783
|
if (!isValidSlug(target)) {
|
|
19405
20784
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
19406
20785
|
}
|
|
19407
|
-
assignmentDir =
|
|
20786
|
+
assignmentDir = resolve54(baseDir, options.project, "assignments", target);
|
|
19408
20787
|
targetRef = target;
|
|
19409
20788
|
} else {
|
|
19410
20789
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -19414,7 +20793,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
19414
20793
|
assignmentDir = resolved.assignmentDir;
|
|
19415
20794
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
19416
20795
|
}
|
|
19417
|
-
const assignmentMdPath2 =
|
|
20796
|
+
const assignmentMdPath2 = resolve54(assignmentDir, "assignment.md");
|
|
19418
20797
|
if (!await fileExists(assignmentMdPath2)) {
|
|
19419
20798
|
throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
|
|
19420
20799
|
}
|
|
@@ -19431,11 +20810,11 @@ async function requestCommand(target, text, options = {}) {
|
|
|
19431
20810
|
// src/commands/plan.ts
|
|
19432
20811
|
init_fs();
|
|
19433
20812
|
init_paths();
|
|
19434
|
-
import { Command as
|
|
20813
|
+
import { Command as Command7 } from "commander";
|
|
19435
20814
|
import { readFile as readFile36, readdir as readdir18 } from "fs/promises";
|
|
19436
|
-
import { resolve as
|
|
20815
|
+
import { resolve as resolve55 } from "path";
|
|
19437
20816
|
async function readContextAssignmentDir(cwd) {
|
|
19438
|
-
const path =
|
|
20817
|
+
const path = resolve55(cwd, ".syntaur", "context.json");
|
|
19439
20818
|
if (!await fileExists(path)) return null;
|
|
19440
20819
|
try {
|
|
19441
20820
|
const raw = await readFile36(path, "utf-8");
|
|
@@ -19452,9 +20831,9 @@ async function resolveAssignmentDir(opts) {
|
|
|
19452
20831
|
const cwd = opts.cwd ?? process.cwd();
|
|
19453
20832
|
if (opts.assignment) {
|
|
19454
20833
|
if (opts.project) {
|
|
19455
|
-
return
|
|
20834
|
+
return resolve55(defaultProjectDir(), opts.project, "assignments", opts.assignment);
|
|
19456
20835
|
}
|
|
19457
|
-
return
|
|
20836
|
+
return resolve55(assignmentsDir(), opts.assignment);
|
|
19458
20837
|
}
|
|
19459
20838
|
const fromCtx = await readContextAssignmentDir(cwd);
|
|
19460
20839
|
if (fromCtx) return fromCtx;
|
|
@@ -19597,7 +20976,7 @@ async function runPlanVersion(options) {
|
|
|
19597
20976
|
if (!await fileExists(assignmentDir)) {
|
|
19598
20977
|
throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
|
|
19599
20978
|
}
|
|
19600
|
-
const assignmentMdPath2 =
|
|
20979
|
+
const assignmentMdPath2 = resolve55(assignmentDir, "assignment.md");
|
|
19601
20980
|
if (!await fileExists(assignmentMdPath2)) {
|
|
19602
20981
|
throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
|
|
19603
20982
|
}
|
|
@@ -19609,14 +20988,14 @@ async function runPlanVersion(options) {
|
|
|
19609
20988
|
}
|
|
19610
20989
|
const current = planFiles[planFiles.length - 1];
|
|
19611
20990
|
const next = nextPlanFileName(current.version);
|
|
19612
|
-
const newPath =
|
|
20991
|
+
const newPath = resolve55(assignmentDir, next.fileName);
|
|
19613
20992
|
if (await fileExists(newPath) && !options.force) {
|
|
19614
20993
|
throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
|
|
19615
20994
|
}
|
|
19616
20995
|
const assignmentMd = await readFile36(assignmentMdPath2, "utf-8");
|
|
19617
20996
|
const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
|
|
19618
20997
|
const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
|
|
19619
|
-
const oldPlanPath =
|
|
20998
|
+
const oldPlanPath = resolve55(assignmentDir, current.fileName);
|
|
19620
20999
|
const oldPlanContent = await readFile36(oldPlanPath, "utf-8");
|
|
19621
21000
|
const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
|
|
19622
21001
|
const carriedTodos = extractUncheckedTodos(oldBody);
|
|
@@ -19639,7 +21018,7 @@ async function runPlanVersion(options) {
|
|
|
19639
21018
|
console.log(`Path: ${newPath}`);
|
|
19640
21019
|
console.log(`Carried forward: ${carriedTodos.length} unchecked task(s).`);
|
|
19641
21020
|
}
|
|
19642
|
-
var planCommand = new
|
|
21021
|
+
var planCommand = new Command7("plan").description("Manage plan files for the active assignment");
|
|
19643
21022
|
planCommand.command("version").description(
|
|
19644
21023
|
"Create the next plan-v<N>.md, supersede the prior plan in assignment.md ## Todos, and carry forward unchecked tasks"
|
|
19645
21024
|
).option("--assignment <slug>", "Assignment slug (UUID for standalone). Defaults to .syntaur/context.json").option("--project <slug>", "Project slug. Required when --assignment is given for a project-nested assignment").option("--force", "Overwrite if the next plan-v<N>.md already exists").action(async (options) => {
|
|
@@ -19653,11 +21032,11 @@ planCommand.command("version").description(
|
|
|
19653
21032
|
|
|
19654
21033
|
// src/commands/session.ts
|
|
19655
21034
|
init_fs();
|
|
19656
|
-
import { Command as
|
|
19657
|
-
import { readFile as readFile37, readdir as readdir19, stat as
|
|
19658
|
-
import { resolve as
|
|
21035
|
+
import { Command as Command8 } from "commander";
|
|
21036
|
+
import { readFile as readFile37, readdir as readdir19, stat as stat9 } from "fs/promises";
|
|
21037
|
+
import { resolve as resolve56 } from "path";
|
|
19659
21038
|
async function readContext(cwd) {
|
|
19660
|
-
const path =
|
|
21039
|
+
const path = resolve56(cwd, ".syntaur", "context.json");
|
|
19661
21040
|
if (!await fileExists(path)) return null;
|
|
19662
21041
|
try {
|
|
19663
21042
|
const raw = await readFile37(path, "utf-8");
|
|
@@ -19667,15 +21046,15 @@ async function readContext(cwd) {
|
|
|
19667
21046
|
}
|
|
19668
21047
|
}
|
|
19669
21048
|
async function findLatestSessionSummary(assignmentDir) {
|
|
19670
|
-
const sessionsRoot =
|
|
21049
|
+
const sessionsRoot = resolve56(assignmentDir, "sessions");
|
|
19671
21050
|
if (!await fileExists(sessionsRoot)) return null;
|
|
19672
21051
|
const entries = await readdir19(sessionsRoot, { withFileTypes: true });
|
|
19673
21052
|
let best = null;
|
|
19674
21053
|
for (const entry of entries) {
|
|
19675
21054
|
if (!entry.isDirectory()) continue;
|
|
19676
|
-
const summaryPath =
|
|
21055
|
+
const summaryPath = resolve56(sessionsRoot, entry.name, "summary.md");
|
|
19677
21056
|
if (!await fileExists(summaryPath)) continue;
|
|
19678
|
-
const st = await
|
|
21057
|
+
const st = await stat9(summaryPath);
|
|
19679
21058
|
if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
|
|
19680
21059
|
best = { sessionId: entry.name, path: summaryPath, mtime: st.mtime };
|
|
19681
21060
|
}
|
|
@@ -19683,7 +21062,7 @@ async function findLatestSessionSummary(assignmentDir) {
|
|
|
19683
21062
|
return best;
|
|
19684
21063
|
}
|
|
19685
21064
|
async function findOpenHandoff(assignmentDir) {
|
|
19686
|
-
const handoffPath =
|
|
21065
|
+
const handoffPath = resolve56(assignmentDir, "handoff.md");
|
|
19687
21066
|
if (!await fileExists(handoffPath)) return null;
|
|
19688
21067
|
const content = await readFile37(handoffPath, "utf-8");
|
|
19689
21068
|
const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
|
|
@@ -19775,7 +21154,7 @@ async function runSessionResume(options, cwd = process.cwd()) {
|
|
|
19775
21154
|
}
|
|
19776
21155
|
return out;
|
|
19777
21156
|
}
|
|
19778
|
-
var sessionCommand = new
|
|
21157
|
+
var sessionCommand = new Command8("session").description("Manage agent sessions for the active assignment");
|
|
19779
21158
|
sessionCommand.command("resume").description(
|
|
19780
21159
|
"Re-orient a fresh session: print active assignment context, latest saved session summary, and any open handoff. Idempotent \u2014 does not mutate state."
|
|
19781
21160
|
).option("--json", "Emit machine-readable JSON instead of human-readable text").action(async (options) => {
|
|
@@ -19792,11 +21171,11 @@ sessionCommand.command("resume").description(
|
|
|
19792
21171
|
init_git_worktree();
|
|
19793
21172
|
init_fs();
|
|
19794
21173
|
init_paths();
|
|
19795
|
-
import { Command as
|
|
21174
|
+
import { Command as Command9 } from "commander";
|
|
19796
21175
|
import { readFile as readFile38 } from "fs/promises";
|
|
19797
|
-
import { resolve as
|
|
21176
|
+
import { resolve as resolve57 } from "path";
|
|
19798
21177
|
async function readContext2(cwd) {
|
|
19799
|
-
const path =
|
|
21178
|
+
const path = resolve57(cwd, ".syntaur", "context.json");
|
|
19800
21179
|
if (!await fileExists(path)) return null;
|
|
19801
21180
|
try {
|
|
19802
21181
|
return JSON.parse(await readFile38(path, "utf-8"));
|
|
@@ -19807,7 +21186,7 @@ async function readContext2(cwd) {
|
|
|
19807
21186
|
async function resolveAssignmentPath2(opts) {
|
|
19808
21187
|
if (opts.assignment) {
|
|
19809
21188
|
if (opts.project) {
|
|
19810
|
-
return
|
|
21189
|
+
return resolve57(
|
|
19811
21190
|
defaultProjectDir(),
|
|
19812
21191
|
opts.project,
|
|
19813
21192
|
"assignments",
|
|
@@ -19815,10 +21194,10 @@ async function resolveAssignmentPath2(opts) {
|
|
|
19815
21194
|
"assignment.md"
|
|
19816
21195
|
);
|
|
19817
21196
|
}
|
|
19818
|
-
return
|
|
21197
|
+
return resolve57(assignmentsDir(), opts.assignment, "assignment.md");
|
|
19819
21198
|
}
|
|
19820
21199
|
const ctx = await readContext2(opts.cwd);
|
|
19821
|
-
if (ctx?.assignmentDir) return
|
|
21200
|
+
if (ctx?.assignmentDir) return resolve57(ctx.assignmentDir, "assignment.md");
|
|
19822
21201
|
throw new Error(
|
|
19823
21202
|
"No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
|
|
19824
21203
|
);
|
|
@@ -19829,7 +21208,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
|
|
|
19829
21208
|
}
|
|
19830
21209
|
const repository = options.repository ?? cwd;
|
|
19831
21210
|
const parentBranch = options.parentBranch ?? "main";
|
|
19832
|
-
const worktreePath = options.worktreePath ??
|
|
21211
|
+
const worktreePath = options.worktreePath ?? resolve57(repository, ".worktrees", options.branch);
|
|
19833
21212
|
const assignmentPath = await resolveAssignmentPath2({
|
|
19834
21213
|
assignment: options.assignment,
|
|
19835
21214
|
project: options.project,
|
|
@@ -19847,7 +21226,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
|
|
|
19847
21226
|
});
|
|
19848
21227
|
return { worktreePath, assignmentPath };
|
|
19849
21228
|
}
|
|
19850
|
-
var worktreeCommand = new
|
|
21229
|
+
var worktreeCommand = new Command9("worktree").description("Manage git worktrees bound to Syntaur assignments");
|
|
19851
21230
|
worktreeCommand.command("create").description(
|
|
19852
21231
|
"Create a worktree at <repository>/.worktrees/<branch> and record it in the assignment workspace block. Atomic \u2014 rolls back the worktree if writing assignment.md fails."
|
|
19853
21232
|
).requiredOption("--branch <name>", "Branch name to create (also used as worktree dir name)").option("--repository <path>", "Repository root (defaults to current working directory)").option("--parent-branch <name>", "Parent branch to fork from", "main").option("--assignment <slug>", "Assignment slug (UUID for standalone). Defaults to .syntaur/context.json").option("--project <slug>", "Project slug. Required when --assignment is given for a project-nested assignment").option("--worktree-path <path>", "Override the computed <repository>/.worktrees/<branch> path").action(async (options) => {
|
|
@@ -19865,15 +21244,15 @@ worktreeCommand.command("create").description(
|
|
|
19865
21244
|
init_paths();
|
|
19866
21245
|
init_fs();
|
|
19867
21246
|
init_slug();
|
|
19868
|
-
import { Command as
|
|
19869
|
-
import { resolve as
|
|
21247
|
+
import { Command as Command10 } from "commander";
|
|
21248
|
+
import { resolve as resolve59 } from "path";
|
|
19870
21249
|
|
|
19871
21250
|
// src/utils/project-indexes.ts
|
|
19872
21251
|
init_parser();
|
|
19873
21252
|
init_fs();
|
|
19874
21253
|
import { readdir as readdir20, readFile as readFile39 } from "fs/promises";
|
|
19875
|
-
import { resolve as
|
|
19876
|
-
function
|
|
21254
|
+
import { resolve as resolve58 } from "path";
|
|
21255
|
+
function nowIso2() {
|
|
19877
21256
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19878
21257
|
}
|
|
19879
21258
|
function readProjectSlug(projectDir) {
|
|
@@ -19891,14 +21270,14 @@ function joinList(items) {
|
|
|
19891
21270
|
return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
|
|
19892
21271
|
}
|
|
19893
21272
|
async function rebuildResourcesIndex(projectDir) {
|
|
19894
|
-
const dir =
|
|
21273
|
+
const dir = resolve58(projectDir, "resources");
|
|
19895
21274
|
await ensureDir(dir);
|
|
19896
21275
|
const files = await listSlugFiles(dir);
|
|
19897
21276
|
const slug = readProjectSlug(projectDir);
|
|
19898
21277
|
const lines = [];
|
|
19899
21278
|
lines.push("---");
|
|
19900
21279
|
lines.push(`project: ${slug}`);
|
|
19901
|
-
lines.push(`generated: "${
|
|
21280
|
+
lines.push(`generated: "${nowIso2()}"`);
|
|
19902
21281
|
lines.push(`total: ${files.length}`);
|
|
19903
21282
|
lines.push("---");
|
|
19904
21283
|
lines.push("");
|
|
@@ -19907,7 +21286,7 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
19907
21286
|
lines.push("| Name | Category | Source | Related Assignments | Updated |");
|
|
19908
21287
|
lines.push("|------|----------|--------|---------------------|---------|");
|
|
19909
21288
|
for (const fileName of files) {
|
|
19910
|
-
const content = await readFile39(
|
|
21289
|
+
const content = await readFile39(resolve58(dir, fileName), "utf-8");
|
|
19911
21290
|
const parsed = parseResource(content);
|
|
19912
21291
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
19913
21292
|
const name = parsed.name || slugBase;
|
|
@@ -19917,19 +21296,19 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
19917
21296
|
);
|
|
19918
21297
|
}
|
|
19919
21298
|
lines.push("");
|
|
19920
|
-
const indexPath =
|
|
21299
|
+
const indexPath = resolve58(dir, "_index.md");
|
|
19921
21300
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
19922
21301
|
return { total: files.length, path: indexPath };
|
|
19923
21302
|
}
|
|
19924
21303
|
async function rebuildMemoriesIndex(projectDir) {
|
|
19925
|
-
const dir =
|
|
21304
|
+
const dir = resolve58(projectDir, "memories");
|
|
19926
21305
|
await ensureDir(dir);
|
|
19927
21306
|
const files = await listSlugFiles(dir);
|
|
19928
21307
|
const slug = readProjectSlug(projectDir);
|
|
19929
21308
|
const lines = [];
|
|
19930
21309
|
lines.push("---");
|
|
19931
21310
|
lines.push(`project: ${slug}`);
|
|
19932
|
-
lines.push(`generated: "${
|
|
21311
|
+
lines.push(`generated: "${nowIso2()}"`);
|
|
19933
21312
|
lines.push(`total: ${files.length}`);
|
|
19934
21313
|
lines.push("---");
|
|
19935
21314
|
lines.push("");
|
|
@@ -19938,7 +21317,7 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
19938
21317
|
lines.push("| Name | Source | Scope | Source Assignment | Updated |");
|
|
19939
21318
|
lines.push("|------|--------|-------|-------------------|---------|");
|
|
19940
21319
|
for (const fileName of files) {
|
|
19941
|
-
const content = await readFile39(
|
|
21320
|
+
const content = await readFile39(resolve58(dir, fileName), "utf-8");
|
|
19942
21321
|
const parsed = parseMemory(content);
|
|
19943
21322
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
19944
21323
|
const name = parsed.name || slugBase;
|
|
@@ -19948,13 +21327,13 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
19948
21327
|
);
|
|
19949
21328
|
}
|
|
19950
21329
|
lines.push("");
|
|
19951
|
-
const indexPath =
|
|
21330
|
+
const indexPath = resolve58(dir, "_index.md");
|
|
19952
21331
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
19953
21332
|
return { total: files.length, path: indexPath };
|
|
19954
21333
|
}
|
|
19955
21334
|
|
|
19956
21335
|
// src/commands/resource.ts
|
|
19957
|
-
function
|
|
21336
|
+
function nowIso3() {
|
|
19958
21337
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19959
21338
|
}
|
|
19960
21339
|
function parseList(value) {
|
|
@@ -19965,7 +21344,7 @@ function yamlQuote(value) {
|
|
|
19965
21344
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
19966
21345
|
}
|
|
19967
21346
|
function renderResourceFile(opts) {
|
|
19968
|
-
const created =
|
|
21347
|
+
const created = nowIso3();
|
|
19969
21348
|
const related = opts.relatedAssignments.length === 0 ? "[]" : `
|
|
19970
21349
|
${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
|
|
19971
21350
|
return `---
|
|
@@ -19986,8 +21365,8 @@ async function runResourceAdd(options) {
|
|
|
19986
21365
|
if (!isValidSlug(options.project)) {
|
|
19987
21366
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
19988
21367
|
}
|
|
19989
|
-
const projectDir =
|
|
19990
|
-
if (!await fileExists(
|
|
21368
|
+
const projectDir = resolve59(defaultProjectDir(), options.project);
|
|
21369
|
+
if (!await fileExists(resolve59(projectDir, "project.md"))) {
|
|
19991
21370
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
19992
21371
|
}
|
|
19993
21372
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -19996,7 +21375,7 @@ async function runResourceAdd(options) {
|
|
|
19996
21375
|
if (!isValidSlug(slug)) {
|
|
19997
21376
|
throw new Error(`Invalid resource slug: "${slug}".`);
|
|
19998
21377
|
}
|
|
19999
|
-
const filePath =
|
|
21378
|
+
const filePath = resolve59(projectDir, "resources", `${slug}.md`);
|
|
20000
21379
|
if (await fileExists(filePath) && !options.force) {
|
|
20001
21380
|
throw new Error(
|
|
20002
21381
|
`Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -20012,7 +21391,7 @@ async function runResourceAdd(options) {
|
|
|
20012
21391
|
const { path: indexPath, total } = await rebuildResourcesIndex(projectDir);
|
|
20013
21392
|
return { filePath, indexPath, total };
|
|
20014
21393
|
}
|
|
20015
|
-
var resourceCommand = new
|
|
21394
|
+
var resourceCommand = new Command10("resource").description("Manage project-level resource entries");
|
|
20016
21395
|
resourceCommand.command("add").description(
|
|
20017
21396
|
"Add a project resource at <projectDir>/resources/<slug>.md and regenerate _index.md"
|
|
20018
21397
|
).requiredOption("--project <slug>", "Project slug").requiredOption("--name <name>", "Human-readable resource name").requiredOption("--source <url-or-path>", "Resource source (URL or path)").option("--category <name>", "Optional category (e.g. dashboard, doc, ticket)").option("--slug <slug>", "Override the slug (default: slugify(--name))").option("--related-assignments <slugs>", "Comma-separated related assignment slugs").option("--force", "Overwrite if the resource file already exists").action(async (options) => {
|
|
@@ -20030,9 +21409,9 @@ resourceCommand.command("add").description(
|
|
|
20030
21409
|
init_paths();
|
|
20031
21410
|
init_fs();
|
|
20032
21411
|
init_slug();
|
|
20033
|
-
import { Command as
|
|
20034
|
-
import { resolve as
|
|
20035
|
-
function
|
|
21412
|
+
import { Command as Command11 } from "commander";
|
|
21413
|
+
import { resolve as resolve60 } from "path";
|
|
21414
|
+
function nowIso4() {
|
|
20036
21415
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
20037
21416
|
}
|
|
20038
21417
|
function parseList2(value) {
|
|
@@ -20043,7 +21422,7 @@ function yamlQuote2(value) {
|
|
|
20043
21422
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
20044
21423
|
}
|
|
20045
21424
|
function renderMemoryFile(opts) {
|
|
20046
|
-
const created =
|
|
21425
|
+
const created = nowIso4();
|
|
20047
21426
|
const related = opts.relatedAssignments.length === 0 ? "[]" : `
|
|
20048
21427
|
${opts.relatedAssignments.map((a) => ` - ${a}`).join("\n")}`;
|
|
20049
21428
|
const sourceAssignment = opts.sourceAssignment === null ? "null" : yamlQuote2(opts.sourceAssignment);
|
|
@@ -20066,8 +21445,8 @@ async function runMemoryAdd(options) {
|
|
|
20066
21445
|
if (!isValidSlug(options.project)) {
|
|
20067
21446
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
20068
21447
|
}
|
|
20069
|
-
const projectDir =
|
|
20070
|
-
if (!await fileExists(
|
|
21448
|
+
const projectDir = resolve60(defaultProjectDir(), options.project);
|
|
21449
|
+
if (!await fileExists(resolve60(projectDir, "project.md"))) {
|
|
20071
21450
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
20072
21451
|
}
|
|
20073
21452
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -20076,7 +21455,7 @@ async function runMemoryAdd(options) {
|
|
|
20076
21455
|
if (!isValidSlug(slug)) {
|
|
20077
21456
|
throw new Error(`Invalid memory slug: "${slug}".`);
|
|
20078
21457
|
}
|
|
20079
|
-
const filePath =
|
|
21458
|
+
const filePath = resolve60(projectDir, "memories", `${slug}.md`);
|
|
20080
21459
|
if (await fileExists(filePath) && !options.force) {
|
|
20081
21460
|
throw new Error(
|
|
20082
21461
|
`Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -20093,7 +21472,7 @@ async function runMemoryAdd(options) {
|
|
|
20093
21472
|
const { path: indexPath, total } = await rebuildMemoriesIndex(projectDir);
|
|
20094
21473
|
return { filePath, indexPath, total };
|
|
20095
21474
|
}
|
|
20096
|
-
var memoryCommand = new
|
|
21475
|
+
var memoryCommand = new Command11("memory").description("Manage project-level memory entries");
|
|
20097
21476
|
memoryCommand.command("add").description(
|
|
20098
21477
|
"Add a project memory at <projectDir>/memories/<slug>.md and regenerate _index.md"
|
|
20099
21478
|
).requiredOption("--project <slug>", "Project slug").requiredOption("--name <name>", "Human-readable memory name").requiredOption("--source <text>", "Where this memory came from (conversation, decision, doc URL, etc.)").option("--scope <scope>", "Memory scope (default: project)", "project").option("--source-assignment <slug>", "Assignment slug this memory was captured during").option("--slug <slug>", "Override the slug (default: slugify(--name))").option("--related-assignments <slugs>", "Comma-separated related assignment slugs").option("--force", "Overwrite if the memory file already exists").action(async (options) => {
|
|
@@ -20112,9 +21491,9 @@ init_api();
|
|
|
20112
21491
|
init_paths();
|
|
20113
21492
|
init_fs();
|
|
20114
21493
|
init_frontmatter();
|
|
20115
|
-
import { Command as
|
|
21494
|
+
import { Command as Command12 } from "commander";
|
|
20116
21495
|
import { readFile as readFile40 } from "fs/promises";
|
|
20117
|
-
import { resolve as
|
|
21496
|
+
import { resolve as resolve61 } from "path";
|
|
20118
21497
|
var AGE_PATTERN = /^(\d+)([dhwm])$/i;
|
|
20119
21498
|
function parseAgeToCutoff(age) {
|
|
20120
21499
|
const match = age.match(AGE_PATTERN);
|
|
@@ -20133,7 +21512,7 @@ function parseAgeToCutoff(age) {
|
|
|
20133
21512
|
}
|
|
20134
21513
|
function assignmentMdPath(item) {
|
|
20135
21514
|
if (item.projectSlug) {
|
|
20136
|
-
return
|
|
21515
|
+
return resolve61(
|
|
20137
21516
|
defaultProjectDir(),
|
|
20138
21517
|
item.projectSlug,
|
|
20139
21518
|
"assignments",
|
|
@@ -20141,7 +21520,7 @@ function assignmentMdPath(item) {
|
|
|
20141
21520
|
"assignment.md"
|
|
20142
21521
|
);
|
|
20143
21522
|
}
|
|
20144
|
-
return
|
|
21523
|
+
return resolve61(assignmentsDir(), item.id, "assignment.md");
|
|
20145
21524
|
}
|
|
20146
21525
|
async function loadTags(item) {
|
|
20147
21526
|
const path = assignmentMdPath(item);
|
|
@@ -20207,7 +21586,7 @@ function renderTable(items) {
|
|
|
20207
21586
|
);
|
|
20208
21587
|
return all.map((row) => row.map((cell, c2) => pad(cell ?? "", widths[c2])).join(" ")).join("\n");
|
|
20209
21588
|
}
|
|
20210
|
-
var lsCommand = new
|
|
21589
|
+
var lsCommand = new Command12("ls").description(
|
|
20211
21590
|
"List assignments across all projects with optional filters by status, project, tag, or age."
|
|
20212
21591
|
).option("--status <list>", "Comma-separated status filter (e.g. pending,in_progress)").option("--project <slug>", "Filter to one project").option("--tag <list>", "Comma-separated tag filter (assignment must have ALL tags)").option("--age <duration>", "Only include assignments updated within duration (e.g. 7d, 24h, 2w, 1m)").option("--json", "Emit JSON instead of a table").action(async (options) => {
|
|
20213
21592
|
try {
|
|
@@ -20266,19 +21645,19 @@ init_paths();
|
|
|
20266
21645
|
init_fs();
|
|
20267
21646
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
20268
21647
|
import { readFile as readFile42 } from "fs/promises";
|
|
20269
|
-
import { dirname as
|
|
20270
|
-
import { spawn as
|
|
21648
|
+
import { dirname as dirname16, join as join9, resolve as resolve62 } from "path";
|
|
21649
|
+
import { spawn as spawn5 } from "child_process";
|
|
20271
21650
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
20272
21651
|
|
|
20273
21652
|
// src/utils/version.ts
|
|
20274
21653
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
20275
21654
|
import { readFile as readFile41 } from "fs/promises";
|
|
20276
|
-
import { dirname as
|
|
21655
|
+
import { dirname as dirname15, join as join8 } from "path";
|
|
20277
21656
|
async function readPackageVersion(scriptUrl) {
|
|
20278
21657
|
try {
|
|
20279
21658
|
const scriptPath = fileURLToPath7(scriptUrl);
|
|
20280
|
-
const pkgRoot =
|
|
20281
|
-
const raw = await readFile41(
|
|
21659
|
+
const pkgRoot = dirname15(dirname15(scriptPath));
|
|
21660
|
+
const raw = await readFile41(join8(pkgRoot, "package.json"), "utf-8");
|
|
20282
21661
|
const parsed = JSON.parse(raw);
|
|
20283
21662
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
20284
21663
|
} catch {
|
|
@@ -20287,7 +21666,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
20287
21666
|
}
|
|
20288
21667
|
|
|
20289
21668
|
// src/utils/npx-prompt.ts
|
|
20290
|
-
var STATE_FILE =
|
|
21669
|
+
var STATE_FILE = resolve62(syntaurRoot(), "npx-install.json");
|
|
20291
21670
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
20292
21671
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
20293
21672
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -20319,10 +21698,10 @@ async function writeState(state) {
|
|
|
20319
21698
|
`);
|
|
20320
21699
|
}
|
|
20321
21700
|
async function resolveNpmBin() {
|
|
20322
|
-
const nodeDir =
|
|
21701
|
+
const nodeDir = dirname16(process.execPath);
|
|
20323
21702
|
const isWin = process.platform === "win32";
|
|
20324
21703
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
20325
|
-
const nearNode =
|
|
21704
|
+
const nearNode = join9(nodeDir, npmName);
|
|
20326
21705
|
if (await fileExists(nearNode)) {
|
|
20327
21706
|
return { cmd: nearNode, shell: false };
|
|
20328
21707
|
}
|
|
@@ -20331,7 +21710,7 @@ async function resolveNpmBin() {
|
|
|
20331
21710
|
async function installGlobally() {
|
|
20332
21711
|
const { cmd, shell } = await resolveNpmBin();
|
|
20333
21712
|
return new Promise((resolvePromise) => {
|
|
20334
|
-
const child =
|
|
21713
|
+
const child = spawn5(cmd, ["install", "-g", "syntaur"], {
|
|
20335
21714
|
stdio: "inherit",
|
|
20336
21715
|
shell
|
|
20337
21716
|
});
|
|
@@ -20342,7 +21721,7 @@ async function installGlobally() {
|
|
|
20342
21721
|
async function readGlobalVersion() {
|
|
20343
21722
|
const { cmd, shell } = await resolveNpmBin();
|
|
20344
21723
|
const rootPath = await new Promise((resolvePromise) => {
|
|
20345
|
-
const child =
|
|
21724
|
+
const child = spawn5(cmd, ["root", "-g"], {
|
|
20346
21725
|
shell,
|
|
20347
21726
|
stdio: ["ignore", "pipe", "ignore"]
|
|
20348
21727
|
});
|
|
@@ -20365,7 +21744,7 @@ async function readGlobalVersion() {
|
|
|
20365
21744
|
});
|
|
20366
21745
|
if (!rootPath) return null;
|
|
20367
21746
|
try {
|
|
20368
|
-
const manifestPath =
|
|
21747
|
+
const manifestPath = join9(rootPath, "syntaur", "package.json");
|
|
20369
21748
|
if (!await fileExists(manifestPath)) return null;
|
|
20370
21749
|
const raw = await readFile42(manifestPath, "utf-8");
|
|
20371
21750
|
const parsed = JSON.parse(raw);
|
|
@@ -20486,7 +21865,7 @@ async function maybePromptInstall(scriptUrl) {
|
|
|
20486
21865
|
|
|
20487
21866
|
// src/index.ts
|
|
20488
21867
|
await maybePromptInstall(import.meta.url);
|
|
20489
|
-
var program = new
|
|
21868
|
+
var program = new Command13();
|
|
20490
21869
|
var version = await readPackageVersion(import.meta.url) ?? "0.0.0";
|
|
20491
21870
|
program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version(version);
|
|
20492
21871
|
program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
|
|
@@ -20537,7 +21916,7 @@ program.command("comment").description("Add a comment to an assignment (CLI-medi
|
|
|
20537
21916
|
process.exit(1);
|
|
20538
21917
|
}
|
|
20539
21918
|
});
|
|
20540
|
-
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) => {
|
|
20541
21920
|
try {
|
|
20542
21921
|
await captureCommand(target, options);
|
|
20543
21922
|
} catch (error) {
|
|
@@ -20865,6 +22244,7 @@ program.addCommand(worktreeCommand);
|
|
|
20865
22244
|
program.addCommand(resourceCommand);
|
|
20866
22245
|
program.addCommand(memoryCommand);
|
|
20867
22246
|
program.addCommand(lsCommand);
|
|
22247
|
+
program.addCommand(leaseCommand);
|
|
20868
22248
|
if (process.argv.length <= 2) {
|
|
20869
22249
|
process.argv.push(await getDefaultCommandName());
|
|
20870
22250
|
}
|