syntaur 0.26.0 → 0.27.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/dashboard/dist/assets/{_basePickBy-jPItyrQO.js → _basePickBy-DPBuiT9A.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-pEwUwurC.js → _baseUniq-B5Q4dkW3.js} +1 -1
- package/dashboard/dist/assets/{arc-ZZtp507S.js → arc-Bp71QC_v.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-BNUerPqd.js → architectureDiagram-2XIMDMQ5-CWHBISZ5.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-CQyovXFv.js → blockDiagram-WCTKOSBZ-D0txIHgi.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-wNQ6EHeF.js → c4Diagram-IC4MRINW-D_Hpnc38.js} +1 -1
- package/dashboard/dist/assets/channel-D41AslDq.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-ZaueC30R.js → chunk-4BX2VUAB-D0A_A8qn.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-BjsRB0t8.js → chunk-55IACEB6-DuK8QvrD.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-BHuSr-Tl.js → chunk-FMBD7UC4-B5WfIDS6.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-SHNJA0es.js → chunk-JSJVCQXG-D3jB_ZJP.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-JXFPjeo4.js → chunk-KX2RTZJC-DtxN1mOD.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-BiJqWT0B.js → chunk-NQ4KR5QH-4fQpgivN.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-DoXWBqP2.js → chunk-QZHKN3VN-BOf9TZCT.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-Dqtf_5it.js → chunk-WL4C6EOR-D9HeEPWL.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BnKy62Yt.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BnKy62Yt.js +1 -0
- package/dashboard/dist/assets/clone-Cz7h9axV.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-Cr6bkSKq.js → cose-bilkent-S5V4N54A-CpzWcyB7.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-oXpXFuJQ.js → dagre-KLK3FWXG-CC9-omFF.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-Bq_xdDbg.js → diagram-E7M64L7V-q_F9KKPz.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-N7Er4Dui.js → diagram-IFDJBPK2-CbYvNpQB.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-BU0Zm2Fn.js → diagram-P4PSJMXO-q8XUUKRC.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BSgZb5me.js → erDiagram-INFDFZHY-Q-oL35fO.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-Bn7pEu0U.js → flowDiagram-PKNHOUZH-Cptj-2yF.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-B8Xq9tyM.js → ganttDiagram-A5KZAMGK-BYmgXBad.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-BoLUjYDa.js → gitGraphDiagram-K3NZZRJ6-DHF3w-Cn.js} +1 -1
- package/dashboard/dist/assets/{graph-Pde_ni_y.js → graph-Br4uG9xg.js} +1 -1
- package/dashboard/dist/assets/index-Ds1-e_jv.css +1 -0
- package/dashboard/dist/assets/index-dyJ_mu3x.js +555 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Brv2khjP.js → infoDiagram-LFFYTUFH-Ckb3YLUI.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-D5hxQ0Ke.js → ishikawaDiagram-PHBUUO56-DSXXm4hL.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CUevv5jA.js → journeyDiagram-4ABVD52K-D4JJ4wn_.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-Cf6XyrAC.js → kanban-definition-K7BYSVSG-DZeWPcIi.js} +1 -1
- package/dashboard/dist/assets/{layout-Bc8RP2w3.js → layout-DU5mcBKh.js} +1 -1
- package/dashboard/dist/assets/{linear-Cd_XUbl7.js → linear-h7AvdT63.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-Bx8MuMEM.js → mermaid.core-DIOnVuDB.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-D_4Pl3Mu.js → mindmap-definition-YRQLILUH-BVSORv6W.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DRVbjwxO.js → pieDiagram-SKSYHLDU-BEdO084J.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-BciLlBMH.js → quadrantDiagram-337W2JSQ-3Dc5mQ7q.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Bprwe8Z2.js → requirementDiagram-Z7DCOOCP-eu-8doSY.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DI0t8Uiu.js → sankeyDiagram-WA2Y5GQK-jA292hzv.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CpCLCs5J.js → sequenceDiagram-2WXFIKYE-et31a6Tg.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-V-1VCApT.js → stateDiagram-RAJIS63D-D6MtTWaR.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-sYL-A3ib.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DCAo6tA7.js → timeline-definition-YZTLITO2-Oa_SYaCP.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-CKlbZ6Y_.js → treemap-KZPCXAKY-vrIbKmuv.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-CJSijre_.js → vennDiagram-LZ73GAT5-B3UlkEHW.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-DXd1BBmK.js → xychartDiagram-JWTSCODW-BLiVVy6A.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +612 -225
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1204 -827
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/channel-BYnzdl2x.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BnPZbM4g.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BnPZbM4g.js +0 -1
- package/dashboard/dist/assets/clone-DYNFxLr3.js +0 -1
- package/dashboard/dist/assets/index-7rNWNKq7.css +0 -1
- package/dashboard/dist/assets/index-Nc9kfSW-.js +0 -550
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-B6S2ctrX.js +0 -1
package/dist/index.js
CHANGED
|
@@ -187,8 +187,8 @@ function extractFrontmatter(fileContent) {
|
|
|
187
187
|
const body = fileContent.slice(match[0].length).trim();
|
|
188
188
|
return [frontmatterBlock, body];
|
|
189
189
|
}
|
|
190
|
-
function parseSimpleValue(
|
|
191
|
-
const trimmed =
|
|
190
|
+
function parseSimpleValue(raw2) {
|
|
191
|
+
const trimmed = raw2.trim();
|
|
192
192
|
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
193
193
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
194
194
|
return trimmed.slice(1, -1);
|
|
@@ -540,8 +540,8 @@ ${key}: ${formatted}${content.slice(closingIdx)}`;
|
|
|
540
540
|
function readFrontmatterField(content, key) {
|
|
541
541
|
const match = content.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
542
542
|
if (!match) return null;
|
|
543
|
-
const
|
|
544
|
-
return
|
|
543
|
+
const raw2 = match[1].trim().replace(/^['"]|['"]$/g, "");
|
|
544
|
+
return raw2 === "" || raw2 === "null" ? null : raw2;
|
|
545
545
|
}
|
|
546
546
|
async function migrateLegacyArchivedProjects(projectsDir2) {
|
|
547
547
|
const result = { reconciled: [] };
|
|
@@ -811,9 +811,9 @@ function normalizeHiddenList(input4) {
|
|
|
811
811
|
if (!Array.isArray(input4)) return [];
|
|
812
812
|
const seen = /* @__PURE__ */ new Set();
|
|
813
813
|
const out = [];
|
|
814
|
-
for (const
|
|
815
|
-
if (typeof
|
|
816
|
-
const name =
|
|
814
|
+
for (const raw2 of input4) {
|
|
815
|
+
if (typeof raw2 !== "string") continue;
|
|
816
|
+
const name = raw2.trim();
|
|
817
817
|
if (name.length === 0) continue;
|
|
818
818
|
if (name.length > MAX_WORKSPACE_NAME_LENGTH) continue;
|
|
819
819
|
if (/[\r\n]/.test(name)) continue;
|
|
@@ -1192,13 +1192,13 @@ function parsePlaybooksConfig(fmBlock) {
|
|
|
1192
1192
|
continue;
|
|
1193
1193
|
}
|
|
1194
1194
|
if (currentSection === "disabled" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
1195
|
-
const
|
|
1196
|
-
if (
|
|
1197
|
-
if (/\s/.test(
|
|
1198
|
-
console.warn(`Warning: config.md playbooks.disabled entry "${
|
|
1195
|
+
const raw2 = trimmed.slice(2).trim().replace(/^["']|["']$/g, "");
|
|
1196
|
+
if (raw2.length === 0) continue;
|
|
1197
|
+
if (/\s/.test(raw2)) {
|
|
1198
|
+
console.warn(`Warning: config.md playbooks.disabled entry "${raw2}" contains whitespace, ignoring`);
|
|
1199
1199
|
continue;
|
|
1200
1200
|
}
|
|
1201
|
-
disabled.push(
|
|
1201
|
+
disabled.push(raw2);
|
|
1202
1202
|
continue;
|
|
1203
1203
|
}
|
|
1204
1204
|
}
|
|
@@ -1478,9 +1478,9 @@ function serializeHotkeyBindingsConfig(cfg) {
|
|
|
1478
1478
|
async function writeHotkeyBindingsConfig(cfg) {
|
|
1479
1479
|
const cleaned = {};
|
|
1480
1480
|
for (const kind of BINDABLE_ACTION_KINDS) {
|
|
1481
|
-
const
|
|
1482
|
-
if (typeof
|
|
1483
|
-
const canonical = canonicalizeCombo(
|
|
1481
|
+
const raw2 = cfg.bindings[kind];
|
|
1482
|
+
if (typeof raw2 !== "string" || raw2.trim() === "") continue;
|
|
1483
|
+
const canonical = canonicalizeCombo(raw2);
|
|
1484
1484
|
if (!canonical) continue;
|
|
1485
1485
|
if (isReservedCombo(canonical)) continue;
|
|
1486
1486
|
cleaned[kind] = canonical;
|
|
@@ -2277,8 +2277,8 @@ async function resolvePlaybookSlug(playbooksDir3, slug) {
|
|
|
2277
2277
|
for (const entry of entries) {
|
|
2278
2278
|
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
2279
2279
|
const filePath = resolve4(playbooksDir3, entry.name);
|
|
2280
|
-
const
|
|
2281
|
-
const parsed = parsePlaybook(
|
|
2280
|
+
const raw2 = await readFile4(filePath, "utf-8");
|
|
2281
|
+
const parsed = parsePlaybook(raw2);
|
|
2282
2282
|
const canonical = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
2283
2283
|
if (canonical === slug) {
|
|
2284
2284
|
return { filename: entry.name, slug: canonical, parsed };
|
|
@@ -2325,8 +2325,8 @@ async function rebuildPlaybookManifest(playbooksDir3) {
|
|
|
2325
2325
|
const rows = [];
|
|
2326
2326
|
for (const entry of entries) {
|
|
2327
2327
|
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
2328
|
-
const
|
|
2329
|
-
const parsed = parsePlaybook(
|
|
2328
|
+
const raw2 = await readFile4(resolve4(playbooksDir3, entry.name), "utf-8");
|
|
2329
|
+
const parsed = parsePlaybook(raw2);
|
|
2330
2330
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
2331
2331
|
if (disabledSet.has(slug)) continue;
|
|
2332
2332
|
rows.push({
|
|
@@ -2403,8 +2403,8 @@ async function renamePlaybook(playbooksDir3, oldSlug, newSlug) {
|
|
|
2403
2403
|
);
|
|
2404
2404
|
}
|
|
2405
2405
|
}
|
|
2406
|
-
const
|
|
2407
|
-
let next = setFrontmatterField2(
|
|
2406
|
+
const raw2 = await readFile4(oldPath, "utf-8");
|
|
2407
|
+
let next = setFrontmatterField2(raw2, "slug", newSlug);
|
|
2408
2408
|
next = setFrontmatterField2(next, "updated", `"${nowTimestamp()}"`);
|
|
2409
2409
|
await writeFileForce(newPath, next);
|
|
2410
2410
|
if (!renamedInPlace) {
|
|
@@ -3683,8 +3683,8 @@ function extractFrontmatter2(fileContent) {
|
|
|
3683
3683
|
const body = fileContent.slice(match[0].length);
|
|
3684
3684
|
return [frontmatterBlock, body];
|
|
3685
3685
|
}
|
|
3686
|
-
function parseSimpleValue2(
|
|
3687
|
-
const trimmed =
|
|
3686
|
+
function parseSimpleValue2(raw2) {
|
|
3687
|
+
const trimmed = raw2.trim();
|
|
3688
3688
|
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
3689
3689
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
3690
3690
|
return trimmed.slice(1, -1);
|
|
@@ -5283,9 +5283,9 @@ async function migrateFromMarkdown(projectsDir2) {
|
|
|
5283
5283
|
}
|
|
5284
5284
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
5285
5285
|
const { readFile: readFile56 } = await import("fs/promises");
|
|
5286
|
-
const
|
|
5286
|
+
const raw2 = await readFile56(filePath, "utf-8");
|
|
5287
5287
|
const sessions = [];
|
|
5288
|
-
const lines =
|
|
5288
|
+
const lines = raw2.split("\n");
|
|
5289
5289
|
let inTable = false;
|
|
5290
5290
|
let headerSeen = false;
|
|
5291
5291
|
for (const line of lines) {
|
|
@@ -5441,8 +5441,8 @@ async function deleteSessions(sessionIds) {
|
|
|
5441
5441
|
}
|
|
5442
5442
|
async function readAssignmentStatusFromPath(assignmentMdPath2) {
|
|
5443
5443
|
if (!await fileExists(assignmentMdPath2)) return null;
|
|
5444
|
-
const
|
|
5445
|
-
const match =
|
|
5444
|
+
const raw2 = await readFile9(assignmentMdPath2, "utf-8");
|
|
5445
|
+
const match = raw2.match(/^status:\s*(.+)$/m);
|
|
5446
5446
|
return match ? match[1].trim() : null;
|
|
5447
5447
|
}
|
|
5448
5448
|
async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
@@ -5586,8 +5586,8 @@ async function listSessionFiles(dir) {
|
|
|
5586
5586
|
async function readSessionFile(dir, name) {
|
|
5587
5587
|
const filePath = resolve14(dir, `${sanitizeSessionName(name)}.md`);
|
|
5588
5588
|
if (!await fileExists(filePath)) return null;
|
|
5589
|
-
const
|
|
5590
|
-
const [frontmatter] = extractFrontmatter(
|
|
5589
|
+
const raw2 = await readFile10(filePath, "utf-8");
|
|
5590
|
+
const [frontmatter] = extractFrontmatter(raw2);
|
|
5591
5591
|
if (!frontmatter) return null;
|
|
5592
5592
|
const session = getField(frontmatter, "session") ?? name;
|
|
5593
5593
|
const registered = getField(frontmatter, "registered") ?? "";
|
|
@@ -5715,8 +5715,8 @@ function scanKey(serversDir2, projectsDir2, assignmentsDir2) {
|
|
|
5715
5715
|
return `${serversDir2}\0${projectsDir2}\0${assignmentsDir2 ?? ""}`;
|
|
5716
5716
|
}
|
|
5717
5717
|
function delay(ms) {
|
|
5718
|
-
return new Promise((
|
|
5719
|
-
const timer2 = setTimeout(
|
|
5718
|
+
return new Promise((resolve83) => {
|
|
5719
|
+
const timer2 = setTimeout(resolve83, ms);
|
|
5720
5720
|
if (typeof timer2.unref === "function") {
|
|
5721
5721
|
timer2.unref();
|
|
5722
5722
|
}
|
|
@@ -6323,8 +6323,8 @@ async function listProjects(projectsDir2) {
|
|
|
6323
6323
|
async function readWorkspaceRegistry(projectsDir2) {
|
|
6324
6324
|
const registryPath = resolve16(dirname3(projectsDir2), "workspaces.json");
|
|
6325
6325
|
try {
|
|
6326
|
-
const
|
|
6327
|
-
const parsed = JSON.parse(
|
|
6326
|
+
const raw2 = await readFile11(registryPath, "utf-8");
|
|
6327
|
+
const parsed = JSON.parse(raw2);
|
|
6328
6328
|
return Array.isArray(parsed) ? parsed.filter((w) => typeof w === "string") : [];
|
|
6329
6329
|
} catch {
|
|
6330
6330
|
return [];
|
|
@@ -6412,8 +6412,8 @@ async function deleteWorkspace(projectsDir2, name, opts = {}) {
|
|
|
6412
6412
|
const timestamp = nowTimestamp();
|
|
6413
6413
|
for (const slug of projectsReferencing) {
|
|
6414
6414
|
const path = resolve16(projectsDir2, slug, "project.md");
|
|
6415
|
-
const
|
|
6416
|
-
let next = clearFrontmatterField(
|
|
6415
|
+
const raw2 = await readFile11(path, "utf-8");
|
|
6416
|
+
let next = clearFrontmatterField(raw2, "workspace");
|
|
6417
6417
|
next = setUpdatedField(next, timestamp);
|
|
6418
6418
|
await writeFileForce(path, next);
|
|
6419
6419
|
rewroteFiles = true;
|
|
@@ -6421,8 +6421,8 @@ async function deleteWorkspace(projectsDir2, name, opts = {}) {
|
|
|
6421
6421
|
for (const id of standalonesReferencing) {
|
|
6422
6422
|
if (!opts.assignmentsDir) break;
|
|
6423
6423
|
const path = resolve16(opts.assignmentsDir, id, "assignment.md");
|
|
6424
|
-
const
|
|
6425
|
-
let next = clearFrontmatterField(
|
|
6424
|
+
const raw2 = await readFile11(path, "utf-8");
|
|
6425
|
+
let next = clearFrontmatterField(raw2, "workspaceGroup");
|
|
6426
6426
|
next = setUpdatedField(next, timestamp);
|
|
6427
6427
|
await writeFileForce(path, next);
|
|
6428
6428
|
rewroteFiles = true;
|
|
@@ -7872,8 +7872,8 @@ async function listPlaybooks(playbooksDir3) {
|
|
|
7872
7872
|
for (const entry of entries) {
|
|
7873
7873
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
|
|
7874
7874
|
const filePath = resolve16(playbooksDir3, entry.name);
|
|
7875
|
-
const
|
|
7876
|
-
const parsed = parsePlaybook(
|
|
7875
|
+
const raw2 = await readFile11(filePath, "utf-8");
|
|
7876
|
+
const parsed = parsePlaybook(raw2);
|
|
7877
7877
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
7878
7878
|
playbooks.push({
|
|
7879
7879
|
slug,
|
|
@@ -8446,8 +8446,8 @@ __export(launch_exports, {
|
|
|
8446
8446
|
shellQuote: () => shellQuote
|
|
8447
8447
|
});
|
|
8448
8448
|
import { spawn as spawn3 } from "child_process";
|
|
8449
|
-
import { mkdir as
|
|
8450
|
-
import { isAbsolute as isAbsolute5, resolve as
|
|
8449
|
+
import { mkdir as mkdir6, writeFile as writeFile10 } from "fs/promises";
|
|
8450
|
+
import { isAbsolute as isAbsolute5, resolve as resolve40 } from "path";
|
|
8451
8451
|
function shellQuote(arg) {
|
|
8452
8452
|
if (arg === "") return "''";
|
|
8453
8453
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
@@ -8483,8 +8483,8 @@ async function launchAgent(options) {
|
|
|
8483
8483
|
console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
|
|
8484
8484
|
process.exit(1);
|
|
8485
8485
|
}
|
|
8486
|
-
const projectDir =
|
|
8487
|
-
const assignmentDir =
|
|
8486
|
+
const projectDir = resolve40(projectsDir2, projectSlug);
|
|
8487
|
+
const assignmentDir = resolve40(projectDir, "assignments", assignmentSlug);
|
|
8488
8488
|
let workspaceDir;
|
|
8489
8489
|
if (cwdOverride) {
|
|
8490
8490
|
if (!isExistingDir(cwdOverride)) {
|
|
@@ -8516,8 +8516,8 @@ async function launchAgent(options) {
|
|
|
8516
8516
|
});
|
|
8517
8517
|
if (warning) console.warn(warning);
|
|
8518
8518
|
}
|
|
8519
|
-
const contextDir =
|
|
8520
|
-
await
|
|
8519
|
+
const contextDir = resolve40(workspaceDir, ".syntaur");
|
|
8520
|
+
await mkdir6(contextDir, { recursive: true });
|
|
8521
8521
|
const context = {
|
|
8522
8522
|
projectSlug,
|
|
8523
8523
|
assignmentSlug,
|
|
@@ -8528,8 +8528,8 @@ async function launchAgent(options) {
|
|
|
8528
8528
|
branch: detail.workspace.branch ?? null,
|
|
8529
8529
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8530
8530
|
};
|
|
8531
|
-
await
|
|
8532
|
-
|
|
8531
|
+
await writeFile10(
|
|
8532
|
+
resolve40(contextDir, "context.json"),
|
|
8533
8533
|
JSON.stringify(context, null, 2) + "\n"
|
|
8534
8534
|
);
|
|
8535
8535
|
const { argv, shellFallbackWarning } = buildAgentArgv(
|
|
@@ -8604,7 +8604,7 @@ import {
|
|
|
8604
8604
|
unlinkSync
|
|
8605
8605
|
} from "fs";
|
|
8606
8606
|
import { fileURLToPath as fileURLToPath9, pathToFileURL } from "url";
|
|
8607
|
-
import { dirname as
|
|
8607
|
+
import { dirname as dirname17, resolve as resolve50, join as join9 } from "path";
|
|
8608
8608
|
import { homedir as homedir9, tmpdir as tmpdir2 } from "os";
|
|
8609
8609
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
8610
8610
|
function syntaurRootMjs() {
|
|
@@ -8626,7 +8626,7 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
8626
8626
|
}
|
|
8627
8627
|
const stateRoot = syntaurRootMjs();
|
|
8628
8628
|
mkdirSync2(stateRoot, { recursive: true });
|
|
8629
|
-
const lockPath =
|
|
8629
|
+
const lockPath = resolve50(stateRoot, "install-url-handler.lock");
|
|
8630
8630
|
let lockFd;
|
|
8631
8631
|
try {
|
|
8632
8632
|
lockFd = openSync(lockPath, "wx");
|
|
@@ -8642,8 +8642,8 @@ async function registerMacosUrlHandler(options = { throwOnFailure: false }) {
|
|
|
8642
8642
|
throw err2;
|
|
8643
8643
|
}
|
|
8644
8644
|
try {
|
|
8645
|
-
const pkgRoot =
|
|
8646
|
-
const cliBin = realpathSync3(
|
|
8645
|
+
const pkgRoot = resolve50(dirname17(fileURLToPath9(import.meta.url)), "..");
|
|
8646
|
+
const cliBin = realpathSync3(resolve50(pkgRoot, "bin/syntaur.js"));
|
|
8647
8647
|
const nodeBin = process.execPath;
|
|
8648
8648
|
const bundleParent = join9(
|
|
8649
8649
|
homedir9(),
|
|
@@ -9651,7 +9651,7 @@ init_create_assignment();
|
|
|
9651
9651
|
init_config2();
|
|
9652
9652
|
import { spawn as spawn2 } from "child_process";
|
|
9653
9653
|
import { createServer as createNetServer } from "net";
|
|
9654
|
-
import { resolve as
|
|
9654
|
+
import { resolve as resolve32, dirname as dirname9 } from "path";
|
|
9655
9655
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9656
9656
|
|
|
9657
9657
|
// src/dashboard/server.ts
|
|
@@ -9661,8 +9661,8 @@ init_assignment_resolver();
|
|
|
9661
9661
|
init_agent_sessions();
|
|
9662
9662
|
import express from "express";
|
|
9663
9663
|
import { createServer } from "http";
|
|
9664
|
-
import { resolve as
|
|
9665
|
-
import { writeFile as
|
|
9664
|
+
import { resolve as resolve31 } from "path";
|
|
9665
|
+
import { writeFile as writeFile7, unlink as unlink7 } from "fs/promises";
|
|
9666
9666
|
import { WebSocketServer, WebSocket } from "ws";
|
|
9667
9667
|
|
|
9668
9668
|
// src/dashboard/session-liveness.ts
|
|
@@ -10136,15 +10136,15 @@ async function readViewPrefsFile() {
|
|
|
10136
10136
|
if (!await fileExists(path)) {
|
|
10137
10137
|
return { ...DEFAULT_VIEW_PREFS_FILE };
|
|
10138
10138
|
}
|
|
10139
|
-
let
|
|
10139
|
+
let raw2;
|
|
10140
10140
|
try {
|
|
10141
|
-
|
|
10141
|
+
raw2 = await readFile12(path, "utf-8");
|
|
10142
10142
|
} catch {
|
|
10143
10143
|
return { ...DEFAULT_VIEW_PREFS_FILE };
|
|
10144
10144
|
}
|
|
10145
10145
|
let parsed;
|
|
10146
10146
|
try {
|
|
10147
|
-
parsed = JSON.parse(
|
|
10147
|
+
parsed = JSON.parse(raw2);
|
|
10148
10148
|
} catch {
|
|
10149
10149
|
await backupCorrupt(path);
|
|
10150
10150
|
return { ...DEFAULT_VIEW_PREFS_FILE };
|
|
@@ -10349,15 +10349,15 @@ async function readSavedViewsFile() {
|
|
|
10349
10349
|
if (!await fileExists(path)) {
|
|
10350
10350
|
return cloneDefault();
|
|
10351
10351
|
}
|
|
10352
|
-
let
|
|
10352
|
+
let raw2;
|
|
10353
10353
|
try {
|
|
10354
|
-
|
|
10354
|
+
raw2 = await readFile13(path, "utf-8");
|
|
10355
10355
|
} catch {
|
|
10356
10356
|
return cloneDefault();
|
|
10357
10357
|
}
|
|
10358
10358
|
let parsed;
|
|
10359
10359
|
try {
|
|
10360
|
-
parsed = JSON.parse(
|
|
10360
|
+
parsed = JSON.parse(raw2);
|
|
10361
10361
|
} catch {
|
|
10362
10362
|
await backupCorrupt2(path);
|
|
10363
10363
|
return cloneDefault();
|
|
@@ -10768,8 +10768,8 @@ async function getProjectRepositoryCandidates(projectsDir2, projectSlug) {
|
|
|
10768
10768
|
const projectPath = resolve20(projectsDir2, projectSlug, "project.md");
|
|
10769
10769
|
if (await fileExists(projectPath)) {
|
|
10770
10770
|
const project = parseProject(await readFile15(projectPath, "utf-8"));
|
|
10771
|
-
for (const
|
|
10772
|
-
const path =
|
|
10771
|
+
for (const raw2 of project.repositories) {
|
|
10772
|
+
const path = raw2.trim();
|
|
10773
10773
|
if (!path) continue;
|
|
10774
10774
|
const abs = resolve20(path);
|
|
10775
10775
|
if (seen.has(abs)) continue;
|
|
@@ -13678,8 +13678,8 @@ function mapAgentErrorToFieldErrors(err2) {
|
|
|
13678
13678
|
}
|
|
13679
13679
|
return { error: message };
|
|
13680
13680
|
}
|
|
13681
|
-
function coerceAgentRow(
|
|
13682
|
-
if (!
|
|
13681
|
+
function coerceAgentRow(raw2, index) {
|
|
13682
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) {
|
|
13683
13683
|
return {
|
|
13684
13684
|
ok: false,
|
|
13685
13685
|
status: 400,
|
|
@@ -13689,7 +13689,7 @@ function coerceAgentRow(raw, index) {
|
|
|
13689
13689
|
}
|
|
13690
13690
|
};
|
|
13691
13691
|
}
|
|
13692
|
-
const entry =
|
|
13692
|
+
const entry = raw2;
|
|
13693
13693
|
if (typeof entry.id !== "string" || entry.id.length === 0) {
|
|
13694
13694
|
return {
|
|
13695
13695
|
ok: false,
|
|
@@ -13798,14 +13798,14 @@ function createAgentsRouter() {
|
|
|
13798
13798
|
});
|
|
13799
13799
|
router.put("/", async (req, res) => {
|
|
13800
13800
|
try {
|
|
13801
|
-
const
|
|
13802
|
-
if (!Array.isArray(
|
|
13801
|
+
const raw2 = req.body && typeof req.body === "object" ? req.body : {};
|
|
13802
|
+
if (!Array.isArray(raw2.agents)) {
|
|
13803
13803
|
res.status(400).json({ error: "agents must be an array" });
|
|
13804
13804
|
return;
|
|
13805
13805
|
}
|
|
13806
13806
|
const cleaned = [];
|
|
13807
|
-
for (let i = 0; i <
|
|
13808
|
-
const result = coerceAgentRow(
|
|
13807
|
+
for (let i = 0; i < raw2.agents.length; i++) {
|
|
13808
|
+
const result = coerceAgentRow(raw2.agents[i], i);
|
|
13809
13809
|
if (!result.ok) {
|
|
13810
13810
|
res.status(result.status).json(result.body);
|
|
13811
13811
|
return;
|
|
@@ -14363,18 +14363,18 @@ function buildAffectedResponse(id, list) {
|
|
|
14363
14363
|
function isString(x) {
|
|
14364
14364
|
return typeof x === "string" && x.length > 0;
|
|
14365
14365
|
}
|
|
14366
|
-
function parseResolutions(
|
|
14367
|
-
if (
|
|
14366
|
+
function parseResolutions(raw2) {
|
|
14367
|
+
if (raw2 === void 0) {
|
|
14368
14368
|
return { resolutions: [], malformed: null, duplicateIds: null };
|
|
14369
14369
|
}
|
|
14370
|
-
if (!Array.isArray(
|
|
14370
|
+
if (!Array.isArray(raw2)) {
|
|
14371
14371
|
return { resolutions: [], malformed: "resolutions must be an array", duplicateIds: null };
|
|
14372
14372
|
}
|
|
14373
14373
|
const out = [];
|
|
14374
14374
|
const seen = /* @__PURE__ */ new Set();
|
|
14375
14375
|
const dups = /* @__PURE__ */ new Set();
|
|
14376
|
-
for (let i = 0; i <
|
|
14377
|
-
const r =
|
|
14376
|
+
for (let i = 0; i < raw2.length; i++) {
|
|
14377
|
+
const r = raw2[i];
|
|
14378
14378
|
if (!r || typeof r !== "object") {
|
|
14379
14379
|
return { resolutions: [], malformed: `resolutions[${i}] must be an object`, duplicateIds: null };
|
|
14380
14380
|
}
|
|
@@ -15863,13 +15863,362 @@ init_fs_migration();
|
|
|
15863
15863
|
init_parser2();
|
|
15864
15864
|
init_fs();
|
|
15865
15865
|
init_paths();
|
|
15866
|
-
import { Router as
|
|
15867
|
-
import { readdir as
|
|
15868
|
-
import { resolve as resolvePath, dirname as
|
|
15869
|
-
import { rename as
|
|
15866
|
+
import { Router as Router14 } from "express";
|
|
15867
|
+
import { readdir as readdir12 } from "fs/promises";
|
|
15868
|
+
import { resolve as resolvePath, dirname as dirname7 } from "path";
|
|
15869
|
+
import { rename as rename6, mkdir as mkdir3 } from "fs/promises";
|
|
15870
15870
|
init_slug();
|
|
15871
15871
|
init_promote_todos();
|
|
15872
15872
|
init_api();
|
|
15873
|
+
|
|
15874
|
+
// src/dashboard/todo-attachments-routes.ts
|
|
15875
|
+
import { raw } from "express";
|
|
15876
|
+
|
|
15877
|
+
// src/todos/attachments.ts
|
|
15878
|
+
import { mkdir as mkdir2, readdir as readdir11, stat, rename as rename5, rm as rm3, unlink as unlink5, writeFile as writeFile5, cp } from "fs/promises";
|
|
15879
|
+
import { resolve as resolve27, basename as basename4, dirname as dirname6, extname } from "path";
|
|
15880
|
+
|
|
15881
|
+
// src/utils/proof-artifact-id.ts
|
|
15882
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
15883
|
+
function generateArtifactId() {
|
|
15884
|
+
const ts = Date.now().toString(36);
|
|
15885
|
+
const rand = randomBytes2(2).toString("hex");
|
|
15886
|
+
return `${ts}-${rand}`;
|
|
15887
|
+
}
|
|
15888
|
+
var EXTENSIONS = {
|
|
15889
|
+
screenshot: "png",
|
|
15890
|
+
video: "mp4",
|
|
15891
|
+
asciinema: "cast",
|
|
15892
|
+
http: "txt",
|
|
15893
|
+
text: "txt"
|
|
15894
|
+
};
|
|
15895
|
+
function extensionForKind(kind) {
|
|
15896
|
+
const ext = EXTENSIONS[kind];
|
|
15897
|
+
if (!ext) {
|
|
15898
|
+
throw new Error(`Unknown artifact kind: ${kind}`);
|
|
15899
|
+
}
|
|
15900
|
+
return ext;
|
|
15901
|
+
}
|
|
15902
|
+
var ARTIFACT_KINDS = [
|
|
15903
|
+
"screenshot",
|
|
15904
|
+
"video",
|
|
15905
|
+
"asciinema",
|
|
15906
|
+
"http",
|
|
15907
|
+
"text"
|
|
15908
|
+
];
|
|
15909
|
+
function isArtifactKind(value) {
|
|
15910
|
+
return typeof value === "string" && ARTIFACT_KINDS.includes(value);
|
|
15911
|
+
}
|
|
15912
|
+
|
|
15913
|
+
// src/todos/attachments.ts
|
|
15914
|
+
var SCOPE_RE = /^[a-z0-9_][a-z0-9-]*$/;
|
|
15915
|
+
var TODO_ID_RE = /^[a-f0-9]{4}$/;
|
|
15916
|
+
var ATTACHMENT_ID_RE = /^[a-z0-9]+-[0-9a-f]{4}$/;
|
|
15917
|
+
var AttachmentValidationError = class extends Error {
|
|
15918
|
+
constructor(message) {
|
|
15919
|
+
super(message);
|
|
15920
|
+
this.name = "AttachmentValidationError";
|
|
15921
|
+
}
|
|
15922
|
+
};
|
|
15923
|
+
function assertScope(scopeId) {
|
|
15924
|
+
if (!SCOPE_RE.test(scopeId)) throw new AttachmentValidationError(`Invalid scope id: "${scopeId}"`);
|
|
15925
|
+
}
|
|
15926
|
+
function assertTodoId(todoId) {
|
|
15927
|
+
if (!TODO_ID_RE.test(todoId)) throw new AttachmentValidationError(`Invalid todo id: "${todoId}"`);
|
|
15928
|
+
}
|
|
15929
|
+
function assertAttachmentId(attachmentId) {
|
|
15930
|
+
if (!ATTACHMENT_ID_RE.test(attachmentId)) {
|
|
15931
|
+
throw new AttachmentValidationError(`Invalid attachment id: "${attachmentId}"`);
|
|
15932
|
+
}
|
|
15933
|
+
}
|
|
15934
|
+
var EXT_MIME = {
|
|
15935
|
+
png: "image/png",
|
|
15936
|
+
jpg: "image/jpeg",
|
|
15937
|
+
jpeg: "image/jpeg",
|
|
15938
|
+
gif: "image/gif",
|
|
15939
|
+
webp: "image/webp",
|
|
15940
|
+
bmp: "image/bmp",
|
|
15941
|
+
ico: "image/x-icon",
|
|
15942
|
+
svg: "image/svg+xml",
|
|
15943
|
+
pdf: "application/pdf",
|
|
15944
|
+
txt: "text/plain",
|
|
15945
|
+
log: "text/plain",
|
|
15946
|
+
md: "text/markdown",
|
|
15947
|
+
json: "application/json",
|
|
15948
|
+
csv: "text/csv",
|
|
15949
|
+
html: "text/html",
|
|
15950
|
+
htm: "text/html",
|
|
15951
|
+
xml: "application/xml",
|
|
15952
|
+
mp4: "video/mp4",
|
|
15953
|
+
mov: "video/quicktime",
|
|
15954
|
+
webm: "video/webm",
|
|
15955
|
+
mp3: "audio/mpeg",
|
|
15956
|
+
wav: "audio/wav",
|
|
15957
|
+
m4a: "audio/mp4",
|
|
15958
|
+
zip: "application/zip"
|
|
15959
|
+
};
|
|
15960
|
+
function mimeForName(name) {
|
|
15961
|
+
const ext = extname(name).slice(1).toLowerCase();
|
|
15962
|
+
return EXT_MIME[ext] ?? "application/octet-stream";
|
|
15963
|
+
}
|
|
15964
|
+
var SAFE_INLINE_MIME = /* @__PURE__ */ new Set([
|
|
15965
|
+
"image/png",
|
|
15966
|
+
"image/jpeg",
|
|
15967
|
+
"image/gif",
|
|
15968
|
+
"image/webp",
|
|
15969
|
+
"application/pdf",
|
|
15970
|
+
"text/plain"
|
|
15971
|
+
]);
|
|
15972
|
+
function isSafeInlineMime(mime) {
|
|
15973
|
+
return SAFE_INLINE_MIME.has(mime);
|
|
15974
|
+
}
|
|
15975
|
+
function sanitizeAttachmentName(name) {
|
|
15976
|
+
let n = basename4(name || "").replace(/["'\\/]/g, "_");
|
|
15977
|
+
n = Array.from(n, (ch) => {
|
|
15978
|
+
const code = ch.charCodeAt(0);
|
|
15979
|
+
return code < 32 || code === 127 ? "_" : ch;
|
|
15980
|
+
}).join("");
|
|
15981
|
+
n = n.trim();
|
|
15982
|
+
if (!n || n === "." || n === "..") n = "file";
|
|
15983
|
+
if (n.length > 120) {
|
|
15984
|
+
const ext = extname(n);
|
|
15985
|
+
n = n.slice(0, Math.max(1, 120 - ext.length)) + ext;
|
|
15986
|
+
}
|
|
15987
|
+
return n;
|
|
15988
|
+
}
|
|
15989
|
+
function attachmentsRootDir(todosDir2) {
|
|
15990
|
+
return resolve27(todosDir2, "attachments");
|
|
15991
|
+
}
|
|
15992
|
+
function attachmentDirFor(todosDir2, scopeId, todoId) {
|
|
15993
|
+
assertScope(scopeId);
|
|
15994
|
+
assertTodoId(todoId);
|
|
15995
|
+
return resolve27(attachmentsRootDir(todosDir2), scopeId, todoId);
|
|
15996
|
+
}
|
|
15997
|
+
async function dirExists(p) {
|
|
15998
|
+
try {
|
|
15999
|
+
return (await stat(p)).isDirectory();
|
|
16000
|
+
} catch {
|
|
16001
|
+
return false;
|
|
16002
|
+
}
|
|
16003
|
+
}
|
|
16004
|
+
async function writeAttachment(todosDir2, scopeId, todoId, originalName, bytes) {
|
|
16005
|
+
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
16006
|
+
await mkdir2(dir, { recursive: true });
|
|
16007
|
+
const id = generateArtifactId();
|
|
16008
|
+
const filename = sanitizeAttachmentName(originalName);
|
|
16009
|
+
await writeFile5(resolve27(dir, `${id}__${filename}`), bytes);
|
|
16010
|
+
return {
|
|
16011
|
+
id,
|
|
16012
|
+
filename,
|
|
16013
|
+
mime: mimeForName(filename),
|
|
16014
|
+
size: bytes.length,
|
|
16015
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16016
|
+
};
|
|
16017
|
+
}
|
|
16018
|
+
async function listAttachments(todosDir2, scopeId, todoId) {
|
|
16019
|
+
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
16020
|
+
let names;
|
|
16021
|
+
try {
|
|
16022
|
+
names = await readdir11(dir);
|
|
16023
|
+
} catch {
|
|
16024
|
+
return [];
|
|
16025
|
+
}
|
|
16026
|
+
const out = [];
|
|
16027
|
+
for (const stored of names) {
|
|
16028
|
+
const sep2 = stored.indexOf("__");
|
|
16029
|
+
if (sep2 <= 0) continue;
|
|
16030
|
+
const id = stored.slice(0, sep2);
|
|
16031
|
+
if (!ATTACHMENT_ID_RE.test(id)) continue;
|
|
16032
|
+
const filename = stored.slice(sep2 + 2);
|
|
16033
|
+
try {
|
|
16034
|
+
const st = await stat(resolve27(dir, stored));
|
|
16035
|
+
if (!st.isFile()) continue;
|
|
16036
|
+
out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
|
|
16037
|
+
} catch {
|
|
16038
|
+
}
|
|
16039
|
+
}
|
|
16040
|
+
out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
16041
|
+
return out;
|
|
16042
|
+
}
|
|
16043
|
+
async function readScopeAttachments(todosDir2, scopeId) {
|
|
16044
|
+
assertScope(scopeId);
|
|
16045
|
+
const scopeDir = resolve27(attachmentsRootDir(todosDir2), scopeId);
|
|
16046
|
+
let todoIds;
|
|
16047
|
+
try {
|
|
16048
|
+
todoIds = await readdir11(scopeDir);
|
|
16049
|
+
} catch {
|
|
16050
|
+
return {};
|
|
16051
|
+
}
|
|
16052
|
+
const result = {};
|
|
16053
|
+
for (const todoId of todoIds) {
|
|
16054
|
+
if (!TODO_ID_RE.test(todoId)) continue;
|
|
16055
|
+
const list = await listAttachments(todosDir2, scopeId, todoId);
|
|
16056
|
+
if (list.length) result[todoId] = list;
|
|
16057
|
+
}
|
|
16058
|
+
return result;
|
|
16059
|
+
}
|
|
16060
|
+
async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
|
|
16061
|
+
assertAttachmentId(attachmentId);
|
|
16062
|
+
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
16063
|
+
let names;
|
|
16064
|
+
try {
|
|
16065
|
+
names = await readdir11(dir);
|
|
16066
|
+
} catch {
|
|
16067
|
+
return null;
|
|
16068
|
+
}
|
|
16069
|
+
const prefix = `${attachmentId}__`;
|
|
16070
|
+
const stored = names.find((n) => n.startsWith(prefix));
|
|
16071
|
+
if (!stored) return null;
|
|
16072
|
+
const filename = stored.slice(prefix.length);
|
|
16073
|
+
return { path: resolve27(dir, stored), filename, mime: mimeForName(filename) };
|
|
16074
|
+
}
|
|
16075
|
+
async function deleteAttachment(todosDir2, scopeId, todoId, attachmentId) {
|
|
16076
|
+
const resolved = await resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId);
|
|
16077
|
+
if (!resolved) return false;
|
|
16078
|
+
await unlink5(resolved.path);
|
|
16079
|
+
return true;
|
|
16080
|
+
}
|
|
16081
|
+
async function deleteAllAttachments(todosDir2, scopeId, todoId) {
|
|
16082
|
+
await rm3(attachmentDirFor(todosDir2, scopeId, todoId), { recursive: true, force: true });
|
|
16083
|
+
}
|
|
16084
|
+
async function attachmentMoveConflict(srcTodosDir, srcScopeId, dstTodosDir, dstScopeId, todoId) {
|
|
16085
|
+
const src = attachmentDirFor(srcTodosDir, srcScopeId, todoId);
|
|
16086
|
+
const dst = attachmentDirFor(dstTodosDir, dstScopeId, todoId);
|
|
16087
|
+
return await dirExists(src) && await dirExists(dst);
|
|
16088
|
+
}
|
|
16089
|
+
async function moveAttachments(srcTodosDir, srcScopeId, dstTodosDir, dstScopeId, todoId) {
|
|
16090
|
+
const src = attachmentDirFor(srcTodosDir, srcScopeId, todoId);
|
|
16091
|
+
if (!await dirExists(src)) return;
|
|
16092
|
+
const dst = attachmentDirFor(dstTodosDir, dstScopeId, todoId);
|
|
16093
|
+
await mkdir2(dirname6(dst), { recursive: true });
|
|
16094
|
+
try {
|
|
16095
|
+
await rename5(src, dst);
|
|
16096
|
+
} catch (err2) {
|
|
16097
|
+
if (err2?.code === "EXDEV") {
|
|
16098
|
+
await cp(src, dst, { recursive: true });
|
|
16099
|
+
await rm3(src, { recursive: true, force: true });
|
|
16100
|
+
} else {
|
|
16101
|
+
throw err2;
|
|
16102
|
+
}
|
|
16103
|
+
}
|
|
16104
|
+
}
|
|
16105
|
+
|
|
16106
|
+
// src/dashboard/todo-attachments-routes.ts
|
|
16107
|
+
var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
16108
|
+
function headerValue(req, name) {
|
|
16109
|
+
const v = req.headers[name];
|
|
16110
|
+
return Array.isArray(v) ? v[0] : v;
|
|
16111
|
+
}
|
|
16112
|
+
function paramStr(v) {
|
|
16113
|
+
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
16114
|
+
}
|
|
16115
|
+
function sendError(res, err2) {
|
|
16116
|
+
if (err2 instanceof AttachmentValidationError) {
|
|
16117
|
+
res.status(400).json({ error: err2.message });
|
|
16118
|
+
return;
|
|
16119
|
+
}
|
|
16120
|
+
res.status(500).json({ error: err2 instanceof Error ? err2.message : "Attachment operation failed" });
|
|
16121
|
+
}
|
|
16122
|
+
function contentDisposition(filename, inline) {
|
|
16123
|
+
const disp = inline ? "inline" : "attachment";
|
|
16124
|
+
const asciiFallback = Array.from(filename, (ch) => {
|
|
16125
|
+
const code = ch.charCodeAt(0);
|
|
16126
|
+
return code >= 32 && code <= 126 && ch !== '"' && ch !== "\\" ? ch : "_";
|
|
16127
|
+
}).join("");
|
|
16128
|
+
return `${disp}; filename="${asciiFallback}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
16129
|
+
}
|
|
16130
|
+
function installTodoAttachmentRoutes(router, prefix, opts) {
|
|
16131
|
+
router.post(
|
|
16132
|
+
`${prefix}/attachments`,
|
|
16133
|
+
raw({ type: () => true, limit: MAX_UPLOAD_BYTES }),
|
|
16134
|
+
async (req, res) => {
|
|
16135
|
+
try {
|
|
16136
|
+
const rawName = headerValue(req, "x-attachment-filename");
|
|
16137
|
+
let filename = "file";
|
|
16138
|
+
if (rawName) {
|
|
16139
|
+
try {
|
|
16140
|
+
filename = decodeURIComponent(rawName);
|
|
16141
|
+
} catch {
|
|
16142
|
+
res.status(400).json({ error: "Invalid x-attachment-filename header" });
|
|
16143
|
+
return;
|
|
16144
|
+
}
|
|
16145
|
+
}
|
|
16146
|
+
const body = req.body;
|
|
16147
|
+
if (!Buffer.isBuffer(body) || body.length === 0) {
|
|
16148
|
+
res.status(400).json({ error: "Empty upload body" });
|
|
16149
|
+
return;
|
|
16150
|
+
}
|
|
16151
|
+
const scope = opts.resolveScope(req);
|
|
16152
|
+
const result = await opts.withScopeLock(req, async () => {
|
|
16153
|
+
if (!await opts.todoExists(scope)) return null;
|
|
16154
|
+
return writeAttachment(scope.todosDir, scope.scopeId, scope.todoId, filename, body);
|
|
16155
|
+
});
|
|
16156
|
+
if (!result) {
|
|
16157
|
+
res.status(404).json({ error: `Todo "${scope.todoId}" not found` });
|
|
16158
|
+
return;
|
|
16159
|
+
}
|
|
16160
|
+
opts.onChange(req);
|
|
16161
|
+
res.status(201).json(result);
|
|
16162
|
+
} catch (err2) {
|
|
16163
|
+
sendError(res, err2);
|
|
16164
|
+
}
|
|
16165
|
+
}
|
|
16166
|
+
);
|
|
16167
|
+
router.get(`${prefix}/attachments/:attachmentId`, async (req, res) => {
|
|
16168
|
+
try {
|
|
16169
|
+
const scope = opts.resolveScope(req);
|
|
16170
|
+
const attachmentId = paramStr(req.params.attachmentId);
|
|
16171
|
+
const resolved = await opts.withScopeLock(req, async () => {
|
|
16172
|
+
if (!await opts.todoExists(scope)) return { notFound: true };
|
|
16173
|
+
return {
|
|
16174
|
+
file: await resolveAttachmentFile(scope.todosDir, scope.scopeId, scope.todoId, attachmentId)
|
|
16175
|
+
};
|
|
16176
|
+
});
|
|
16177
|
+
if ("notFound" in resolved) {
|
|
16178
|
+
res.status(404).json({ error: `Todo "${scope.todoId}" not found` });
|
|
16179
|
+
return;
|
|
16180
|
+
}
|
|
16181
|
+
if (!resolved.file) {
|
|
16182
|
+
res.status(404).json({ error: `Attachment "${attachmentId}" not found` });
|
|
16183
|
+
return;
|
|
16184
|
+
}
|
|
16185
|
+
const { path, filename, mime } = resolved.file;
|
|
16186
|
+
const inline = isSafeInlineMime(mime);
|
|
16187
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
16188
|
+
res.setHeader("Content-Type", inline ? mime : "application/octet-stream");
|
|
16189
|
+
res.setHeader("Content-Disposition", contentDisposition(filename, inline));
|
|
16190
|
+
res.sendFile(path, (err2) => {
|
|
16191
|
+
if (err2 && !res.headersSent) res.status(500).end();
|
|
16192
|
+
});
|
|
16193
|
+
} catch (err2) {
|
|
16194
|
+
sendError(res, err2);
|
|
16195
|
+
}
|
|
16196
|
+
});
|
|
16197
|
+
router.delete(`${prefix}/attachments/:attachmentId`, async (req, res) => {
|
|
16198
|
+
try {
|
|
16199
|
+
const scope = opts.resolveScope(req);
|
|
16200
|
+
const attachmentId = paramStr(req.params.attachmentId);
|
|
16201
|
+
const result = await opts.withScopeLock(req, async () => {
|
|
16202
|
+
if (!await opts.todoExists(scope)) return { notFound: true };
|
|
16203
|
+
return { deleted: await deleteAttachment(scope.todosDir, scope.scopeId, scope.todoId, attachmentId) };
|
|
16204
|
+
});
|
|
16205
|
+
if ("notFound" in result) {
|
|
16206
|
+
res.status(404).json({ error: `Todo "${scope.todoId}" not found` });
|
|
16207
|
+
return;
|
|
16208
|
+
}
|
|
16209
|
+
if (!result.deleted) {
|
|
16210
|
+
res.status(404).json({ error: `Attachment "${attachmentId}" not found` });
|
|
16211
|
+
return;
|
|
16212
|
+
}
|
|
16213
|
+
opts.onChange(req);
|
|
16214
|
+
res.json({ deleted: attachmentId });
|
|
16215
|
+
} catch (err2) {
|
|
16216
|
+
sendError(res, err2);
|
|
16217
|
+
}
|
|
16218
|
+
});
|
|
16219
|
+
}
|
|
16220
|
+
|
|
16221
|
+
// src/dashboard/api-todos.ts
|
|
15873
16222
|
var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
|
|
15874
16223
|
function getWorkspaceParam(value) {
|
|
15875
16224
|
if (Array.isArray(value)) {
|
|
@@ -15883,7 +16232,7 @@ function touchItem3(item) {
|
|
|
15883
16232
|
item.updatedAt = now;
|
|
15884
16233
|
}
|
|
15885
16234
|
function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
15886
|
-
const router =
|
|
16235
|
+
const router = Router14();
|
|
15887
16236
|
installRecordsInvalidation(router);
|
|
15888
16237
|
function broadcastUpdate() {
|
|
15889
16238
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -15900,6 +16249,19 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
15900
16249
|
next();
|
|
15901
16250
|
}
|
|
15902
16251
|
router.param("workspace", validateWorkspace);
|
|
16252
|
+
installTodoAttachmentRoutes(router, "/:workspace/:id", {
|
|
16253
|
+
resolveScope: (req) => ({
|
|
16254
|
+
todosDir: todosDir2,
|
|
16255
|
+
scopeId: getWorkspaceParam(req.params.workspace),
|
|
16256
|
+
todoId: getWorkspaceParam(req.params.id)
|
|
16257
|
+
}),
|
|
16258
|
+
withScopeLock: (req, fn) => wsLock(getWorkspaceParam(req.params.workspace), fn),
|
|
16259
|
+
todoExists: async (scope) => {
|
|
16260
|
+
const checklist = await readChecklist(scope.todosDir, scope.scopeId);
|
|
16261
|
+
return checklist.items.some((i) => i.id === scope.todoId);
|
|
16262
|
+
},
|
|
16263
|
+
onChange: () => broadcastUpdate()
|
|
16264
|
+
});
|
|
15903
16265
|
router.post("/promote-bulk", async (req, res) => {
|
|
15904
16266
|
try {
|
|
15905
16267
|
const { groups, mode, target, title, type, priority, keepSource } = req.body ?? {};
|
|
@@ -16004,17 +16366,18 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
16004
16366
|
router.get("/", async (_req, res) => {
|
|
16005
16367
|
try {
|
|
16006
16368
|
await ensureDir(todosDir2);
|
|
16007
|
-
const files = await
|
|
16369
|
+
const files = await readdir12(todosDir2).catch(() => []);
|
|
16008
16370
|
const workspaces = [];
|
|
16009
16371
|
for (const file of files) {
|
|
16010
16372
|
if (typeof file !== "string") continue;
|
|
16011
16373
|
if (!file.endsWith(".md") || file.endsWith("-log.md")) continue;
|
|
16012
16374
|
const workspace = file.replace(".md", "");
|
|
16013
16375
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
16376
|
+
const attachmentsByTodo = await readScopeAttachments(todosDir2, checklist.workspace);
|
|
16014
16377
|
workspaces.push({
|
|
16015
16378
|
workspace: checklist.workspace,
|
|
16016
16379
|
archiveInterval: checklist.archiveInterval,
|
|
16017
|
-
items: checklist.items,
|
|
16380
|
+
items: checklist.items.map((i) => ({ ...i, attachments: attachmentsByTodo[i.id] ?? [] })),
|
|
16018
16381
|
counts: computeCounts(checklist.items)
|
|
16019
16382
|
});
|
|
16020
16383
|
}
|
|
@@ -16027,10 +16390,11 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
16027
16390
|
try {
|
|
16028
16391
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
16029
16392
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
16393
|
+
const attachmentsByTodo = await readScopeAttachments(todosDir2, workspace);
|
|
16030
16394
|
res.json({
|
|
16031
16395
|
workspace: checklist.workspace,
|
|
16032
16396
|
archiveInterval: checklist.archiveInterval,
|
|
16033
|
-
items: checklist.items,
|
|
16397
|
+
items: checklist.items.map((i) => ({ ...i, attachments: attachmentsByTodo[i.id] ?? [] })),
|
|
16034
16398
|
counts: computeCounts(checklist.items)
|
|
16035
16399
|
});
|
|
16036
16400
|
} catch (error) {
|
|
@@ -16118,63 +16482,68 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
16118
16482
|
router.post("/:workspace/archive", async (req, res) => {
|
|
16119
16483
|
try {
|
|
16120
16484
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
16121
|
-
const { resolve:
|
|
16485
|
+
const { resolve: resolve83 } = await import("path");
|
|
16122
16486
|
const { readFile: readFile56 } = await import("fs/promises");
|
|
16123
16487
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
16124
16488
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
16125
|
-
const
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16489
|
+
const outcome = await wsLock(workspace, async () => {
|
|
16490
|
+
const checklist = await readChecklist(todosDir2, workspace);
|
|
16491
|
+
const log = await readLog(todosDir2, workspace);
|
|
16492
|
+
const completedIds = new Set(
|
|
16493
|
+
checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
|
|
16494
|
+
);
|
|
16495
|
+
if (completedIds.size === 0) {
|
|
16496
|
+
return { archived: 0, message: "No completed items to archive" };
|
|
16497
|
+
}
|
|
16498
|
+
const toArchive = log.entries.filter(
|
|
16499
|
+
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
16500
|
+
);
|
|
16501
|
+
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
16502
|
+
await ensureDir(resolve83(todosDir2, "archive"));
|
|
16503
|
+
let archContent = "";
|
|
16504
|
+
if (await fileExists(archFile)) {
|
|
16505
|
+
archContent = await readFile56(archFile, "utf-8");
|
|
16506
|
+
archContent = archContent.trimEnd() + "\n\n";
|
|
16507
|
+
} else {
|
|
16508
|
+
archContent = `---
|
|
16145
16509
|
workspace: ${workspace}
|
|
16146
16510
|
---
|
|
16147
16511
|
|
|
16148
16512
|
# Archive
|
|
16149
16513
|
|
|
16150
16514
|
`;
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16515
|
+
}
|
|
16516
|
+
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
16517
|
+
for (const item of completedItems) {
|
|
16518
|
+
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
16155
16519
|
`;
|
|
16156
|
-
|
|
16157
|
-
|
|
16158
|
-
|
|
16159
|
-
|
|
16520
|
+
}
|
|
16521
|
+
archContent += "\n";
|
|
16522
|
+
for (const entry of toArchive) {
|
|
16523
|
+
archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
|
|
16160
16524
|
`;
|
|
16161
|
-
|
|
16525
|
+
if (entry.items) archContent += `**Items:** ${entry.items}
|
|
16162
16526
|
`;
|
|
16163
|
-
|
|
16527
|
+
if (entry.session) archContent += `**Session:** ${entry.session}
|
|
16164
16528
|
`;
|
|
16165
|
-
|
|
16529
|
+
if (entry.branch) archContent += `**Branch:** ${entry.branch}
|
|
16166
16530
|
`;
|
|
16167
|
-
|
|
16531
|
+
if (entry.summary) archContent += `**Summary:** ${entry.summary}
|
|
16168
16532
|
`;
|
|
16169
|
-
|
|
16533
|
+
if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
|
|
16170
16534
|
`;
|
|
16171
|
-
|
|
16172
|
-
|
|
16173
|
-
|
|
16174
|
-
|
|
16175
|
-
|
|
16176
|
-
|
|
16177
|
-
|
|
16535
|
+
archContent += "\n";
|
|
16536
|
+
}
|
|
16537
|
+
await writeFileForce2(archFile, archContent);
|
|
16538
|
+
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
16539
|
+
await writeChecklist(todosDir2, checklist);
|
|
16540
|
+
for (const id of completedIds) {
|
|
16541
|
+
await deleteAllAttachments(todosDir2, workspace, id);
|
|
16542
|
+
}
|
|
16543
|
+
return { archived: completedIds.size, logEntries: toArchive.length };
|
|
16544
|
+
});
|
|
16545
|
+
if (outcome.archived > 0) broadcastUpdate();
|
|
16546
|
+
res.json(outcome);
|
|
16178
16547
|
} catch (error) {
|
|
16179
16548
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to archive" });
|
|
16180
16549
|
}
|
|
@@ -16199,7 +16568,8 @@ workspace: ${workspace}
|
|
|
16199
16568
|
}
|
|
16200
16569
|
const log = await readLog(todosDir2, workspace);
|
|
16201
16570
|
const logEntries = log.entries.filter((e) => e.itemIds.includes(req.params.id));
|
|
16202
|
-
|
|
16571
|
+
const attachments = await listAttachments(todosDir2, workspace, item.id);
|
|
16572
|
+
res.json({ ...item, attachments, log: logEntries });
|
|
16203
16573
|
} catch (error) {
|
|
16204
16574
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
|
|
16205
16575
|
}
|
|
@@ -16236,6 +16606,7 @@ workspace: ${workspace}
|
|
|
16236
16606
|
if (idx === -1) return false;
|
|
16237
16607
|
checklist.items.splice(idx, 1);
|
|
16238
16608
|
await writeChecklist(todosDir2, checklist);
|
|
16609
|
+
await deleteAllAttachments(todosDir2, workspace, req.params.id);
|
|
16239
16610
|
return true;
|
|
16240
16611
|
});
|
|
16241
16612
|
if (!deleted) {
|
|
@@ -16547,15 +16918,22 @@ workspace: ${workspace}
|
|
|
16547
16918
|
return { status: 409, error: "id already exists in target" };
|
|
16548
16919
|
}
|
|
16549
16920
|
const item = sourceChecklist.items[idx];
|
|
16921
|
+
let newPlanDir = null;
|
|
16550
16922
|
if (item.planDir) {
|
|
16551
|
-
|
|
16923
|
+
newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
16552
16924
|
if (await fileExists(newPlanDir)) {
|
|
16553
16925
|
return { status: 409, error: "plan dir already exists in target" };
|
|
16554
16926
|
}
|
|
16555
|
-
|
|
16556
|
-
|
|
16927
|
+
}
|
|
16928
|
+
if (await attachmentMoveConflict(todosDir2, sourceWs, target.todosPath, target.id, id)) {
|
|
16929
|
+
return { status: 409, error: "attachments already exist in target" };
|
|
16930
|
+
}
|
|
16931
|
+
if (item.planDir && newPlanDir) {
|
|
16932
|
+
await mkdir3(dirname7(newPlanDir), { recursive: true });
|
|
16933
|
+
await rename6(item.planDir, newPlanDir);
|
|
16557
16934
|
item.planDir = newPlanDir;
|
|
16558
16935
|
}
|
|
16936
|
+
await moveAttachments(todosDir2, sourceWs, target.todosPath, target.id, id);
|
|
16559
16937
|
sourceChecklist.items.splice(idx, 1);
|
|
16560
16938
|
targetChecklist.items.push(item);
|
|
16561
16939
|
await writeChecklist(todosDir2, sourceChecklist);
|
|
@@ -16628,9 +17006,9 @@ init_parser2();
|
|
|
16628
17006
|
init_fs();
|
|
16629
17007
|
init_paths();
|
|
16630
17008
|
init_slug();
|
|
16631
|
-
import { Router as
|
|
16632
|
-
import { mkdir as
|
|
16633
|
-
import { resolve as
|
|
17009
|
+
import { Router as Router15 } from "express";
|
|
17010
|
+
import { mkdir as mkdir4, readFile as readFile19, rename as rename7 } from "fs/promises";
|
|
17011
|
+
import { resolve as resolve28, dirname as dirname8 } from "path";
|
|
16634
17012
|
init_promote_todos();
|
|
16635
17013
|
init_api();
|
|
16636
17014
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
@@ -16647,12 +17025,12 @@ function params(req) {
|
|
|
16647
17025
|
return req.params;
|
|
16648
17026
|
}
|
|
16649
17027
|
async function projectExists(projectsDir2, slug) {
|
|
16650
|
-
return fileExists(
|
|
17028
|
+
return fileExists(resolve28(projectsDir2, slug, "project.md"));
|
|
16651
17029
|
}
|
|
16652
17030
|
async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
16653
17031
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
16654
17032
|
try {
|
|
16655
|
-
await
|
|
17033
|
+
await mkdir4(todosDir2, { recursive: false });
|
|
16656
17034
|
} catch (err2) {
|
|
16657
17035
|
const code = err2.code;
|
|
16658
17036
|
if (code === "EEXIST") return;
|
|
@@ -16664,7 +17042,7 @@ async function ensureProjectTodosDir(projectsDir2, slug) {
|
|
|
16664
17042
|
throw err2;
|
|
16665
17043
|
}
|
|
16666
17044
|
try {
|
|
16667
|
-
await
|
|
17045
|
+
await mkdir4(resolve28(todosDir2, "archive"), { recursive: false });
|
|
16668
17046
|
} catch (err2) {
|
|
16669
17047
|
const code = err2.code;
|
|
16670
17048
|
if (code === "EEXIST") return;
|
|
@@ -16680,7 +17058,7 @@ function notFound(res, slug) {
|
|
|
16680
17058
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
16681
17059
|
}
|
|
16682
17060
|
function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
16683
|
-
const router =
|
|
17061
|
+
const router = Router15({ mergeParams: true });
|
|
16684
17062
|
installRecordsInvalidation(router);
|
|
16685
17063
|
function broadcastUpdate(projectSlug) {
|
|
16686
17064
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -16697,6 +17075,18 @@ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
|
16697
17075
|
next();
|
|
16698
17076
|
}
|
|
16699
17077
|
router.use(validateProjectId);
|
|
17078
|
+
installTodoAttachmentRoutes(router, "/:id", {
|
|
17079
|
+
resolveScope: (req) => {
|
|
17080
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
17081
|
+
return { todosDir: projectTodosDir(projectsDir2, slug), scopeId: slug, todoId: params(req).id ?? "" };
|
|
17082
|
+
},
|
|
17083
|
+
withScopeLock: (req, fn) => projLock(getProjectIdParam(params(req).projectId), fn),
|
|
17084
|
+
todoExists: async (scope) => {
|
|
17085
|
+
const checklist = await readChecklist(scope.todosDir, scope.scopeId);
|
|
17086
|
+
return checklist.items.some((i) => i.id === scope.todoId);
|
|
17087
|
+
},
|
|
17088
|
+
onChange: (req) => broadcastUpdate(getProjectIdParam(params(req).projectId))
|
|
17089
|
+
});
|
|
16700
17090
|
router.get("/", async (req, res) => {
|
|
16701
17091
|
try {
|
|
16702
17092
|
const slug = getProjectIdParam(params(req).projectId);
|
|
@@ -16706,10 +17096,11 @@ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
|
16706
17096
|
}
|
|
16707
17097
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
16708
17098
|
const checklist = await readChecklist(todosDir2, slug);
|
|
17099
|
+
const attachmentsByTodo = await readScopeAttachments(todosDir2, slug);
|
|
16709
17100
|
res.json({
|
|
16710
17101
|
workspace: checklist.workspace,
|
|
16711
17102
|
archiveInterval: checklist.archiveInterval,
|
|
16712
|
-
items: checklist.items,
|
|
17103
|
+
items: checklist.items.map((i) => ({ ...i, attachments: attachmentsByTodo[i.id] ?? [] })),
|
|
16713
17104
|
counts: computeCounts(checklist.items)
|
|
16714
17105
|
});
|
|
16715
17106
|
} catch (error) {
|
|
@@ -16837,61 +17228,71 @@ function createProjectTodosRouter(projectsDir2, broadcast, workspaceTodosDir) {
|
|
|
16837
17228
|
notFound(res, slug);
|
|
16838
17229
|
return;
|
|
16839
17230
|
}
|
|
16840
|
-
const
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
|
|
16860
|
-
|
|
17231
|
+
const outcome = await projLock(slug, async () => {
|
|
17232
|
+
if (!await projectExists(projectsDir2, slug)) return "gone";
|
|
17233
|
+
await ensureProjectTodosDir(projectsDir2, slug);
|
|
17234
|
+
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
17235
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
17236
|
+
const log = await readLog(todosDir2, slug);
|
|
17237
|
+
const completedIds = new Set(
|
|
17238
|
+
checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
|
|
17239
|
+
);
|
|
17240
|
+
if (completedIds.size === 0) {
|
|
17241
|
+
return { archived: 0, message: "No completed items to archive" };
|
|
17242
|
+
}
|
|
17243
|
+
const toArchive = log.entries.filter(
|
|
17244
|
+
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
17245
|
+
);
|
|
17246
|
+
const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
|
|
17247
|
+
let archContent = "";
|
|
17248
|
+
if (await fileExists(archFile)) {
|
|
17249
|
+
archContent = await readFile19(archFile, "utf-8");
|
|
17250
|
+
archContent = archContent.trimEnd() + "\n\n";
|
|
17251
|
+
} else {
|
|
17252
|
+
archContent = `---
|
|
16861
17253
|
workspace: ${slug}
|
|
16862
17254
|
---
|
|
16863
17255
|
|
|
16864
17256
|
# Archive
|
|
16865
17257
|
|
|
16866
17258
|
`;
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
17259
|
+
}
|
|
17260
|
+
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
17261
|
+
for (const item of completedItems) {
|
|
17262
|
+
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
16871
17263
|
`;
|
|
16872
|
-
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
17264
|
+
}
|
|
17265
|
+
archContent += "\n";
|
|
17266
|
+
for (const entry of toArchive) {
|
|
17267
|
+
archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
|
|
16876
17268
|
`;
|
|
16877
|
-
|
|
17269
|
+
if (entry.items) archContent += `**Items:** ${entry.items}
|
|
16878
17270
|
`;
|
|
16879
|
-
|
|
17271
|
+
if (entry.session) archContent += `**Session:** ${entry.session}
|
|
16880
17272
|
`;
|
|
16881
|
-
|
|
17273
|
+
if (entry.branch) archContent += `**Branch:** ${entry.branch}
|
|
16882
17274
|
`;
|
|
16883
|
-
|
|
17275
|
+
if (entry.summary) archContent += `**Summary:** ${entry.summary}
|
|
16884
17276
|
`;
|
|
16885
|
-
|
|
17277
|
+
if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
|
|
16886
17278
|
`;
|
|
16887
|
-
|
|
17279
|
+
archContent += "\n";
|
|
17280
|
+
}
|
|
17281
|
+
await writeFileForce(archFile, archContent);
|
|
17282
|
+
checklist.workspace = slug;
|
|
17283
|
+
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
17284
|
+
await writeChecklist(todosDir2, checklist);
|
|
17285
|
+
for (const id of completedIds) {
|
|
17286
|
+
await deleteAllAttachments(todosDir2, slug, id);
|
|
17287
|
+
}
|
|
17288
|
+
return { archived: completedIds.size, logEntries: toArchive.length };
|
|
17289
|
+
});
|
|
17290
|
+
if (outcome === "gone") {
|
|
17291
|
+
notFound(res, slug);
|
|
17292
|
+
return;
|
|
16888
17293
|
}
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
16892
|
-
await writeChecklist(todosDir2, checklist);
|
|
16893
|
-
broadcastUpdate(slug);
|
|
16894
|
-
res.json({ archived: completedIds.size, logEntries: toArchive.length });
|
|
17294
|
+
if (outcome.archived > 0) broadcastUpdate(slug);
|
|
17295
|
+
res.json(outcome);
|
|
16895
17296
|
} catch (error) {
|
|
16896
17297
|
if (error.code === "PROJECT_GONE") {
|
|
16897
17298
|
notFound(res, getProjectIdParam(params(req).projectId));
|
|
@@ -16931,7 +17332,8 @@ workspace: ${slug}
|
|
|
16931
17332
|
}
|
|
16932
17333
|
const log = await readLog(todosDir2, slug);
|
|
16933
17334
|
const logEntries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
|
|
16934
|
-
|
|
17335
|
+
const attachments = await listAttachments(todosDir2, slug, item.id);
|
|
17336
|
+
res.json({ ...item, attachments, log: logEntries });
|
|
16935
17337
|
} catch (error) {
|
|
16936
17338
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
|
|
16937
17339
|
}
|
|
@@ -16987,11 +17389,13 @@ workspace: ${slug}
|
|
|
16987
17389
|
await ensureProjectTodosDir(projectsDir2, slug);
|
|
16988
17390
|
const todosDir2 = projectTodosDir(projectsDir2, slug);
|
|
16989
17391
|
const checklist = await readChecklist(todosDir2, slug);
|
|
16990
|
-
const
|
|
17392
|
+
const targetId = params(req).id ?? "";
|
|
17393
|
+
const idx = checklist.items.findIndex((i) => i.id === targetId);
|
|
16991
17394
|
if (idx === -1) return false;
|
|
16992
17395
|
checklist.items.splice(idx, 1);
|
|
16993
17396
|
checklist.workspace = slug;
|
|
16994
17397
|
await writeChecklist(todosDir2, checklist);
|
|
17398
|
+
await deleteAllAttachments(todosDir2, slug, targetId);
|
|
16995
17399
|
return true;
|
|
16996
17400
|
});
|
|
16997
17401
|
if (deleted === "gone") {
|
|
@@ -17301,15 +17705,15 @@ workspace: ${slug}
|
|
|
17301
17705
|
if (tg.includes("/")) {
|
|
17302
17706
|
const parts = tg.split("/");
|
|
17303
17707
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
17304
|
-
assignmentDir =
|
|
17708
|
+
assignmentDir = resolve28(projectsDir2, parts[0], "assignments", parts[1]);
|
|
17305
17709
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
17306
17710
|
} 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)) {
|
|
17307
|
-
assignmentDir =
|
|
17711
|
+
assignmentDir = resolve28(assignmentsDirFn(), tg);
|
|
17308
17712
|
assignmentRef = tg;
|
|
17309
17713
|
} else {
|
|
17310
17714
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
17311
17715
|
}
|
|
17312
|
-
const assignmentMdPath2 =
|
|
17716
|
+
const assignmentMdPath2 = resolve28(assignmentDir, "assignment.md");
|
|
17313
17717
|
if (!await fileExists(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
17314
17718
|
let content = await readFile19(assignmentMdPath2, "utf-8");
|
|
17315
17719
|
content = appendTodosToAssignmentBody2(
|
|
@@ -17439,15 +17843,22 @@ workspace: ${slug}
|
|
|
17439
17843
|
return { status: 409, error: "id already exists in target" };
|
|
17440
17844
|
}
|
|
17441
17845
|
const item = sourceChecklist.items[idx];
|
|
17846
|
+
let newPlanDir = null;
|
|
17442
17847
|
if (item.planDir) {
|
|
17443
|
-
|
|
17848
|
+
newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
17444
17849
|
if (await fileExists(newPlanDir)) {
|
|
17445
17850
|
return { status: 409, error: "plan dir already exists in target" };
|
|
17446
17851
|
}
|
|
17447
|
-
|
|
17448
|
-
|
|
17852
|
+
}
|
|
17853
|
+
if (await attachmentMoveConflict(sourceTodosDir, sourceSlug, target.todosPath, target.id, id)) {
|
|
17854
|
+
return { status: 409, error: "attachments already exist in target" };
|
|
17855
|
+
}
|
|
17856
|
+
if (item.planDir && newPlanDir) {
|
|
17857
|
+
await mkdir4(dirname8(newPlanDir), { recursive: true });
|
|
17858
|
+
await rename7(item.planDir, newPlanDir);
|
|
17449
17859
|
item.planDir = newPlanDir;
|
|
17450
17860
|
}
|
|
17861
|
+
await moveAttachments(sourceTodosDir, sourceSlug, target.todosPath, target.id, id);
|
|
17451
17862
|
sourceChecklist.items.splice(idx, 1);
|
|
17452
17863
|
targetChecklist.items.push(item);
|
|
17453
17864
|
sourceChecklist.workspace = sourceSlug;
|
|
@@ -17506,22 +17917,22 @@ workspace: ${slug}
|
|
|
17506
17917
|
}
|
|
17507
17918
|
|
|
17508
17919
|
// src/dashboard/api-bundles.ts
|
|
17509
|
-
import { Router as
|
|
17510
|
-
import { readdir as
|
|
17920
|
+
import { Router as Router16 } from "express";
|
|
17921
|
+
import { readdir as readdir13 } from "fs/promises";
|
|
17511
17922
|
|
|
17512
17923
|
// src/todos/bundle-parser.ts
|
|
17513
17924
|
init_parser();
|
|
17514
17925
|
init_fs();
|
|
17515
17926
|
init_paths();
|
|
17516
17927
|
init_parser2();
|
|
17517
|
-
import { randomBytes as
|
|
17928
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
17518
17929
|
import { readFile as readFile20 } from "fs/promises";
|
|
17519
17930
|
var BUNDLE_ID_REGEX = /^[a-f0-9]{4}$/;
|
|
17520
17931
|
var SCOPE_VALUES = /* @__PURE__ */ new Set(["workspace", "project", "global"]);
|
|
17521
17932
|
var SCOPE_ID_REGEX = /^[a-z0-9_][a-z0-9_-]*$/;
|
|
17522
17933
|
var BUNDLE_LINE_REGEX = /^- b:([a-f0-9]{4})\s+<([^>]*)>\s*$/;
|
|
17523
17934
|
function generateShortBundleId() {
|
|
17524
|
-
return
|
|
17935
|
+
return randomBytes3(2).toString("hex");
|
|
17525
17936
|
}
|
|
17526
17937
|
function generateUniqueBundleId(existing) {
|
|
17527
17938
|
let id = generateShortBundleId();
|
|
@@ -17532,19 +17943,19 @@ function generateUniqueBundleId(existing) {
|
|
|
17532
17943
|
}
|
|
17533
17944
|
return id;
|
|
17534
17945
|
}
|
|
17535
|
-
function parseScopeToken(
|
|
17536
|
-
const idx =
|
|
17946
|
+
function parseScopeToken(raw2) {
|
|
17947
|
+
const idx = raw2.indexOf(":");
|
|
17537
17948
|
if (idx < 0) return null;
|
|
17538
|
-
const scopeRaw =
|
|
17539
|
-
const scopeId =
|
|
17949
|
+
const scopeRaw = raw2.slice(0, idx);
|
|
17950
|
+
const scopeId = raw2.slice(idx + 1);
|
|
17540
17951
|
if (!SCOPE_VALUES.has(scopeRaw)) return null;
|
|
17541
17952
|
if (!scopeId) return null;
|
|
17542
17953
|
if (!SCOPE_ID_REGEX.test(scopeId)) return null;
|
|
17543
17954
|
return { scope: scopeRaw, scopeId };
|
|
17544
17955
|
}
|
|
17545
|
-
function parseTodosToken(
|
|
17546
|
-
if (!
|
|
17547
|
-
return
|
|
17956
|
+
function parseTodosToken(raw2) {
|
|
17957
|
+
if (!raw2) return [];
|
|
17958
|
+
return raw2.split(",").map((s) => s.trim()).filter((s) => BUNDLE_ID_REGEX.test(s));
|
|
17548
17959
|
}
|
|
17549
17960
|
function parseBundleLine(line) {
|
|
17550
17961
|
const match = line.match(BUNDLE_LINE_REGEX);
|
|
@@ -17657,7 +18068,7 @@ function annotate(bundle, items) {
|
|
|
17657
18068
|
}
|
|
17658
18069
|
function createBundlesRouter(todosDir2, broadcast) {
|
|
17659
18070
|
void broadcast;
|
|
17660
|
-
const router =
|
|
18071
|
+
const router = Router16();
|
|
17661
18072
|
function validateWorkspace(req, res, next) {
|
|
17662
18073
|
const workspace = getWorkspaceParam2(req.params.workspace);
|
|
17663
18074
|
if (workspace && !WORKSPACE_REGEX3.test(workspace)) {
|
|
@@ -17671,7 +18082,7 @@ function createBundlesRouter(todosDir2, broadcast) {
|
|
|
17671
18082
|
try {
|
|
17672
18083
|
await ensureDir(todosDir2);
|
|
17673
18084
|
const bundles = await readBundles(todosDir2);
|
|
17674
|
-
const workspaceFiles = await
|
|
18085
|
+
const workspaceFiles = await readdir13(todosDir2).catch(() => []);
|
|
17675
18086
|
const itemsByKey = /* @__PURE__ */ new Map();
|
|
17676
18087
|
for (const f of workspaceFiles) {
|
|
17677
18088
|
if (typeof f !== "string") continue;
|
|
@@ -17723,8 +18134,8 @@ function createBundlesRouter(todosDir2, broadcast) {
|
|
|
17723
18134
|
init_fs();
|
|
17724
18135
|
init_paths();
|
|
17725
18136
|
init_slug();
|
|
17726
|
-
import { Router as
|
|
17727
|
-
import { resolve as
|
|
18137
|
+
import { Router as Router17 } from "express";
|
|
18138
|
+
import { resolve as resolve29 } from "path";
|
|
17728
18139
|
init_parser2();
|
|
17729
18140
|
function deriveStatus2(bundle, items) {
|
|
17730
18141
|
const members = bundle.todoIds.map((id) => items.find((i) => i.id === id)).filter((i) => i !== void 0);
|
|
@@ -17753,7 +18164,7 @@ function notFound2(res, slug) {
|
|
|
17753
18164
|
}
|
|
17754
18165
|
function createProjectBundlesRouter(projectsDir2, broadcast) {
|
|
17755
18166
|
void broadcast;
|
|
17756
|
-
const router =
|
|
18167
|
+
const router = Router17({ mergeParams: true });
|
|
17757
18168
|
function validateProjectId(req, res, next) {
|
|
17758
18169
|
const slug = getProjectIdParam2(req.params.projectId);
|
|
17759
18170
|
if (!slug || !isValidSlug(slug)) {
|
|
@@ -17766,7 +18177,7 @@ function createProjectBundlesRouter(projectsDir2, broadcast) {
|
|
|
17766
18177
|
router.get("/", async (req, res) => {
|
|
17767
18178
|
try {
|
|
17768
18179
|
const slug = getProjectIdParam2(req.params.projectId);
|
|
17769
|
-
const projectMd =
|
|
18180
|
+
const projectMd = resolve29(projectsDir2, slug, "project.md");
|
|
17770
18181
|
if (!await fileExists(projectMd)) {
|
|
17771
18182
|
notFound2(res, slug);
|
|
17772
18183
|
return;
|
|
@@ -17787,7 +18198,7 @@ function createProjectBundlesRouter(projectsDir2, broadcast) {
|
|
|
17787
18198
|
init_config2();
|
|
17788
18199
|
init_api();
|
|
17789
18200
|
init_scanner();
|
|
17790
|
-
import { Router as
|
|
18201
|
+
import { Router as Router18 } from "express";
|
|
17791
18202
|
|
|
17792
18203
|
// src/utils/github-backup.ts
|
|
17793
18204
|
init_paths();
|
|
@@ -17795,8 +18206,8 @@ init_fs();
|
|
|
17795
18206
|
init_config2();
|
|
17796
18207
|
import { execFile as execFile2 } from "child_process";
|
|
17797
18208
|
import { promisify as promisify2 } from "util";
|
|
17798
|
-
import { cp, mkdtemp, rm as
|
|
17799
|
-
import { resolve as
|
|
18209
|
+
import { cp as cp2, mkdtemp, rm as rm4, readFile as readFile21, writeFile as writeFile6, unlink as unlink6, stat as stat2, open as open2, rename as rename8 } from "fs/promises";
|
|
18210
|
+
import { resolve as resolve30, join as join3 } from "path";
|
|
17800
18211
|
import { tmpdir } from "os";
|
|
17801
18212
|
var exec2 = promisify2(execFile2);
|
|
17802
18213
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -17836,7 +18247,7 @@ async function resolveCategoryPath(category) {
|
|
|
17836
18247
|
case "servers":
|
|
17837
18248
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
17838
18249
|
case "config":
|
|
17839
|
-
return { sourcePath:
|
|
18250
|
+
return { sourcePath: resolve30(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
17840
18251
|
}
|
|
17841
18252
|
}
|
|
17842
18253
|
async function checkGitInstalled() {
|
|
@@ -17847,7 +18258,7 @@ async function checkGitInstalled() {
|
|
|
17847
18258
|
}
|
|
17848
18259
|
}
|
|
17849
18260
|
async function acquireLock() {
|
|
17850
|
-
const lockPath =
|
|
18261
|
+
const lockPath = resolve30(syntaurRoot(), LOCK_FILE_NAME);
|
|
17851
18262
|
await ensureDir(syntaurRoot());
|
|
17852
18263
|
try {
|
|
17853
18264
|
const handle = await open2(lockPath, "wx");
|
|
@@ -17866,7 +18277,7 @@ async function acquireLock() {
|
|
|
17866
18277
|
}
|
|
17867
18278
|
async function releaseLock(lockPath) {
|
|
17868
18279
|
try {
|
|
17869
|
-
await
|
|
18280
|
+
await unlink6(lockPath);
|
|
17870
18281
|
} catch {
|
|
17871
18282
|
}
|
|
17872
18283
|
}
|
|
@@ -17889,13 +18300,13 @@ async function cloneOrInit(repoUrl, destDir) {
|
|
|
17889
18300
|
}
|
|
17890
18301
|
async function copyRecursive(src, dest) {
|
|
17891
18302
|
if (!await fileExists(src)) return;
|
|
17892
|
-
const s = await
|
|
18303
|
+
const s = await stat2(src);
|
|
17893
18304
|
if (s.isDirectory()) {
|
|
17894
18305
|
await ensureDir(dest);
|
|
17895
|
-
await
|
|
18306
|
+
await cp2(src, dest, { recursive: true, force: true });
|
|
17896
18307
|
} else {
|
|
17897
|
-
await ensureDir(
|
|
17898
|
-
await
|
|
18308
|
+
await ensureDir(resolve30(dest, ".."));
|
|
18309
|
+
await cp2(src, dest, { force: true });
|
|
17899
18310
|
}
|
|
17900
18311
|
}
|
|
17901
18312
|
function resolveCategoriesStrict(csv) {
|
|
@@ -17932,9 +18343,9 @@ async function backupToGithub(overrides) {
|
|
|
17932
18343
|
const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
|
|
17933
18344
|
const destPath = join3(tmpDir, repoPath);
|
|
17934
18345
|
if (isFile) {
|
|
17935
|
-
await
|
|
18346
|
+
await rm4(destPath, { force: true });
|
|
17936
18347
|
} else {
|
|
17937
|
-
await
|
|
18348
|
+
await rm4(destPath, { recursive: true, force: true });
|
|
17938
18349
|
}
|
|
17939
18350
|
if (!await fileExists(sourcePath)) {
|
|
17940
18351
|
console.warn(`Category "${category}": no local data at ${sourcePath}; backup will reflect deletion.`);
|
|
@@ -17942,8 +18353,8 @@ async function backupToGithub(overrides) {
|
|
|
17942
18353
|
}
|
|
17943
18354
|
if (category === "config") {
|
|
17944
18355
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
17945
|
-
await ensureDir(
|
|
17946
|
-
await
|
|
18356
|
+
await ensureDir(resolve30(destPath, ".."));
|
|
18357
|
+
await writeFile6(destPath, sanitized, "utf-8");
|
|
17947
18358
|
} else {
|
|
17948
18359
|
await copyRecursive(sourcePath, destPath);
|
|
17949
18360
|
}
|
|
@@ -17988,7 +18399,7 @@ async function backupToGithub(overrides) {
|
|
|
17988
18399
|
};
|
|
17989
18400
|
} finally {
|
|
17990
18401
|
if (tmpDir) {
|
|
17991
|
-
await
|
|
18402
|
+
await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
17992
18403
|
});
|
|
17993
18404
|
}
|
|
17994
18405
|
await releaseLock(lockPath);
|
|
@@ -17996,18 +18407,18 @@ async function backupToGithub(overrides) {
|
|
|
17996
18407
|
}
|
|
17997
18408
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
17998
18409
|
if (isFile) {
|
|
17999
|
-
await ensureDir(
|
|
18000
|
-
await
|
|
18410
|
+
await ensureDir(resolve30(localPath, ".."));
|
|
18411
|
+
await cp2(repoSrcPath, localPath, { force: true });
|
|
18001
18412
|
return;
|
|
18002
18413
|
}
|
|
18003
18414
|
const stagingPath = `${localPath}.syntaur-restore-staging`;
|
|
18004
18415
|
const backupPath = `${localPath}.syntaur-restore-backup`;
|
|
18005
|
-
await
|
|
18416
|
+
await rm4(stagingPath, { recursive: true, force: true });
|
|
18006
18417
|
const backupExistsBefore = await fileExists(backupPath);
|
|
18007
18418
|
const localExistsBefore = await fileExists(localPath);
|
|
18008
18419
|
if (backupExistsBefore) {
|
|
18009
18420
|
if (!localExistsBefore) {
|
|
18010
|
-
await
|
|
18421
|
+
await rename8(backupPath, localPath);
|
|
18011
18422
|
} else {
|
|
18012
18423
|
throw new Error(
|
|
18013
18424
|
`Cannot restore "${localPath}": a stale crash-recovery backup exists at ${backupPath} while the current path also exists. Inspect both and remove the one you don't need, then retry.`
|
|
@@ -18016,21 +18427,21 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
|
18016
18427
|
}
|
|
18017
18428
|
let localMovedAside = false;
|
|
18018
18429
|
try {
|
|
18019
|
-
await
|
|
18430
|
+
await cp2(repoSrcPath, stagingPath, { recursive: true, force: true });
|
|
18020
18431
|
const localExists = await fileExists(localPath);
|
|
18021
18432
|
if (localExists) {
|
|
18022
|
-
await
|
|
18433
|
+
await rename8(localPath, backupPath);
|
|
18023
18434
|
localMovedAside = true;
|
|
18024
18435
|
}
|
|
18025
|
-
await
|
|
18026
|
-
await
|
|
18436
|
+
await rename8(stagingPath, localPath);
|
|
18437
|
+
await rm4(backupPath, { recursive: true, force: true }).catch(() => {
|
|
18027
18438
|
});
|
|
18028
18439
|
} catch (err2) {
|
|
18029
18440
|
if (localMovedAside && await fileExists(backupPath)) {
|
|
18030
|
-
await
|
|
18441
|
+
await rename8(backupPath, localPath).catch(() => {
|
|
18031
18442
|
});
|
|
18032
18443
|
}
|
|
18033
|
-
await
|
|
18444
|
+
await rm4(stagingPath, { recursive: true, force: true }).catch(() => {
|
|
18034
18445
|
});
|
|
18035
18446
|
throw err2;
|
|
18036
18447
|
}
|
|
@@ -18089,7 +18500,7 @@ async function restoreFromGithub(overrides) {
|
|
|
18089
18500
|
};
|
|
18090
18501
|
} finally {
|
|
18091
18502
|
if (tmpDir) {
|
|
18092
|
-
await
|
|
18503
|
+
await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
18093
18504
|
});
|
|
18094
18505
|
}
|
|
18095
18506
|
await releaseLock(lockPath);
|
|
@@ -18097,7 +18508,7 @@ async function restoreFromGithub(overrides) {
|
|
|
18097
18508
|
}
|
|
18098
18509
|
async function getBackupStatus() {
|
|
18099
18510
|
const config = await readConfig();
|
|
18100
|
-
const lockPath =
|
|
18511
|
+
const lockPath = resolve30(syntaurRoot(), LOCK_FILE_NAME);
|
|
18101
18512
|
const locked = await fileExists(lockPath);
|
|
18102
18513
|
return {
|
|
18103
18514
|
repo: config.backup?.repo ?? null,
|
|
@@ -18110,7 +18521,7 @@ async function getBackupStatus() {
|
|
|
18110
18521
|
|
|
18111
18522
|
// src/dashboard/api-backup.ts
|
|
18112
18523
|
function createBackupRouter() {
|
|
18113
|
-
const router =
|
|
18524
|
+
const router = Router18();
|
|
18114
18525
|
router.get("/", async (_req, res) => {
|
|
18115
18526
|
try {
|
|
18116
18527
|
const status = await getBackupStatus();
|
|
@@ -18445,7 +18856,7 @@ function createDashboardServer(options) {
|
|
|
18445
18856
|
(async () => {
|
|
18446
18857
|
try {
|
|
18447
18858
|
const configResult = await migrateLegacyConfig(
|
|
18448
|
-
|
|
18859
|
+
resolve31(syntaurRoot(), "config.md")
|
|
18449
18860
|
);
|
|
18450
18861
|
const projectResult = await migrateLegacyProjectFiles(projectsDir2);
|
|
18451
18862
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -18546,8 +18957,8 @@ function createDashboardServer(options) {
|
|
|
18546
18957
|
});
|
|
18547
18958
|
app.put("/api/config/hotkeys", async (req, res) => {
|
|
18548
18959
|
try {
|
|
18549
|
-
const
|
|
18550
|
-
const incoming =
|
|
18960
|
+
const raw2 = req.body && typeof req.body === "object" ? req.body : {};
|
|
18961
|
+
const incoming = raw2.bindings;
|
|
18551
18962
|
if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) {
|
|
18552
18963
|
res.status(400).json({ error: "bindings must be an object keyed by action kind" });
|
|
18553
18964
|
return;
|
|
@@ -18963,14 +19374,14 @@ function createDashboardServer(options) {
|
|
|
18963
19374
|
app.use("/api/backup", createBackupRouter());
|
|
18964
19375
|
if (serveStaticUi && dashboardDistPath) {
|
|
18965
19376
|
const sendOpts = { dotfiles: "allow" };
|
|
18966
|
-
app.use("/assets", express.static(
|
|
19377
|
+
app.use("/assets", express.static(resolve31(dashboardDistPath, "assets"), sendOpts));
|
|
18967
19378
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
18968
19379
|
app.get("{*path}", async (req, res) => {
|
|
18969
19380
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
18970
19381
|
res.status(404).json({ error: "Not Found" });
|
|
18971
19382
|
return;
|
|
18972
19383
|
}
|
|
18973
|
-
const indexPath =
|
|
19384
|
+
const indexPath = resolve31(dashboardDistPath, "index.html");
|
|
18974
19385
|
if (!await fileExists(indexPath)) {
|
|
18975
19386
|
res.status(503).send(
|
|
18976
19387
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -18994,7 +19405,7 @@ function createDashboardServer(options) {
|
|
|
18994
19405
|
serversDir: serversDir2,
|
|
18995
19406
|
playbooksDir: playbooksDir3,
|
|
18996
19407
|
todosDir: todosDir2,
|
|
18997
|
-
dbPath:
|
|
19408
|
+
dbPath: resolve31(syntaurRoot(), "syntaur.db"),
|
|
18998
19409
|
onMessage: broadcast
|
|
18999
19410
|
});
|
|
19000
19411
|
startAutodiscovery({ serversDir: serversDir2, projectsDir: projectsDir2, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -19009,8 +19420,8 @@ function createDashboardServer(options) {
|
|
|
19009
19420
|
}
|
|
19010
19421
|
});
|
|
19011
19422
|
server.listen(port, () => {
|
|
19012
|
-
const portFile =
|
|
19013
|
-
|
|
19423
|
+
const portFile = resolve31(syntaurRoot(), "dashboard-port");
|
|
19424
|
+
writeFile7(portFile, String(port), "utf-8").catch(() => {
|
|
19014
19425
|
});
|
|
19015
19426
|
resolvePromise();
|
|
19016
19427
|
});
|
|
@@ -19028,8 +19439,8 @@ function createDashboardServer(options) {
|
|
|
19028
19439
|
client.terminate();
|
|
19029
19440
|
}
|
|
19030
19441
|
clients.clear();
|
|
19031
|
-
const portFile =
|
|
19032
|
-
await
|
|
19442
|
+
const portFile = resolve31(syntaurRoot(), "dashboard-port");
|
|
19443
|
+
await unlink7(portFile).catch(() => {
|
|
19033
19444
|
});
|
|
19034
19445
|
server.closeAllConnections?.();
|
|
19035
19446
|
return new Promise((resolvePromise) => {
|
|
@@ -19108,8 +19519,8 @@ async function dashboardCommand(options) {
|
|
|
19108
19519
|
port = availablePort;
|
|
19109
19520
|
}
|
|
19110
19521
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
19111
|
-
const packageRoot =
|
|
19112
|
-
const dashboardDist =
|
|
19522
|
+
const packageRoot = resolve32(dirname9(thisFile), "..");
|
|
19523
|
+
const dashboardDist = resolve32(packageRoot, "dashboard", "dist");
|
|
19113
19524
|
const server = createDashboardServer({
|
|
19114
19525
|
port,
|
|
19115
19526
|
projectsDir: projectsDir2,
|
|
@@ -19123,8 +19534,8 @@ async function dashboardCommand(options) {
|
|
|
19123
19534
|
await server.start();
|
|
19124
19535
|
let viteProcess = null;
|
|
19125
19536
|
if (mode === "dev") {
|
|
19126
|
-
const dashboardDir =
|
|
19127
|
-
const viteBin =
|
|
19537
|
+
const dashboardDir = resolve32(packageRoot, "dashboard");
|
|
19538
|
+
const viteBin = resolve32(dashboardDir, "node_modules", ".bin", "vite");
|
|
19128
19539
|
if (!await fileExists(viteBin)) {
|
|
19129
19540
|
console.error(
|
|
19130
19541
|
'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
|
|
@@ -19199,7 +19610,7 @@ init_config2();
|
|
|
19199
19610
|
init_slug();
|
|
19200
19611
|
init_lifecycle();
|
|
19201
19612
|
init_assignment_resolver();
|
|
19202
|
-
import { resolve as
|
|
19613
|
+
import { resolve as resolve33 } from "path";
|
|
19203
19614
|
function resolveLinkedTodosLookup(projectsDir2) {
|
|
19204
19615
|
return { todosDir: todosDir(), projectsDir: projectsDir2 };
|
|
19205
19616
|
}
|
|
@@ -19213,8 +19624,8 @@ async function runTransition(assignment, command, options = {}) {
|
|
|
19213
19624
|
if (!isValidSlug(assignment)) {
|
|
19214
19625
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
19215
19626
|
}
|
|
19216
|
-
const projectDir =
|
|
19217
|
-
const projectMdPath =
|
|
19627
|
+
const projectDir = resolve33(baseDir, options.project);
|
|
19628
|
+
const projectMdPath = resolve33(projectDir, "project.md");
|
|
19218
19629
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
19219
19630
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
19220
19631
|
}
|
|
@@ -19247,8 +19658,8 @@ async function runAssign(assignment, agent, options = {}) {
|
|
|
19247
19658
|
if (!isValidSlug(assignment)) {
|
|
19248
19659
|
throw new Error(`Invalid assignment slug "${assignment}".`);
|
|
19249
19660
|
}
|
|
19250
|
-
const projectDir =
|
|
19251
|
-
const projectMdPath =
|
|
19661
|
+
const projectDir = resolve33(baseDir, options.project);
|
|
19662
|
+
const projectMdPath = resolve33(projectDir, "project.md");
|
|
19252
19663
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
19253
19664
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
19254
19665
|
}
|
|
@@ -19295,8 +19706,8 @@ init_slug();
|
|
|
19295
19706
|
init_frontmatter();
|
|
19296
19707
|
init_timestamp();
|
|
19297
19708
|
init_assignment_resolver();
|
|
19298
|
-
import { resolve as
|
|
19299
|
-
import { readFile as readFile22, writeFile as
|
|
19709
|
+
import { resolve as resolve34 } from "path";
|
|
19710
|
+
import { readFile as readFile22, writeFile as writeFile8 } from "fs/promises";
|
|
19300
19711
|
async function resolveTarget(target, options) {
|
|
19301
19712
|
const config = await readConfig();
|
|
19302
19713
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
@@ -19307,7 +19718,7 @@ async function resolveTarget(target, options) {
|
|
|
19307
19718
|
if (!isValidSlug(target)) {
|
|
19308
19719
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
19309
19720
|
}
|
|
19310
|
-
const assignmentMd =
|
|
19721
|
+
const assignmentMd = resolve34(baseDir, options.project, "assignments", target, "assignment.md");
|
|
19311
19722
|
if (!await fileExists(assignmentMd)) {
|
|
19312
19723
|
throw new Error(`Assignment "${target}" not found in project "${options.project}".`);
|
|
19313
19724
|
}
|
|
@@ -19317,11 +19728,11 @@ async function resolveTarget(target, options) {
|
|
|
19317
19728
|
if (resolved) {
|
|
19318
19729
|
return {
|
|
19319
19730
|
kind: "assignment",
|
|
19320
|
-
filePath:
|
|
19731
|
+
filePath: resolve34(resolved.assignmentDir, "assignment.md"),
|
|
19321
19732
|
label: resolved.projectSlug ? `assignment "${resolved.projectSlug}/${resolved.assignmentSlug}"` : `assignment "${target}"`
|
|
19322
19733
|
};
|
|
19323
19734
|
}
|
|
19324
|
-
const projectMd =
|
|
19735
|
+
const projectMd = resolve34(baseDir, target, "project.md");
|
|
19325
19736
|
if (await fileExists(projectMd)) {
|
|
19326
19737
|
return { kind: "project", filePath: projectMd, label: `project "${target}"` };
|
|
19327
19738
|
}
|
|
@@ -19335,7 +19746,7 @@ async function writeArchiveState(filePath, archived, reason) {
|
|
|
19335
19746
|
archivedReason: archived ? reason : null,
|
|
19336
19747
|
updated: nowTimestamp()
|
|
19337
19748
|
});
|
|
19338
|
-
await
|
|
19749
|
+
await writeFile8(filePath, updated, "utf-8");
|
|
19339
19750
|
}
|
|
19340
19751
|
async function runArchive(target, options = {}) {
|
|
19341
19752
|
const resolved = await resolveTarget(target, options);
|
|
@@ -19396,8 +19807,8 @@ init_fs();
|
|
|
19396
19807
|
init_config2();
|
|
19397
19808
|
init_frontmatter();
|
|
19398
19809
|
init_timestamp();
|
|
19399
|
-
import { resolve as
|
|
19400
|
-
import { readdir as
|
|
19810
|
+
import { resolve as resolve35 } from "path";
|
|
19811
|
+
import { readdir as readdir14, readFile as readFile23 } from "fs/promises";
|
|
19401
19812
|
var PROMOTABLE_STATUSES = /* @__PURE__ */ new Set(["pending"]);
|
|
19402
19813
|
function objectiveIsFleshedOut(content) {
|
|
19403
19814
|
const match = content.match(/##\s+Objective\s*\n([\s\S]*?)(?=\n##\s+|$)/);
|
|
@@ -19415,11 +19826,11 @@ async function collectCandidates(baseDirs) {
|
|
|
19415
19826
|
const candidates = [];
|
|
19416
19827
|
for (const baseDir of baseDirs) {
|
|
19417
19828
|
if (!await fileExists(baseDir)) continue;
|
|
19418
|
-
const projects = await
|
|
19829
|
+
const projects = await readdir14(baseDir, { withFileTypes: true });
|
|
19419
19830
|
for (const m of projects) {
|
|
19420
19831
|
if (!m.isDirectory()) continue;
|
|
19421
19832
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
19422
|
-
const directAssignmentMd =
|
|
19833
|
+
const directAssignmentMd = resolve35(baseDir, m.name, "assignment.md");
|
|
19423
19834
|
if (await fileExists(directAssignmentMd)) {
|
|
19424
19835
|
const fm = await parseSafe(directAssignmentMd);
|
|
19425
19836
|
if (fm && PROMOTABLE_STATUSES.has(fm.status)) {
|
|
@@ -19436,13 +19847,13 @@ async function collectCandidates(baseDirs) {
|
|
|
19436
19847
|
}
|
|
19437
19848
|
continue;
|
|
19438
19849
|
}
|
|
19439
|
-
const assignmentsDir2 =
|
|
19850
|
+
const assignmentsDir2 = resolve35(baseDir, m.name, "assignments");
|
|
19440
19851
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
19441
|
-
const entries = await
|
|
19852
|
+
const entries = await readdir14(assignmentsDir2, { withFileTypes: true });
|
|
19442
19853
|
for (const a of entries) {
|
|
19443
19854
|
if (!a.isDirectory()) continue;
|
|
19444
19855
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
19445
|
-
const assignmentMd =
|
|
19856
|
+
const assignmentMd = resolve35(assignmentsDir2, a.name, "assignment.md");
|
|
19446
19857
|
if (!await fileExists(assignmentMd)) continue;
|
|
19447
19858
|
const fm = await parseSafe(assignmentMd);
|
|
19448
19859
|
if (!fm || !PROMOTABLE_STATUSES.has(fm.status)) continue;
|
|
@@ -19546,33 +19957,33 @@ init_config2();
|
|
|
19546
19957
|
init_config2();
|
|
19547
19958
|
init_fs();
|
|
19548
19959
|
import {
|
|
19549
|
-
cp as
|
|
19550
|
-
readdir as
|
|
19960
|
+
cp as cp3,
|
|
19961
|
+
readdir as readdir15,
|
|
19551
19962
|
symlink,
|
|
19552
19963
|
lstat,
|
|
19553
19964
|
readFile as readFile24,
|
|
19554
19965
|
readlink,
|
|
19555
|
-
rename as
|
|
19556
|
-
rm as
|
|
19557
|
-
unlink as
|
|
19558
|
-
writeFile as
|
|
19966
|
+
rename as rename9,
|
|
19967
|
+
rm as rm5,
|
|
19968
|
+
unlink as unlink8,
|
|
19969
|
+
writeFile as writeFile9
|
|
19559
19970
|
} from "fs/promises";
|
|
19560
19971
|
import { existsSync as existsSync3 } from "fs";
|
|
19561
19972
|
import { homedir as homedir3 } from "os";
|
|
19562
|
-
import { basename as
|
|
19973
|
+
import { basename as basename5, dirname as dirname11, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve37 } from "path";
|
|
19563
19974
|
|
|
19564
19975
|
// src/utils/package-root.ts
|
|
19565
19976
|
init_fs();
|
|
19566
|
-
import { dirname as
|
|
19977
|
+
import { dirname as dirname10, resolve as resolve36 } from "path";
|
|
19567
19978
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
19568
19979
|
async function findPackageRoot(expectedRelativePath) {
|
|
19569
|
-
let currentDir =
|
|
19980
|
+
let currentDir = dirname10(fileURLToPath3(import.meta.url));
|
|
19570
19981
|
while (true) {
|
|
19571
|
-
const candidate =
|
|
19982
|
+
const candidate = resolve36(currentDir, expectedRelativePath);
|
|
19572
19983
|
if (await fileExists(candidate)) {
|
|
19573
19984
|
return currentDir;
|
|
19574
19985
|
}
|
|
19575
|
-
const parentDir =
|
|
19986
|
+
const parentDir = resolve36(currentDir, "..");
|
|
19576
19987
|
if (parentDir === currentDir) {
|
|
19577
19988
|
throw new Error(
|
|
19578
19989
|
`Could not locate package root containing ${expectedRelativePath}.`
|
|
@@ -19593,50 +20004,50 @@ function getPluginManifestRelativePath(pluginKind) {
|
|
|
19593
20004
|
}
|
|
19594
20005
|
function getDefaultPluginTargetDir(pluginKind) {
|
|
19595
20006
|
const home2 = homedir3();
|
|
19596
|
-
return pluginKind === "claude" ?
|
|
20007
|
+
return pluginKind === "claude" ? resolve37(home2, ".claude", "plugins", "syntaur") : resolve37(home2, "plugins", "syntaur");
|
|
19597
20008
|
}
|
|
19598
20009
|
function getDefaultMarketplacePath() {
|
|
19599
|
-
return
|
|
20010
|
+
return resolve37(homedir3(), ".agents", "plugins", "marketplace.json");
|
|
19600
20011
|
}
|
|
19601
20012
|
function getClaudeMarketplacesRoot() {
|
|
19602
|
-
return
|
|
20013
|
+
return resolve37(homedir3(), ".claude", "plugins", "marketplaces");
|
|
19603
20014
|
}
|
|
19604
20015
|
function getClaudeKnownMarketplacesPath() {
|
|
19605
|
-
return
|
|
20016
|
+
return resolve37(homedir3(), ".claude", "plugins", "known_marketplaces.json");
|
|
19606
20017
|
}
|
|
19607
20018
|
function getClaudeInstalledPluginsPath() {
|
|
19608
|
-
return
|
|
20019
|
+
return resolve37(homedir3(), ".claude", "plugins", "installed_plugins.json");
|
|
19609
20020
|
}
|
|
19610
20021
|
function getInstallMarkerPath(targetDir) {
|
|
19611
|
-
return
|
|
20022
|
+
return resolve37(targetDir, INSTALL_MARKER_FILENAME);
|
|
19612
20023
|
}
|
|
19613
20024
|
async function readPackageManifest(packageRoot) {
|
|
19614
|
-
const
|
|
19615
|
-
return JSON.parse(
|
|
20025
|
+
const raw2 = await readFile24(resolve37(packageRoot, "package.json"), "utf-8");
|
|
20026
|
+
return JSON.parse(raw2);
|
|
19616
20027
|
}
|
|
19617
20028
|
async function readJsonFileIfExists(pathValue) {
|
|
19618
20029
|
if (!await fileExists(pathValue)) {
|
|
19619
20030
|
return null;
|
|
19620
20031
|
}
|
|
19621
20032
|
try {
|
|
19622
|
-
const
|
|
19623
|
-
return JSON.parse(
|
|
20033
|
+
const raw2 = await readFile24(pathValue, "utf-8");
|
|
20034
|
+
return JSON.parse(raw2);
|
|
19624
20035
|
} catch {
|
|
19625
20036
|
return null;
|
|
19626
20037
|
}
|
|
19627
20038
|
}
|
|
19628
20039
|
async function readClaudePluginManifest(pluginDir) {
|
|
19629
20040
|
return await readJsonFileIfExists(
|
|
19630
|
-
|
|
20041
|
+
resolve37(pluginDir, ".claude-plugin", "plugin.json")
|
|
19631
20042
|
) ?? {};
|
|
19632
20043
|
}
|
|
19633
20044
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
19634
|
-
const manifestPath =
|
|
20045
|
+
const manifestPath = resolve37(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
19635
20046
|
if (!await fileExists(manifestPath)) {
|
|
19636
20047
|
return void 0;
|
|
19637
20048
|
}
|
|
19638
|
-
const
|
|
19639
|
-
const parsed = JSON.parse(
|
|
20049
|
+
const raw2 = await readFile24(manifestPath, "utf-8");
|
|
20050
|
+
const parsed = JSON.parse(raw2);
|
|
19640
20051
|
return parsed.name;
|
|
19641
20052
|
}
|
|
19642
20053
|
async function readInstallMetadata(targetDir) {
|
|
@@ -19645,8 +20056,8 @@ async function readInstallMetadata(targetDir) {
|
|
|
19645
20056
|
return null;
|
|
19646
20057
|
}
|
|
19647
20058
|
try {
|
|
19648
|
-
const
|
|
19649
|
-
return JSON.parse(
|
|
20059
|
+
const raw2 = await readFile24(markerPath, "utf-8");
|
|
20060
|
+
return JSON.parse(raw2);
|
|
19650
20061
|
} catch {
|
|
19651
20062
|
return null;
|
|
19652
20063
|
}
|
|
@@ -19658,7 +20069,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
19658
20069
|
const info = await lstat(targetDir);
|
|
19659
20070
|
if (info.isSymbolicLink()) {
|
|
19660
20071
|
const symlinkTarget = await readlink(targetDir);
|
|
19661
|
-
const resolvedTarget =
|
|
20072
|
+
const resolvedTarget = resolve37(dirname11(targetDir), symlinkTarget);
|
|
19662
20073
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
19663
20074
|
return {
|
|
19664
20075
|
exists: true,
|
|
@@ -19687,7 +20098,7 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
19687
20098
|
installMode,
|
|
19688
20099
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19689
20100
|
};
|
|
19690
|
-
await
|
|
20101
|
+
await writeFile9(
|
|
19691
20102
|
getInstallMarkerPath(targetDir),
|
|
19692
20103
|
`${JSON.stringify(metadata, null, 2)}
|
|
19693
20104
|
`,
|
|
@@ -19695,21 +20106,21 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
19695
20106
|
);
|
|
19696
20107
|
}
|
|
19697
20108
|
async function installCopy(paths, pluginKind) {
|
|
19698
|
-
await ensureDir(
|
|
19699
|
-
await
|
|
20109
|
+
await ensureDir(dirname11(paths.targetDir));
|
|
20110
|
+
await cp3(paths.sourceDir, paths.targetDir, { recursive: true });
|
|
19700
20111
|
const packageManifest = await readPackageManifest(paths.packageRoot);
|
|
19701
20112
|
await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
|
|
19702
20113
|
}
|
|
19703
20114
|
async function installLink(paths) {
|
|
19704
|
-
await ensureDir(
|
|
19705
|
-
await
|
|
19706
|
-
await ensureDir(
|
|
19707
|
-
await symlink(
|
|
20115
|
+
await ensureDir(dirname11(paths.targetDir));
|
|
20116
|
+
await rm5(paths.targetDir, { recursive: true, force: true });
|
|
20117
|
+
await ensureDir(dirname11(paths.targetDir));
|
|
20118
|
+
await symlink(resolve37(paths.sourceDir), paths.targetDir, "dir");
|
|
19708
20119
|
}
|
|
19709
20120
|
async function removeInstallMarker(targetDir) {
|
|
19710
20121
|
const markerPath = getInstallMarkerPath(targetDir);
|
|
19711
20122
|
if (await fileExists(markerPath)) {
|
|
19712
|
-
await
|
|
20123
|
+
await unlink8(markerPath).catch(() => {
|
|
19713
20124
|
});
|
|
19714
20125
|
}
|
|
19715
20126
|
}
|
|
@@ -19718,13 +20129,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
|
19718
20129
|
if (!isAbsolute4(expanded)) {
|
|
19719
20130
|
throw new Error(`${label} must be an absolute path.`);
|
|
19720
20131
|
}
|
|
19721
|
-
return
|
|
20132
|
+
return resolve37(expanded);
|
|
19722
20133
|
}
|
|
19723
20134
|
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
19724
20135
|
const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
19725
20136
|
return {
|
|
19726
20137
|
packageRoot,
|
|
19727
|
-
sourceDir:
|
|
20138
|
+
sourceDir: resolve37(packageRoot, getPluginRelativePath(pluginKind)),
|
|
19728
20139
|
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
19729
20140
|
};
|
|
19730
20141
|
}
|
|
@@ -19758,7 +20169,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
|
|
|
19758
20169
|
};
|
|
19759
20170
|
}
|
|
19760
20171
|
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
19761
|
-
await ensureDir(
|
|
20172
|
+
await ensureDir(dirname11(manifestPath));
|
|
19762
20173
|
if (Array.isArray(marketplace.plugins)) {
|
|
19763
20174
|
marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
|
|
19764
20175
|
const an = a?.name ?? "";
|
|
@@ -19771,7 +20182,7 @@ async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
|
19771
20182
|
const prev = await readFile24(manifestPath, "utf-8");
|
|
19772
20183
|
JSON.parse(prev);
|
|
19773
20184
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19774
|
-
await
|
|
20185
|
+
await writeFile9(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
|
|
19775
20186
|
} catch {
|
|
19776
20187
|
throw new Error(
|
|
19777
20188
|
`Refusing to overwrite ${manifestPath}: existing file is not valid JSON. Inspect and remove or repair it manually before re-running.`
|
|
@@ -19779,9 +20190,9 @@ async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
|
19779
20190
|
}
|
|
19780
20191
|
}
|
|
19781
20192
|
const tmpPath = `${manifestPath}.tmp`;
|
|
19782
|
-
await
|
|
20193
|
+
await writeFile9(tmpPath, `${JSON.stringify(marketplace, null, 2)}
|
|
19783
20194
|
`, "utf-8");
|
|
19784
|
-
await
|
|
20195
|
+
await rename9(tmpPath, manifestPath);
|
|
19785
20196
|
}
|
|
19786
20197
|
function buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir) {
|
|
19787
20198
|
const relPath = relative2(marketplaceRootDir, pluginTargetDir).replaceAll("\\", "/");
|
|
@@ -19841,7 +20252,7 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
19841
20252
|
const [knownMarketplaces, activeMarketplaceNames, entries] = await Promise.all([
|
|
19842
20253
|
readKnownClaudeMarketplaceRecords(),
|
|
19843
20254
|
readInstalledClaudeMarketplaceNames(),
|
|
19844
|
-
|
|
20255
|
+
readdir15(rootDir, { withFileTypes: true })
|
|
19845
20256
|
]);
|
|
19846
20257
|
const candidates = [];
|
|
19847
20258
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -19849,8 +20260,8 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
19849
20260
|
if (!entry.isDirectory()) {
|
|
19850
20261
|
continue;
|
|
19851
20262
|
}
|
|
19852
|
-
const candidateRoot =
|
|
19853
|
-
const manifestPath =
|
|
20263
|
+
const candidateRoot = resolve37(rootDir, entry.name);
|
|
20264
|
+
const manifestPath = resolve37(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
19854
20265
|
if (!await fileExists(manifestPath)) {
|
|
19855
20266
|
continue;
|
|
19856
20267
|
}
|
|
@@ -19877,11 +20288,11 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
19877
20288
|
if (!installLocation) {
|
|
19878
20289
|
continue;
|
|
19879
20290
|
}
|
|
19880
|
-
const candidateRoot =
|
|
20291
|
+
const candidateRoot = resolve37(expandHome(installLocation));
|
|
19881
20292
|
if (seen.has(candidateRoot)) {
|
|
19882
20293
|
continue;
|
|
19883
20294
|
}
|
|
19884
|
-
const manifestPath =
|
|
20295
|
+
const manifestPath = resolve37(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
19885
20296
|
if (!await fileExists(manifestPath)) {
|
|
19886
20297
|
continue;
|
|
19887
20298
|
}
|
|
@@ -19909,16 +20320,16 @@ async function getPreferredClaudeMarketplace() {
|
|
|
19909
20320
|
name: candidate.name,
|
|
19910
20321
|
rootDir: candidate.rootDir,
|
|
19911
20322
|
manifestPath: candidate.manifestPath,
|
|
19912
|
-
targetDir:
|
|
20323
|
+
targetDir: resolve37(candidate.rootDir, "plugins", "syntaur")
|
|
19913
20324
|
};
|
|
19914
20325
|
}
|
|
19915
20326
|
async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
19916
20327
|
const manifestPath = getClaudeKnownMarketplacesPath();
|
|
19917
20328
|
let existing = {};
|
|
19918
20329
|
if (await fileExists(manifestPath)) {
|
|
19919
|
-
const
|
|
20330
|
+
const raw2 = await readFile24(manifestPath, "utf-8");
|
|
19920
20331
|
try {
|
|
19921
|
-
const parsed = JSON.parse(
|
|
20332
|
+
const parsed = JSON.parse(raw2);
|
|
19922
20333
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
19923
20334
|
existing = parsed;
|
|
19924
20335
|
} else {
|
|
@@ -19941,29 +20352,29 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
|
19941
20352
|
};
|
|
19942
20353
|
existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
19943
20354
|
existing[name].autoUpdate = true;
|
|
19944
|
-
await ensureDir(
|
|
20355
|
+
await ensureDir(dirname11(manifestPath));
|
|
19945
20356
|
if (await fileExists(manifestPath)) {
|
|
19946
20357
|
const prev = await readFile24(manifestPath, "utf-8");
|
|
19947
20358
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19948
|
-
await
|
|
20359
|
+
await writeFile9(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
|
|
19949
20360
|
}
|
|
19950
20361
|
const tmpPath = `${manifestPath}.tmp`;
|
|
19951
|
-
await
|
|
20362
|
+
await writeFile9(tmpPath, `${JSON.stringify(existing, null, 2)}
|
|
19952
20363
|
`, "utf-8");
|
|
19953
|
-
await
|
|
20364
|
+
await rename9(tmpPath, manifestPath);
|
|
19954
20365
|
return { added: !had, updated: had };
|
|
19955
20366
|
}
|
|
19956
20367
|
async function ensureKnownClaudeMarketplaceForRoot(options) {
|
|
19957
20368
|
return registerKnownClaudeMarketplace(options.name, options.rootDir);
|
|
19958
20369
|
}
|
|
19959
20370
|
async function setSyntaurPluginEnabled(options) {
|
|
19960
|
-
const settingsPath =
|
|
20371
|
+
const settingsPath = resolve37(homedir3(), ".claude", "settings.json");
|
|
19961
20372
|
const key = `syntaur@${options.marketplaceName}`;
|
|
19962
20373
|
let parsed = {};
|
|
19963
20374
|
if (await fileExists(settingsPath)) {
|
|
19964
|
-
const
|
|
20375
|
+
const raw2 = await readFile24(settingsPath, "utf-8");
|
|
19965
20376
|
try {
|
|
19966
|
-
parsed = JSON.parse(
|
|
20377
|
+
parsed = JSON.parse(raw2);
|
|
19967
20378
|
} catch {
|
|
19968
20379
|
throw new Error(
|
|
19969
20380
|
`Cannot toggle plugin: ${settingsPath} is not valid JSON. Inspect and repair it before retrying.`
|
|
@@ -19977,16 +20388,16 @@ async function setSyntaurPluginEnabled(options) {
|
|
|
19977
20388
|
}
|
|
19978
20389
|
enabledPlugins[key] = options.enabled;
|
|
19979
20390
|
parsed.enabledPlugins = enabledPlugins;
|
|
19980
|
-
await ensureDir(
|
|
20391
|
+
await ensureDir(dirname11(settingsPath));
|
|
19981
20392
|
if (await fileExists(settingsPath)) {
|
|
19982
20393
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19983
20394
|
const prev = await readFile24(settingsPath, "utf-8");
|
|
19984
|
-
await
|
|
20395
|
+
await writeFile9(`${settingsPath}.bak-${stamp}`, prev, "utf-8");
|
|
19985
20396
|
}
|
|
19986
20397
|
const tmpPath = `${settingsPath}.tmp`;
|
|
19987
|
-
await
|
|
20398
|
+
await writeFile9(tmpPath, `${JSON.stringify(parsed, null, 2)}
|
|
19988
20399
|
`, "utf-8");
|
|
19989
|
-
await
|
|
20400
|
+
await rename9(tmpPath, settingsPath);
|
|
19990
20401
|
return { key, previous, current: options.enabled, changed: true };
|
|
19991
20402
|
}
|
|
19992
20403
|
async function ensureClaudeUserMarketplace() {
|
|
@@ -19994,9 +20405,9 @@ async function ensureClaudeUserMarketplace() {
|
|
|
19994
20405
|
if (existing) {
|
|
19995
20406
|
return existing;
|
|
19996
20407
|
}
|
|
19997
|
-
const rootDir =
|
|
19998
|
-
const manifestPath =
|
|
19999
|
-
await ensureDir(
|
|
20408
|
+
const rootDir = resolve37(getClaudeMarketplacesRoot(), "user-plugins");
|
|
20409
|
+
const manifestPath = resolve37(rootDir, ".claude-plugin", "marketplace.json");
|
|
20410
|
+
await ensureDir(resolve37(rootDir, "plugins"));
|
|
20000
20411
|
if (!await fileExists(manifestPath)) {
|
|
20001
20412
|
const scaffold = {
|
|
20002
20413
|
plugins: []
|
|
@@ -20015,22 +20426,22 @@ async function ensureClaudeUserMarketplace() {
|
|
|
20015
20426
|
name: "user-plugins",
|
|
20016
20427
|
rootDir,
|
|
20017
20428
|
manifestPath,
|
|
20018
|
-
targetDir:
|
|
20429
|
+
targetDir: resolve37(rootDir, "plugins", "syntaur")
|
|
20019
20430
|
};
|
|
20020
20431
|
}
|
|
20021
20432
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
20022
20433
|
const normalizedTargetDir = normalizeAbsoluteInstallPath(targetDir, "Claude plugin target");
|
|
20023
|
-
const pluginsDir =
|
|
20024
|
-
if (
|
|
20434
|
+
const pluginsDir = dirname11(normalizedTargetDir);
|
|
20435
|
+
if (basename5(pluginsDir) !== "plugins") {
|
|
20025
20436
|
return null;
|
|
20026
20437
|
}
|
|
20027
|
-
const rootDir =
|
|
20028
|
-
const manifestPath =
|
|
20438
|
+
const rootDir = dirname11(pluginsDir);
|
|
20439
|
+
const manifestPath = resolve37(rootDir, ".claude-plugin", "marketplace.json");
|
|
20029
20440
|
if (!await fileExists(manifestPath)) {
|
|
20030
20441
|
return null;
|
|
20031
20442
|
}
|
|
20032
20443
|
const marketplace = await readClaudeMarketplaceFile(manifestPath);
|
|
20033
|
-
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name :
|
|
20444
|
+
const name = typeof marketplace.name === "string" && marketplace.name.trim() !== "" ? marketplace.name : basename5(rootDir);
|
|
20034
20445
|
return {
|
|
20035
20446
|
name,
|
|
20036
20447
|
rootDir,
|
|
@@ -20041,7 +20452,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
20041
20452
|
async function findManagedClaudeMarketplacePluginDir() {
|
|
20042
20453
|
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
20043
20454
|
for (const marketplace of marketplaces) {
|
|
20044
|
-
const targetDir =
|
|
20455
|
+
const targetDir = resolve37(marketplace.rootDir, "plugins", "syntaur");
|
|
20045
20456
|
const status = await getInstallStatus(targetDir, "claude");
|
|
20046
20457
|
if (status.exists && status.managed) {
|
|
20047
20458
|
return targetDir;
|
|
@@ -20166,7 +20577,7 @@ async function installManagedPlugin(options) {
|
|
|
20166
20577
|
`${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
|
|
20167
20578
|
);
|
|
20168
20579
|
}
|
|
20169
|
-
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget ===
|
|
20580
|
+
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve37(paths.sourceDir) && !force) {
|
|
20170
20581
|
return {
|
|
20171
20582
|
targetDir: paths.targetDir,
|
|
20172
20583
|
sourceDir: paths.sourceDir,
|
|
@@ -20175,7 +20586,7 @@ async function installManagedPlugin(options) {
|
|
|
20175
20586
|
};
|
|
20176
20587
|
}
|
|
20177
20588
|
if (existing.exists) {
|
|
20178
|
-
await
|
|
20589
|
+
await rm5(paths.targetDir, { recursive: true, force: true });
|
|
20179
20590
|
}
|
|
20180
20591
|
if (desiredMode === "link") {
|
|
20181
20592
|
await installLink(paths);
|
|
@@ -20190,7 +20601,7 @@ async function installManagedPlugin(options) {
|
|
|
20190
20601
|
};
|
|
20191
20602
|
}
|
|
20192
20603
|
function buildMarketplaceSourcePath(pluginTargetDir, marketplacePath) {
|
|
20193
|
-
const relPath = relative2(
|
|
20604
|
+
const relPath = relative2(dirname11(marketplacePath), pluginTargetDir).replaceAll("\\", "/");
|
|
20194
20605
|
if (relPath === "") {
|
|
20195
20606
|
return ".";
|
|
20196
20607
|
}
|
|
@@ -20218,8 +20629,8 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
20218
20629
|
plugins: []
|
|
20219
20630
|
};
|
|
20220
20631
|
}
|
|
20221
|
-
const
|
|
20222
|
-
const parsed = JSON.parse(
|
|
20632
|
+
const raw2 = await readFile24(marketplacePath, "utf-8");
|
|
20633
|
+
const parsed = JSON.parse(raw2);
|
|
20223
20634
|
return {
|
|
20224
20635
|
name: parsed.name ?? "local",
|
|
20225
20636
|
interface: parsed.interface ?? { displayName: "Local Plugins" },
|
|
@@ -20227,8 +20638,8 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
20227
20638
|
};
|
|
20228
20639
|
}
|
|
20229
20640
|
async function writeMarketplaceFile(marketplacePath, marketplace) {
|
|
20230
|
-
await ensureDir(
|
|
20231
|
-
await
|
|
20641
|
+
await ensureDir(dirname11(marketplacePath));
|
|
20642
|
+
await writeFile9(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
|
|
20232
20643
|
`, "utf-8");
|
|
20233
20644
|
}
|
|
20234
20645
|
async function hasAnySyntaurMarketplaceEntry(marketplacePath) {
|
|
@@ -20314,7 +20725,7 @@ async function removeMarketplaceEntry(options) {
|
|
|
20314
20725
|
return { marketplacePath, removed: false };
|
|
20315
20726
|
}
|
|
20316
20727
|
if (marketplace.plugins.length === 0 && isDefaultMarketplaceShell(marketplace)) {
|
|
20317
|
-
await
|
|
20728
|
+
await rm5(marketplacePath, { force: true });
|
|
20318
20729
|
return { marketplacePath, removed: true };
|
|
20319
20730
|
}
|
|
20320
20731
|
await writeMarketplaceFile(marketplacePath, marketplace);
|
|
@@ -20335,7 +20746,7 @@ async function uninstallManagedPlugin(pluginKind, targetDir = getDefaultPluginTa
|
|
|
20335
20746
|
);
|
|
20336
20747
|
}
|
|
20337
20748
|
await removeInstallMarker(normalizedTarget);
|
|
20338
|
-
await
|
|
20749
|
+
await rm5(normalizedTarget, { recursive: true, force: true });
|
|
20339
20750
|
return { removed: true, targetDir: normalizedTarget };
|
|
20340
20751
|
}
|
|
20341
20752
|
async function getConfiguredOrLegacyManagedPluginDir(pluginKind) {
|
|
@@ -20379,13 +20790,13 @@ async function recommendMarketplacePath() {
|
|
|
20379
20790
|
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
20380
20791
|
}
|
|
20381
20792
|
async function isSyntaurDataInstalled() {
|
|
20382
|
-
return fileExists(
|
|
20793
|
+
return fileExists(resolve37(syntaurRoot(), "config.md"));
|
|
20383
20794
|
}
|
|
20384
20795
|
async function removeSyntaurData() {
|
|
20385
|
-
await
|
|
20796
|
+
await rm5(syntaurRoot(), { recursive: true, force: true });
|
|
20386
20797
|
}
|
|
20387
20798
|
async function getConfiguredProjectDir() {
|
|
20388
|
-
if (!await fileExists(
|
|
20799
|
+
if (!await fileExists(resolve37(syntaurRoot(), "config.md"))) {
|
|
20389
20800
|
return null;
|
|
20390
20801
|
}
|
|
20391
20802
|
return (await readConfig()).defaultProjectDir;
|
|
@@ -20454,25 +20865,25 @@ async function textPrompt(question, defaultValue) {
|
|
|
20454
20865
|
|
|
20455
20866
|
// src/utils/install-skills.ts
|
|
20456
20867
|
init_fs();
|
|
20457
|
-
import { readFile as readFile26, readdir as
|
|
20458
|
-
import { resolve as
|
|
20868
|
+
import { readFile as readFile26, readdir as readdir16, mkdir as mkdir5, copyFile, rm as rm6, lstat as lstat2 } from "fs/promises";
|
|
20869
|
+
import { resolve as resolve39, relative as relative3, join as join4 } from "path";
|
|
20459
20870
|
import { homedir as homedir5 } from "os";
|
|
20460
20871
|
|
|
20461
20872
|
// src/utils/plugin-state.ts
|
|
20462
20873
|
init_fs();
|
|
20463
20874
|
import { readFile as readFile25 } from "fs/promises";
|
|
20464
|
-
import { resolve as
|
|
20875
|
+
import { resolve as resolve38 } from "path";
|
|
20465
20876
|
import { homedir as homedir4 } from "os";
|
|
20466
20877
|
function settingsPathFor(agent) {
|
|
20467
|
-
if (agent === "claude") return
|
|
20878
|
+
if (agent === "claude") return resolve38(homedir4(), ".claude", "settings.json");
|
|
20468
20879
|
return null;
|
|
20469
20880
|
}
|
|
20470
20881
|
async function readJsonOrNull(path) {
|
|
20471
20882
|
if (!path) return null;
|
|
20472
20883
|
if (!await fileExists(path)) return null;
|
|
20473
20884
|
try {
|
|
20474
|
-
const
|
|
20475
|
-
return JSON.parse(
|
|
20885
|
+
const raw2 = await readFile25(path, "utf-8");
|
|
20886
|
+
return JSON.parse(raw2);
|
|
20476
20887
|
} catch {
|
|
20477
20888
|
return null;
|
|
20478
20889
|
}
|
|
@@ -20521,16 +20932,16 @@ var KNOWN_SKILL_NAMES = [
|
|
|
20521
20932
|
var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
|
|
20522
20933
|
async function getSkillsDir() {
|
|
20523
20934
|
const packageRoot = await findPackageRoot("skills");
|
|
20524
|
-
return
|
|
20935
|
+
return resolve39(packageRoot, "skills");
|
|
20525
20936
|
}
|
|
20526
20937
|
function defaultSkillTargetDir(target) {
|
|
20527
|
-
if (target === "claude") return
|
|
20528
|
-
return
|
|
20938
|
+
if (target === "claude") return resolve39(homedir5(), ".claude", "skills");
|
|
20939
|
+
return resolve39(homedir5(), ".codex", "skills");
|
|
20529
20940
|
}
|
|
20530
20941
|
async function walkFiles(root) {
|
|
20531
20942
|
const out = [];
|
|
20532
20943
|
async function walk(dir) {
|
|
20533
|
-
const entries = await
|
|
20944
|
+
const entries = await readdir16(dir, { withFileTypes: true });
|
|
20534
20945
|
for (const entry of entries) {
|
|
20535
20946
|
const full = join4(dir, entry.name);
|
|
20536
20947
|
if (entry.isDirectory()) {
|
|
@@ -20553,8 +20964,8 @@ async function filesEqual(a, b) {
|
|
|
20553
20964
|
}
|
|
20554
20965
|
}
|
|
20555
20966
|
async function copyDir(srcDir, destDir) {
|
|
20556
|
-
await
|
|
20557
|
-
const entries = await
|
|
20967
|
+
await mkdir5(destDir, { recursive: true });
|
|
20968
|
+
const entries = await readdir16(srcDir, { withFileTypes: true });
|
|
20558
20969
|
for (const entry of entries) {
|
|
20559
20970
|
const src = join4(srcDir, entry.name);
|
|
20560
20971
|
const dest = join4(destDir, entry.name);
|
|
@@ -20579,8 +20990,8 @@ async function skillMatches(srcDir, destDir) {
|
|
|
20579
20990
|
}
|
|
20580
20991
|
async function isSymlink(path) {
|
|
20581
20992
|
try {
|
|
20582
|
-
const
|
|
20583
|
-
return
|
|
20993
|
+
const stat13 = await lstat2(path);
|
|
20994
|
+
return stat13.isSymbolicLink();
|
|
20584
20995
|
} catch {
|
|
20585
20996
|
return false;
|
|
20586
20997
|
}
|
|
@@ -20601,14 +21012,14 @@ async function installSkillDir(srcDir, destDir, skillName, force) {
|
|
|
20601
21012
|
return { skill: skillName, status: "already-current", targetPath: destDir };
|
|
20602
21013
|
}
|
|
20603
21014
|
if (force) {
|
|
20604
|
-
await
|
|
21015
|
+
await rm6(destDir, { recursive: true, force: true });
|
|
20605
21016
|
await copyDir(srcDir, destDir);
|
|
20606
21017
|
return { skill: skillName, status: "overwritten", targetPath: destDir };
|
|
20607
21018
|
}
|
|
20608
21019
|
return { skill: skillName, status: "differs-preserved", targetPath: destDir };
|
|
20609
21020
|
}
|
|
20610
21021
|
async function discoverSkillNames(sourceDir) {
|
|
20611
|
-
const entries = await
|
|
21022
|
+
const entries = await readdir16(sourceDir, { withFileTypes: true });
|
|
20612
21023
|
const names = [];
|
|
20613
21024
|
for (const entry of entries) {
|
|
20614
21025
|
if (!entry.isDirectory()) continue;
|
|
@@ -20638,7 +21049,7 @@ async function installSkillsWithReport(options) {
|
|
|
20638
21049
|
}
|
|
20639
21050
|
const skillNames = await discoverSkillNames(source);
|
|
20640
21051
|
const results = [];
|
|
20641
|
-
await
|
|
21052
|
+
await mkdir5(targetRoot, { recursive: true });
|
|
20642
21053
|
for (const skill of skillNames) {
|
|
20643
21054
|
const srcDir = join4(source, skill);
|
|
20644
21055
|
const destDir = join4(targetRoot, skill);
|
|
@@ -20655,7 +21066,7 @@ async function installSkillsToDir(options) {
|
|
|
20655
21066
|
}
|
|
20656
21067
|
const force = options.force ?? false;
|
|
20657
21068
|
const skillNames = await discoverSkillNames(source);
|
|
20658
|
-
await
|
|
21069
|
+
await mkdir5(options.targetDir, { recursive: true });
|
|
20659
21070
|
const results = [];
|
|
20660
21071
|
for (const skill of skillNames) {
|
|
20661
21072
|
const srcDir = join4(source, skill);
|
|
@@ -20688,7 +21099,7 @@ async function uninstallSkills(options) {
|
|
|
20688
21099
|
const content = await readFile26(skillMd, "utf-8").catch(() => "");
|
|
20689
21100
|
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
20690
21101
|
if (!match || match[1] !== skill) continue;
|
|
20691
|
-
await
|
|
21102
|
+
await rm6(destDir, { recursive: true, force: true });
|
|
20692
21103
|
removed.push(destDir);
|
|
20693
21104
|
}
|
|
20694
21105
|
return removed;
|
|
@@ -20971,14 +21382,14 @@ function parseOpenUrl(input4) {
|
|
|
20971
21382
|
}
|
|
20972
21383
|
let mode = "resume";
|
|
20973
21384
|
if (modeVals.length === 1) {
|
|
20974
|
-
const
|
|
20975
|
-
if (!SESSION_MODES.includes(
|
|
21385
|
+
const raw2 = modeVals[0];
|
|
21386
|
+
if (!SESSION_MODES.includes(raw2)) {
|
|
20976
21387
|
throw new OpenUrlError(
|
|
20977
21388
|
"bad-mode",
|
|
20978
|
-
`\`mode\` must be one of ${SESSION_MODES.join("|")} (got "${
|
|
21389
|
+
`\`mode\` must be one of ${SESSION_MODES.join("|")} (got "${raw2}")`
|
|
20979
21390
|
);
|
|
20980
21391
|
}
|
|
20981
|
-
mode =
|
|
21392
|
+
mode = raw2;
|
|
20982
21393
|
}
|
|
20983
21394
|
return { kind: "session", id, mode, ...terminal ? { terminal } : {} };
|
|
20984
21395
|
}
|
|
@@ -21213,7 +21624,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
21213
21624
|
`Spawn failed: ${msg}. Verify the terminal is installed and on PATH.`
|
|
21214
21625
|
);
|
|
21215
21626
|
}
|
|
21216
|
-
await new Promise((
|
|
21627
|
+
await new Promise((resolve83, reject) => {
|
|
21217
21628
|
let settled = false;
|
|
21218
21629
|
let stderr = "";
|
|
21219
21630
|
const finishOk = () => {
|
|
@@ -21223,7 +21634,7 @@ async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
|
21223
21634
|
child.unref();
|
|
21224
21635
|
} catch {
|
|
21225
21636
|
}
|
|
21226
|
-
|
|
21637
|
+
resolve83();
|
|
21227
21638
|
};
|
|
21228
21639
|
const finishErr = (remediation) => {
|
|
21229
21640
|
if (settled) return;
|
|
@@ -21368,7 +21779,7 @@ function appleScriptString(value) {
|
|
|
21368
21779
|
init_paths();
|
|
21369
21780
|
init_fs();
|
|
21370
21781
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
21371
|
-
import { dirname as
|
|
21782
|
+
import { dirname as dirname12, resolve as resolve41, join as join5 } from "path";
|
|
21372
21783
|
import { realpathSync, readFileSync, mkdirSync } from "fs";
|
|
21373
21784
|
var NPX_PATTERNS = [
|
|
21374
21785
|
{ kind: "npm", re: /\/_npx\/([^/]+)\/node_modules(?:\/|$)/ },
|
|
@@ -21410,26 +21821,26 @@ function detectInstallKind(scriptUrl, opts = {}) {
|
|
|
21410
21821
|
if (GLOBAL_PATTERN.test(norm)) {
|
|
21411
21822
|
return "global";
|
|
21412
21823
|
}
|
|
21413
|
-
let dir =
|
|
21824
|
+
let dir = dirname12(resolved);
|
|
21414
21825
|
for (let depth = 0; depth < 8; depth++) {
|
|
21415
21826
|
const pkgJsonPath = join5(dir, "package.json");
|
|
21416
|
-
let
|
|
21827
|
+
let raw2;
|
|
21417
21828
|
try {
|
|
21418
|
-
|
|
21829
|
+
raw2 = readFile56(pkgJsonPath);
|
|
21419
21830
|
} catch {
|
|
21420
|
-
const parent2 =
|
|
21831
|
+
const parent2 = dirname12(dir);
|
|
21421
21832
|
if (parent2 === dir) break;
|
|
21422
21833
|
dir = parent2;
|
|
21423
21834
|
continue;
|
|
21424
21835
|
}
|
|
21425
21836
|
try {
|
|
21426
|
-
const pkg = JSON.parse(
|
|
21837
|
+
const pkg = JSON.parse(raw2);
|
|
21427
21838
|
if (typeof pkg.name === "string" && pkg.name === "syntaur" && !normalizeSlashes(dir).includes("/node_modules/")) {
|
|
21428
21839
|
return "local";
|
|
21429
21840
|
}
|
|
21430
21841
|
} catch {
|
|
21431
21842
|
}
|
|
21432
|
-
const parent =
|
|
21843
|
+
const parent = dirname12(dir);
|
|
21433
21844
|
if (parent === dir) break;
|
|
21434
21845
|
dir = parent;
|
|
21435
21846
|
}
|
|
@@ -21447,7 +21858,7 @@ function extractNpxHash(scriptUrl, opts = {}) {
|
|
|
21447
21858
|
return null;
|
|
21448
21859
|
}
|
|
21449
21860
|
function nudgeStampDir() {
|
|
21450
|
-
return
|
|
21861
|
+
return resolve41(syntaurRoot(), "npx-handler-nudge");
|
|
21451
21862
|
}
|
|
21452
21863
|
function sanitizeHash(hash) {
|
|
21453
21864
|
return hash.replace(/[^A-Za-z0-9_-]/g, "_") || "_";
|
|
@@ -21469,9 +21880,9 @@ async function recordNudge(hash) {
|
|
|
21469
21880
|
}
|
|
21470
21881
|
}
|
|
21471
21882
|
function isHandlerNudgeDisabled() {
|
|
21472
|
-
const
|
|
21473
|
-
if (
|
|
21474
|
-
const trimmed =
|
|
21883
|
+
const raw2 = process.env.SYNTAUR_SKIP_HANDLER_NUDGE;
|
|
21884
|
+
if (raw2 === void 0) return false;
|
|
21885
|
+
const trimmed = raw2.trim();
|
|
21475
21886
|
return /^(1|true|yes)$/i.test(trimmed);
|
|
21476
21887
|
}
|
|
21477
21888
|
function nudgeMessage() {
|
|
@@ -21501,20 +21912,20 @@ init_paths();
|
|
|
21501
21912
|
init_fs();
|
|
21502
21913
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
21503
21914
|
import { readFile as readFile28 } from "fs/promises";
|
|
21504
|
-
import { dirname as
|
|
21915
|
+
import { dirname as dirname14, join as join7, resolve as resolve42 } from "path";
|
|
21505
21916
|
import { spawn as spawn5 } from "child_process";
|
|
21506
21917
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
21507
21918
|
|
|
21508
21919
|
// src/utils/version.ts
|
|
21509
21920
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
21510
21921
|
import { readFile as readFile27 } from "fs/promises";
|
|
21511
|
-
import { dirname as
|
|
21922
|
+
import { dirname as dirname13, join as join6 } from "path";
|
|
21512
21923
|
async function readPackageVersion(scriptUrl) {
|
|
21513
21924
|
try {
|
|
21514
21925
|
const scriptPath = fileURLToPath5(scriptUrl);
|
|
21515
|
-
const pkgRoot =
|
|
21516
|
-
const
|
|
21517
|
-
const parsed = JSON.parse(
|
|
21926
|
+
const pkgRoot = dirname13(dirname13(scriptPath));
|
|
21927
|
+
const raw2 = await readFile27(join6(pkgRoot, "package.json"), "utf-8");
|
|
21928
|
+
const parsed = JSON.parse(raw2);
|
|
21518
21929
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
21519
21930
|
} catch {
|
|
21520
21931
|
return null;
|
|
@@ -21522,7 +21933,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
21522
21933
|
}
|
|
21523
21934
|
|
|
21524
21935
|
// src/utils/npx-prompt.ts
|
|
21525
|
-
var STATE_FILE =
|
|
21936
|
+
var STATE_FILE = resolve42(syntaurRoot(), "npx-install.json");
|
|
21526
21937
|
var META_ARGS2 = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
21527
21938
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
21528
21939
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -21543,8 +21954,8 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
21543
21954
|
async function readState() {
|
|
21544
21955
|
if (!await fileExists(STATE_FILE)) return null;
|
|
21545
21956
|
try {
|
|
21546
|
-
const
|
|
21547
|
-
return JSON.parse(
|
|
21957
|
+
const raw2 = await readFile28(STATE_FILE, "utf-8");
|
|
21958
|
+
return JSON.parse(raw2);
|
|
21548
21959
|
} catch {
|
|
21549
21960
|
return null;
|
|
21550
21961
|
}
|
|
@@ -21554,7 +21965,7 @@ async function writeState(state) {
|
|
|
21554
21965
|
`);
|
|
21555
21966
|
}
|
|
21556
21967
|
async function resolveNpmBin() {
|
|
21557
|
-
const nodeDir =
|
|
21968
|
+
const nodeDir = dirname14(process.execPath);
|
|
21558
21969
|
const isWin = process.platform === "win32";
|
|
21559
21970
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
21560
21971
|
const nearNode = join7(nodeDir, npmName);
|
|
@@ -21602,8 +22013,8 @@ async function readGlobalVersion() {
|
|
|
21602
22013
|
try {
|
|
21603
22014
|
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
21604
22015
|
if (!await fileExists(manifestPath)) return null;
|
|
21605
|
-
const
|
|
21606
|
-
const parsed = JSON.parse(
|
|
22016
|
+
const raw2 = await readFile28(manifestPath, "utf-8");
|
|
22017
|
+
const parsed = JSON.parse(raw2);
|
|
21607
22018
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
21608
22019
|
} catch {
|
|
21609
22020
|
return null;
|
|
@@ -21632,10 +22043,10 @@ async function askChoice(promptLabel) {
|
|
|
21632
22043
|
const onSigint = () => controller.abort();
|
|
21633
22044
|
process.once("SIGINT", onSigint);
|
|
21634
22045
|
try {
|
|
21635
|
-
const
|
|
22046
|
+
const raw2 = await rl.question(promptLabel, {
|
|
21636
22047
|
signal: controller.signal
|
|
21637
22048
|
});
|
|
21638
|
-
return
|
|
22049
|
+
return raw2.trim();
|
|
21639
22050
|
} catch {
|
|
21640
22051
|
return "";
|
|
21641
22052
|
} finally {
|
|
@@ -21960,16 +22371,16 @@ async function refreshPluginSkills(options, pm, runner, getManagedDir, resolveFr
|
|
|
21960
22371
|
// src/commands/install-statusline.ts
|
|
21961
22372
|
init_paths();
|
|
21962
22373
|
init_fs();
|
|
21963
|
-
import { readFile as readFile30, writeFile as
|
|
21964
|
-
import { resolve as
|
|
22374
|
+
import { readFile as readFile30, writeFile as writeFile12, copyFile as copyFile2, rm as rm7, stat as stat3, symlink as symlink2, unlink as unlink9, lstat as lstat3 } from "fs/promises";
|
|
22375
|
+
import { resolve as resolve44, dirname as dirname16 } from "path";
|
|
21965
22376
|
import { homedir as homedir7 } from "os";
|
|
21966
22377
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
21967
22378
|
|
|
21968
22379
|
// src/commands/configure-statusline.ts
|
|
21969
22380
|
init_paths();
|
|
21970
22381
|
init_fs();
|
|
21971
|
-
import { readFile as readFile29, writeFile as
|
|
21972
|
-
import { resolve as
|
|
22382
|
+
import { readFile as readFile29, writeFile as writeFile11 } from "fs/promises";
|
|
22383
|
+
import { resolve as resolve43, dirname as dirname15 } from "path";
|
|
21973
22384
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
21974
22385
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
21975
22386
|
var AVAILABLE_SEGMENTS = [
|
|
@@ -21990,13 +22401,13 @@ var PRESETS = {
|
|
|
21990
22401
|
tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
|
|
21991
22402
|
};
|
|
21992
22403
|
function getConfigPath(installRoot) {
|
|
21993
|
-
return
|
|
22404
|
+
return resolve43(installRoot, "statusline.config.json");
|
|
21994
22405
|
}
|
|
21995
22406
|
async function readConfig2(path) {
|
|
21996
22407
|
if (!await fileExists(path)) return null;
|
|
21997
22408
|
try {
|
|
21998
|
-
const
|
|
21999
|
-
const parsed = JSON.parse(
|
|
22409
|
+
const raw2 = await readFile29(path, "utf-8");
|
|
22410
|
+
const parsed = JSON.parse(raw2);
|
|
22000
22411
|
if (!parsed || typeof parsed !== "object") return null;
|
|
22001
22412
|
const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
|
|
22002
22413
|
const separator = typeof parsed.separator === "string" ? parsed.separator : " \xB7 ";
|
|
@@ -22045,7 +22456,7 @@ async function promptSegmentsInteractive(current) {
|
|
|
22045
22456
|
default: false
|
|
22046
22457
|
});
|
|
22047
22458
|
if (wantReorder) {
|
|
22048
|
-
const
|
|
22459
|
+
const raw2 = await input2({
|
|
22049
22460
|
message: `Enter the segments in the order you want, comma-separated:`,
|
|
22050
22461
|
default: defaultReorderHint,
|
|
22051
22462
|
validate: (value) => {
|
|
@@ -22061,7 +22472,7 @@ async function promptSegmentsInteractive(current) {
|
|
|
22061
22472
|
return true;
|
|
22062
22473
|
}
|
|
22063
22474
|
});
|
|
22064
|
-
orderedSegments =
|
|
22475
|
+
orderedSegments = raw2.split(",").map((s) => s.trim()).filter(Boolean);
|
|
22065
22476
|
}
|
|
22066
22477
|
const separator = await input2({
|
|
22067
22478
|
message: "Separator between segments:",
|
|
@@ -22090,7 +22501,7 @@ function renderPreview(config, statuslineScript, cwd) {
|
|
|
22090
22501
|
env: {
|
|
22091
22502
|
...process.env,
|
|
22092
22503
|
// Force the child to pick up the freshly-written config from install root.
|
|
22093
|
-
HOME:
|
|
22504
|
+
HOME: dirname15(dirname15(statuslineScript))
|
|
22094
22505
|
}
|
|
22095
22506
|
});
|
|
22096
22507
|
if (res.status !== 0) return null;
|
|
@@ -22141,14 +22552,14 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
22141
22552
|
console.log("(preview mode \u2014 config NOT written)");
|
|
22142
22553
|
return;
|
|
22143
22554
|
}
|
|
22144
|
-
await ensureDir(
|
|
22145
|
-
await
|
|
22555
|
+
await ensureDir(dirname15(configPath));
|
|
22556
|
+
await writeFile11(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
22146
22557
|
console.log("Wrote statusline config:");
|
|
22147
22558
|
console.log(` path: ${configPath}`);
|
|
22148
22559
|
console.log(` segments: ${config.segments.join(", ")}`);
|
|
22149
22560
|
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
22150
22561
|
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
22151
|
-
const script = options.statuslineScript ??
|
|
22562
|
+
const script = options.statuslineScript ?? resolve43(installRoot, "statusline.sh");
|
|
22152
22563
|
if (await fileExists(script)) {
|
|
22153
22564
|
console.log("");
|
|
22154
22565
|
console.log("Live preview:");
|
|
@@ -22168,25 +22579,25 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
22168
22579
|
async function writeDefaultConfigIfMissing(installRoot) {
|
|
22169
22580
|
const path = getConfigPath(installRoot);
|
|
22170
22581
|
if (await fileExists(path)) return;
|
|
22171
|
-
await ensureDir(
|
|
22582
|
+
await ensureDir(dirname15(path));
|
|
22172
22583
|
const defaultConfig = {
|
|
22173
22584
|
segments: ["git", "assignment", "session"],
|
|
22174
22585
|
separator: " \xB7 "
|
|
22175
22586
|
};
|
|
22176
|
-
await
|
|
22587
|
+
await writeFile11(path, JSON.stringify(defaultConfig, null, 2) + "\n", "utf-8");
|
|
22177
22588
|
}
|
|
22178
22589
|
|
|
22179
22590
|
// src/commands/install-statusline.ts
|
|
22180
22591
|
function getPackageStatuslineSource() {
|
|
22181
|
-
const here =
|
|
22182
|
-
return
|
|
22592
|
+
const here = dirname16(fileURLToPath8(import.meta.url));
|
|
22593
|
+
return resolve44(here, "..", "statusline", "statusline.sh");
|
|
22183
22594
|
}
|
|
22184
22595
|
async function readSettingsJson(settingsPath) {
|
|
22185
22596
|
if (!await fileExists(settingsPath)) return {};
|
|
22186
|
-
const
|
|
22187
|
-
if (
|
|
22597
|
+
const raw2 = await readFile30(settingsPath, "utf-8");
|
|
22598
|
+
if (raw2.trim() === "") return {};
|
|
22188
22599
|
try {
|
|
22189
|
-
const parsed = JSON.parse(
|
|
22600
|
+
const parsed = JSON.parse(raw2);
|
|
22190
22601
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
22191
22602
|
} catch (error) {
|
|
22192
22603
|
throw new Error(
|
|
@@ -22195,8 +22606,8 @@ async function readSettingsJson(settingsPath) {
|
|
|
22195
22606
|
}
|
|
22196
22607
|
}
|
|
22197
22608
|
async function writeSettingsJson(settingsPath, data) {
|
|
22198
|
-
await ensureDir(
|
|
22199
|
-
await
|
|
22609
|
+
await ensureDir(dirname16(settingsPath));
|
|
22610
|
+
await writeFile12(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
22200
22611
|
}
|
|
22201
22612
|
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
22202
22613
|
if (mode !== "ask") return mode;
|
|
@@ -22230,8 +22641,8 @@ function extractExistingCommand(settings) {
|
|
|
22230
22641
|
};
|
|
22231
22642
|
}
|
|
22232
22643
|
async function backupSettings(settingsSnapshot, backupPath) {
|
|
22233
|
-
await ensureDir(
|
|
22234
|
-
await
|
|
22644
|
+
await ensureDir(dirname16(backupPath));
|
|
22645
|
+
await writeFile12(
|
|
22235
22646
|
backupPath,
|
|
22236
22647
|
JSON.stringify(
|
|
22237
22648
|
{
|
|
@@ -22247,11 +22658,11 @@ async function backupSettings(settingsSnapshot, backupPath) {
|
|
|
22247
22658
|
);
|
|
22248
22659
|
}
|
|
22249
22660
|
async function installScript(sourceScript, destScript, link) {
|
|
22250
|
-
await ensureDir(
|
|
22661
|
+
await ensureDir(dirname16(destScript));
|
|
22251
22662
|
try {
|
|
22252
22663
|
const s = await lstat3(destScript);
|
|
22253
22664
|
if (s.isSymbolicLink() || s.isFile()) {
|
|
22254
|
-
await
|
|
22665
|
+
await unlink9(destScript);
|
|
22255
22666
|
}
|
|
22256
22667
|
} catch {
|
|
22257
22668
|
}
|
|
@@ -22263,12 +22674,12 @@ async function installScript(sourceScript, destScript, link) {
|
|
|
22263
22674
|
}
|
|
22264
22675
|
async function installStatuslineCommand(options = {}) {
|
|
22265
22676
|
const mode = options.mode ?? "ask";
|
|
22266
|
-
const settingsPath = options.settingsPath ??
|
|
22677
|
+
const settingsPath = options.settingsPath ?? resolve44(homedir7(), ".claude", "settings.json");
|
|
22267
22678
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
22268
22679
|
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
22269
|
-
const destScript =
|
|
22270
|
-
const confPath =
|
|
22271
|
-
const backupPath =
|
|
22680
|
+
const destScript = resolve44(installRoot, "statusline.sh");
|
|
22681
|
+
const confPath = resolve44(installRoot, "statusline.conf");
|
|
22682
|
+
const backupPath = resolve44(installRoot, "statusline.backup.json");
|
|
22272
22683
|
if (!await fileExists(sourceScript)) {
|
|
22273
22684
|
throw new Error(
|
|
22274
22685
|
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
@@ -22304,19 +22715,19 @@ async function installStatuslineCommand(options = {}) {
|
|
|
22304
22715
|
if (parsed) {
|
|
22305
22716
|
wrapTarget = parsed;
|
|
22306
22717
|
} else {
|
|
22307
|
-
const wrapperPath =
|
|
22718
|
+
const wrapperPath = resolve44(installRoot, "statusline-wrapped.sh");
|
|
22308
22719
|
const wrapperBody = `#!/usr/bin/env bash
|
|
22309
22720
|
# Auto-generated by syntaur install-statusline.
|
|
22310
22721
|
# Executes the previously configured statusLine command.
|
|
22311
22722
|
exec ${existingCommand}
|
|
22312
22723
|
`;
|
|
22313
|
-
await
|
|
22724
|
+
await writeFile12(wrapperPath, wrapperBody, "utf-8");
|
|
22314
22725
|
await chmodExec(wrapperPath);
|
|
22315
22726
|
wrapTarget = wrapperPath;
|
|
22316
22727
|
}
|
|
22317
22728
|
}
|
|
22318
|
-
await ensureDir(
|
|
22319
|
-
await
|
|
22729
|
+
await ensureDir(dirname16(confPath));
|
|
22730
|
+
await writeFile12(
|
|
22320
22731
|
confPath,
|
|
22321
22732
|
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
22322
22733
|
# stdout becomes the leading segment of the statusline. Remove this
|
|
@@ -22353,26 +22764,26 @@ function parseWrapCommand(command) {
|
|
|
22353
22764
|
async function chmodExec(path) {
|
|
22354
22765
|
const fs = await import("fs/promises");
|
|
22355
22766
|
try {
|
|
22356
|
-
const s = await
|
|
22767
|
+
const s = await stat3(path);
|
|
22357
22768
|
await fs.chmod(path, s.mode | 73);
|
|
22358
22769
|
} catch {
|
|
22359
22770
|
}
|
|
22360
22771
|
}
|
|
22361
22772
|
async function uninstallStatuslineCommand(options = {}) {
|
|
22362
|
-
const settingsPath = options.settingsPath ??
|
|
22773
|
+
const settingsPath = options.settingsPath ?? resolve44(homedir7(), ".claude", "settings.json");
|
|
22363
22774
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
22364
|
-
const destScript =
|
|
22365
|
-
const confPath =
|
|
22366
|
-
const backupPath =
|
|
22367
|
-
const wrapperPath =
|
|
22775
|
+
const destScript = resolve44(installRoot, "statusline.sh");
|
|
22776
|
+
const confPath = resolve44(installRoot, "statusline.conf");
|
|
22777
|
+
const backupPath = resolve44(installRoot, "statusline.backup.json");
|
|
22778
|
+
const wrapperPath = resolve44(installRoot, "statusline-wrapped.sh");
|
|
22368
22779
|
const settings = await readSettingsJson(settingsPath);
|
|
22369
22780
|
const existing = extractExistingCommand(settings);
|
|
22370
22781
|
const ourCommand = `bash ${destScript}`;
|
|
22371
22782
|
let restored = null;
|
|
22372
22783
|
if (await fileExists(backupPath)) {
|
|
22373
22784
|
try {
|
|
22374
|
-
const
|
|
22375
|
-
const parsed = JSON.parse(
|
|
22785
|
+
const raw2 = await readFile30(backupPath, "utf-8");
|
|
22786
|
+
const parsed = JSON.parse(raw2);
|
|
22376
22787
|
const prev = parsed?.previousStatusLine;
|
|
22377
22788
|
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
22378
22789
|
restored = { command: prev.command };
|
|
@@ -22392,10 +22803,10 @@ async function uninstallStatuslineCommand(options = {}) {
|
|
|
22392
22803
|
await writeSettingsJson(settingsPath, settings);
|
|
22393
22804
|
}
|
|
22394
22805
|
if (!options.keepScript) {
|
|
22395
|
-
const configPath =
|
|
22806
|
+
const configPath = resolve44(installRoot, "statusline.config.json");
|
|
22396
22807
|
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
22397
22808
|
try {
|
|
22398
|
-
await
|
|
22809
|
+
await rm7(path, { force: true });
|
|
22399
22810
|
} catch {
|
|
22400
22811
|
}
|
|
22401
22812
|
}
|
|
@@ -22552,7 +22963,7 @@ init_config2();
|
|
|
22552
22963
|
// src/commands/cross-agent-install.ts
|
|
22553
22964
|
init_fs();
|
|
22554
22965
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
22555
|
-
import { resolve as
|
|
22966
|
+
import { resolve as resolve47 } from "path";
|
|
22556
22967
|
import { readFile as readFile31 } from "fs/promises";
|
|
22557
22968
|
|
|
22558
22969
|
// src/commands/setup-adapter.ts
|
|
@@ -22560,28 +22971,28 @@ init_paths();
|
|
|
22560
22971
|
init_fs();
|
|
22561
22972
|
init_config2();
|
|
22562
22973
|
init_slug();
|
|
22563
|
-
import { resolve as
|
|
22974
|
+
import { resolve as resolve46 } from "path";
|
|
22564
22975
|
|
|
22565
22976
|
// src/targets/registry.ts
|
|
22566
22977
|
init_fs();
|
|
22567
22978
|
import { homedir as homedir8 } from "os";
|
|
22568
|
-
import { resolve as
|
|
22979
|
+
import { resolve as resolve45 } from "path";
|
|
22569
22980
|
function home(...segments) {
|
|
22570
|
-
return
|
|
22981
|
+
return resolve45(homedir8(), ...segments);
|
|
22571
22982
|
}
|
|
22572
22983
|
function hermesHome() {
|
|
22573
22984
|
const env = process.env.HERMES_HOME;
|
|
22574
|
-
return env && env.length > 0 ?
|
|
22985
|
+
return env && env.length > 0 ? resolve45(env) : home(".hermes");
|
|
22575
22986
|
}
|
|
22576
22987
|
function hermesSkillsDir() {
|
|
22577
|
-
return
|
|
22988
|
+
return resolve45(hermesHome(), "skills");
|
|
22578
22989
|
}
|
|
22579
22990
|
function isHermesHomeCustom() {
|
|
22580
22991
|
return hermesHome() !== home(".hermes");
|
|
22581
22992
|
}
|
|
22582
22993
|
function codexHome() {
|
|
22583
22994
|
const env = process.env.CODEX_HOME;
|
|
22584
|
-
return env && env.length > 0 ?
|
|
22995
|
+
return env && env.length > 0 ? resolve45(env) : home(".codex");
|
|
22585
22996
|
}
|
|
22586
22997
|
var detectDir = (dir) => () => fileExists(dir);
|
|
22587
22998
|
var AGENT_TARGETS = [
|
|
@@ -22605,7 +23016,7 @@ var AGENT_TARGETS = [
|
|
|
22605
23016
|
skillsShAgentId: "codex",
|
|
22606
23017
|
nativePlugin: "codex",
|
|
22607
23018
|
detect: detectDir(codexHome()),
|
|
22608
|
-
skillsDir: { global:
|
|
23019
|
+
skillsDir: { global: resolve45(codexHome(), "skills") },
|
|
22609
23020
|
instructions: { files: [{ path: "AGENTS.md", renderer: "codexAgents" }] }
|
|
22610
23021
|
},
|
|
22611
23022
|
{
|
|
@@ -22709,13 +23120,13 @@ async function setupAdapterCommand(framework, options) {
|
|
|
22709
23120
|
}
|
|
22710
23121
|
const config = await readConfig();
|
|
22711
23122
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
22712
|
-
const projectDir =
|
|
22713
|
-
const assignmentDir =
|
|
22714
|
-
const projectMdPath =
|
|
23123
|
+
const projectDir = resolve46(baseDir, options.project);
|
|
23124
|
+
const assignmentDir = resolve46(projectDir, "assignments", options.assignment);
|
|
23125
|
+
const projectMdPath = resolve46(projectDir, "project.md");
|
|
22715
23126
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
22716
23127
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
22717
23128
|
}
|
|
22718
|
-
const assignmentMdPath2 =
|
|
23129
|
+
const assignmentMdPath2 = resolve46(assignmentDir, "assignment.md");
|
|
22719
23130
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath2)) {
|
|
22720
23131
|
throw new Error(
|
|
22721
23132
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -22732,7 +23143,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
22732
23143
|
const upToDateFiles = [];
|
|
22733
23144
|
const skippedFiles = [];
|
|
22734
23145
|
for (const file of target.instructions.files) {
|
|
22735
|
-
const filePath =
|
|
23146
|
+
const filePath = resolve46(cwd, file.path);
|
|
22736
23147
|
const content = RENDERERS[file.renderer](rendererParams);
|
|
22737
23148
|
const status = await writeFileReport(filePath, content, {
|
|
22738
23149
|
force: options.force
|
|
@@ -22772,8 +23183,8 @@ async function setupAdapterCommand(framework, options) {
|
|
|
22772
23183
|
init_config2();
|
|
22773
23184
|
var DEFAULT_SKILLS_SOURCE = "prong-horn/syntaur";
|
|
22774
23185
|
function parseTargetIds(options) {
|
|
22775
|
-
const
|
|
22776
|
-
const ids =
|
|
23186
|
+
const raw2 = [options.target, options.agent].filter(Boolean).join(",");
|
|
23187
|
+
const ids = raw2.split(",").map((s) => s.trim()).filter(Boolean);
|
|
22777
23188
|
return [...new Set(ids)];
|
|
22778
23189
|
}
|
|
22779
23190
|
function isNpxAvailable() {
|
|
@@ -22785,7 +23196,7 @@ function isNpxAvailable() {
|
|
|
22785
23196
|
}
|
|
22786
23197
|
}
|
|
22787
23198
|
async function readAssignmentContext() {
|
|
22788
|
-
const p =
|
|
23199
|
+
const p = resolve47(process.cwd(), ".syntaur", "context.json");
|
|
22789
23200
|
if (!await fileExists(p)) return null;
|
|
22790
23201
|
try {
|
|
22791
23202
|
return JSON.parse(await readFile31(p, "utf-8"));
|
|
@@ -22858,7 +23269,7 @@ async function crossAgentInstallCommand(options) {
|
|
|
22858
23269
|
if (dryRun) {
|
|
22859
23270
|
for (const f of t.instructions.files) {
|
|
22860
23271
|
console.log(
|
|
22861
|
-
`${prefix}Tier 2 (${t.id}): ${
|
|
23272
|
+
`${prefix}Tier 2 (${t.id}): ${resolve47(process.cwd(), f.path)}`
|
|
22862
23273
|
);
|
|
22863
23274
|
}
|
|
22864
23275
|
continue;
|
|
@@ -23005,7 +23416,7 @@ async function setupCommand(options) {
|
|
|
23005
23416
|
}
|
|
23006
23417
|
|
|
23007
23418
|
// src/commands/uninstall.ts
|
|
23008
|
-
import { resolve as
|
|
23419
|
+
import { resolve as resolve48 } from "path";
|
|
23009
23420
|
init_paths();
|
|
23010
23421
|
function expandTargets(options) {
|
|
23011
23422
|
if (options.all) {
|
|
@@ -23085,7 +23496,7 @@ async function uninstallCommand(options) {
|
|
|
23085
23496
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
23086
23497
|
await removeSyntaurData();
|
|
23087
23498
|
console.log(`Removed ${syntaurRoot()}`);
|
|
23088
|
-
if (configuredProjectDir &&
|
|
23499
|
+
if (configuredProjectDir && resolve48(configuredProjectDir) !== resolve48(syntaurRoot(), "projects")) {
|
|
23089
23500
|
console.warn(
|
|
23090
23501
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
23091
23502
|
);
|
|
@@ -23100,7 +23511,7 @@ async function uninstallCommand(options) {
|
|
|
23100
23511
|
init_paths();
|
|
23101
23512
|
init_fs();
|
|
23102
23513
|
init_config2();
|
|
23103
|
-
import { resolve as
|
|
23514
|
+
import { resolve as resolve49 } from "path";
|
|
23104
23515
|
init_session_db();
|
|
23105
23516
|
init_agent_sessions();
|
|
23106
23517
|
async function trackSessionCommand(options) {
|
|
@@ -23115,7 +23526,7 @@ async function trackSessionCommand(options) {
|
|
|
23115
23526
|
if (options.project) {
|
|
23116
23527
|
const config = await readConfig();
|
|
23117
23528
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
23118
|
-
const projectDir =
|
|
23529
|
+
const projectDir = resolve49(baseDir, options.project);
|
|
23119
23530
|
if (!await fileExists(projectDir)) {
|
|
23120
23531
|
throw new Error(
|
|
23121
23532
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -23247,7 +23658,7 @@ function formatInstallUrlHandlerError(err2) {
|
|
|
23247
23658
|
init_config2();
|
|
23248
23659
|
init_paths();
|
|
23249
23660
|
init_fs();
|
|
23250
|
-
import { resolve as
|
|
23661
|
+
import { resolve as resolve51, isAbsolute as isAbsolute7 } from "path";
|
|
23251
23662
|
import { readFile as readFile32 } from "fs/promises";
|
|
23252
23663
|
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
23253
23664
|
async function browseCommand(options) {
|
|
@@ -23317,7 +23728,7 @@ async function pickAgent2(agents) {
|
|
|
23317
23728
|
return picked;
|
|
23318
23729
|
}
|
|
23319
23730
|
async function ensureWorktree(opts) {
|
|
23320
|
-
const assignmentPath =
|
|
23731
|
+
const assignmentPath = resolve51(
|
|
23321
23732
|
opts.projectsDir,
|
|
23322
23733
|
opts.projectSlug,
|
|
23323
23734
|
"assignments",
|
|
@@ -23397,7 +23808,7 @@ async function ensureWorktree(opts) {
|
|
|
23397
23808
|
async function runCreate(opts) {
|
|
23398
23809
|
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
23399
23810
|
const expandedWorktree = expandHome(opts.worktreePath);
|
|
23400
|
-
const absWorktree = isAbsolute7(expandedWorktree) ? expandedWorktree :
|
|
23811
|
+
const absWorktree = isAbsolute7(expandedWorktree) ? expandedWorktree : resolve51(expandedWorktree);
|
|
23401
23812
|
try {
|
|
23402
23813
|
await createWorktreeAndRecord2({
|
|
23403
23814
|
assignmentPath: opts.assignmentPath,
|
|
@@ -23426,7 +23837,7 @@ init_paths();
|
|
|
23426
23837
|
init_fs();
|
|
23427
23838
|
init_playbook();
|
|
23428
23839
|
init_playbooks();
|
|
23429
|
-
import { resolve as
|
|
23840
|
+
import { resolve as resolve52 } from "path";
|
|
23430
23841
|
async function createPlaybookCommand(name, options) {
|
|
23431
23842
|
if (!name.trim()) {
|
|
23432
23843
|
throw new Error("Playbook name cannot be empty.");
|
|
@@ -23439,7 +23850,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
23439
23850
|
}
|
|
23440
23851
|
const dir = playbooksDir();
|
|
23441
23852
|
await ensureDir(dir);
|
|
23442
|
-
const filePath =
|
|
23853
|
+
const filePath = resolve52(dir, `${slug}.md`);
|
|
23443
23854
|
if (await fileExists(filePath)) {
|
|
23444
23855
|
throw new Error(
|
|
23445
23856
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -23461,8 +23872,8 @@ init_paths();
|
|
|
23461
23872
|
init_fs();
|
|
23462
23873
|
init_parser();
|
|
23463
23874
|
init_config2();
|
|
23464
|
-
import { readdir as
|
|
23465
|
-
import { resolve as
|
|
23875
|
+
import { readdir as readdir17, readFile as readFile33 } from "fs/promises";
|
|
23876
|
+
import { resolve as resolve53 } from "path";
|
|
23466
23877
|
async function listPlaybooksCommand(options = {}) {
|
|
23467
23878
|
const dir = playbooksDir();
|
|
23468
23879
|
if (!await fileExists(dir)) {
|
|
@@ -23471,15 +23882,15 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
23471
23882
|
}
|
|
23472
23883
|
const config = await readConfig();
|
|
23473
23884
|
const disabledSet = new Set(config.playbooks.disabled);
|
|
23474
|
-
const entries = await
|
|
23885
|
+
const entries = await readdir17(dir, { withFileTypes: true });
|
|
23475
23886
|
const mdFiles = entries.filter(
|
|
23476
23887
|
(e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md"
|
|
23477
23888
|
);
|
|
23478
23889
|
const rows = [];
|
|
23479
23890
|
for (const entry of mdFiles) {
|
|
23480
|
-
const filePath =
|
|
23481
|
-
const
|
|
23482
|
-
const parsed = parsePlaybook(
|
|
23891
|
+
const filePath = resolve53(dir, entry.name);
|
|
23892
|
+
const raw2 = await readFile33(filePath, "utf-8");
|
|
23893
|
+
const parsed = parsePlaybook(raw2);
|
|
23483
23894
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
23484
23895
|
const disabled = disabledSet.has(slug);
|
|
23485
23896
|
if (disabled && !options.all) continue;
|
|
@@ -23602,13 +24013,13 @@ init_config2();
|
|
|
23602
24013
|
init_slug();
|
|
23603
24014
|
import { Command as Command2 } from "commander";
|
|
23604
24015
|
import { readFile as readFile35 } from "fs/promises";
|
|
23605
|
-
import { resolve as
|
|
24016
|
+
import { resolve as resolve55 } from "path";
|
|
23606
24017
|
|
|
23607
24018
|
// src/commands/bundle.ts
|
|
23608
24019
|
init_paths();
|
|
23609
24020
|
import { Command } from "commander";
|
|
23610
|
-
import { mkdir as
|
|
23611
|
-
import { resolve as
|
|
24021
|
+
import { mkdir as mkdir8, readFile as readFile34, readdir as readdir18, rm as rm8, writeFile as writeFile13 } from "fs/promises";
|
|
24022
|
+
import { resolve as resolve54 } from "path";
|
|
23612
24023
|
init_parser2();
|
|
23613
24024
|
init_fs();
|
|
23614
24025
|
init_config2();
|
|
@@ -23626,7 +24037,7 @@ async function resolveBundleScope(options) {
|
|
|
23626
24037
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
23627
24038
|
}
|
|
23628
24039
|
const config = await readConfig();
|
|
23629
|
-
const projectMd =
|
|
24040
|
+
const projectMd = resolve54(config.defaultProjectDir, options.project, "project.md");
|
|
23630
24041
|
if (!await fileExists(projectMd)) {
|
|
23631
24042
|
throw new Error(`Project "${options.project}" not found.`);
|
|
23632
24043
|
}
|
|
@@ -23696,10 +24107,10 @@ function pickNextPlanFile(planDir, existingFiles) {
|
|
|
23696
24107
|
const m = f.match(/^plan-v(\d+)\.md$/);
|
|
23697
24108
|
if (m) versions.add(parseInt(m[1], 10));
|
|
23698
24109
|
}
|
|
23699
|
-
if (versions.size === 0) return { target:
|
|
24110
|
+
if (versions.size === 0) return { target: resolve54(planDir, "plan.md"), version: 1 };
|
|
23700
24111
|
let n = 2;
|
|
23701
24112
|
while (versions.has(n)) n++;
|
|
23702
|
-
return { target:
|
|
24113
|
+
return { target: resolve54(planDir, `plan-v${n}.md`), version: n };
|
|
23703
24114
|
}
|
|
23704
24115
|
function dedupePreserveOrder(ids) {
|
|
23705
24116
|
const out = [];
|
|
@@ -23783,7 +24194,7 @@ bundleCommand.command("new").description("Create a new bundle from 2+ existing t
|
|
|
23783
24194
|
if (options.plan) {
|
|
23784
24195
|
const planDir = bundlePlanDir(sc.todosPath, sc.scopeId, id);
|
|
23785
24196
|
await ensureDir(planDir);
|
|
23786
|
-
const target =
|
|
24197
|
+
const target = resolve54(planDir, "plan.md");
|
|
23787
24198
|
const memberLines = items.map((it) => `- ${it.description} [t:${it.id}]`).join("\n");
|
|
23788
24199
|
const stub = [
|
|
23789
24200
|
"---",
|
|
@@ -23900,7 +24311,7 @@ bundleCommand.command("plan").description("Create or open the bundle's shared pl
|
|
|
23900
24311
|
}
|
|
23901
24312
|
const planDir = bundlePlanDir(sc.todosPath, sc.scopeId, id);
|
|
23902
24313
|
await ensureDir(planDir);
|
|
23903
|
-
const existing = (await
|
|
24314
|
+
const existing = (await readdir18(planDir).catch(() => [])).filter((f) => /^plan(?:-v\d+)?\.md$/.test(f));
|
|
23904
24315
|
const { target } = pickNextPlanFile(planDir, existing);
|
|
23905
24316
|
const checklist = await readChecklist(sc.todosPath, sc.checklistKey);
|
|
23906
24317
|
if (!await fileExists(target)) {
|
|
@@ -23959,7 +24370,7 @@ bundleCommand.command("worktree").description("Create a git worktree for the bun
|
|
|
23959
24370
|
}
|
|
23960
24371
|
const repository = options.repository ?? process.cwd();
|
|
23961
24372
|
const parentBranch = options.parentBranch ?? "main";
|
|
23962
|
-
const worktreePath = options.worktreePath ??
|
|
24373
|
+
const worktreePath = options.worktreePath ?? resolve54(repository, ".worktrees", options.branch);
|
|
23963
24374
|
const checklist = await readChecklist(sc.todosPath, sc.checklistKey);
|
|
23964
24375
|
for (const memberId of bundle.todoIds) {
|
|
23965
24376
|
const item = checklist.items.find((i) => i.id === memberId);
|
|
@@ -23986,8 +24397,8 @@ bundleCommand.command("worktree").description("Create a git worktree for the bun
|
|
|
23986
24397
|
try {
|
|
23987
24398
|
await writeBundles(sc.todosPath, bundles);
|
|
23988
24399
|
await writeChecklist(sc.todosPath, checklist);
|
|
23989
|
-
const ctxDir =
|
|
23990
|
-
await
|
|
24400
|
+
const ctxDir = resolve54(worktreePath, ".syntaur");
|
|
24401
|
+
await mkdir8(ctxDir, { recursive: true });
|
|
23991
24402
|
const payload = {
|
|
23992
24403
|
bundleId: bundle.id,
|
|
23993
24404
|
bundleSlug: bundle.slug,
|
|
@@ -24000,16 +24411,16 @@ bundleCommand.command("worktree").description("Create a git worktree for the bun
|
|
|
24000
24411
|
repository,
|
|
24001
24412
|
boundAt: nowISO()
|
|
24002
24413
|
};
|
|
24003
|
-
await
|
|
24414
|
+
await writeFile13(resolve54(ctxDir, "context.json"), JSON.stringify(payload, null, 2) + "\n");
|
|
24004
24415
|
} catch (err2) {
|
|
24005
24416
|
try {
|
|
24006
24417
|
if (bundlesSnapshot === null) {
|
|
24007
|
-
await
|
|
24418
|
+
await rm8(bundlesFilePath, { force: true });
|
|
24008
24419
|
} else {
|
|
24009
24420
|
await writeFileForce(bundlesFilePath, bundlesSnapshot);
|
|
24010
24421
|
}
|
|
24011
24422
|
if (checklistSnapshot === null) {
|
|
24012
|
-
await
|
|
24423
|
+
await rm8(checklistFilePath, { force: true });
|
|
24013
24424
|
} else {
|
|
24014
24425
|
await writeFileForce(checklistFilePath, checklistSnapshot);
|
|
24015
24426
|
}
|
|
@@ -24190,7 +24601,7 @@ async function resolveScope(options) {
|
|
|
24190
24601
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
24191
24602
|
}
|
|
24192
24603
|
const config = await readConfig();
|
|
24193
|
-
const projectMd =
|
|
24604
|
+
const projectMd = resolve55(config.defaultProjectDir, options.project, "project.md");
|
|
24194
24605
|
if (!await fileExists(projectMd)) {
|
|
24195
24606
|
throw new Error(`Project "${options.project}" not found.`);
|
|
24196
24607
|
}
|
|
@@ -24513,7 +24924,7 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
24513
24924
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
24514
24925
|
);
|
|
24515
24926
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
24516
|
-
await ensureDir(
|
|
24927
|
+
await ensureDir(resolve55(todosPath, "archive"));
|
|
24517
24928
|
let archContent = "";
|
|
24518
24929
|
if (await fileExists(archFile)) {
|
|
24519
24930
|
archContent = await readFile35(archFile, "utf-8");
|
|
@@ -24743,13 +25154,13 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
24743
25154
|
}
|
|
24744
25155
|
const planDir = todoPlanDir(todosPath, workspace, id);
|
|
24745
25156
|
await ensureDir(planDir);
|
|
24746
|
-
const { readdir:
|
|
24747
|
-
const existingFiles = (await
|
|
25157
|
+
const { readdir: readdir30 } = await import("fs/promises");
|
|
25158
|
+
const existingFiles = (await readdir30(planDir).catch(() => [])).filter(
|
|
24748
25159
|
(f) => /^plan(?:-v\d+)?\.md$/.test(f)
|
|
24749
25160
|
);
|
|
24750
25161
|
let target;
|
|
24751
25162
|
if (existingFiles.length === 0) {
|
|
24752
|
-
target =
|
|
25163
|
+
target = resolve55(planDir, "plan.md");
|
|
24753
25164
|
} else {
|
|
24754
25165
|
const versions = /* @__PURE__ */ new Set();
|
|
24755
25166
|
for (const f of existingFiles) {
|
|
@@ -24759,7 +25170,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
24759
25170
|
}
|
|
24760
25171
|
let n = 2;
|
|
24761
25172
|
while (versions.has(n)) n++;
|
|
24762
|
-
target =
|
|
25173
|
+
target = resolve55(planDir, `plan-v${n}.md`);
|
|
24763
25174
|
}
|
|
24764
25175
|
if (!await fileExists(target)) {
|
|
24765
25176
|
const stub = `---
|
|
@@ -24829,10 +25240,10 @@ async function moveTodo(id, options) {
|
|
|
24829
25240
|
if (await fileExists(newPlanDir)) {
|
|
24830
25241
|
throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
|
|
24831
25242
|
}
|
|
24832
|
-
const { rename:
|
|
24833
|
-
const { dirname:
|
|
24834
|
-
await
|
|
24835
|
-
await
|
|
25243
|
+
const { rename: rename11, mkdir: mkdir11 } = await import("fs/promises");
|
|
25244
|
+
const { dirname: dirname23 } = await import("path");
|
|
25245
|
+
await mkdir11(dirname23(newPlanDir), { recursive: true });
|
|
25246
|
+
await rename11(item.planDir, newPlanDir);
|
|
24836
25247
|
item.planDir = newPlanDir;
|
|
24837
25248
|
}
|
|
24838
25249
|
sourceChecklist.items.splice(idx, 1);
|
|
@@ -24949,23 +25360,23 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
24949
25360
|
// src/commands/doctor.ts
|
|
24950
25361
|
import { Command as Command4 } from "commander";
|
|
24951
25362
|
import { readFile as readFile43 } from "fs/promises";
|
|
24952
|
-
import { isAbsolute as isAbsolute10, resolve as
|
|
25363
|
+
import { isAbsolute as isAbsolute10, resolve as resolve68 } from "path";
|
|
24953
25364
|
|
|
24954
25365
|
// src/utils/doctor/index.ts
|
|
24955
25366
|
import { fileURLToPath as fileURLToPath11 } from "url";
|
|
24956
25367
|
import { readFile as readFile42 } from "fs/promises";
|
|
24957
|
-
import { dirname as
|
|
25368
|
+
import { dirname as dirname20, join as join13 } from "path";
|
|
24958
25369
|
|
|
24959
25370
|
// src/utils/doctor/context.ts
|
|
24960
25371
|
init_config2();
|
|
24961
25372
|
init_paths();
|
|
24962
25373
|
init_fs();
|
|
24963
25374
|
import Database4 from "better-sqlite3";
|
|
24964
|
-
import { resolve as
|
|
25375
|
+
import { resolve as resolve56 } from "path";
|
|
24965
25376
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
24966
25377
|
const config = await readConfig();
|
|
24967
25378
|
const root = syntaurRoot();
|
|
24968
|
-
const dbPath =
|
|
25379
|
+
const dbPath = resolve56(root, "syntaur.db");
|
|
24969
25380
|
let db5 = null;
|
|
24970
25381
|
let dbError = null;
|
|
24971
25382
|
if (await fileExists(dbPath)) {
|
|
@@ -24999,10 +25410,10 @@ function closeCheckContext(ctx) {
|
|
|
24999
25410
|
// src/utils/doctor/checks/env.ts
|
|
25000
25411
|
init_fs();
|
|
25001
25412
|
init_paths();
|
|
25002
|
-
import { resolve as
|
|
25003
|
-
import { readFile as readFile36, stat as
|
|
25413
|
+
import { resolve as resolve57, isAbsolute as isAbsolute8 } from "path";
|
|
25414
|
+
import { readFile as readFile36, stat as stat4 } from "fs/promises";
|
|
25004
25415
|
import { fileURLToPath as fileURLToPath10 } from "url";
|
|
25005
|
-
import { dirname as
|
|
25416
|
+
import { dirname as dirname18, join as join10 } from "path";
|
|
25006
25417
|
var CATEGORY = "env";
|
|
25007
25418
|
var syntaurRootExists = {
|
|
25008
25419
|
id: "env.syntaur-root-exists",
|
|
@@ -25010,7 +25421,7 @@ var syntaurRootExists = {
|
|
|
25010
25421
|
title: "~/.syntaur/ directory exists",
|
|
25011
25422
|
async run(ctx) {
|
|
25012
25423
|
try {
|
|
25013
|
-
const s = await
|
|
25424
|
+
const s = await stat4(ctx.syntaurRoot);
|
|
25014
25425
|
if (!s.isDirectory()) {
|
|
25015
25426
|
return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
|
|
25016
25427
|
ctx.syntaurRoot
|
|
@@ -25040,7 +25451,7 @@ var configValid = {
|
|
|
25040
25451
|
category: CATEGORY,
|
|
25041
25452
|
title: "~/.syntaur/config.md is valid",
|
|
25042
25453
|
async run(ctx) {
|
|
25043
|
-
const configPath =
|
|
25454
|
+
const configPath = resolve57(ctx.syntaurRoot, "config.md");
|
|
25044
25455
|
if (!await fileExists(configPath)) {
|
|
25045
25456
|
return {
|
|
25046
25457
|
id: this.id,
|
|
@@ -25153,8 +25564,8 @@ function detectNestedParseMismatch(fmBlock, config) {
|
|
|
25153
25564
|
["integrations.codexMarketplacePath", config.integrations.codexMarketplacePath]
|
|
25154
25565
|
];
|
|
25155
25566
|
for (const [dotted, parsedValue] of integrationChecks2) {
|
|
25156
|
-
const
|
|
25157
|
-
if (
|
|
25567
|
+
const raw2 = readNestedField(fmBlock, dotted);
|
|
25568
|
+
if (raw2 !== null && raw2 !== "null" && raw2 !== "" && parsedValue === null) {
|
|
25158
25569
|
return { field: dotted, parent: "integrations" };
|
|
25159
25570
|
}
|
|
25160
25571
|
}
|
|
@@ -25164,8 +25575,8 @@ function detectNestedParseMismatch(fmBlock, config) {
|
|
|
25164
25575
|
["backup.lastRestore", config.backup?.lastRestore ?? null]
|
|
25165
25576
|
];
|
|
25166
25577
|
for (const [dotted, parsedValue] of backupFields) {
|
|
25167
|
-
const
|
|
25168
|
-
if (
|
|
25578
|
+
const raw2 = readNestedField(fmBlock, dotted);
|
|
25579
|
+
if (raw2 !== null && raw2 !== "null" && raw2 !== "" && parsedValue === null) {
|
|
25169
25580
|
return { field: dotted, parent: "backup" };
|
|
25170
25581
|
}
|
|
25171
25582
|
}
|
|
@@ -25203,11 +25614,11 @@ function readNestedField(fmBlock, dotted) {
|
|
|
25203
25614
|
if (colonIdx < 0) continue;
|
|
25204
25615
|
const lineKey = stripped.slice(0, colonIdx).trim();
|
|
25205
25616
|
if (lineKey !== key) continue;
|
|
25206
|
-
const
|
|
25207
|
-
if (
|
|
25208
|
-
return
|
|
25617
|
+
const raw2 = stripped.slice(colonIdx + 1).trim();
|
|
25618
|
+
if (raw2.startsWith('"') && raw2.endsWith('"') || raw2.startsWith("'") && raw2.endsWith("'")) {
|
|
25619
|
+
return raw2.slice(1, -1);
|
|
25209
25620
|
}
|
|
25210
|
-
return
|
|
25621
|
+
return raw2;
|
|
25211
25622
|
}
|
|
25212
25623
|
return null;
|
|
25213
25624
|
}
|
|
@@ -25319,28 +25730,28 @@ function err(check, detail, affected) {
|
|
|
25319
25730
|
};
|
|
25320
25731
|
}
|
|
25321
25732
|
async function readEngineMin() {
|
|
25322
|
-
const
|
|
25323
|
-
if (!
|
|
25324
|
-
const engine =
|
|
25733
|
+
const raw2 = await readLocalPkg();
|
|
25734
|
+
if (!raw2) return null;
|
|
25735
|
+
const engine = raw2.engines?.node;
|
|
25325
25736
|
if (typeof engine !== "string") return null;
|
|
25326
25737
|
const match = engine.match(/(\d+(?:\.\d+){0,2})/);
|
|
25327
25738
|
return match ? match[1] : null;
|
|
25328
25739
|
}
|
|
25329
25740
|
async function readLocalVersion() {
|
|
25330
|
-
const
|
|
25331
|
-
return typeof
|
|
25741
|
+
const raw2 = await readLocalPkg();
|
|
25742
|
+
return typeof raw2?.version === "string" ? raw2.version : null;
|
|
25332
25743
|
}
|
|
25333
25744
|
async function readLocalPkg() {
|
|
25334
25745
|
try {
|
|
25335
25746
|
const here = fileURLToPath10(import.meta.url);
|
|
25336
|
-
let dir =
|
|
25747
|
+
let dir = dirname18(here);
|
|
25337
25748
|
for (let i = 0; i < 6; i++) {
|
|
25338
25749
|
const candidate = join10(dir, "package.json");
|
|
25339
25750
|
try {
|
|
25340
25751
|
const text = await readFile36(candidate, "utf-8");
|
|
25341
25752
|
return JSON.parse(text);
|
|
25342
25753
|
} catch {
|
|
25343
|
-
dir =
|
|
25754
|
+
dir = dirname18(dir);
|
|
25344
25755
|
}
|
|
25345
25756
|
}
|
|
25346
25757
|
return null;
|
|
@@ -25367,12 +25778,12 @@ async function fetchLatestNpmVersion2(pkg, timeoutMs) {
|
|
|
25367
25778
|
function readTopLevelField(fmBlock, key) {
|
|
25368
25779
|
const match = fmBlock.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
25369
25780
|
if (!match) return null;
|
|
25370
|
-
const
|
|
25371
|
-
if (
|
|
25372
|
-
if (
|
|
25373
|
-
return
|
|
25781
|
+
const raw2 = match[1].trim();
|
|
25782
|
+
if (raw2 === "") return null;
|
|
25783
|
+
if (raw2.startsWith('"') && raw2.endsWith('"') || raw2.startsWith("'") && raw2.endsWith("'")) {
|
|
25784
|
+
return raw2.slice(1, -1);
|
|
25374
25785
|
}
|
|
25375
|
-
return
|
|
25786
|
+
return raw2;
|
|
25376
25787
|
}
|
|
25377
25788
|
function versionGte(a, b) {
|
|
25378
25789
|
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -25389,8 +25800,8 @@ function versionGte(a, b) {
|
|
|
25389
25800
|
|
|
25390
25801
|
// src/utils/doctor/checks/structure.ts
|
|
25391
25802
|
init_fs();
|
|
25392
|
-
import { resolve as
|
|
25393
|
-
import { readdir as
|
|
25803
|
+
import { resolve as resolve58 } from "path";
|
|
25804
|
+
import { readdir as readdir19, stat as stat5 } from "fs/promises";
|
|
25394
25805
|
var CATEGORY2 = "structure";
|
|
25395
25806
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
25396
25807
|
"projects",
|
|
@@ -25409,7 +25820,7 @@ var projectsDir = {
|
|
|
25409
25820
|
category: CATEGORY2,
|
|
25410
25821
|
title: "projects/ directory exists",
|
|
25411
25822
|
async run(ctx) {
|
|
25412
|
-
const p =
|
|
25823
|
+
const p = resolve58(ctx.syntaurRoot, "projects");
|
|
25413
25824
|
if (!await fileExists(p)) {
|
|
25414
25825
|
return {
|
|
25415
25826
|
id: this.id,
|
|
@@ -25434,7 +25845,7 @@ var playbooksDir2 = {
|
|
|
25434
25845
|
category: CATEGORY2,
|
|
25435
25846
|
title: "playbooks/ directory exists",
|
|
25436
25847
|
async run(ctx) {
|
|
25437
|
-
const p =
|
|
25848
|
+
const p = resolve58(ctx.syntaurRoot, "playbooks");
|
|
25438
25849
|
if (!await fileExists(p)) {
|
|
25439
25850
|
return {
|
|
25440
25851
|
id: this.id,
|
|
@@ -25459,7 +25870,7 @@ var todosDirValid = {
|
|
|
25459
25870
|
category: CATEGORY2,
|
|
25460
25871
|
title: "todos/ directory is readable (if present)",
|
|
25461
25872
|
async run(ctx) {
|
|
25462
|
-
const p =
|
|
25873
|
+
const p = resolve58(ctx.syntaurRoot, "todos");
|
|
25463
25874
|
if (!await fileExists(p)) {
|
|
25464
25875
|
return {
|
|
25465
25876
|
id: this.id,
|
|
@@ -25470,7 +25881,7 @@ var todosDirValid = {
|
|
|
25470
25881
|
autoFixable: false
|
|
25471
25882
|
};
|
|
25472
25883
|
}
|
|
25473
|
-
const s = await
|
|
25884
|
+
const s = await stat5(p);
|
|
25474
25885
|
if (!s.isDirectory()) {
|
|
25475
25886
|
return {
|
|
25476
25887
|
id: this.id,
|
|
@@ -25490,7 +25901,7 @@ var serversDirValid = {
|
|
|
25490
25901
|
category: CATEGORY2,
|
|
25491
25902
|
title: "servers/ directory is readable (if present)",
|
|
25492
25903
|
async run(ctx) {
|
|
25493
|
-
const p =
|
|
25904
|
+
const p = resolve58(ctx.syntaurRoot, "servers");
|
|
25494
25905
|
if (!await fileExists(p)) {
|
|
25495
25906
|
return {
|
|
25496
25907
|
id: this.id,
|
|
@@ -25501,7 +25912,7 @@ var serversDirValid = {
|
|
|
25501
25912
|
autoFixable: false
|
|
25502
25913
|
};
|
|
25503
25914
|
}
|
|
25504
|
-
const s = await
|
|
25915
|
+
const s = await stat5(p);
|
|
25505
25916
|
if (!s.isDirectory()) {
|
|
25506
25917
|
return {
|
|
25507
25918
|
id: this.id,
|
|
@@ -25521,7 +25932,7 @@ var knownFilesRecognized = {
|
|
|
25521
25932
|
category: CATEGORY2,
|
|
25522
25933
|
title: "No unexpected top-level entries under ~/.syntaur/",
|
|
25523
25934
|
async run(ctx) {
|
|
25524
|
-
const entries = await
|
|
25935
|
+
const entries = await readdir19(ctx.syntaurRoot, { withFileTypes: true });
|
|
25525
25936
|
const unexpected = [];
|
|
25526
25937
|
for (const e of entries) {
|
|
25527
25938
|
if (e.name.startsWith(".")) continue;
|
|
@@ -25535,7 +25946,7 @@ var knownFilesRecognized = {
|
|
|
25535
25946
|
title: this.title,
|
|
25536
25947
|
status: "warn",
|
|
25537
25948
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
25538
|
-
affected: unexpected.map((n) =>
|
|
25949
|
+
affected: unexpected.map((n) => resolve58(ctx.syntaurRoot, n)),
|
|
25539
25950
|
remediation: {
|
|
25540
25951
|
kind: "manual",
|
|
25541
25952
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -25564,8 +25975,8 @@ function pass2(check) {
|
|
|
25564
25975
|
|
|
25565
25976
|
// src/utils/doctor/checks/project.ts
|
|
25566
25977
|
init_fs();
|
|
25567
|
-
import { resolve as
|
|
25568
|
-
import { readdir as
|
|
25978
|
+
import { resolve as resolve59 } from "path";
|
|
25979
|
+
import { readdir as readdir20, stat as stat6 } from "fs/promises";
|
|
25569
25980
|
var CATEGORY3 = "project";
|
|
25570
25981
|
var REQUIRED_PROJECT_FILES = [
|
|
25571
25982
|
"project.md",
|
|
@@ -25589,15 +26000,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
|
|
|
25589
26000
|
async function listProjects2(ctx) {
|
|
25590
26001
|
const dir = ctx.config.defaultProjectDir;
|
|
25591
26002
|
if (!await fileExists(dir)) return [];
|
|
25592
|
-
const entries = await
|
|
26003
|
+
const entries = await readdir20(dir, { withFileTypes: true });
|
|
25593
26004
|
const result = [];
|
|
25594
26005
|
for (const e of entries) {
|
|
25595
26006
|
if (!e.isDirectory()) continue;
|
|
25596
26007
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
25597
|
-
const projectDir =
|
|
26008
|
+
const projectDir = resolve59(dir, e.name);
|
|
25598
26009
|
let looksLikeProject = false;
|
|
25599
26010
|
for (const marker of PROJECT_MARKERS) {
|
|
25600
|
-
if (await fileExists(
|
|
26011
|
+
if (await fileExists(resolve59(projectDir, marker))) {
|
|
25601
26012
|
looksLikeProject = true;
|
|
25602
26013
|
break;
|
|
25603
26014
|
}
|
|
@@ -25616,7 +26027,7 @@ var requiredFiles = {
|
|
|
25616
26027
|
for (const projectDir of projects) {
|
|
25617
26028
|
const missing = [];
|
|
25618
26029
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
25619
|
-
const p =
|
|
26030
|
+
const p = resolve59(projectDir, rel);
|
|
25620
26031
|
if (!await fileExists(p)) missing.push(rel);
|
|
25621
26032
|
}
|
|
25622
26033
|
if (missing.length === 0) continue;
|
|
@@ -25626,7 +26037,7 @@ var requiredFiles = {
|
|
|
25626
26037
|
title: this.title,
|
|
25627
26038
|
status: "error",
|
|
25628
26039
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
25629
|
-
affected: missing.map((m) =>
|
|
26040
|
+
affected: missing.map((m) => resolve59(projectDir, m)),
|
|
25630
26041
|
remediation: {
|
|
25631
26042
|
kind: "manual",
|
|
25632
26043
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -25649,9 +26060,9 @@ var manifestStale = {
|
|
|
25649
26060
|
const projects = await listProjects2(ctx);
|
|
25650
26061
|
const results = [];
|
|
25651
26062
|
for (const projectDir of projects) {
|
|
25652
|
-
const manifestPath =
|
|
26063
|
+
const manifestPath = resolve59(projectDir, "manifest.md");
|
|
25653
26064
|
if (!await fileExists(manifestPath)) continue;
|
|
25654
|
-
const manifestMtime = (await
|
|
26065
|
+
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
25655
26066
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
25656
26067
|
if (newestAssignment === 0) continue;
|
|
25657
26068
|
if (newestAssignment > manifestMtime) {
|
|
@@ -25683,7 +26094,7 @@ var orphanFiles = {
|
|
|
25683
26094
|
const projects = await listProjects2(ctx);
|
|
25684
26095
|
const results = [];
|
|
25685
26096
|
for (const projectDir of projects) {
|
|
25686
|
-
const entries = await
|
|
26097
|
+
const entries = await readdir20(projectDir, { withFileTypes: true });
|
|
25687
26098
|
const orphans = [];
|
|
25688
26099
|
for (const e of entries) {
|
|
25689
26100
|
if (e.name.startsWith(".")) continue;
|
|
@@ -25698,7 +26109,7 @@ var orphanFiles = {
|
|
|
25698
26109
|
title: this.title,
|
|
25699
26110
|
status: "warn",
|
|
25700
26111
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
25701
|
-
affected: orphans.map((o) =>
|
|
26112
|
+
affected: orphans.map((o) => resolve59(projectDir, o)),
|
|
25702
26113
|
autoFixable: false
|
|
25703
26114
|
});
|
|
25704
26115
|
}
|
|
@@ -25708,20 +26119,20 @@ var orphanFiles = {
|
|
|
25708
26119
|
};
|
|
25709
26120
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
25710
26121
|
async function newestAssignmentMtime(projectDir) {
|
|
25711
|
-
const assignmentsRoot =
|
|
26122
|
+
const assignmentsRoot = resolve59(projectDir, "assignments");
|
|
25712
26123
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
25713
26124
|
let newest = 0;
|
|
25714
26125
|
let entries;
|
|
25715
26126
|
try {
|
|
25716
|
-
entries = await
|
|
26127
|
+
entries = await readdir20(assignmentsRoot, { withFileTypes: true });
|
|
25717
26128
|
} catch {
|
|
25718
26129
|
return 0;
|
|
25719
26130
|
}
|
|
25720
26131
|
for (const e of entries) {
|
|
25721
26132
|
if (!e.isDirectory()) continue;
|
|
25722
|
-
const assignmentMd =
|
|
26133
|
+
const assignmentMd = resolve59(assignmentsRoot, e.name, "assignment.md");
|
|
25723
26134
|
try {
|
|
25724
|
-
const s = await
|
|
26135
|
+
const s = await stat6(assignmentMd);
|
|
25725
26136
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
25726
26137
|
} catch {
|
|
25727
26138
|
}
|
|
@@ -25743,8 +26154,8 @@ init_fs();
|
|
|
25743
26154
|
init_parser();
|
|
25744
26155
|
init_types();
|
|
25745
26156
|
init_paths();
|
|
25746
|
-
import { resolve as
|
|
25747
|
-
import { readFile as readFile37, readdir as
|
|
26157
|
+
import { resolve as resolve60 } from "path";
|
|
26158
|
+
import { readFile as readFile37, readdir as readdir21 } from "fs/promises";
|
|
25748
26159
|
var CATEGORY4 = "assignment";
|
|
25749
26160
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
25750
26161
|
var PRE_WORKSPACE_STATUSES = /* @__PURE__ */ new Set([
|
|
@@ -25838,7 +26249,7 @@ var invalidStatus = {
|
|
|
25838
26249
|
const allowed = configuredStatuses(ctx);
|
|
25839
26250
|
const results = [];
|
|
25840
26251
|
for (const a of withAssignmentMd) {
|
|
25841
|
-
const path =
|
|
26252
|
+
const path = resolve60(a.assignmentDir, "assignment.md");
|
|
25842
26253
|
const parsed = await parseSafe2(path);
|
|
25843
26254
|
if (!parsed) continue;
|
|
25844
26255
|
if (!allowed.has(parsed.status)) {
|
|
@@ -25871,7 +26282,7 @@ var workspaceMissing = {
|
|
|
25871
26282
|
const terminal = terminalStatuses(ctx);
|
|
25872
26283
|
const results = [];
|
|
25873
26284
|
for (const a of withAssignmentMd) {
|
|
25874
|
-
const path =
|
|
26285
|
+
const path = resolve60(a.assignmentDir, "assignment.md");
|
|
25875
26286
|
const parsed = await parseSafe2(path);
|
|
25876
26287
|
if (!parsed) continue;
|
|
25877
26288
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -25918,12 +26329,12 @@ var requiredFilesByStatus = {
|
|
|
25918
26329
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
25919
26330
|
const results = [];
|
|
25920
26331
|
for (const a of withAssignmentMd) {
|
|
25921
|
-
const assignmentPath =
|
|
26332
|
+
const assignmentPath = resolve60(a.assignmentDir, "assignment.md");
|
|
25922
26333
|
const parsed = await parseSafe2(assignmentPath);
|
|
25923
26334
|
if (!parsed) continue;
|
|
25924
26335
|
const missing = [];
|
|
25925
26336
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
25926
|
-
const handoffPath =
|
|
26337
|
+
const handoffPath = resolve60(a.assignmentDir, "handoff.md");
|
|
25927
26338
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
25928
26339
|
}
|
|
25929
26340
|
if (missing.length === 0) continue;
|
|
@@ -25933,7 +26344,7 @@ var requiredFilesByStatus = {
|
|
|
25933
26344
|
title: this.title,
|
|
25934
26345
|
status: "warn",
|
|
25935
26346
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
25936
|
-
affected: missing.map((m) =>
|
|
26347
|
+
affected: missing.map((m) => resolve60(a.assignmentDir, m)),
|
|
25937
26348
|
remediation: {
|
|
25938
26349
|
kind: "manual",
|
|
25939
26350
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -25956,7 +26367,7 @@ var companionFilesScaffolded = {
|
|
|
25956
26367
|
for (const a of withAssignmentMd) {
|
|
25957
26368
|
const missing = [];
|
|
25958
26369
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
25959
|
-
if (!await fileExists(
|
|
26370
|
+
if (!await fileExists(resolve60(a.assignmentDir, filename))) {
|
|
25960
26371
|
missing.push(filename);
|
|
25961
26372
|
}
|
|
25962
26373
|
}
|
|
@@ -25968,7 +26379,7 @@ var companionFilesScaffolded = {
|
|
|
25968
26379
|
title: this.title,
|
|
25969
26380
|
status: "warn",
|
|
25970
26381
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
25971
|
-
affected: missing.map((m) =>
|
|
26382
|
+
affected: missing.map((m) => resolve60(a.assignmentDir, m)),
|
|
25972
26383
|
remediation: {
|
|
25973
26384
|
kind: "manual",
|
|
25974
26385
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -26001,7 +26412,7 @@ var typeDefinition = {
|
|
|
26001
26412
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
26002
26413
|
const results = [];
|
|
26003
26414
|
for (const a of withAssignmentMd) {
|
|
26004
|
-
const path =
|
|
26415
|
+
const path = resolve60(a.assignmentDir, "assignment.md");
|
|
26005
26416
|
const parsed = await parseSafe2(path);
|
|
26006
26417
|
if (!parsed) continue;
|
|
26007
26418
|
if (!parsed.type) continue;
|
|
@@ -26035,7 +26446,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
26035
26446
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
26036
26447
|
const results = [];
|
|
26037
26448
|
for (const a of withAssignmentMd) {
|
|
26038
|
-
const path =
|
|
26449
|
+
const path = resolve60(a.assignmentDir, "assignment.md");
|
|
26039
26450
|
const parsed = await parseSafe2(path);
|
|
26040
26451
|
if (!parsed) continue;
|
|
26041
26452
|
if (a.standalone) {
|
|
@@ -26086,17 +26497,17 @@ var draftMissingObjective = {
|
|
|
26086
26497
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
26087
26498
|
const results = [];
|
|
26088
26499
|
for (const a of withAssignmentMd) {
|
|
26089
|
-
const path =
|
|
26500
|
+
const path = resolve60(a.assignmentDir, "assignment.md");
|
|
26090
26501
|
const parsed = await parseSafe2(path);
|
|
26091
26502
|
if (!parsed) continue;
|
|
26092
26503
|
if (parsed.status !== "draft") continue;
|
|
26093
|
-
let
|
|
26504
|
+
let raw2;
|
|
26094
26505
|
try {
|
|
26095
|
-
|
|
26506
|
+
raw2 = await readFile37(path, "utf-8");
|
|
26096
26507
|
} catch {
|
|
26097
26508
|
continue;
|
|
26098
26509
|
}
|
|
26099
|
-
if (!objectiveBodyIsEmpty(
|
|
26510
|
+
if (!objectiveBodyIsEmpty(raw2)) continue;
|
|
26100
26511
|
const label = a.standalone ? `standalone/${a.assignmentSlug}` : `${a.projectSlug}/${a.assignmentSlug}`;
|
|
26101
26512
|
results.push({
|
|
26102
26513
|
id: this.id,
|
|
@@ -26125,16 +26536,16 @@ var readyToImplementMissingPlan = {
|
|
|
26125
26536
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
26126
26537
|
const results = [];
|
|
26127
26538
|
for (const a of withAssignmentMd) {
|
|
26128
|
-
const path =
|
|
26539
|
+
const path = resolve60(a.assignmentDir, "assignment.md");
|
|
26129
26540
|
const parsed = await parseSafe2(path);
|
|
26130
26541
|
if (!parsed) continue;
|
|
26131
26542
|
if (parsed.status !== "ready_to_implement") continue;
|
|
26132
|
-
const entries = await
|
|
26543
|
+
const entries = await readdir21(a.assignmentDir).catch(() => []);
|
|
26133
26544
|
const planFiles = entries.filter((f) => /^plan(?:-v\d+)?\.md$/i.test(f));
|
|
26134
26545
|
let hasPlanContent = false;
|
|
26135
26546
|
for (const f of planFiles) {
|
|
26136
26547
|
try {
|
|
26137
|
-
const c2 = await readFile37(
|
|
26548
|
+
const c2 = await readFile37(resolve60(a.assignmentDir, f), "utf-8");
|
|
26138
26549
|
if (c2.trim().length > 0) {
|
|
26139
26550
|
hasPlanContent = true;
|
|
26140
26551
|
break;
|
|
@@ -26150,7 +26561,7 @@ var readyToImplementMissingPlan = {
|
|
|
26150
26561
|
title: this.title,
|
|
26151
26562
|
status: "warn",
|
|
26152
26563
|
detail: `${label} (status: ready_to_implement) has no plan.md or plan-v<N>.md`,
|
|
26153
|
-
affected: [
|
|
26564
|
+
affected: [resolve60(a.assignmentDir, "plan.md")],
|
|
26154
26565
|
remediation: {
|
|
26155
26566
|
kind: "manual",
|
|
26156
26567
|
suggestion: `Write a plan with '/plan-assignment' (or 'syntaur plan'), then re-mark ready_to_implement`,
|
|
@@ -26196,7 +26607,7 @@ function pass4(check, detail) {
|
|
|
26196
26607
|
|
|
26197
26608
|
// src/utils/doctor/checks/dashboard.ts
|
|
26198
26609
|
init_fs();
|
|
26199
|
-
import { resolve as
|
|
26610
|
+
import { resolve as resolve61 } from "path";
|
|
26200
26611
|
var CATEGORY5 = "dashboard";
|
|
26201
26612
|
var dbReachable = {
|
|
26202
26613
|
id: "dashboard.db-reachable",
|
|
@@ -26210,7 +26621,7 @@ var dbReachable = {
|
|
|
26210
26621
|
title: this.title,
|
|
26211
26622
|
status: "error",
|
|
26212
26623
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
26213
|
-
affected: [
|
|
26624
|
+
affected: [resolve61(ctx.syntaurRoot, "syntaur.db")],
|
|
26214
26625
|
remediation: {
|
|
26215
26626
|
kind: "manual",
|
|
26216
26627
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -26228,7 +26639,7 @@ var dbReachable = {
|
|
|
26228
26639
|
title: this.title,
|
|
26229
26640
|
status: "error",
|
|
26230
26641
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
26231
|
-
affected: [
|
|
26642
|
+
affected: [resolve61(ctx.syntaurRoot, "syntaur.db")],
|
|
26232
26643
|
autoFixable: false
|
|
26233
26644
|
};
|
|
26234
26645
|
}
|
|
@@ -26240,7 +26651,7 @@ var dbReachable = {
|
|
|
26240
26651
|
title: this.title,
|
|
26241
26652
|
status: "error",
|
|
26242
26653
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
26243
|
-
affected: [
|
|
26654
|
+
affected: [resolve61(ctx.syntaurRoot, "syntaur.db")],
|
|
26244
26655
|
autoFixable: false
|
|
26245
26656
|
};
|
|
26246
26657
|
}
|
|
@@ -26266,7 +26677,7 @@ var ghostSessions = {
|
|
|
26266
26677
|
const results = [];
|
|
26267
26678
|
for (const row of rows) {
|
|
26268
26679
|
if (!row.project_slug) continue;
|
|
26269
|
-
const projectPath =
|
|
26680
|
+
const projectPath = resolve61(projectsDir2, row.project_slug, "project.md");
|
|
26270
26681
|
if (!await fileExists(projectPath)) {
|
|
26271
26682
|
results.push({
|
|
26272
26683
|
id: this.id,
|
|
@@ -26285,7 +26696,7 @@ var ghostSessions = {
|
|
|
26285
26696
|
continue;
|
|
26286
26697
|
}
|
|
26287
26698
|
if (row.assignment_slug) {
|
|
26288
|
-
const assignmentPath =
|
|
26699
|
+
const assignmentPath = resolve61(
|
|
26289
26700
|
projectsDir2,
|
|
26290
26701
|
row.project_slug,
|
|
26291
26702
|
"assignments",
|
|
@@ -26337,8 +26748,8 @@ function skipped(check, reason) {
|
|
|
26337
26748
|
|
|
26338
26749
|
// src/utils/doctor/checks/integrations.ts
|
|
26339
26750
|
init_fs();
|
|
26340
|
-
import { resolve as
|
|
26341
|
-
import { readdir as
|
|
26751
|
+
import { resolve as resolve62, dirname as dirname19, basename as basename6 } from "path";
|
|
26752
|
+
import { readdir as readdir22, readFile as readFile38 } from "fs/promises";
|
|
26342
26753
|
import { homedir as homedir10 } from "os";
|
|
26343
26754
|
var CATEGORY6 = "integrations";
|
|
26344
26755
|
var claudePluginLinked = {
|
|
@@ -26401,7 +26812,7 @@ var backupConfigured = {
|
|
|
26401
26812
|
if (ctx.config.backup?.repo) return pass6(this);
|
|
26402
26813
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
26403
26814
|
if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
|
|
26404
|
-
const entries = await
|
|
26815
|
+
const entries = await readdir22(projectsDir2, { withFileTypes: true });
|
|
26405
26816
|
const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
|
|
26406
26817
|
if (!hasProjects) return skipped2(this, "no projects yet");
|
|
26407
26818
|
return {
|
|
@@ -26420,11 +26831,11 @@ var backupConfigured = {
|
|
|
26420
26831
|
}
|
|
26421
26832
|
};
|
|
26422
26833
|
async function readKnownMarketplaces() {
|
|
26423
|
-
const path =
|
|
26834
|
+
const path = resolve62(homedir10(), ".claude", "plugins", "known_marketplaces.json");
|
|
26424
26835
|
if (!await fileExists(path)) return {};
|
|
26425
26836
|
try {
|
|
26426
|
-
const
|
|
26427
|
-
return JSON.parse(
|
|
26837
|
+
const raw2 = await readFile38(path, "utf-8");
|
|
26838
|
+
return JSON.parse(raw2);
|
|
26428
26839
|
} catch {
|
|
26429
26840
|
return {};
|
|
26430
26841
|
}
|
|
@@ -26439,8 +26850,8 @@ var claudeMarketplaceRegistered = {
|
|
|
26439
26850
|
if (!await fileExists(dir)) {
|
|
26440
26851
|
return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
|
|
26441
26852
|
}
|
|
26442
|
-
const pluginsParent =
|
|
26443
|
-
if (
|
|
26853
|
+
const pluginsParent = dirname19(dir);
|
|
26854
|
+
if (basename6(pluginsParent) !== "plugins") {
|
|
26444
26855
|
return {
|
|
26445
26856
|
id: this.id,
|
|
26446
26857
|
category: this.category,
|
|
@@ -26456,8 +26867,8 @@ var claudeMarketplaceRegistered = {
|
|
|
26456
26867
|
autoFixable: false
|
|
26457
26868
|
};
|
|
26458
26869
|
}
|
|
26459
|
-
const marketplaceRoot =
|
|
26460
|
-
const marketplaceManifest =
|
|
26870
|
+
const marketplaceRoot = dirname19(pluginsParent);
|
|
26871
|
+
const marketplaceManifest = resolve62(marketplaceRoot, ".claude-plugin", "marketplace.json");
|
|
26461
26872
|
if (!await fileExists(marketplaceManifest)) {
|
|
26462
26873
|
return {
|
|
26463
26874
|
id: this.id,
|
|
@@ -26488,7 +26899,7 @@ var claudeMarketplaceRegistered = {
|
|
|
26488
26899
|
autoFixable: false
|
|
26489
26900
|
};
|
|
26490
26901
|
}
|
|
26491
|
-
const marketplaceName = parsed.name ??
|
|
26902
|
+
const marketplaceName = parsed.name ?? basename6(marketplaceRoot);
|
|
26492
26903
|
const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
|
|
26493
26904
|
const known = await readKnownMarketplaces();
|
|
26494
26905
|
const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
|
|
@@ -26508,7 +26919,7 @@ var claudeMarketplaceRegistered = {
|
|
|
26508
26919
|
title: this.title,
|
|
26509
26920
|
status: "error",
|
|
26510
26921
|
detail: issues.join("; "),
|
|
26511
|
-
affected: [marketplaceManifest,
|
|
26922
|
+
affected: [marketplaceManifest, resolve62(homedir10(), ".claude", "plugins", "known_marketplaces.json")],
|
|
26512
26923
|
remediation: {
|
|
26513
26924
|
kind: "manual",
|
|
26514
26925
|
suggestion: "Re-run install-plugin to ensure both files are in sync.",
|
|
@@ -26548,7 +26959,7 @@ function skipped2(check, reason) {
|
|
|
26548
26959
|
init_fs();
|
|
26549
26960
|
init_parser();
|
|
26550
26961
|
init_types();
|
|
26551
|
-
import { resolve as
|
|
26962
|
+
import { resolve as resolve63 } from "path";
|
|
26552
26963
|
import { readFile as readFile39 } from "fs/promises";
|
|
26553
26964
|
var CATEGORY7 = "workspace";
|
|
26554
26965
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
@@ -26570,13 +26981,13 @@ function isStandaloneSession(ctx) {
|
|
|
26570
26981
|
return !hasAnyAssignmentField(ctx) && !hasAnyBundleField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
26571
26982
|
}
|
|
26572
26983
|
async function loadContext(ctx) {
|
|
26573
|
-
const path =
|
|
26984
|
+
const path = resolve63(ctx.cwd, ".syntaur", "context.json");
|
|
26574
26985
|
if (!await fileExists(path)) {
|
|
26575
26986
|
return { data: null, path, exists: false, parseError: null };
|
|
26576
26987
|
}
|
|
26577
26988
|
try {
|
|
26578
|
-
const
|
|
26579
|
-
return { data: JSON.parse(
|
|
26989
|
+
const raw2 = await readFile39(path, "utf-8");
|
|
26990
|
+
return { data: JSON.parse(raw2), path, exists: true, parseError: null };
|
|
26580
26991
|
} catch (err2) {
|
|
26581
26992
|
return {
|
|
26582
26993
|
data: null,
|
|
@@ -26669,7 +27080,7 @@ var contextAssignmentResolves = {
|
|
|
26669
27080
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
26670
27081
|
if (isBundleContext(data)) return skipped3(this, "bundle context \u2014 no assignment to resolve");
|
|
26671
27082
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
26672
|
-
const assignmentMd =
|
|
27083
|
+
const assignmentMd = resolve63(data.assignmentDir, "assignment.md");
|
|
26673
27084
|
if (!await fileExists(assignmentMd)) {
|
|
26674
27085
|
return {
|
|
26675
27086
|
id: this.id,
|
|
@@ -26699,7 +27110,7 @@ var contextTerminal = {
|
|
|
26699
27110
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
26700
27111
|
if (isBundleContext(data)) return skipped3(this, "bundle context \u2014 no assignment to check");
|
|
26701
27112
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
26702
|
-
const assignmentMd =
|
|
27113
|
+
const assignmentMd = resolve63(data.assignmentDir, "assignment.md");
|
|
26703
27114
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
26704
27115
|
try {
|
|
26705
27116
|
const content = await readFile39(assignmentMd, "utf-8");
|
|
@@ -26849,30 +27260,30 @@ var agentChecks = [agentsResolvable];
|
|
|
26849
27260
|
init_config2();
|
|
26850
27261
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
26851
27262
|
import { readFile as readFile40 } from "fs/promises";
|
|
26852
|
-
import { resolve as
|
|
27263
|
+
import { resolve as resolve64 } from "path";
|
|
26853
27264
|
init_paths();
|
|
26854
27265
|
init_fs();
|
|
26855
27266
|
var CATEGORY9 = "terminal";
|
|
26856
27267
|
async function readRawTerminalKey() {
|
|
26857
|
-
const configPath =
|
|
27268
|
+
const configPath = resolve64(syntaurRoot(), "config.md");
|
|
26858
27269
|
if (!await fileExists(configPath)) return null;
|
|
26859
27270
|
const content = await readFile40(configPath, "utf-8");
|
|
26860
27271
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
26861
27272
|
if (!fmMatch) return null;
|
|
26862
27273
|
const line = fmMatch[1].split("\n").find((l) => /^terminal:\s*/.test(l));
|
|
26863
27274
|
if (!line) return null;
|
|
26864
|
-
const
|
|
26865
|
-
if (
|
|
26866
|
-
const unquoted = /^"(.*)"$|^'(.*)'$/.exec(
|
|
26867
|
-
return unquoted ? unquoted[1] ?? unquoted[2] :
|
|
27275
|
+
const raw2 = line.replace(/^terminal:\s*/, "").trim();
|
|
27276
|
+
if (raw2.length === 0) return null;
|
|
27277
|
+
const unquoted = /^"(.*)"$|^'(.*)'$/.exec(raw2);
|
|
27278
|
+
return unquoted ? unquoted[1] ?? unquoted[2] : raw2;
|
|
26868
27279
|
}
|
|
26869
27280
|
var terminalValueValid = {
|
|
26870
27281
|
id: "terminal.value-valid",
|
|
26871
27282
|
category: CATEGORY9,
|
|
26872
27283
|
title: "Configured terminal value is recognized",
|
|
26873
27284
|
async run(ctx) {
|
|
26874
|
-
const
|
|
26875
|
-
if (
|
|
27285
|
+
const raw2 = await readRawTerminalKey();
|
|
27286
|
+
if (raw2 === null) {
|
|
26876
27287
|
return {
|
|
26877
27288
|
id: this.id,
|
|
26878
27289
|
category: this.category,
|
|
@@ -26882,13 +27293,13 @@ var terminalValueValid = {
|
|
|
26882
27293
|
autoFixable: false
|
|
26883
27294
|
};
|
|
26884
27295
|
}
|
|
26885
|
-
if (!TERMINAL_CHOICES.includes(
|
|
27296
|
+
if (!TERMINAL_CHOICES.includes(raw2)) {
|
|
26886
27297
|
return {
|
|
26887
27298
|
id: this.id,
|
|
26888
27299
|
category: this.category,
|
|
26889
27300
|
title: this.title,
|
|
26890
27301
|
status: "warn",
|
|
26891
|
-
detail: `unknown terminal "${
|
|
27302
|
+
detail: `unknown terminal "${raw2}" in ~/.syntaur/config.md`,
|
|
26892
27303
|
remediation: {
|
|
26893
27304
|
kind: "manual",
|
|
26894
27305
|
suggestion: `Set \`terminal:\` in ~/.syntaur/config.md to one of: ${TERMINAL_CHOICES.join(", ")}`,
|
|
@@ -26902,7 +27313,7 @@ var terminalValueValid = {
|
|
|
26902
27313
|
category: this.category,
|
|
26903
27314
|
title: this.title,
|
|
26904
27315
|
status: "pass",
|
|
26905
|
-
detail: `terminal: ${
|
|
27316
|
+
detail: `terminal: ${raw2}`,
|
|
26906
27317
|
autoFixable: false
|
|
26907
27318
|
};
|
|
26908
27319
|
}
|
|
@@ -27006,13 +27417,13 @@ var terminalChecks = [
|
|
|
27006
27417
|
|
|
27007
27418
|
// src/utils/doctor/checks/skills.ts
|
|
27008
27419
|
init_fs();
|
|
27009
|
-
import { resolve as
|
|
27010
|
-
import { readdir as
|
|
27420
|
+
import { resolve as resolve65, join as join11 } from "path";
|
|
27421
|
+
import { readdir as readdir23, readFile as readFile41, lstat as lstat4 } from "fs/promises";
|
|
27011
27422
|
import { homedir as homedir11 } from "os";
|
|
27012
27423
|
var CATEGORY10 = "skills";
|
|
27013
27424
|
var skillTargets = [
|
|
27014
|
-
{ agent: "claude", dir:
|
|
27015
|
-
{ agent: "codex", dir:
|
|
27425
|
+
{ agent: "claude", dir: resolve65(homedir11(), ".claude", "skills"), label: "~/.claude/skills" },
|
|
27426
|
+
{ agent: "codex", dir: resolve65(homedir11(), ".codex", "skills"), label: "~/.codex/skills" }
|
|
27016
27427
|
];
|
|
27017
27428
|
var skillsDedupCheck = {
|
|
27018
27429
|
id: "skills.dedup",
|
|
@@ -27027,7 +27438,7 @@ var skillsDedupCheck = {
|
|
|
27027
27438
|
const present = [];
|
|
27028
27439
|
let entries;
|
|
27029
27440
|
try {
|
|
27030
|
-
entries = await
|
|
27441
|
+
entries = await readdir23(dir, { withFileTypes: true });
|
|
27031
27442
|
} catch {
|
|
27032
27443
|
continue;
|
|
27033
27444
|
}
|
|
@@ -27086,7 +27497,7 @@ var skillsChecks = [skillsDedupCheck];
|
|
|
27086
27497
|
|
|
27087
27498
|
// src/utils/doctor/checks/cross-agent.ts
|
|
27088
27499
|
init_fs();
|
|
27089
|
-
import { join as join12, resolve as
|
|
27500
|
+
import { join as join12, resolve as resolve66 } from "path";
|
|
27090
27501
|
var CATEGORY11 = "cross-agent";
|
|
27091
27502
|
async function countSyntaurSkills(dir) {
|
|
27092
27503
|
if (!await fileExists(dir)) return 0;
|
|
@@ -27125,7 +27536,7 @@ var crossAgentSkillsCheck = {
|
|
|
27125
27536
|
}
|
|
27126
27537
|
if (recorded && t.instructions) {
|
|
27127
27538
|
for (const f of t.instructions.files) {
|
|
27128
|
-
const p =
|
|
27539
|
+
const p = resolve66(ctx.cwd, f.path);
|
|
27129
27540
|
if (!await fileExists(p)) {
|
|
27130
27541
|
problems.push(`${t.displayName}: missing protocol file ${f.path} in cwd`);
|
|
27131
27542
|
affected.push(p);
|
|
@@ -27174,8 +27585,8 @@ var crossAgentChecks = [crossAgentSkillsCheck];
|
|
|
27174
27585
|
// src/utils/doctor/checks/bundles.ts
|
|
27175
27586
|
init_fs();
|
|
27176
27587
|
init_paths();
|
|
27177
|
-
import { resolve as
|
|
27178
|
-
import { readdir as
|
|
27588
|
+
import { resolve as resolve67 } from "path";
|
|
27589
|
+
import { readdir as readdir24 } from "fs/promises";
|
|
27179
27590
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
27180
27591
|
init_parser2();
|
|
27181
27592
|
var CATEGORY12 = "bundles";
|
|
@@ -27183,7 +27594,7 @@ async function listScopes(ctx) {
|
|
|
27183
27594
|
const out = [];
|
|
27184
27595
|
const td = todosDir();
|
|
27185
27596
|
if (await fileExists(td)) {
|
|
27186
|
-
const entries = await
|
|
27597
|
+
const entries = await readdir24(td).catch(() => []);
|
|
27187
27598
|
for (const f of entries) {
|
|
27188
27599
|
if (typeof f !== "string") continue;
|
|
27189
27600
|
if (!f.endsWith(".md") || f.endsWith("-log.md")) continue;
|
|
@@ -27199,12 +27610,12 @@ async function listScopes(ctx) {
|
|
|
27199
27610
|
}
|
|
27200
27611
|
}
|
|
27201
27612
|
if (await fileExists(ctx.config.defaultProjectDir)) {
|
|
27202
|
-
const projectEntries = await
|
|
27613
|
+
const projectEntries = await readdir24(ctx.config.defaultProjectDir, { withFileTypes: true }).catch(() => []);
|
|
27203
27614
|
for (const e of projectEntries) {
|
|
27204
27615
|
if (!e.isDirectory()) continue;
|
|
27205
27616
|
const slug = e.name;
|
|
27206
27617
|
if (typeof slug !== "string" || slug.startsWith(".")) continue;
|
|
27207
|
-
const projectMd =
|
|
27618
|
+
const projectMd = resolve67(ctx.config.defaultProjectDir, slug, "project.md");
|
|
27208
27619
|
if (!await fileExists(projectMd)) continue;
|
|
27209
27620
|
out.push({
|
|
27210
27621
|
scopeLabel: `project:${slug}`,
|
|
@@ -27493,14 +27904,14 @@ async function finalize(checks) {
|
|
|
27493
27904
|
async function readVersion() {
|
|
27494
27905
|
try {
|
|
27495
27906
|
const here = fileURLToPath11(import.meta.url);
|
|
27496
|
-
let dir =
|
|
27907
|
+
let dir = dirname20(here);
|
|
27497
27908
|
for (let i = 0; i < 6; i++) {
|
|
27498
27909
|
try {
|
|
27499
|
-
const
|
|
27500
|
-
const parsed = JSON.parse(
|
|
27910
|
+
const raw2 = await readFile42(join13(dir, "package.json"), "utf-8");
|
|
27911
|
+
const parsed = JSON.parse(raw2);
|
|
27501
27912
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
27502
27913
|
} catch {
|
|
27503
|
-
dir =
|
|
27914
|
+
dir = dirname20(dir);
|
|
27504
27915
|
}
|
|
27505
27916
|
}
|
|
27506
27917
|
return null;
|
|
@@ -27593,7 +28004,7 @@ var REQUIRED_WORKSPACE_FIELDS = [
|
|
|
27593
28004
|
];
|
|
27594
28005
|
var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
27595
28006
|
async function validateAssignmentFile(inputPath, cwd = process.cwd()) {
|
|
27596
|
-
const absolute = isAbsolute10(inputPath) ? inputPath :
|
|
28007
|
+
const absolute = isAbsolute10(inputPath) ? inputPath : resolve68(cwd, inputPath);
|
|
27597
28008
|
const errors = [];
|
|
27598
28009
|
const warnings = [];
|
|
27599
28010
|
if (!await fileExists(absolute)) {
|
|
@@ -27930,7 +28341,7 @@ init_uuid();
|
|
|
27930
28341
|
init_timestamp();
|
|
27931
28342
|
init_assignment_resolver();
|
|
27932
28343
|
init_templates();
|
|
27933
|
-
import { resolve as
|
|
28344
|
+
import { resolve as resolve69 } from "path";
|
|
27934
28345
|
import { readFile as readFile44 } from "fs/promises";
|
|
27935
28346
|
function shortId() {
|
|
27936
28347
|
return generateId().split("-")[0];
|
|
@@ -27961,7 +28372,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
27961
28372
|
if (!isValidSlug(target)) {
|
|
27962
28373
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
27963
28374
|
}
|
|
27964
|
-
assignmentDir =
|
|
28375
|
+
assignmentDir = resolve69(baseDir, options.project, "assignments", target);
|
|
27965
28376
|
assignmentRef = target;
|
|
27966
28377
|
} else {
|
|
27967
28378
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -27971,7 +28382,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
27971
28382
|
assignmentDir = resolved.assignmentDir;
|
|
27972
28383
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
27973
28384
|
}
|
|
27974
|
-
const commentsPath =
|
|
28385
|
+
const commentsPath = resolve69(assignmentDir, "comments.md");
|
|
27975
28386
|
const timestamp = nowTimestamp();
|
|
27976
28387
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
27977
28388
|
let currentContent;
|
|
@@ -28011,8 +28422,8 @@ ${entry}`;
|
|
|
28011
28422
|
}
|
|
28012
28423
|
|
|
28013
28424
|
// src/commands/capture.ts
|
|
28014
|
-
import { resolve as
|
|
28015
|
-
import { copyFile as copyFile3, mkdir as
|
|
28425
|
+
import { resolve as resolve73, relative as relative4, dirname as dirname21 } from "path";
|
|
28426
|
+
import { copyFile as copyFile3, mkdir as mkdir10, realpath as realpath2, rm as rm13, stat as stat9, writeFile as writeFile15 } from "fs/promises";
|
|
28016
28427
|
import { existsSync as existsSync6 } from "fs";
|
|
28017
28428
|
|
|
28018
28429
|
// src/utils/assignment-target.ts
|
|
@@ -28022,7 +28433,7 @@ init_config2();
|
|
|
28022
28433
|
init_slug();
|
|
28023
28434
|
init_assignment_resolver();
|
|
28024
28435
|
init_parser();
|
|
28025
|
-
import { resolve as
|
|
28436
|
+
import { resolve as resolve70 } from "path";
|
|
28026
28437
|
import { readFile as readFile45 } from "fs/promises";
|
|
28027
28438
|
var AssignmentTargetError = class extends Error {
|
|
28028
28439
|
};
|
|
@@ -28035,7 +28446,7 @@ function classifyContext(ctx) {
|
|
|
28035
28446
|
return "empty";
|
|
28036
28447
|
}
|
|
28037
28448
|
async function readAssignmentFrontmatterId(assignmentDir) {
|
|
28038
|
-
const path =
|
|
28449
|
+
const path = resolve70(assignmentDir, "assignment.md");
|
|
28039
28450
|
if (!await fileExists(path)) return null;
|
|
28040
28451
|
try {
|
|
28041
28452
|
const content = await readFile45(path, "utf-8");
|
|
@@ -28046,11 +28457,11 @@ async function readAssignmentFrontmatterId(assignmentDir) {
|
|
|
28046
28457
|
}
|
|
28047
28458
|
}
|
|
28048
28459
|
async function readContextJson(cwd) {
|
|
28049
|
-
const path =
|
|
28460
|
+
const path = resolve70(cwd, ".syntaur", "context.json");
|
|
28050
28461
|
if (!await fileExists(path)) return null;
|
|
28051
28462
|
try {
|
|
28052
|
-
const
|
|
28053
|
-
return JSON.parse(
|
|
28463
|
+
const raw2 = await readFile45(path, "utf-8");
|
|
28464
|
+
return JSON.parse(raw2);
|
|
28054
28465
|
} catch {
|
|
28055
28466
|
return null;
|
|
28056
28467
|
}
|
|
@@ -28070,15 +28481,15 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
28070
28481
|
if (!isValidSlug(input4)) {
|
|
28071
28482
|
throw new AssignmentTargetError(`Invalid assignment slug "${input4}".`);
|
|
28072
28483
|
}
|
|
28073
|
-
const projectDir =
|
|
28074
|
-
const projectMdPath =
|
|
28484
|
+
const projectDir = resolve70(baseDir, opts.project);
|
|
28485
|
+
const projectMdPath = resolve70(projectDir, "project.md");
|
|
28075
28486
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
28076
28487
|
throw new AssignmentTargetError(
|
|
28077
28488
|
`Project "${opts.project}" not found at ${projectDir}.`
|
|
28078
28489
|
);
|
|
28079
28490
|
}
|
|
28080
|
-
const assignmentDir =
|
|
28081
|
-
const assignmentMdPath2 =
|
|
28491
|
+
const assignmentDir = resolve70(projectDir, "assignments", input4);
|
|
28492
|
+
const assignmentMdPath2 = resolve70(assignmentDir, "assignment.md");
|
|
28082
28493
|
if (!await fileExists(assignmentMdPath2)) {
|
|
28083
28494
|
throw new AssignmentTargetError(
|
|
28084
28495
|
`Assignment "${input4}" not found in project "${opts.project}".`
|
|
@@ -28117,7 +28528,7 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
28117
28528
|
}
|
|
28118
28529
|
if (ctx.assignmentDir) {
|
|
28119
28530
|
const dir = expandHome(ctx.assignmentDir);
|
|
28120
|
-
const assignmentMdPath2 =
|
|
28531
|
+
const assignmentMdPath2 = resolve70(dir, "assignment.md");
|
|
28121
28532
|
if (!await fileExists(assignmentMdPath2)) {
|
|
28122
28533
|
throw new AssignmentTargetError(
|
|
28123
28534
|
`.syntaur/context.json points to a missing assignment dir: ${dir}.`
|
|
@@ -28146,8 +28557,8 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
28146
28557
|
`.syntaur/context.json contains invalid slugs: project="${ctx.projectSlug}" assignment="${ctx.assignmentSlug}".`
|
|
28147
28558
|
);
|
|
28148
28559
|
}
|
|
28149
|
-
const assignmentDir =
|
|
28150
|
-
const assignmentMdPath2 =
|
|
28560
|
+
const assignmentDir = resolve70(baseDir, ctx.projectSlug, "assignments", ctx.assignmentSlug);
|
|
28561
|
+
const assignmentMdPath2 = resolve70(assignmentDir, "assignment.md");
|
|
28151
28562
|
if (!await fileExists(assignmentMdPath2)) {
|
|
28152
28563
|
throw new AssignmentTargetError(
|
|
28153
28564
|
`.syntaur/context.json points to a missing assignment: ${assignmentDir}.`
|
|
@@ -28170,45 +28581,11 @@ async function resolveAssignmentTarget(input4, opts = {}) {
|
|
|
28170
28581
|
|
|
28171
28582
|
// src/commands/capture.ts
|
|
28172
28583
|
init_paths();
|
|
28173
|
-
|
|
28174
|
-
// src/utils/proof-artifact-id.ts
|
|
28175
|
-
import { randomBytes as randomBytes3 } from "crypto";
|
|
28176
|
-
function generateArtifactId() {
|
|
28177
|
-
const ts = Date.now().toString(36);
|
|
28178
|
-
const rand = randomBytes3(2).toString("hex");
|
|
28179
|
-
return `${ts}-${rand}`;
|
|
28180
|
-
}
|
|
28181
|
-
var EXTENSIONS = {
|
|
28182
|
-
screenshot: "png",
|
|
28183
|
-
video: "mp4",
|
|
28184
|
-
asciinema: "cast",
|
|
28185
|
-
http: "txt",
|
|
28186
|
-
text: "txt"
|
|
28187
|
-
};
|
|
28188
|
-
function extensionForKind(kind) {
|
|
28189
|
-
const ext = EXTENSIONS[kind];
|
|
28190
|
-
if (!ext) {
|
|
28191
|
-
throw new Error(`Unknown artifact kind: ${kind}`);
|
|
28192
|
-
}
|
|
28193
|
-
return ext;
|
|
28194
|
-
}
|
|
28195
|
-
var ARTIFACT_KINDS = [
|
|
28196
|
-
"screenshot",
|
|
28197
|
-
"video",
|
|
28198
|
-
"asciinema",
|
|
28199
|
-
"http",
|
|
28200
|
-
"text"
|
|
28201
|
-
];
|
|
28202
|
-
function isArtifactKind(value) {
|
|
28203
|
-
return typeof value === "string" && ARTIFACT_KINDS.includes(value);
|
|
28204
|
-
}
|
|
28205
|
-
|
|
28206
|
-
// src/commands/capture.ts
|
|
28207
28584
|
init_fs();
|
|
28208
28585
|
|
|
28209
28586
|
// src/utils/screencapture.ts
|
|
28210
28587
|
import { spawn as spawn7 } from "child_process";
|
|
28211
|
-
import { mkdtemp as mkdtemp2, rm as
|
|
28588
|
+
import { mkdtemp as mkdtemp2, rm as rm9, stat as stat7 } from "fs/promises";
|
|
28212
28589
|
import { tmpdir as tmpdir3 } from "os";
|
|
28213
28590
|
import { join as join14 } from "path";
|
|
28214
28591
|
function argsFor(mode, pngPath) {
|
|
@@ -28246,7 +28623,7 @@ async function captureScreenshot(mode) {
|
|
|
28246
28623
|
const tmpDir = await mkdtemp2(join14(tmpdir3(), "syntaur-screenshot-"));
|
|
28247
28624
|
const pngPath = join14(tmpDir, "shot.png");
|
|
28248
28625
|
const cleanup = async () => {
|
|
28249
|
-
await
|
|
28626
|
+
await rm9(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
28250
28627
|
});
|
|
28251
28628
|
};
|
|
28252
28629
|
try {
|
|
@@ -28266,7 +28643,7 @@ async function captureScreenshot(mode) {
|
|
|
28266
28643
|
}
|
|
28267
28644
|
let size = 0;
|
|
28268
28645
|
try {
|
|
28269
|
-
size = (await
|
|
28646
|
+
size = (await stat7(pngPath)).size;
|
|
28270
28647
|
} catch {
|
|
28271
28648
|
throw new Error("screencapture exited 0 but produced no image.");
|
|
28272
28649
|
}
|
|
@@ -28282,7 +28659,7 @@ async function captureScreenshot(mode) {
|
|
|
28282
28659
|
|
|
28283
28660
|
// src/utils/asciinema.ts
|
|
28284
28661
|
import { spawn as spawn8 } from "child_process";
|
|
28285
|
-
import { mkdtemp as mkdtemp3, readFile as readFile46, rm as
|
|
28662
|
+
import { mkdtemp as mkdtemp3, readFile as readFile46, rm as rm10 } from "fs/promises";
|
|
28286
28663
|
import { tmpdir as tmpdir4 } from "os";
|
|
28287
28664
|
import { join as join15 } from "path";
|
|
28288
28665
|
var SAFE_RE = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
@@ -28297,10 +28674,10 @@ function joinCommandForShell(argv) {
|
|
|
28297
28674
|
function hasRecordedData(text) {
|
|
28298
28675
|
const lines = text.split("\n");
|
|
28299
28676
|
for (let i = 1; i < lines.length; i++) {
|
|
28300
|
-
const
|
|
28301
|
-
if (
|
|
28302
|
-
if (
|
|
28303
|
-
if (/^\s*\[\s*[\d.eE+-]+\s*,\s*"([oi])"/.test(
|
|
28677
|
+
const raw2 = lines[i];
|
|
28678
|
+
if (raw2 === "") continue;
|
|
28679
|
+
if (raw2.startsWith("#")) continue;
|
|
28680
|
+
if (/^\s*\[\s*[\d.eE+-]+\s*,\s*"([oi])"/.test(raw2)) return true;
|
|
28304
28681
|
}
|
|
28305
28682
|
return false;
|
|
28306
28683
|
}
|
|
@@ -28324,7 +28701,7 @@ async function captureAsciinema(opts) {
|
|
|
28324
28701
|
const tmpDir = await mkdtemp3(join15(tmpdir4(), "syntaur-asciinema-"));
|
|
28325
28702
|
const castPath = join15(tmpDir, "session.cast");
|
|
28326
28703
|
const cleanup = async () => {
|
|
28327
|
-
await
|
|
28704
|
+
await rm10(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
28328
28705
|
});
|
|
28329
28706
|
};
|
|
28330
28707
|
const noopSigint = () => {
|
|
@@ -28373,36 +28750,36 @@ async function captureAsciinema(opts) {
|
|
|
28373
28750
|
// src/utils/recording.ts
|
|
28374
28751
|
init_paths();
|
|
28375
28752
|
import { spawn as spawn9 } from "child_process";
|
|
28376
|
-
import { mkdir as
|
|
28753
|
+
import { mkdir as mkdir9, mkdtemp as mkdtemp4, open as open3, readFile as readFile47, rm as rm11, stat as stat8, unlink as unlink10, writeFile as writeFile14 } from "fs/promises";
|
|
28377
28754
|
import { tmpdir as tmpdir5 } from "os";
|
|
28378
|
-
import { join as join16, resolve as
|
|
28755
|
+
import { join as join16, resolve as resolve71 } from "path";
|
|
28379
28756
|
import { setTimeout as sleep } from "timers/promises";
|
|
28380
28757
|
function sigintPollIntervalMs() {
|
|
28381
|
-
const
|
|
28382
|
-
if (
|
|
28383
|
-
const parsed = Number(
|
|
28758
|
+
const raw2 = process.env.SYNTAUR_RECORDING_POLL_INTERVAL_MS;
|
|
28759
|
+
if (raw2 === void 0) return 500;
|
|
28760
|
+
const parsed = Number(raw2);
|
|
28384
28761
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 500;
|
|
28385
28762
|
}
|
|
28386
28763
|
function sigintPollCount() {
|
|
28387
|
-
const
|
|
28388
|
-
if (
|
|
28389
|
-
const parsed = Number(
|
|
28764
|
+
const raw2 = process.env.SYNTAUR_RECORDING_POLL_COUNT;
|
|
28765
|
+
if (raw2 === void 0) return 20;
|
|
28766
|
+
const parsed = Number(raw2);
|
|
28390
28767
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 20;
|
|
28391
28768
|
}
|
|
28392
28769
|
function sigtermWaitMs() {
|
|
28393
|
-
const
|
|
28394
|
-
if (
|
|
28395
|
-
const parsed = Number(
|
|
28770
|
+
const raw2 = process.env.SYNTAUR_RECORDING_SIGTERM_WAIT_MS;
|
|
28771
|
+
if (raw2 === void 0) return 1e3;
|
|
28772
|
+
const parsed = Number(raw2);
|
|
28396
28773
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1e3;
|
|
28397
28774
|
}
|
|
28398
28775
|
function pidfilePath() {
|
|
28399
|
-
return
|
|
28776
|
+
return resolve71(syntaurRoot(), "recording.pid");
|
|
28400
28777
|
}
|
|
28401
28778
|
function logPath2() {
|
|
28402
|
-
return
|
|
28779
|
+
return resolve71(syntaurRoot(), "recording.log");
|
|
28403
28780
|
}
|
|
28404
28781
|
function sidecarPath() {
|
|
28405
|
-
return
|
|
28782
|
+
return resolve71(syntaurRoot(), "recording.json");
|
|
28406
28783
|
}
|
|
28407
28784
|
function ffmpegArgs(device, fps, mp4Path) {
|
|
28408
28785
|
return [
|
|
@@ -28427,9 +28804,9 @@ function ffmpegArgs(device, fps, mp4Path) {
|
|
|
28427
28804
|
];
|
|
28428
28805
|
}
|
|
28429
28806
|
function defaultWarmupMs() {
|
|
28430
|
-
const
|
|
28431
|
-
if (
|
|
28432
|
-
const parsed = Number(
|
|
28807
|
+
const raw2 = process.env.SYNTAUR_RECORDING_WARMUP_MS;
|
|
28808
|
+
if (raw2 === void 0) return 1500;
|
|
28809
|
+
const parsed = Number(raw2);
|
|
28433
28810
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1500;
|
|
28434
28811
|
}
|
|
28435
28812
|
var STARTING_SENTINEL_PREFIX = "STARTING:";
|
|
@@ -28456,7 +28833,7 @@ async function acquirePidfile(pidfile) {
|
|
|
28456
28833
|
`Recording startup already in progress (parent PID ${parentPid}). Wait a moment and retry, or kill ${parentPid} and remove ${pidfile} manually if it's truly stuck.`
|
|
28457
28834
|
);
|
|
28458
28835
|
}
|
|
28459
|
-
await
|
|
28836
|
+
await unlink10(pidfile).catch(() => {
|
|
28460
28837
|
});
|
|
28461
28838
|
continue;
|
|
28462
28839
|
}
|
|
@@ -28472,7 +28849,7 @@ async function acquirePidfile(pidfile) {
|
|
|
28472
28849
|
`Recording already in progress (PID ${existingPid}). Stop with: syntaur capture --kind video --stop`
|
|
28473
28850
|
);
|
|
28474
28851
|
}
|
|
28475
|
-
await
|
|
28852
|
+
await unlink10(pidfile).catch(() => {
|
|
28476
28853
|
});
|
|
28477
28854
|
continue;
|
|
28478
28855
|
}
|
|
@@ -28489,7 +28866,7 @@ async function startRecording(input4) {
|
|
|
28489
28866
|
);
|
|
28490
28867
|
}
|
|
28491
28868
|
const root = syntaurRoot();
|
|
28492
|
-
await
|
|
28869
|
+
await mkdir9(root, { recursive: true });
|
|
28493
28870
|
const pidfile = pidfilePath();
|
|
28494
28871
|
const log = logPath2();
|
|
28495
28872
|
const sidecar = sidecarPath();
|
|
@@ -28544,7 +28921,7 @@ async function startRecording(input4) {
|
|
|
28544
28921
|
const pid = await pidReady;
|
|
28545
28922
|
acquiredPid = pid;
|
|
28546
28923
|
child.unref();
|
|
28547
|
-
await
|
|
28924
|
+
await writeFile14(pidfile, String(pid));
|
|
28548
28925
|
await logHandle.close();
|
|
28549
28926
|
logHandle = null;
|
|
28550
28927
|
if (warmupMs > 0) await sleep(warmupMs);
|
|
@@ -28572,7 +28949,7 @@ ${tail}`
|
|
|
28572
28949
|
device: input4.device,
|
|
28573
28950
|
fps: input4.fps
|
|
28574
28951
|
};
|
|
28575
|
-
await
|
|
28952
|
+
await writeFile14(sidecar, JSON.stringify(sidecarData, null, 2));
|
|
28576
28953
|
return {
|
|
28577
28954
|
pid,
|
|
28578
28955
|
logPath: log,
|
|
@@ -28596,9 +28973,9 @@ ${tail}`
|
|
|
28596
28973
|
}
|
|
28597
28974
|
if (logHandle) await logHandle.close().catch(() => {
|
|
28598
28975
|
});
|
|
28599
|
-
if (tmpDir) await
|
|
28976
|
+
if (tmpDir) await rm11(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
28600
28977
|
});
|
|
28601
|
-
await
|
|
28978
|
+
await unlink10(pidfile).catch(() => {
|
|
28602
28979
|
});
|
|
28603
28980
|
throw err2;
|
|
28604
28981
|
}
|
|
@@ -28661,19 +29038,19 @@ async function stopRecording() {
|
|
|
28661
29038
|
if (alive) {
|
|
28662
29039
|
throw new Error(`ffmpeg (PID ${pid}) refused to die after SIGKILL`);
|
|
28663
29040
|
}
|
|
28664
|
-
const st = await
|
|
29041
|
+
const st = await stat8(sidecarData.mp4Path).catch(() => null);
|
|
28665
29042
|
if (st === null || st.size === 0) {
|
|
28666
|
-
await
|
|
29043
|
+
await unlink10(pidfile).catch(() => {
|
|
28667
29044
|
});
|
|
28668
|
-
await
|
|
29045
|
+
await unlink10(sidecar).catch(() => {
|
|
28669
29046
|
});
|
|
28670
29047
|
throw new Error(
|
|
28671
29048
|
`ffmpeg produced no output at ${sidecarData.mp4Path}. Check ${sidecarData.logPath} for errors.`
|
|
28672
29049
|
);
|
|
28673
29050
|
}
|
|
28674
|
-
await
|
|
29051
|
+
await unlink10(pidfile).catch(() => {
|
|
28675
29052
|
});
|
|
28676
|
-
await
|
|
29053
|
+
await unlink10(sidecar).catch(() => {
|
|
28677
29054
|
});
|
|
28678
29055
|
return { mp4Path: sidecarData.mp4Path, sidecar: sidecarData };
|
|
28679
29056
|
}
|
|
@@ -28681,7 +29058,7 @@ async function stopRecording() {
|
|
|
28681
29058
|
// src/db/proof-db.ts
|
|
28682
29059
|
init_paths();
|
|
28683
29060
|
import Database5 from "better-sqlite3";
|
|
28684
|
-
import { resolve as
|
|
29061
|
+
import { resolve as resolve72 } from "path";
|
|
28685
29062
|
var db4 = null;
|
|
28686
29063
|
var PROOF_SCHEMA_VERSION = "1";
|
|
28687
29064
|
var SCHEMA_SQL4 = `
|
|
@@ -28701,7 +29078,7 @@ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
|
28701
29078
|
`;
|
|
28702
29079
|
function initProofDb(dbPath) {
|
|
28703
29080
|
if (db4) return db4;
|
|
28704
|
-
const finalPath = dbPath ??
|
|
29081
|
+
const finalPath = dbPath ?? resolve72(syntaurRoot(), "syntaur.db");
|
|
28705
29082
|
db4 = new Database5(finalPath);
|
|
28706
29083
|
db4.pragma("journal_mode = WAL");
|
|
28707
29084
|
db4.exec(SCHEMA_SQL4);
|
|
@@ -28749,7 +29126,7 @@ function listArtifactsByAssignment(assignmentId) {
|
|
|
28749
29126
|
|
|
28750
29127
|
// src/utils/transcribers/elevenlabs.ts
|
|
28751
29128
|
import { spawn as spawn10 } from "child_process";
|
|
28752
|
-
import { mkdtemp as mkdtemp5, readFile as readFile48, rm as
|
|
29129
|
+
import { mkdtemp as mkdtemp5, readFile as readFile48, rm as rm12 } from "fs/promises";
|
|
28753
29130
|
import { tmpdir as tmpdir6 } from "os";
|
|
28754
29131
|
import { join as join17 } from "path";
|
|
28755
29132
|
var SCRIBE_URL = "https://api.elevenlabs.io/v1/speech-to-text";
|
|
@@ -28847,7 +29224,7 @@ var elevenLabsScribe = {
|
|
|
28847
29224
|
await extractAudio(videoAbsPath, wav);
|
|
28848
29225
|
return await callScribe(wav, apiKey, opts);
|
|
28849
29226
|
} finally {
|
|
28850
|
-
await
|
|
29227
|
+
await rm12(tmp, { recursive: true, force: true }).catch(() => {
|
|
28851
29228
|
});
|
|
28852
29229
|
}
|
|
28853
29230
|
}
|
|
@@ -28897,10 +29274,10 @@ function groupIntoPhrases(words, silenceThresholdSec = 0.5) {
|
|
|
28897
29274
|
const parts = [];
|
|
28898
29275
|
for (const w of currentWords) {
|
|
28899
29276
|
const t = w.type ?? "word";
|
|
28900
|
-
let
|
|
28901
|
-
if (!
|
|
28902
|
-
if (t === "audio_event" && !
|
|
28903
|
-
parts.push(
|
|
29277
|
+
let raw2 = (w.text ?? "").trim();
|
|
29278
|
+
if (!raw2) continue;
|
|
29279
|
+
if (t === "audio_event" && !raw2.startsWith("(")) raw2 = `(${raw2})`;
|
|
29280
|
+
parts.push(raw2);
|
|
28904
29281
|
}
|
|
28905
29282
|
if (parts.length === 0) {
|
|
28906
29283
|
currentWords = [];
|
|
@@ -28968,17 +29345,17 @@ function renderMarkdown(phrases) {
|
|
|
28968
29345
|
|
|
28969
29346
|
// src/commands/capture.ts
|
|
28970
29347
|
var MAX_ID_RETRIES = 5;
|
|
28971
|
-
function normalizeCriterionIndex(
|
|
28972
|
-
if (
|
|
28973
|
-
if (typeof
|
|
28974
|
-
if (!Number.isInteger(
|
|
28975
|
-
throw new Error(`--criterion must be a non-negative integer (got "${
|
|
29348
|
+
function normalizeCriterionIndex(raw2) {
|
|
29349
|
+
if (raw2 === void 0 || raw2 === null || raw2 === "") return null;
|
|
29350
|
+
if (typeof raw2 === "number") {
|
|
29351
|
+
if (!Number.isInteger(raw2) || raw2 < 0) {
|
|
29352
|
+
throw new Error(`--criterion must be a non-negative integer (got "${raw2}")`);
|
|
28976
29353
|
}
|
|
28977
|
-
return
|
|
29354
|
+
return raw2;
|
|
28978
29355
|
}
|
|
28979
|
-
const s = String(
|
|
29356
|
+
const s = String(raw2).trim();
|
|
28980
29357
|
if (!/^\d+$/.test(s)) {
|
|
28981
|
-
throw new Error(`--criterion must be a non-negative integer (got "${
|
|
29358
|
+
throw new Error(`--criterion must be a non-negative integer (got "${raw2}")`);
|
|
28982
29359
|
}
|
|
28983
29360
|
return parseInt(s, 10);
|
|
28984
29361
|
}
|
|
@@ -29116,9 +29493,9 @@ async function captureCommand(target, options = {}) {
|
|
|
29116
29493
|
criterionIndex = sidecar.criterionIndex;
|
|
29117
29494
|
stopNote = sidecar.note;
|
|
29118
29495
|
resolvedSource = mp4Path;
|
|
29119
|
-
const mp4TmpDir =
|
|
29496
|
+
const mp4TmpDir = dirname21(mp4Path);
|
|
29120
29497
|
shelloutCleanup = async () => {
|
|
29121
|
-
await
|
|
29498
|
+
await rm13(mp4TmpDir, { recursive: true, force: true }).catch(() => {
|
|
29122
29499
|
});
|
|
29123
29500
|
};
|
|
29124
29501
|
} else {
|
|
@@ -29129,7 +29506,7 @@ async function captureCommand(target, options = {}) {
|
|
|
29129
29506
|
});
|
|
29130
29507
|
}
|
|
29131
29508
|
if (options.file) {
|
|
29132
|
-
const expanded = options.file.startsWith("~/") ?
|
|
29509
|
+
const expanded = options.file.startsWith("~/") ? resolve73(process.env.HOME ?? "", options.file.slice(2)) : resolve73(options.file);
|
|
29133
29510
|
if (!await fileExists(expanded)) {
|
|
29134
29511
|
throw new Error(`--file does not exist: ${options.file}`);
|
|
29135
29512
|
}
|
|
@@ -29141,7 +29518,7 @@ async function captureCommand(target, options = {}) {
|
|
|
29141
29518
|
`--file is unreadable: ${options.file} (${e instanceof Error ? e.message : String(e)})`
|
|
29142
29519
|
);
|
|
29143
29520
|
}
|
|
29144
|
-
const st = await
|
|
29521
|
+
const st = await stat9(real);
|
|
29145
29522
|
if (!st.isFile()) {
|
|
29146
29523
|
throw new Error(`--file is not a regular file: ${options.file}`);
|
|
29147
29524
|
}
|
|
@@ -29170,8 +29547,8 @@ async function captureCommand(target, options = {}) {
|
|
|
29170
29547
|
}
|
|
29171
29548
|
initProofDb();
|
|
29172
29549
|
const subdir = criterionIndex === null ? "untagged" : String(criterionIndex);
|
|
29173
|
-
const destDir =
|
|
29174
|
-
if (resolvedSource) await
|
|
29550
|
+
const destDir = resolve73(proofDir(resolved.assignmentDir), subdir);
|
|
29551
|
+
if (resolvedSource) await mkdir10(destDir, { recursive: true });
|
|
29175
29552
|
const ext = resolvedSource ? extensionForKind(kind) : null;
|
|
29176
29553
|
let id = null;
|
|
29177
29554
|
let relativeFilePath = null;
|
|
@@ -29179,7 +29556,7 @@ async function captureCommand(target, options = {}) {
|
|
|
29179
29556
|
let lastErr = null;
|
|
29180
29557
|
for (let attempt = 0; attempt < MAX_ID_RETRIES; attempt += 1) {
|
|
29181
29558
|
const candidate = generateArtifactId();
|
|
29182
|
-
const candidateAbsPath = resolvedSource && ext ?
|
|
29559
|
+
const candidateAbsPath = resolvedSource && ext ? resolve73(destDir, `${candidate}.${ext}`) : null;
|
|
29183
29560
|
const candidateRel = candidateAbsPath ? relative4(resolved.assignmentDir, candidateAbsPath) : null;
|
|
29184
29561
|
try {
|
|
29185
29562
|
insertArtifact({
|
|
@@ -29223,7 +29600,7 @@ async function captureCommand(target, options = {}) {
|
|
|
29223
29600
|
}
|
|
29224
29601
|
}
|
|
29225
29602
|
if (options.transcribe && kind === "video" && absPath && id) {
|
|
29226
|
-
const sidecarPath2 =
|
|
29603
|
+
const sidecarPath2 = resolve73(destDir, `${id}.transcript.md`);
|
|
29227
29604
|
if (existsSync6(sidecarPath2)) {
|
|
29228
29605
|
console.warn(
|
|
29229
29606
|
`transcript: ${sidecarPath2} already exists, skipping (delete to re-transcribe)`
|
|
@@ -29232,7 +29609,7 @@ async function captureCommand(target, options = {}) {
|
|
|
29232
29609
|
try {
|
|
29233
29610
|
const json = await getTranscriber().transcribe(absPath);
|
|
29234
29611
|
const md = renderMarkdown(groupIntoPhrases(json.words ?? []));
|
|
29235
|
-
await
|
|
29612
|
+
await writeFile15(sidecarPath2, md, "utf8");
|
|
29236
29613
|
console.log(`transcript: ${sidecarPath2}`);
|
|
29237
29614
|
} catch (err2) {
|
|
29238
29615
|
if (err2 instanceof TranscribeFfmpegMissingError) {
|
|
@@ -29255,7 +29632,7 @@ async function captureCommand(target, options = {}) {
|
|
|
29255
29632
|
const tagSuffix = criterionIndex === null ? "untagged" : `criterion ${criterionIndex}`;
|
|
29256
29633
|
console.log(`Captured artifact ${id} (${kind}) for ${ref} \u2014 ${tagSuffix}.`);
|
|
29257
29634
|
if (relativeFilePath) {
|
|
29258
|
-
console.log(` file: ${
|
|
29635
|
+
console.log(` file: ${resolve73(resolved.assignmentDir, relativeFilePath)}`);
|
|
29259
29636
|
}
|
|
29260
29637
|
} catch (err2) {
|
|
29261
29638
|
if (options.stop && kind === "video" && shelloutCleanup && resolvedSource) {
|
|
@@ -29278,8 +29655,8 @@ async function captureCommand(target, options = {}) {
|
|
|
29278
29655
|
|
|
29279
29656
|
// src/commands/proof.ts
|
|
29280
29657
|
import { Command as Command6 } from "commander";
|
|
29281
|
-
import { readFile as readFile49, writeFile as
|
|
29282
|
-
import { resolve as
|
|
29658
|
+
import { readFile as readFile49, writeFile as writeFile16, rename as rename10, stat as stat10 } from "fs/promises";
|
|
29659
|
+
import { resolve as resolve74, relative as relative5, isAbsolute as isAbsolute11, dirname as dirname22 } from "path";
|
|
29283
29660
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
29284
29661
|
|
|
29285
29662
|
// src/utils/acceptance-criteria-parse.ts
|
|
@@ -29519,7 +29896,7 @@ function renderProofHtml(params2, inlineFiles = /* @__PURE__ */ new Map(), trans
|
|
|
29519
29896
|
|
|
29520
29897
|
// src/commands/proof.ts
|
|
29521
29898
|
async function readAssignmentMeta(assignmentDir) {
|
|
29522
|
-
const path =
|
|
29899
|
+
const path = resolve74(assignmentDir, "assignment.md");
|
|
29523
29900
|
if (!await fileExists(path)) {
|
|
29524
29901
|
return { title: "", body: "" };
|
|
29525
29902
|
}
|
|
@@ -29529,16 +29906,16 @@ async function readAssignmentMeta(assignmentDir) {
|
|
|
29529
29906
|
if (fmMatch) {
|
|
29530
29907
|
const titleLine = fmMatch[1].split("\n").find((l) => /^title:\s/i.test(l));
|
|
29531
29908
|
if (titleLine) {
|
|
29532
|
-
const
|
|
29533
|
-
title =
|
|
29909
|
+
const raw2 = titleLine.replace(/^title:\s*/i, "").trim();
|
|
29910
|
+
title = raw2.replace(/^["']|["']$/g, "");
|
|
29534
29911
|
}
|
|
29535
29912
|
}
|
|
29536
29913
|
return { title, body: content };
|
|
29537
29914
|
}
|
|
29538
29915
|
async function atomicWrite(destPath, content) {
|
|
29539
29916
|
const tmp = `${destPath}.${randomBytes4(4).toString("hex")}.tmp`;
|
|
29540
|
-
await
|
|
29541
|
-
await
|
|
29917
|
+
await writeFile16(tmp, content, "utf-8");
|
|
29918
|
+
await rename10(tmp, destPath);
|
|
29542
29919
|
}
|
|
29543
29920
|
function isWithin(root, target) {
|
|
29544
29921
|
const rel = relative5(root, target);
|
|
@@ -29570,7 +29947,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
29570
29947
|
for (const r of rows) {
|
|
29571
29948
|
if (!r.file_path) continue;
|
|
29572
29949
|
if (r.kind !== "http" && r.kind !== "text") continue;
|
|
29573
|
-
const abs =
|
|
29950
|
+
const abs = resolve74(assignmentDir, r.file_path);
|
|
29574
29951
|
if (!isWithin(proofRoot, abs)) {
|
|
29575
29952
|
out.set(r.file_path, null);
|
|
29576
29953
|
continue;
|
|
@@ -29579,7 +29956,7 @@ async function loadInlineFiles(rows, assignmentDir) {
|
|
|
29579
29956
|
out.set(r.file_path, null);
|
|
29580
29957
|
continue;
|
|
29581
29958
|
}
|
|
29582
|
-
const st = await
|
|
29959
|
+
const st = await stat10(abs);
|
|
29583
29960
|
if (st.size > INLINE_TEXT_LIMIT_BYTES) {
|
|
29584
29961
|
out.set(r.file_path, null);
|
|
29585
29962
|
continue;
|
|
@@ -29597,11 +29974,11 @@ async function loadTranscriptSidecars(rows, assignmentDir) {
|
|
|
29597
29974
|
const proofRoot = proofDir(assignmentDir);
|
|
29598
29975
|
for (const r of rows) {
|
|
29599
29976
|
if (r.kind !== "video" || !r.file_path) continue;
|
|
29600
|
-
const videoAbs =
|
|
29601
|
-
const sidecar =
|
|
29977
|
+
const videoAbs = resolve74(assignmentDir, r.file_path);
|
|
29978
|
+
const sidecar = resolve74(dirname22(videoAbs), `${r.id}.transcript.md`);
|
|
29602
29979
|
if (!isWithin(proofRoot, sidecar)) continue;
|
|
29603
29980
|
if (!await fileExists(sidecar)) continue;
|
|
29604
|
-
const st = await
|
|
29981
|
+
const st = await stat10(sidecar);
|
|
29605
29982
|
if (st.size > INLINE_TEXT_LIMIT_BYTES) continue;
|
|
29606
29983
|
try {
|
|
29607
29984
|
out.set(r.id, await readFile49(sidecar, "utf-8"));
|
|
@@ -29638,8 +30015,8 @@ async function proofBuildCommand(target, options = {}) {
|
|
|
29638
30015
|
};
|
|
29639
30016
|
const md = renderProofMarkdown(renderParams);
|
|
29640
30017
|
const html = renderProofHtml(renderParams, inlineFiles, transcriptSidecars);
|
|
29641
|
-
const mdPath =
|
|
29642
|
-
const htmlPath =
|
|
30018
|
+
const mdPath = resolve74(resolved.assignmentDir, "proof.md");
|
|
30019
|
+
const htmlPath = resolve74(resolved.assignmentDir, "proof.html");
|
|
29643
30020
|
await atomicWrite(mdPath, md);
|
|
29644
30021
|
await atomicWrite(htmlPath, html);
|
|
29645
30022
|
console.log(`Wrote ${htmlPath}`);
|
|
@@ -29675,12 +30052,12 @@ function parseDuration(input4, fallbackSeconds) {
|
|
|
29675
30052
|
function parseMetadataFlags(values) {
|
|
29676
30053
|
if (!values || values.length === 0) return void 0;
|
|
29677
30054
|
const out = {};
|
|
29678
|
-
for (const
|
|
29679
|
-
const eq =
|
|
30055
|
+
for (const raw2 of values) {
|
|
30056
|
+
const eq = raw2.indexOf("=");
|
|
29680
30057
|
if (eq < 0) {
|
|
29681
|
-
throw new Error(`invalid -m flag "${
|
|
30058
|
+
throw new Error(`invalid -m flag "${raw2}" \u2014 use key=value`);
|
|
29682
30059
|
}
|
|
29683
|
-
out[
|
|
30060
|
+
out[raw2.slice(0, eq)] = raw2.slice(eq + 1);
|
|
29684
30061
|
}
|
|
29685
30062
|
return out;
|
|
29686
30063
|
}
|
|
@@ -30149,41 +30526,41 @@ function parseCcusageSession(payload, nowIso6 = () => (/* @__PURE__ */ new Date(
|
|
|
30149
30526
|
}
|
|
30150
30527
|
const rows = [];
|
|
30151
30528
|
let highWaterIso = null;
|
|
30152
|
-
for (const
|
|
30153
|
-
if (!
|
|
30529
|
+
for (const raw2 of sessions) {
|
|
30530
|
+
if (!raw2 || typeof raw2 !== "object") {
|
|
30154
30531
|
warnings.push("skipped non-object session row");
|
|
30155
30532
|
continue;
|
|
30156
30533
|
}
|
|
30157
|
-
const tool = typeof
|
|
30158
|
-
const period = typeof
|
|
30534
|
+
const tool = typeof raw2.agent === "string" ? raw2.agent : null;
|
|
30535
|
+
const period = typeof raw2.period === "string" ? raw2.period : null;
|
|
30159
30536
|
if (!tool || !period) {
|
|
30160
30537
|
warnings.push("skipped session row missing agent or period");
|
|
30161
30538
|
continue;
|
|
30162
30539
|
}
|
|
30163
30540
|
const sessionId = extractSessionId(tool, period);
|
|
30164
|
-
const breakdowns = Array.isArray(
|
|
30541
|
+
const breakdowns = Array.isArray(raw2.modelBreakdowns) ? raw2.modelBreakdowns : [];
|
|
30165
30542
|
const eventTs = normalizeLastActivity(
|
|
30166
|
-
|
|
30543
|
+
raw2.metadata?.lastActivity,
|
|
30167
30544
|
nowIso6
|
|
30168
30545
|
);
|
|
30169
30546
|
if (eventTs && (!highWaterIso || eventTs > highWaterIso)) {
|
|
30170
30547
|
highWaterIso = eventTs;
|
|
30171
30548
|
}
|
|
30172
30549
|
if (breakdowns.length === 0) {
|
|
30173
|
-
const modelsUsed = Array.isArray(
|
|
30550
|
+
const modelsUsed = Array.isArray(raw2.modelsUsed) ? raw2.modelsUsed : [];
|
|
30174
30551
|
const fallbackModel = typeof modelsUsed[0] === "string" ? modelsUsed[0] : "unknown";
|
|
30175
30552
|
rows.push({
|
|
30176
30553
|
sessionId,
|
|
30177
30554
|
model: fallbackModel,
|
|
30178
30555
|
tool,
|
|
30179
30556
|
eventTs,
|
|
30180
|
-
inputTokens: toInt(
|
|
30181
|
-
outputTokens: toInt(
|
|
30182
|
-
cacheCreationTokens: toInt(
|
|
30183
|
-
cacheReadTokens: toInt(
|
|
30184
|
-
totalTokens: toInt(
|
|
30185
|
-
totalCost: toNumber(
|
|
30186
|
-
rawJson: JSON.stringify(
|
|
30557
|
+
inputTokens: toInt(raw2.inputTokens),
|
|
30558
|
+
outputTokens: toInt(raw2.outputTokens),
|
|
30559
|
+
cacheCreationTokens: toInt(raw2.cacheCreationTokens),
|
|
30560
|
+
cacheReadTokens: toInt(raw2.cacheReadTokens),
|
|
30561
|
+
totalTokens: toInt(raw2.totalTokens),
|
|
30562
|
+
totalCost: toNumber(raw2.totalCost),
|
|
30563
|
+
rawJson: JSON.stringify(raw2)
|
|
30187
30564
|
});
|
|
30188
30565
|
continue;
|
|
30189
30566
|
}
|
|
@@ -30213,7 +30590,7 @@ function parseCcusageSession(payload, nowIso6 = () => (/* @__PURE__ */ new Date(
|
|
|
30213
30590
|
cacheReadTokens,
|
|
30214
30591
|
totalTokens,
|
|
30215
30592
|
totalCost: toNumber(b.cost),
|
|
30216
|
-
rawJson: JSON.stringify(
|
|
30593
|
+
rawJson: JSON.stringify(raw2)
|
|
30217
30594
|
});
|
|
30218
30595
|
}
|
|
30219
30596
|
}
|
|
@@ -30304,7 +30681,7 @@ async function runCcusage(opts = {}) {
|
|
|
30304
30681
|
};
|
|
30305
30682
|
}
|
|
30306
30683
|
function runOnce(binary, args, env, timeoutMs, maxOutputBytes) {
|
|
30307
|
-
return new Promise((
|
|
30684
|
+
return new Promise((resolve83) => {
|
|
30308
30685
|
const child = spawn11(binary, args, {
|
|
30309
30686
|
env: env ?? process.env,
|
|
30310
30687
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -30318,7 +30695,7 @@ function runOnce(binary, args, env, timeoutMs, maxOutputBytes) {
|
|
|
30318
30695
|
if (settled) return;
|
|
30319
30696
|
settled = true;
|
|
30320
30697
|
clearTimeout(timer2);
|
|
30321
|
-
|
|
30698
|
+
resolve83(result);
|
|
30322
30699
|
};
|
|
30323
30700
|
const timer2 = setTimeout(() => {
|
|
30324
30701
|
timedOut = true;
|
|
@@ -30362,7 +30739,7 @@ function isoToCcusageDate(iso) {
|
|
|
30362
30739
|
|
|
30363
30740
|
// src/usage/cwd-extractor.ts
|
|
30364
30741
|
init_paths();
|
|
30365
|
-
import { open as open4, readdir as
|
|
30742
|
+
import { open as open4, readdir as readdir25, stat as stat11 } from "fs/promises";
|
|
30366
30743
|
import { join as join18 } from "path";
|
|
30367
30744
|
import { homedir as homedir12 } from "os";
|
|
30368
30745
|
var SCAN_LINE_CAP = 50;
|
|
@@ -30371,8 +30748,8 @@ var TAIL_READ_BYTES_MAX = 64 * 1024;
|
|
|
30371
30748
|
async function extractClaudeSessionMeta(jsonlPath) {
|
|
30372
30749
|
const cwd = await derivePathFromTranscript(jsonlPath);
|
|
30373
30750
|
if (!cwd) return null;
|
|
30374
|
-
const
|
|
30375
|
-
const sessionId =
|
|
30751
|
+
const basename7 = jsonlPath.split("/").pop() ?? "";
|
|
30752
|
+
const sessionId = basename7.replace(/\.jsonl$/, "");
|
|
30376
30753
|
if (!sessionId) return null;
|
|
30377
30754
|
const startTs = await readFirstTimestamp(jsonlPath);
|
|
30378
30755
|
const endTs = await readLastTimestamp(jsonlPath);
|
|
@@ -30465,8 +30842,8 @@ async function* walkClaudeProjects(opts = {}) {
|
|
|
30465
30842
|
async function* walkCodexSessions(opts = {}) {
|
|
30466
30843
|
const root = resolveCodexSessionsRoot(opts.root);
|
|
30467
30844
|
for await (const filePath of walkJsonlRecursive(root)) {
|
|
30468
|
-
const
|
|
30469
|
-
if (!
|
|
30845
|
+
const basename7 = filePath.split("/").pop() ?? "";
|
|
30846
|
+
if (!basename7.endsWith(".jsonl")) continue;
|
|
30470
30847
|
if (opts.sinceMtimeMs !== void 0) {
|
|
30471
30848
|
const mtime = await mtimeMs(filePath);
|
|
30472
30849
|
if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
|
|
@@ -30485,7 +30862,7 @@ function resolveCodexSessionsRoot(override) {
|
|
|
30485
30862
|
}
|
|
30486
30863
|
async function listDirSafe(path) {
|
|
30487
30864
|
try {
|
|
30488
|
-
const entries = await
|
|
30865
|
+
const entries = await readdir25(path, { withFileTypes: true });
|
|
30489
30866
|
return entries.map((e) => ({
|
|
30490
30867
|
name: e.name,
|
|
30491
30868
|
isFile: e.isFile(),
|
|
@@ -30512,7 +30889,7 @@ async function* walkJsonlRecursive(root) {
|
|
|
30512
30889
|
}
|
|
30513
30890
|
async function mtimeMs(path) {
|
|
30514
30891
|
try {
|
|
30515
|
-
const s = await
|
|
30892
|
+
const s = await stat11(path);
|
|
30516
30893
|
return s.mtimeMs;
|
|
30517
30894
|
} catch {
|
|
30518
30895
|
return null;
|
|
@@ -30840,7 +31217,7 @@ init_slug();
|
|
|
30840
31217
|
init_timestamp();
|
|
30841
31218
|
init_assignment_resolver();
|
|
30842
31219
|
init_assignment_todos();
|
|
30843
|
-
import { resolve as
|
|
31220
|
+
import { resolve as resolve75 } from "path";
|
|
30844
31221
|
import { readFile as readFile50 } from "fs/promises";
|
|
30845
31222
|
async function requestCommand(target, text, options = {}) {
|
|
30846
31223
|
if (!text || !text.trim()) {
|
|
@@ -30857,7 +31234,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
30857
31234
|
if (!isValidSlug(target)) {
|
|
30858
31235
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
30859
31236
|
}
|
|
30860
|
-
assignmentDir =
|
|
31237
|
+
assignmentDir = resolve75(baseDir, options.project, "assignments", target);
|
|
30861
31238
|
targetRef = target;
|
|
30862
31239
|
} else {
|
|
30863
31240
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -30867,7 +31244,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
30867
31244
|
assignmentDir = resolved.assignmentDir;
|
|
30868
31245
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
30869
31246
|
}
|
|
30870
|
-
const assignmentMdPath2 =
|
|
31247
|
+
const assignmentMdPath2 = resolve75(assignmentDir, "assignment.md");
|
|
30871
31248
|
if (!await fileExists(assignmentMdPath2)) {
|
|
30872
31249
|
throw new Error(`assignment.md not found at ${assignmentMdPath2}`);
|
|
30873
31250
|
}
|
|
@@ -30885,14 +31262,14 @@ async function requestCommand(target, text, options = {}) {
|
|
|
30885
31262
|
init_fs();
|
|
30886
31263
|
init_paths();
|
|
30887
31264
|
import { Command as Command9 } from "commander";
|
|
30888
|
-
import { readFile as readFile51, readdir as
|
|
30889
|
-
import { resolve as
|
|
31265
|
+
import { readFile as readFile51, readdir as readdir26 } from "fs/promises";
|
|
31266
|
+
import { resolve as resolve76 } from "path";
|
|
30890
31267
|
async function readContextAssignmentDir(cwd) {
|
|
30891
|
-
const path =
|
|
31268
|
+
const path = resolve76(cwd, ".syntaur", "context.json");
|
|
30892
31269
|
if (!await fileExists(path)) return null;
|
|
30893
31270
|
try {
|
|
30894
|
-
const
|
|
30895
|
-
const ctx = JSON.parse(
|
|
31271
|
+
const raw2 = await readFile51(path, "utf-8");
|
|
31272
|
+
const ctx = JSON.parse(raw2);
|
|
30896
31273
|
if (typeof ctx.assignmentDir === "string" && ctx.assignmentDir.length > 0) {
|
|
30897
31274
|
return ctx.assignmentDir;
|
|
30898
31275
|
}
|
|
@@ -30905,9 +31282,9 @@ async function resolveAssignmentDir(opts) {
|
|
|
30905
31282
|
const cwd = opts.cwd ?? process.cwd();
|
|
30906
31283
|
if (opts.assignment) {
|
|
30907
31284
|
if (opts.project) {
|
|
30908
|
-
return
|
|
31285
|
+
return resolve76(defaultProjectDir(), opts.project, "assignments", opts.assignment);
|
|
30909
31286
|
}
|
|
30910
|
-
return
|
|
31287
|
+
return resolve76(assignmentsDir(), opts.assignment);
|
|
30911
31288
|
}
|
|
30912
31289
|
const fromCtx = await readContextAssignmentDir(cwd);
|
|
30913
31290
|
if (fromCtx) return fromCtx;
|
|
@@ -30918,7 +31295,7 @@ async function resolveAssignmentDir(opts) {
|
|
|
30918
31295
|
var PLAN_PATTERN = /^plan(?:-v(\d+))?\.md$/;
|
|
30919
31296
|
async function listPlanFiles(assignmentDir) {
|
|
30920
31297
|
if (!await fileExists(assignmentDir)) return [];
|
|
30921
|
-
const entries = await
|
|
31298
|
+
const entries = await readdir26(assignmentDir, { withFileTypes: true });
|
|
30922
31299
|
const out = [];
|
|
30923
31300
|
for (const e of entries) {
|
|
30924
31301
|
if (!e.isFile()) continue;
|
|
@@ -31050,7 +31427,7 @@ async function runPlanVersion(options) {
|
|
|
31050
31427
|
if (!await fileExists(assignmentDir)) {
|
|
31051
31428
|
throw new Error(`Assignment directory does not exist: ${assignmentDir}`);
|
|
31052
31429
|
}
|
|
31053
|
-
const assignmentMdPath2 =
|
|
31430
|
+
const assignmentMdPath2 = resolve76(assignmentDir, "assignment.md");
|
|
31054
31431
|
if (!await fileExists(assignmentMdPath2)) {
|
|
31055
31432
|
throw new Error(`Missing assignment.md at: ${assignmentMdPath2}`);
|
|
31056
31433
|
}
|
|
@@ -31062,14 +31439,14 @@ async function runPlanVersion(options) {
|
|
|
31062
31439
|
}
|
|
31063
31440
|
const current = planFiles[planFiles.length - 1];
|
|
31064
31441
|
const next = nextPlanFileName(current.version);
|
|
31065
|
-
const newPath =
|
|
31442
|
+
const newPath = resolve76(assignmentDir, next.fileName);
|
|
31066
31443
|
if (await fileExists(newPath) && !options.force) {
|
|
31067
31444
|
throw new Error(`${next.fileName} already exists. Use --force to overwrite.`);
|
|
31068
31445
|
}
|
|
31069
31446
|
const assignmentMd = await readFile51(assignmentMdPath2, "utf-8");
|
|
31070
31447
|
const slugMatch = assignmentMd.match(/^slug:\s*(.+?)\s*$/m);
|
|
31071
31448
|
const slug = slugMatch ? slugMatch[1].trim() : assignmentDir.split("/").pop() ?? "";
|
|
31072
|
-
const oldPlanPath =
|
|
31449
|
+
const oldPlanPath = resolve76(assignmentDir, current.fileName);
|
|
31073
31450
|
const oldPlanContent = await readFile51(oldPlanPath, "utf-8");
|
|
31074
31451
|
const oldBody = oldPlanContent.replace(/^---[\s\S]*?\n---\n?/, "");
|
|
31075
31452
|
const carriedTodos = extractUncheckedTodos(oldBody);
|
|
@@ -31107,28 +31484,28 @@ planCommand.command("version").description(
|
|
|
31107
31484
|
// src/commands/session.ts
|
|
31108
31485
|
init_fs();
|
|
31109
31486
|
import { Command as Command10 } from "commander";
|
|
31110
|
-
import { readFile as readFile52, readdir as
|
|
31111
|
-
import { resolve as
|
|
31487
|
+
import { readFile as readFile52, readdir as readdir27, stat as stat12 } from "fs/promises";
|
|
31488
|
+
import { resolve as resolve77 } from "path";
|
|
31112
31489
|
async function readContext(cwd) {
|
|
31113
|
-
const path =
|
|
31490
|
+
const path = resolve77(cwd, ".syntaur", "context.json");
|
|
31114
31491
|
if (!await fileExists(path)) return null;
|
|
31115
31492
|
try {
|
|
31116
|
-
const
|
|
31117
|
-
return JSON.parse(
|
|
31493
|
+
const raw2 = await readFile52(path, "utf-8");
|
|
31494
|
+
return JSON.parse(raw2);
|
|
31118
31495
|
} catch {
|
|
31119
31496
|
return null;
|
|
31120
31497
|
}
|
|
31121
31498
|
}
|
|
31122
31499
|
async function findLatestSessionSummary(assignmentDir) {
|
|
31123
|
-
const sessionsRoot =
|
|
31500
|
+
const sessionsRoot = resolve77(assignmentDir, "sessions");
|
|
31124
31501
|
if (!await fileExists(sessionsRoot)) return null;
|
|
31125
|
-
const entries = await
|
|
31502
|
+
const entries = await readdir27(sessionsRoot, { withFileTypes: true });
|
|
31126
31503
|
let best = null;
|
|
31127
31504
|
for (const entry of entries) {
|
|
31128
31505
|
if (!entry.isDirectory()) continue;
|
|
31129
|
-
const summaryPath =
|
|
31506
|
+
const summaryPath = resolve77(sessionsRoot, entry.name, "summary.md");
|
|
31130
31507
|
if (!await fileExists(summaryPath)) continue;
|
|
31131
|
-
const st = await
|
|
31508
|
+
const st = await stat12(summaryPath);
|
|
31132
31509
|
if (best === null || st.mtime.getTime() > best.mtime.getTime()) {
|
|
31133
31510
|
best = { sessionId: entry.name, path: summaryPath, mtime: st.mtime };
|
|
31134
31511
|
}
|
|
@@ -31136,7 +31513,7 @@ async function findLatestSessionSummary(assignmentDir) {
|
|
|
31136
31513
|
return best;
|
|
31137
31514
|
}
|
|
31138
31515
|
async function findOpenHandoff(assignmentDir) {
|
|
31139
|
-
const handoffPath =
|
|
31516
|
+
const handoffPath = resolve77(assignmentDir, "handoff.md");
|
|
31140
31517
|
if (!await fileExists(handoffPath)) return null;
|
|
31141
31518
|
const content = await readFile52(handoffPath, "utf-8");
|
|
31142
31519
|
const body = content.replace(/^---[\s\S]*?\n---\n?/, "").trim();
|
|
@@ -31247,9 +31624,9 @@ init_fs();
|
|
|
31247
31624
|
init_paths();
|
|
31248
31625
|
import { Command as Command11 } from "commander";
|
|
31249
31626
|
import { readFile as readFile53 } from "fs/promises";
|
|
31250
|
-
import { resolve as
|
|
31627
|
+
import { resolve as resolve78 } from "path";
|
|
31251
31628
|
async function readContext2(cwd) {
|
|
31252
|
-
const path =
|
|
31629
|
+
const path = resolve78(cwd, ".syntaur", "context.json");
|
|
31253
31630
|
if (!await fileExists(path)) return null;
|
|
31254
31631
|
try {
|
|
31255
31632
|
return JSON.parse(await readFile53(path, "utf-8"));
|
|
@@ -31260,7 +31637,7 @@ async function readContext2(cwd) {
|
|
|
31260
31637
|
async function resolveAssignmentPath2(opts) {
|
|
31261
31638
|
if (opts.assignment) {
|
|
31262
31639
|
if (opts.project) {
|
|
31263
|
-
return
|
|
31640
|
+
return resolve78(
|
|
31264
31641
|
defaultProjectDir(),
|
|
31265
31642
|
opts.project,
|
|
31266
31643
|
"assignments",
|
|
@@ -31268,10 +31645,10 @@ async function resolveAssignmentPath2(opts) {
|
|
|
31268
31645
|
"assignment.md"
|
|
31269
31646
|
);
|
|
31270
31647
|
}
|
|
31271
|
-
return
|
|
31648
|
+
return resolve78(assignmentsDir(), opts.assignment, "assignment.md");
|
|
31272
31649
|
}
|
|
31273
31650
|
const ctx = await readContext2(opts.cwd);
|
|
31274
|
-
if (ctx?.assignmentDir) return
|
|
31651
|
+
if (ctx?.assignmentDir) return resolve78(ctx.assignmentDir, "assignment.md");
|
|
31275
31652
|
throw new Error(
|
|
31276
31653
|
"No active assignment. Pass --assignment <slug> [--project <slug>] or run from a workspace with .syntaur/context.json."
|
|
31277
31654
|
);
|
|
@@ -31282,7 +31659,7 @@ async function runWorktreeCreate(options, cwd = process.cwd()) {
|
|
|
31282
31659
|
}
|
|
31283
31660
|
const repository = options.repository ?? cwd;
|
|
31284
31661
|
const parentBranch = options.parentBranch ?? "main";
|
|
31285
|
-
const worktreePath = options.worktreePath ??
|
|
31662
|
+
const worktreePath = options.worktreePath ?? resolve78(repository, ".worktrees", options.branch);
|
|
31286
31663
|
const assignmentPath = await resolveAssignmentPath2({
|
|
31287
31664
|
assignment: options.assignment,
|
|
31288
31665
|
project: options.project,
|
|
@@ -31319,13 +31696,13 @@ init_paths();
|
|
|
31319
31696
|
init_fs();
|
|
31320
31697
|
init_slug();
|
|
31321
31698
|
import { Command as Command12 } from "commander";
|
|
31322
|
-
import { resolve as
|
|
31699
|
+
import { resolve as resolve80 } from "path";
|
|
31323
31700
|
|
|
31324
31701
|
// src/utils/project-indexes.ts
|
|
31325
31702
|
init_parser();
|
|
31326
31703
|
init_fs();
|
|
31327
|
-
import { readdir as
|
|
31328
|
-
import { resolve as
|
|
31704
|
+
import { readdir as readdir28, readFile as readFile54 } from "fs/promises";
|
|
31705
|
+
import { resolve as resolve79 } from "path";
|
|
31329
31706
|
function nowIso3() {
|
|
31330
31707
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
31331
31708
|
}
|
|
@@ -31334,7 +31711,7 @@ function readProjectSlug(projectDir) {
|
|
|
31334
31711
|
}
|
|
31335
31712
|
async function listSlugFiles(dir) {
|
|
31336
31713
|
if (!await fileExists(dir)) return [];
|
|
31337
|
-
const entries = await
|
|
31714
|
+
const entries = await readdir28(dir, { withFileTypes: true });
|
|
31338
31715
|
return entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_")).map((e) => e.name).sort();
|
|
31339
31716
|
}
|
|
31340
31717
|
function escapeCell(value) {
|
|
@@ -31344,7 +31721,7 @@ function joinList(items) {
|
|
|
31344
31721
|
return items.length === 0 ? "\u2014" : items.map(escapeCell).join(", ");
|
|
31345
31722
|
}
|
|
31346
31723
|
async function rebuildResourcesIndex(projectDir) {
|
|
31347
|
-
const dir =
|
|
31724
|
+
const dir = resolve79(projectDir, "resources");
|
|
31348
31725
|
await ensureDir(dir);
|
|
31349
31726
|
const files = await listSlugFiles(dir);
|
|
31350
31727
|
const slug = readProjectSlug(projectDir);
|
|
@@ -31360,7 +31737,7 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
31360
31737
|
lines.push("| Name | Category | Source | Related Assignments | Updated |");
|
|
31361
31738
|
lines.push("|------|----------|--------|---------------------|---------|");
|
|
31362
31739
|
for (const fileName of files) {
|
|
31363
|
-
const content = await readFile54(
|
|
31740
|
+
const content = await readFile54(resolve79(dir, fileName), "utf-8");
|
|
31364
31741
|
const parsed = parseResource(content);
|
|
31365
31742
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
31366
31743
|
const name = parsed.name || slugBase;
|
|
@@ -31370,12 +31747,12 @@ async function rebuildResourcesIndex(projectDir) {
|
|
|
31370
31747
|
);
|
|
31371
31748
|
}
|
|
31372
31749
|
lines.push("");
|
|
31373
|
-
const indexPath =
|
|
31750
|
+
const indexPath = resolve79(dir, "_index.md");
|
|
31374
31751
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
31375
31752
|
return { total: files.length, path: indexPath };
|
|
31376
31753
|
}
|
|
31377
31754
|
async function rebuildMemoriesIndex(projectDir) {
|
|
31378
|
-
const dir =
|
|
31755
|
+
const dir = resolve79(projectDir, "memories");
|
|
31379
31756
|
await ensureDir(dir);
|
|
31380
31757
|
const files = await listSlugFiles(dir);
|
|
31381
31758
|
const slug = readProjectSlug(projectDir);
|
|
@@ -31391,7 +31768,7 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
31391
31768
|
lines.push("| Name | Source | Scope | Source Assignment | Updated |");
|
|
31392
31769
|
lines.push("|------|--------|-------|-------------------|---------|");
|
|
31393
31770
|
for (const fileName of files) {
|
|
31394
|
-
const content = await readFile54(
|
|
31771
|
+
const content = await readFile54(resolve79(dir, fileName), "utf-8");
|
|
31395
31772
|
const parsed = parseMemory(content);
|
|
31396
31773
|
const slugBase = fileName.replace(/\.md$/, "");
|
|
31397
31774
|
const name = parsed.name || slugBase;
|
|
@@ -31401,7 +31778,7 @@ async function rebuildMemoriesIndex(projectDir) {
|
|
|
31401
31778
|
);
|
|
31402
31779
|
}
|
|
31403
31780
|
lines.push("");
|
|
31404
|
-
const indexPath =
|
|
31781
|
+
const indexPath = resolve79(dir, "_index.md");
|
|
31405
31782
|
await writeFileForce(indexPath, lines.join("\n"));
|
|
31406
31783
|
return { total: files.length, path: indexPath };
|
|
31407
31784
|
}
|
|
@@ -31439,8 +31816,8 @@ async function runResourceAdd(options) {
|
|
|
31439
31816
|
if (!isValidSlug(options.project)) {
|
|
31440
31817
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
31441
31818
|
}
|
|
31442
|
-
const projectDir =
|
|
31443
|
-
if (!await fileExists(
|
|
31819
|
+
const projectDir = resolve80(defaultProjectDir(), options.project);
|
|
31820
|
+
if (!await fileExists(resolve80(projectDir, "project.md"))) {
|
|
31444
31821
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
31445
31822
|
}
|
|
31446
31823
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -31449,7 +31826,7 @@ async function runResourceAdd(options) {
|
|
|
31449
31826
|
if (!isValidSlug(slug)) {
|
|
31450
31827
|
throw new Error(`Invalid resource slug: "${slug}".`);
|
|
31451
31828
|
}
|
|
31452
|
-
const filePath =
|
|
31829
|
+
const filePath = resolve80(projectDir, "resources", `${slug}.md`);
|
|
31453
31830
|
if (await fileExists(filePath) && !options.force) {
|
|
31454
31831
|
throw new Error(
|
|
31455
31832
|
`Resource "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -31484,7 +31861,7 @@ init_paths();
|
|
|
31484
31861
|
init_fs();
|
|
31485
31862
|
init_slug();
|
|
31486
31863
|
import { Command as Command13 } from "commander";
|
|
31487
|
-
import { resolve as
|
|
31864
|
+
import { resolve as resolve81 } from "path";
|
|
31488
31865
|
function nowIso5() {
|
|
31489
31866
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
31490
31867
|
}
|
|
@@ -31519,8 +31896,8 @@ async function runMemoryAdd(options) {
|
|
|
31519
31896
|
if (!isValidSlug(options.project)) {
|
|
31520
31897
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
31521
31898
|
}
|
|
31522
|
-
const projectDir =
|
|
31523
|
-
if (!await fileExists(
|
|
31899
|
+
const projectDir = resolve81(defaultProjectDir(), options.project);
|
|
31900
|
+
if (!await fileExists(resolve81(projectDir, "project.md"))) {
|
|
31524
31901
|
throw new Error(`Project "${options.project}" not found at ${projectDir}.`);
|
|
31525
31902
|
}
|
|
31526
31903
|
if (!options.name) throw new Error("--name is required.");
|
|
@@ -31529,7 +31906,7 @@ async function runMemoryAdd(options) {
|
|
|
31529
31906
|
if (!isValidSlug(slug)) {
|
|
31530
31907
|
throw new Error(`Invalid memory slug: "${slug}".`);
|
|
31531
31908
|
}
|
|
31532
|
-
const filePath =
|
|
31909
|
+
const filePath = resolve81(projectDir, "memories", `${slug}.md`);
|
|
31533
31910
|
if (await fileExists(filePath) && !options.force) {
|
|
31534
31911
|
throw new Error(
|
|
31535
31912
|
`Memory "${slug}" already exists at ${filePath}. Use --force to overwrite.`
|
|
@@ -31567,7 +31944,7 @@ init_fs();
|
|
|
31567
31944
|
init_frontmatter();
|
|
31568
31945
|
import { Command as Command14 } from "commander";
|
|
31569
31946
|
import { readFile as readFile55 } from "fs/promises";
|
|
31570
|
-
import { resolve as
|
|
31947
|
+
import { resolve as resolve82 } from "path";
|
|
31571
31948
|
var AGE_PATTERN = /^(\d+)([dhwm])$/i;
|
|
31572
31949
|
function parseAgeToCutoff(age) {
|
|
31573
31950
|
const match = age.match(AGE_PATTERN);
|
|
@@ -31586,7 +31963,7 @@ function parseAgeToCutoff(age) {
|
|
|
31586
31963
|
}
|
|
31587
31964
|
function assignmentMdPath(item) {
|
|
31588
31965
|
if (item.projectSlug) {
|
|
31589
|
-
return
|
|
31966
|
+
return resolve82(
|
|
31590
31967
|
defaultProjectDir(),
|
|
31591
31968
|
item.projectSlug,
|
|
31592
31969
|
"assignments",
|
|
@@ -31594,7 +31971,7 @@ function assignmentMdPath(item) {
|
|
|
31594
31971
|
"assignment.md"
|
|
31595
31972
|
);
|
|
31596
31973
|
}
|
|
31597
|
-
return
|
|
31974
|
+
return resolve82(assignmentsDir(), item.id, "assignment.md");
|
|
31598
31975
|
}
|
|
31599
31976
|
async function loadTags(item) {
|
|
31600
31977
|
const path = assignmentMdPath(item);
|
|
@@ -32024,10 +32401,10 @@ viewsCommand.command("delete").description("Delete a saved view by id").argument
|
|
|
32024
32401
|
|
|
32025
32402
|
// src/cli-default-command.ts
|
|
32026
32403
|
init_config2();
|
|
32027
|
-
import { readdir as
|
|
32404
|
+
import { readdir as readdir29 } from "fs/promises";
|
|
32028
32405
|
async function hasAnyProjectContent(projectsDir2) {
|
|
32029
32406
|
try {
|
|
32030
|
-
const entries = await
|
|
32407
|
+
const entries = await readdir29(projectsDir2, { withFileTypes: true });
|
|
32031
32408
|
return entries.some((entry) => entry.isDirectory());
|
|
32032
32409
|
} catch {
|
|
32033
32410
|
return false;
|