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/dashboard/server.js
CHANGED
|
@@ -169,8 +169,8 @@ function extractFrontmatter(fileContent) {
|
|
|
169
169
|
const body = fileContent.slice(match[0].length);
|
|
170
170
|
return [frontmatterBlock, body];
|
|
171
171
|
}
|
|
172
|
-
function parseSimpleValue(
|
|
173
|
-
const trimmed =
|
|
172
|
+
function parseSimpleValue(raw2) {
|
|
173
|
+
const trimmed = raw2.trim();
|
|
174
174
|
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
175
175
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
176
176
|
return trimmed.slice(1, -1);
|
|
@@ -468,8 +468,8 @@ function extractFrontmatter2(fileContent) {
|
|
|
468
468
|
const body = fileContent.slice(match[0].length).trim();
|
|
469
469
|
return [frontmatterBlock, body];
|
|
470
470
|
}
|
|
471
|
-
function parseSimpleValue2(
|
|
472
|
-
const trimmed =
|
|
471
|
+
function parseSimpleValue2(raw2) {
|
|
472
|
+
const trimmed = raw2.trim();
|
|
473
473
|
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
474
474
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
475
475
|
return trimmed.slice(1, -1);
|
|
@@ -1500,8 +1500,8 @@ ${key}: ${formatted}${content.slice(closingIdx)}`;
|
|
|
1500
1500
|
function readFrontmatterField(content, key) {
|
|
1501
1501
|
const match = content.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
1502
1502
|
if (!match) return null;
|
|
1503
|
-
const
|
|
1504
|
-
return
|
|
1503
|
+
const raw2 = match[1].trim().replace(/^['"]|['"]$/g, "");
|
|
1504
|
+
return raw2 === "" || raw2 === "null" ? null : raw2;
|
|
1505
1505
|
}
|
|
1506
1506
|
async function migrateLegacyArchivedProjects(projectsDir) {
|
|
1507
1507
|
const result = { reconciled: [] };
|
|
@@ -1771,9 +1771,9 @@ function normalizeHiddenList(input) {
|
|
|
1771
1771
|
if (!Array.isArray(input)) return [];
|
|
1772
1772
|
const seen = /* @__PURE__ */ new Set();
|
|
1773
1773
|
const out = [];
|
|
1774
|
-
for (const
|
|
1775
|
-
if (typeof
|
|
1776
|
-
const name =
|
|
1774
|
+
for (const raw2 of input) {
|
|
1775
|
+
if (typeof raw2 !== "string") continue;
|
|
1776
|
+
const name = raw2.trim();
|
|
1777
1777
|
if (name.length === 0) continue;
|
|
1778
1778
|
if (name.length > MAX_WORKSPACE_NAME_LENGTH) continue;
|
|
1779
1779
|
if (/[\r\n]/.test(name)) continue;
|
|
@@ -2152,13 +2152,13 @@ function parsePlaybooksConfig(fmBlock) {
|
|
|
2152
2152
|
continue;
|
|
2153
2153
|
}
|
|
2154
2154
|
if (currentSection === "disabled" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
2155
|
-
const
|
|
2156
|
-
if (
|
|
2157
|
-
if (/\s/.test(
|
|
2158
|
-
console.warn(`Warning: config.md playbooks.disabled entry "${
|
|
2155
|
+
const raw2 = trimmed.slice(2).trim().replace(/^["']|["']$/g, "");
|
|
2156
|
+
if (raw2.length === 0) continue;
|
|
2157
|
+
if (/\s/.test(raw2)) {
|
|
2158
|
+
console.warn(`Warning: config.md playbooks.disabled entry "${raw2}" contains whitespace, ignoring`);
|
|
2159
2159
|
continue;
|
|
2160
2160
|
}
|
|
2161
|
-
disabled.push(
|
|
2161
|
+
disabled.push(raw2);
|
|
2162
2162
|
continue;
|
|
2163
2163
|
}
|
|
2164
2164
|
}
|
|
@@ -2438,9 +2438,9 @@ function serializeHotkeyBindingsConfig(cfg) {
|
|
|
2438
2438
|
async function writeHotkeyBindingsConfig(cfg) {
|
|
2439
2439
|
const cleaned = {};
|
|
2440
2440
|
for (const kind of BINDABLE_ACTION_KINDS) {
|
|
2441
|
-
const
|
|
2442
|
-
if (typeof
|
|
2443
|
-
const canonical = canonicalizeCombo(
|
|
2441
|
+
const raw2 = cfg.bindings[kind];
|
|
2442
|
+
if (typeof raw2 !== "string" || raw2.trim() === "") continue;
|
|
2443
|
+
const canonical = canonicalizeCombo(raw2);
|
|
2444
2444
|
if (!canonical) continue;
|
|
2445
2445
|
if (isReservedCombo(canonical)) continue;
|
|
2446
2446
|
cleaned[kind] = canonical;
|
|
@@ -3237,8 +3237,8 @@ async function resolvePlaybookSlug(playbooksDir2, slug) {
|
|
|
3237
3237
|
for (const entry of entries) {
|
|
3238
3238
|
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
3239
3239
|
const filePath = resolve7(playbooksDir2, entry.name);
|
|
3240
|
-
const
|
|
3241
|
-
const parsed = parsePlaybook(
|
|
3240
|
+
const raw2 = await readFile6(filePath, "utf-8");
|
|
3241
|
+
const parsed = parsePlaybook(raw2);
|
|
3242
3242
|
const canonical = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
3243
3243
|
if (canonical === slug) {
|
|
3244
3244
|
return { filename: entry.name, slug: canonical, parsed };
|
|
@@ -3285,8 +3285,8 @@ async function rebuildPlaybookManifest(playbooksDir2) {
|
|
|
3285
3285
|
const rows = [];
|
|
3286
3286
|
for (const entry of entries) {
|
|
3287
3287
|
if (!isVisiblePlaybookFile(entry.name, entry.isFile())) continue;
|
|
3288
|
-
const
|
|
3289
|
-
const parsed = parsePlaybook(
|
|
3288
|
+
const raw2 = await readFile6(resolve7(playbooksDir2, entry.name), "utf-8");
|
|
3289
|
+
const parsed = parsePlaybook(raw2);
|
|
3290
3290
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
3291
3291
|
if (disabledSet.has(slug)) continue;
|
|
3292
3292
|
rows.push({
|
|
@@ -3363,8 +3363,8 @@ async function renamePlaybook(playbooksDir2, oldSlug, newSlug) {
|
|
|
3363
3363
|
);
|
|
3364
3364
|
}
|
|
3365
3365
|
}
|
|
3366
|
-
const
|
|
3367
|
-
let next = setFrontmatterField2(
|
|
3366
|
+
const raw2 = await readFile6(oldPath, "utf-8");
|
|
3367
|
+
let next = setFrontmatterField2(raw2, "slug", newSlug);
|
|
3368
3368
|
next = setFrontmatterField2(next, "updated", `"${nowTimestamp()}"`);
|
|
3369
3369
|
await writeFileForce(newPath, next);
|
|
3370
3370
|
if (!renamedInPlace) {
|
|
@@ -4090,9 +4090,9 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
4090
4090
|
}
|
|
4091
4091
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
4092
4092
|
const { readFile: readFile21 } = await import("fs/promises");
|
|
4093
|
-
const
|
|
4093
|
+
const raw2 = await readFile21(filePath, "utf-8");
|
|
4094
4094
|
const sessions = [];
|
|
4095
|
-
const lines =
|
|
4095
|
+
const lines = raw2.split("\n");
|
|
4096
4096
|
let inTable = false;
|
|
4097
4097
|
let headerSeen = false;
|
|
4098
4098
|
for (const line of lines) {
|
|
@@ -4248,8 +4248,8 @@ async function deleteSessions(sessionIds) {
|
|
|
4248
4248
|
}
|
|
4249
4249
|
async function readAssignmentStatusFromPath(assignmentMdPath) {
|
|
4250
4250
|
if (!await fileExists(assignmentMdPath)) return null;
|
|
4251
|
-
const
|
|
4252
|
-
const match =
|
|
4251
|
+
const raw2 = await readFile8(assignmentMdPath, "utf-8");
|
|
4252
|
+
const match = raw2.match(/^status:\s*(.+)$/m);
|
|
4253
4253
|
return match ? match[1].trim() : null;
|
|
4254
4254
|
}
|
|
4255
4255
|
async function readAssignmentStatus(projectDir, assignmentSlug) {
|
|
@@ -4393,8 +4393,8 @@ async function listSessionFiles(dir) {
|
|
|
4393
4393
|
async function readSessionFile(dir, name) {
|
|
4394
4394
|
const filePath = resolve11(dir, `${sanitizeSessionName(name)}.md`);
|
|
4395
4395
|
if (!await fileExists(filePath)) return null;
|
|
4396
|
-
const
|
|
4397
|
-
const [frontmatter] = extractFrontmatter2(
|
|
4396
|
+
const raw2 = await readFile9(filePath, "utf-8");
|
|
4397
|
+
const [frontmatter] = extractFrontmatter2(raw2);
|
|
4398
4398
|
if (!frontmatter) return null;
|
|
4399
4399
|
const session = getField(frontmatter, "session") ?? name;
|
|
4400
4400
|
const registered = getField(frontmatter, "registered") ?? "";
|
|
@@ -4522,8 +4522,8 @@ function scanKey(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
4522
4522
|
return `${serversDir2}\0${projectsDir}\0${assignmentsDir2 ?? ""}`;
|
|
4523
4523
|
}
|
|
4524
4524
|
function delay(ms) {
|
|
4525
|
-
return new Promise((
|
|
4526
|
-
const timer2 = setTimeout(
|
|
4525
|
+
return new Promise((resolve30) => {
|
|
4526
|
+
const timer2 = setTimeout(resolve30, ms);
|
|
4527
4527
|
if (typeof timer2.unref === "function") {
|
|
4528
4528
|
timer2.unref();
|
|
4529
4529
|
}
|
|
@@ -5130,8 +5130,8 @@ async function listProjects(projectsDir) {
|
|
|
5130
5130
|
async function readWorkspaceRegistry(projectsDir) {
|
|
5131
5131
|
const registryPath = resolve13(dirname2(projectsDir), "workspaces.json");
|
|
5132
5132
|
try {
|
|
5133
|
-
const
|
|
5134
|
-
const parsed = JSON.parse(
|
|
5133
|
+
const raw2 = await readFile10(registryPath, "utf-8");
|
|
5134
|
+
const parsed = JSON.parse(raw2);
|
|
5135
5135
|
return Array.isArray(parsed) ? parsed.filter((w) => typeof w === "string") : [];
|
|
5136
5136
|
} catch {
|
|
5137
5137
|
return [];
|
|
@@ -5219,8 +5219,8 @@ async function deleteWorkspace(projectsDir, name, opts = {}) {
|
|
|
5219
5219
|
const timestamp = nowTimestamp();
|
|
5220
5220
|
for (const slug of projectsReferencing) {
|
|
5221
5221
|
const path = resolve13(projectsDir, slug, "project.md");
|
|
5222
|
-
const
|
|
5223
|
-
let next = clearFrontmatterField(
|
|
5222
|
+
const raw2 = await readFile10(path, "utf-8");
|
|
5223
|
+
let next = clearFrontmatterField(raw2, "workspace");
|
|
5224
5224
|
next = setUpdatedField(next, timestamp);
|
|
5225
5225
|
await writeFileForce(path, next);
|
|
5226
5226
|
rewroteFiles = true;
|
|
@@ -5228,8 +5228,8 @@ async function deleteWorkspace(projectsDir, name, opts = {}) {
|
|
|
5228
5228
|
for (const id of standalonesReferencing) {
|
|
5229
5229
|
if (!opts.assignmentsDir) break;
|
|
5230
5230
|
const path = resolve13(opts.assignmentsDir, id, "assignment.md");
|
|
5231
|
-
const
|
|
5232
|
-
let next = clearFrontmatterField(
|
|
5231
|
+
const raw2 = await readFile10(path, "utf-8");
|
|
5232
|
+
let next = clearFrontmatterField(raw2, "workspaceGroup");
|
|
5233
5233
|
next = setUpdatedField(next, timestamp);
|
|
5234
5234
|
await writeFileForce(path, next);
|
|
5235
5235
|
rewroteFiles = true;
|
|
@@ -6679,8 +6679,8 @@ async function listPlaybooks(playbooksDir2) {
|
|
|
6679
6679
|
for (const entry of entries) {
|
|
6680
6680
|
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
|
|
6681
6681
|
const filePath = resolve13(playbooksDir2, entry.name);
|
|
6682
|
-
const
|
|
6683
|
-
const parsed = parsePlaybook(
|
|
6682
|
+
const raw2 = await readFile10(filePath, "utf-8");
|
|
6683
|
+
const parsed = parsePlaybook(raw2);
|
|
6684
6684
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
6685
6685
|
playbooks.push({
|
|
6686
6686
|
slug,
|
|
@@ -6904,8 +6904,8 @@ init_assignment_resolver();
|
|
|
6904
6904
|
init_agent_sessions();
|
|
6905
6905
|
import express from "express";
|
|
6906
6906
|
import { createServer } from "http";
|
|
6907
|
-
import { resolve as
|
|
6908
|
-
import { writeFile as
|
|
6907
|
+
import { resolve as resolve29 } from "path";
|
|
6908
|
+
import { writeFile as writeFile7, unlink as unlink7 } from "fs/promises";
|
|
6909
6909
|
import { WebSocketServer, WebSocket } from "ws";
|
|
6910
6910
|
|
|
6911
6911
|
// src/dashboard/session-liveness.ts
|
|
@@ -7379,15 +7379,15 @@ async function readViewPrefsFile() {
|
|
|
7379
7379
|
if (!await fileExists(path)) {
|
|
7380
7380
|
return { ...DEFAULT_VIEW_PREFS_FILE };
|
|
7381
7381
|
}
|
|
7382
|
-
let
|
|
7382
|
+
let raw2;
|
|
7383
7383
|
try {
|
|
7384
|
-
|
|
7384
|
+
raw2 = await readFile11(path, "utf-8");
|
|
7385
7385
|
} catch {
|
|
7386
7386
|
return { ...DEFAULT_VIEW_PREFS_FILE };
|
|
7387
7387
|
}
|
|
7388
7388
|
let parsed;
|
|
7389
7389
|
try {
|
|
7390
|
-
parsed = JSON.parse(
|
|
7390
|
+
parsed = JSON.parse(raw2);
|
|
7391
7391
|
} catch {
|
|
7392
7392
|
await backupCorrupt(path);
|
|
7393
7393
|
return { ...DEFAULT_VIEW_PREFS_FILE };
|
|
@@ -7592,15 +7592,15 @@ async function readSavedViewsFile() {
|
|
|
7592
7592
|
if (!await fileExists(path)) {
|
|
7593
7593
|
return cloneDefault();
|
|
7594
7594
|
}
|
|
7595
|
-
let
|
|
7595
|
+
let raw2;
|
|
7596
7596
|
try {
|
|
7597
|
-
|
|
7597
|
+
raw2 = await readFile12(path, "utf-8");
|
|
7598
7598
|
} catch {
|
|
7599
7599
|
return cloneDefault();
|
|
7600
7600
|
}
|
|
7601
7601
|
let parsed;
|
|
7602
7602
|
try {
|
|
7603
|
-
parsed = JSON.parse(
|
|
7603
|
+
parsed = JSON.parse(raw2);
|
|
7604
7604
|
} catch {
|
|
7605
7605
|
await backupCorrupt2(path);
|
|
7606
7606
|
return cloneDefault();
|
|
@@ -8145,8 +8145,8 @@ async function getProjectRepositoryCandidates(projectsDir, projectSlug) {
|
|
|
8145
8145
|
const projectPath = resolve17(projectsDir, projectSlug, "project.md");
|
|
8146
8146
|
if (await fileExists(projectPath)) {
|
|
8147
8147
|
const project = parseProject(await readFile14(projectPath, "utf-8"));
|
|
8148
|
-
for (const
|
|
8149
|
-
const path =
|
|
8148
|
+
for (const raw2 of project.repositories) {
|
|
8149
|
+
const path = raw2.trim();
|
|
8150
8150
|
if (!path) continue;
|
|
8151
8151
|
const abs = resolve17(path);
|
|
8152
8152
|
if (seen.has(abs)) continue;
|
|
@@ -11459,8 +11459,8 @@ function mapAgentErrorToFieldErrors(err) {
|
|
|
11459
11459
|
}
|
|
11460
11460
|
return { error: message };
|
|
11461
11461
|
}
|
|
11462
|
-
function coerceAgentRow(
|
|
11463
|
-
if (!
|
|
11462
|
+
function coerceAgentRow(raw2, index) {
|
|
11463
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) {
|
|
11464
11464
|
return {
|
|
11465
11465
|
ok: false,
|
|
11466
11466
|
status: 400,
|
|
@@ -11470,7 +11470,7 @@ function coerceAgentRow(raw, index) {
|
|
|
11470
11470
|
}
|
|
11471
11471
|
};
|
|
11472
11472
|
}
|
|
11473
|
-
const entry =
|
|
11473
|
+
const entry = raw2;
|
|
11474
11474
|
if (typeof entry.id !== "string" || entry.id.length === 0) {
|
|
11475
11475
|
return {
|
|
11476
11476
|
ok: false,
|
|
@@ -11579,14 +11579,14 @@ function createAgentsRouter() {
|
|
|
11579
11579
|
});
|
|
11580
11580
|
router.put("/", async (req, res) => {
|
|
11581
11581
|
try {
|
|
11582
|
-
const
|
|
11583
|
-
if (!Array.isArray(
|
|
11582
|
+
const raw2 = req.body && typeof req.body === "object" ? req.body : {};
|
|
11583
|
+
if (!Array.isArray(raw2.agents)) {
|
|
11584
11584
|
res.status(400).json({ error: "agents must be an array" });
|
|
11585
11585
|
return;
|
|
11586
11586
|
}
|
|
11587
11587
|
const cleaned = [];
|
|
11588
|
-
for (let i = 0; i <
|
|
11589
|
-
const result = coerceAgentRow(
|
|
11588
|
+
for (let i = 0; i < raw2.agents.length; i++) {
|
|
11589
|
+
const result = coerceAgentRow(raw2.agents[i], i);
|
|
11590
11590
|
if (!result.ok) {
|
|
11591
11591
|
res.status(result.status).json(result.body);
|
|
11592
11592
|
return;
|
|
@@ -12186,18 +12186,18 @@ function buildAffectedResponse(id, list) {
|
|
|
12186
12186
|
function isString(x) {
|
|
12187
12187
|
return typeof x === "string" && x.length > 0;
|
|
12188
12188
|
}
|
|
12189
|
-
function parseResolutions(
|
|
12190
|
-
if (
|
|
12189
|
+
function parseResolutions(raw2) {
|
|
12190
|
+
if (raw2 === void 0) {
|
|
12191
12191
|
return { resolutions: [], malformed: null, duplicateIds: null };
|
|
12192
12192
|
}
|
|
12193
|
-
if (!Array.isArray(
|
|
12193
|
+
if (!Array.isArray(raw2)) {
|
|
12194
12194
|
return { resolutions: [], malformed: "resolutions must be an array", duplicateIds: null };
|
|
12195
12195
|
}
|
|
12196
12196
|
const out = [];
|
|
12197
12197
|
const seen = /* @__PURE__ */ new Set();
|
|
12198
12198
|
const dups = /* @__PURE__ */ new Set();
|
|
12199
|
-
for (let i = 0; i <
|
|
12200
|
-
const r =
|
|
12199
|
+
for (let i = 0; i < raw2.length; i++) {
|
|
12200
|
+
const r = raw2[i];
|
|
12201
12201
|
if (!r || typeof r !== "object") {
|
|
12202
12202
|
return { resolutions: [], malformed: `resolutions[${i}] must be an object`, duplicateIds: null };
|
|
12203
12203
|
}
|
|
@@ -13173,10 +13173,10 @@ init_fs_migration();
|
|
|
13173
13173
|
init_parser2();
|
|
13174
13174
|
init_fs();
|
|
13175
13175
|
init_paths();
|
|
13176
|
-
import { Router as
|
|
13177
|
-
import { readdir as
|
|
13178
|
-
import { resolve as resolvePath, dirname as
|
|
13179
|
-
import { rename as
|
|
13176
|
+
import { Router as Router14 } from "express";
|
|
13177
|
+
import { readdir as readdir11 } from "fs/promises";
|
|
13178
|
+
import { resolve as resolvePath, dirname as dirname6 } from "path";
|
|
13179
|
+
import { rename as rename6, mkdir as mkdir3 } from "fs/promises";
|
|
13180
13180
|
init_slug();
|
|
13181
13181
|
|
|
13182
13182
|
// src/utils/promote-todos.ts
|
|
@@ -13515,6 +13515,331 @@ async function promoteTodosToNewAssignment(groups, options) {
|
|
|
13515
13515
|
|
|
13516
13516
|
// src/dashboard/api-todos.ts
|
|
13517
13517
|
init_api();
|
|
13518
|
+
|
|
13519
|
+
// src/dashboard/todo-attachments-routes.ts
|
|
13520
|
+
import { raw } from "express";
|
|
13521
|
+
|
|
13522
|
+
// src/todos/attachments.ts
|
|
13523
|
+
import { mkdir as mkdir2, readdir as readdir10, stat, rename as rename5, rm as rm3, unlink as unlink5, writeFile as writeFile5, cp } from "fs/promises";
|
|
13524
|
+
import { resolve as resolve25, basename as basename4, dirname as dirname5, extname } from "path";
|
|
13525
|
+
|
|
13526
|
+
// src/utils/proof-artifact-id.ts
|
|
13527
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
13528
|
+
function generateArtifactId() {
|
|
13529
|
+
const ts = Date.now().toString(36);
|
|
13530
|
+
const rand = randomBytes2(2).toString("hex");
|
|
13531
|
+
return `${ts}-${rand}`;
|
|
13532
|
+
}
|
|
13533
|
+
|
|
13534
|
+
// src/todos/attachments.ts
|
|
13535
|
+
var SCOPE_RE = /^[a-z0-9_][a-z0-9-]*$/;
|
|
13536
|
+
var TODO_ID_RE = /^[a-f0-9]{4}$/;
|
|
13537
|
+
var ATTACHMENT_ID_RE = /^[a-z0-9]+-[0-9a-f]{4}$/;
|
|
13538
|
+
var AttachmentValidationError = class extends Error {
|
|
13539
|
+
constructor(message) {
|
|
13540
|
+
super(message);
|
|
13541
|
+
this.name = "AttachmentValidationError";
|
|
13542
|
+
}
|
|
13543
|
+
};
|
|
13544
|
+
function assertScope(scopeId) {
|
|
13545
|
+
if (!SCOPE_RE.test(scopeId)) throw new AttachmentValidationError(`Invalid scope id: "${scopeId}"`);
|
|
13546
|
+
}
|
|
13547
|
+
function assertTodoId(todoId) {
|
|
13548
|
+
if (!TODO_ID_RE.test(todoId)) throw new AttachmentValidationError(`Invalid todo id: "${todoId}"`);
|
|
13549
|
+
}
|
|
13550
|
+
function assertAttachmentId(attachmentId) {
|
|
13551
|
+
if (!ATTACHMENT_ID_RE.test(attachmentId)) {
|
|
13552
|
+
throw new AttachmentValidationError(`Invalid attachment id: "${attachmentId}"`);
|
|
13553
|
+
}
|
|
13554
|
+
}
|
|
13555
|
+
var EXT_MIME = {
|
|
13556
|
+
png: "image/png",
|
|
13557
|
+
jpg: "image/jpeg",
|
|
13558
|
+
jpeg: "image/jpeg",
|
|
13559
|
+
gif: "image/gif",
|
|
13560
|
+
webp: "image/webp",
|
|
13561
|
+
bmp: "image/bmp",
|
|
13562
|
+
ico: "image/x-icon",
|
|
13563
|
+
svg: "image/svg+xml",
|
|
13564
|
+
pdf: "application/pdf",
|
|
13565
|
+
txt: "text/plain",
|
|
13566
|
+
log: "text/plain",
|
|
13567
|
+
md: "text/markdown",
|
|
13568
|
+
json: "application/json",
|
|
13569
|
+
csv: "text/csv",
|
|
13570
|
+
html: "text/html",
|
|
13571
|
+
htm: "text/html",
|
|
13572
|
+
xml: "application/xml",
|
|
13573
|
+
mp4: "video/mp4",
|
|
13574
|
+
mov: "video/quicktime",
|
|
13575
|
+
webm: "video/webm",
|
|
13576
|
+
mp3: "audio/mpeg",
|
|
13577
|
+
wav: "audio/wav",
|
|
13578
|
+
m4a: "audio/mp4",
|
|
13579
|
+
zip: "application/zip"
|
|
13580
|
+
};
|
|
13581
|
+
function mimeForName(name) {
|
|
13582
|
+
const ext = extname(name).slice(1).toLowerCase();
|
|
13583
|
+
return EXT_MIME[ext] ?? "application/octet-stream";
|
|
13584
|
+
}
|
|
13585
|
+
var SAFE_INLINE_MIME = /* @__PURE__ */ new Set([
|
|
13586
|
+
"image/png",
|
|
13587
|
+
"image/jpeg",
|
|
13588
|
+
"image/gif",
|
|
13589
|
+
"image/webp",
|
|
13590
|
+
"application/pdf",
|
|
13591
|
+
"text/plain"
|
|
13592
|
+
]);
|
|
13593
|
+
function isSafeInlineMime(mime) {
|
|
13594
|
+
return SAFE_INLINE_MIME.has(mime);
|
|
13595
|
+
}
|
|
13596
|
+
function sanitizeAttachmentName(name) {
|
|
13597
|
+
let n = basename4(name || "").replace(/["'\\/]/g, "_");
|
|
13598
|
+
n = Array.from(n, (ch) => {
|
|
13599
|
+
const code = ch.charCodeAt(0);
|
|
13600
|
+
return code < 32 || code === 127 ? "_" : ch;
|
|
13601
|
+
}).join("");
|
|
13602
|
+
n = n.trim();
|
|
13603
|
+
if (!n || n === "." || n === "..") n = "file";
|
|
13604
|
+
if (n.length > 120) {
|
|
13605
|
+
const ext = extname(n);
|
|
13606
|
+
n = n.slice(0, Math.max(1, 120 - ext.length)) + ext;
|
|
13607
|
+
}
|
|
13608
|
+
return n;
|
|
13609
|
+
}
|
|
13610
|
+
function attachmentsRootDir(todosDir2) {
|
|
13611
|
+
return resolve25(todosDir2, "attachments");
|
|
13612
|
+
}
|
|
13613
|
+
function attachmentDirFor(todosDir2, scopeId, todoId) {
|
|
13614
|
+
assertScope(scopeId);
|
|
13615
|
+
assertTodoId(todoId);
|
|
13616
|
+
return resolve25(attachmentsRootDir(todosDir2), scopeId, todoId);
|
|
13617
|
+
}
|
|
13618
|
+
async function dirExists(p) {
|
|
13619
|
+
try {
|
|
13620
|
+
return (await stat(p)).isDirectory();
|
|
13621
|
+
} catch {
|
|
13622
|
+
return false;
|
|
13623
|
+
}
|
|
13624
|
+
}
|
|
13625
|
+
async function writeAttachment(todosDir2, scopeId, todoId, originalName, bytes) {
|
|
13626
|
+
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
13627
|
+
await mkdir2(dir, { recursive: true });
|
|
13628
|
+
const id = generateArtifactId();
|
|
13629
|
+
const filename = sanitizeAttachmentName(originalName);
|
|
13630
|
+
await writeFile5(resolve25(dir, `${id}__${filename}`), bytes);
|
|
13631
|
+
return {
|
|
13632
|
+
id,
|
|
13633
|
+
filename,
|
|
13634
|
+
mime: mimeForName(filename),
|
|
13635
|
+
size: bytes.length,
|
|
13636
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13637
|
+
};
|
|
13638
|
+
}
|
|
13639
|
+
async function listAttachments(todosDir2, scopeId, todoId) {
|
|
13640
|
+
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
13641
|
+
let names;
|
|
13642
|
+
try {
|
|
13643
|
+
names = await readdir10(dir);
|
|
13644
|
+
} catch {
|
|
13645
|
+
return [];
|
|
13646
|
+
}
|
|
13647
|
+
const out = [];
|
|
13648
|
+
for (const stored of names) {
|
|
13649
|
+
const sep2 = stored.indexOf("__");
|
|
13650
|
+
if (sep2 <= 0) continue;
|
|
13651
|
+
const id = stored.slice(0, sep2);
|
|
13652
|
+
if (!ATTACHMENT_ID_RE.test(id)) continue;
|
|
13653
|
+
const filename = stored.slice(sep2 + 2);
|
|
13654
|
+
try {
|
|
13655
|
+
const st = await stat(resolve25(dir, stored));
|
|
13656
|
+
if (!st.isFile()) continue;
|
|
13657
|
+
out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
|
|
13658
|
+
} catch {
|
|
13659
|
+
}
|
|
13660
|
+
}
|
|
13661
|
+
out.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
13662
|
+
return out;
|
|
13663
|
+
}
|
|
13664
|
+
async function readScopeAttachments(todosDir2, scopeId) {
|
|
13665
|
+
assertScope(scopeId);
|
|
13666
|
+
const scopeDir = resolve25(attachmentsRootDir(todosDir2), scopeId);
|
|
13667
|
+
let todoIds;
|
|
13668
|
+
try {
|
|
13669
|
+
todoIds = await readdir10(scopeDir);
|
|
13670
|
+
} catch {
|
|
13671
|
+
return {};
|
|
13672
|
+
}
|
|
13673
|
+
const result = {};
|
|
13674
|
+
for (const todoId of todoIds) {
|
|
13675
|
+
if (!TODO_ID_RE.test(todoId)) continue;
|
|
13676
|
+
const list = await listAttachments(todosDir2, scopeId, todoId);
|
|
13677
|
+
if (list.length) result[todoId] = list;
|
|
13678
|
+
}
|
|
13679
|
+
return result;
|
|
13680
|
+
}
|
|
13681
|
+
async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
|
|
13682
|
+
assertAttachmentId(attachmentId);
|
|
13683
|
+
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
13684
|
+
let names;
|
|
13685
|
+
try {
|
|
13686
|
+
names = await readdir10(dir);
|
|
13687
|
+
} catch {
|
|
13688
|
+
return null;
|
|
13689
|
+
}
|
|
13690
|
+
const prefix = `${attachmentId}__`;
|
|
13691
|
+
const stored = names.find((n) => n.startsWith(prefix));
|
|
13692
|
+
if (!stored) return null;
|
|
13693
|
+
const filename = stored.slice(prefix.length);
|
|
13694
|
+
return { path: resolve25(dir, stored), filename, mime: mimeForName(filename) };
|
|
13695
|
+
}
|
|
13696
|
+
async function deleteAttachment(todosDir2, scopeId, todoId, attachmentId) {
|
|
13697
|
+
const resolved = await resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId);
|
|
13698
|
+
if (!resolved) return false;
|
|
13699
|
+
await unlink5(resolved.path);
|
|
13700
|
+
return true;
|
|
13701
|
+
}
|
|
13702
|
+
async function deleteAllAttachments(todosDir2, scopeId, todoId) {
|
|
13703
|
+
await rm3(attachmentDirFor(todosDir2, scopeId, todoId), { recursive: true, force: true });
|
|
13704
|
+
}
|
|
13705
|
+
async function attachmentMoveConflict(srcTodosDir, srcScopeId, dstTodosDir, dstScopeId, todoId) {
|
|
13706
|
+
const src = attachmentDirFor(srcTodosDir, srcScopeId, todoId);
|
|
13707
|
+
const dst = attachmentDirFor(dstTodosDir, dstScopeId, todoId);
|
|
13708
|
+
return await dirExists(src) && await dirExists(dst);
|
|
13709
|
+
}
|
|
13710
|
+
async function moveAttachments(srcTodosDir, srcScopeId, dstTodosDir, dstScopeId, todoId) {
|
|
13711
|
+
const src = attachmentDirFor(srcTodosDir, srcScopeId, todoId);
|
|
13712
|
+
if (!await dirExists(src)) return;
|
|
13713
|
+
const dst = attachmentDirFor(dstTodosDir, dstScopeId, todoId);
|
|
13714
|
+
await mkdir2(dirname5(dst), { recursive: true });
|
|
13715
|
+
try {
|
|
13716
|
+
await rename5(src, dst);
|
|
13717
|
+
} catch (err) {
|
|
13718
|
+
if (err?.code === "EXDEV") {
|
|
13719
|
+
await cp(src, dst, { recursive: true });
|
|
13720
|
+
await rm3(src, { recursive: true, force: true });
|
|
13721
|
+
} else {
|
|
13722
|
+
throw err;
|
|
13723
|
+
}
|
|
13724
|
+
}
|
|
13725
|
+
}
|
|
13726
|
+
|
|
13727
|
+
// src/dashboard/todo-attachments-routes.ts
|
|
13728
|
+
var MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
13729
|
+
function headerValue(req, name) {
|
|
13730
|
+
const v = req.headers[name];
|
|
13731
|
+
return Array.isArray(v) ? v[0] : v;
|
|
13732
|
+
}
|
|
13733
|
+
function paramStr(v) {
|
|
13734
|
+
return Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
13735
|
+
}
|
|
13736
|
+
function sendError(res, err) {
|
|
13737
|
+
if (err instanceof AttachmentValidationError) {
|
|
13738
|
+
res.status(400).json({ error: err.message });
|
|
13739
|
+
return;
|
|
13740
|
+
}
|
|
13741
|
+
res.status(500).json({ error: err instanceof Error ? err.message : "Attachment operation failed" });
|
|
13742
|
+
}
|
|
13743
|
+
function contentDisposition(filename, inline) {
|
|
13744
|
+
const disp = inline ? "inline" : "attachment";
|
|
13745
|
+
const asciiFallback = Array.from(filename, (ch) => {
|
|
13746
|
+
const code = ch.charCodeAt(0);
|
|
13747
|
+
return code >= 32 && code <= 126 && ch !== '"' && ch !== "\\" ? ch : "_";
|
|
13748
|
+
}).join("");
|
|
13749
|
+
return `${disp}; filename="${asciiFallback}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
13750
|
+
}
|
|
13751
|
+
function installTodoAttachmentRoutes(router, prefix, opts) {
|
|
13752
|
+
router.post(
|
|
13753
|
+
`${prefix}/attachments`,
|
|
13754
|
+
raw({ type: () => true, limit: MAX_UPLOAD_BYTES }),
|
|
13755
|
+
async (req, res) => {
|
|
13756
|
+
try {
|
|
13757
|
+
const rawName = headerValue(req, "x-attachment-filename");
|
|
13758
|
+
let filename = "file";
|
|
13759
|
+
if (rawName) {
|
|
13760
|
+
try {
|
|
13761
|
+
filename = decodeURIComponent(rawName);
|
|
13762
|
+
} catch {
|
|
13763
|
+
res.status(400).json({ error: "Invalid x-attachment-filename header" });
|
|
13764
|
+
return;
|
|
13765
|
+
}
|
|
13766
|
+
}
|
|
13767
|
+
const body = req.body;
|
|
13768
|
+
if (!Buffer.isBuffer(body) || body.length === 0) {
|
|
13769
|
+
res.status(400).json({ error: "Empty upload body" });
|
|
13770
|
+
return;
|
|
13771
|
+
}
|
|
13772
|
+
const scope = opts.resolveScope(req);
|
|
13773
|
+
const result = await opts.withScopeLock(req, async () => {
|
|
13774
|
+
if (!await opts.todoExists(scope)) return null;
|
|
13775
|
+
return writeAttachment(scope.todosDir, scope.scopeId, scope.todoId, filename, body);
|
|
13776
|
+
});
|
|
13777
|
+
if (!result) {
|
|
13778
|
+
res.status(404).json({ error: `Todo "${scope.todoId}" not found` });
|
|
13779
|
+
return;
|
|
13780
|
+
}
|
|
13781
|
+
opts.onChange(req);
|
|
13782
|
+
res.status(201).json(result);
|
|
13783
|
+
} catch (err) {
|
|
13784
|
+
sendError(res, err);
|
|
13785
|
+
}
|
|
13786
|
+
}
|
|
13787
|
+
);
|
|
13788
|
+
router.get(`${prefix}/attachments/:attachmentId`, async (req, res) => {
|
|
13789
|
+
try {
|
|
13790
|
+
const scope = opts.resolveScope(req);
|
|
13791
|
+
const attachmentId = paramStr(req.params.attachmentId);
|
|
13792
|
+
const resolved = await opts.withScopeLock(req, async () => {
|
|
13793
|
+
if (!await opts.todoExists(scope)) return { notFound: true };
|
|
13794
|
+
return {
|
|
13795
|
+
file: await resolveAttachmentFile(scope.todosDir, scope.scopeId, scope.todoId, attachmentId)
|
|
13796
|
+
};
|
|
13797
|
+
});
|
|
13798
|
+
if ("notFound" in resolved) {
|
|
13799
|
+
res.status(404).json({ error: `Todo "${scope.todoId}" not found` });
|
|
13800
|
+
return;
|
|
13801
|
+
}
|
|
13802
|
+
if (!resolved.file) {
|
|
13803
|
+
res.status(404).json({ error: `Attachment "${attachmentId}" not found` });
|
|
13804
|
+
return;
|
|
13805
|
+
}
|
|
13806
|
+
const { path, filename, mime } = resolved.file;
|
|
13807
|
+
const inline = isSafeInlineMime(mime);
|
|
13808
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
13809
|
+
res.setHeader("Content-Type", inline ? mime : "application/octet-stream");
|
|
13810
|
+
res.setHeader("Content-Disposition", contentDisposition(filename, inline));
|
|
13811
|
+
res.sendFile(path, (err) => {
|
|
13812
|
+
if (err && !res.headersSent) res.status(500).end();
|
|
13813
|
+
});
|
|
13814
|
+
} catch (err) {
|
|
13815
|
+
sendError(res, err);
|
|
13816
|
+
}
|
|
13817
|
+
});
|
|
13818
|
+
router.delete(`${prefix}/attachments/:attachmentId`, async (req, res) => {
|
|
13819
|
+
try {
|
|
13820
|
+
const scope = opts.resolveScope(req);
|
|
13821
|
+
const attachmentId = paramStr(req.params.attachmentId);
|
|
13822
|
+
const result = await opts.withScopeLock(req, async () => {
|
|
13823
|
+
if (!await opts.todoExists(scope)) return { notFound: true };
|
|
13824
|
+
return { deleted: await deleteAttachment(scope.todosDir, scope.scopeId, scope.todoId, attachmentId) };
|
|
13825
|
+
});
|
|
13826
|
+
if ("notFound" in result) {
|
|
13827
|
+
res.status(404).json({ error: `Todo "${scope.todoId}" not found` });
|
|
13828
|
+
return;
|
|
13829
|
+
}
|
|
13830
|
+
if (!result.deleted) {
|
|
13831
|
+
res.status(404).json({ error: `Attachment "${attachmentId}" not found` });
|
|
13832
|
+
return;
|
|
13833
|
+
}
|
|
13834
|
+
opts.onChange(req);
|
|
13835
|
+
res.json({ deleted: attachmentId });
|
|
13836
|
+
} catch (err) {
|
|
13837
|
+
sendError(res, err);
|
|
13838
|
+
}
|
|
13839
|
+
});
|
|
13840
|
+
}
|
|
13841
|
+
|
|
13842
|
+
// src/dashboard/api-todos.ts
|
|
13518
13843
|
var WORKSPACE_REGEX = /^[a-z0-9_][a-z0-9-]*$/;
|
|
13519
13844
|
function getWorkspaceParam(value) {
|
|
13520
13845
|
if (Array.isArray(value)) {
|
|
@@ -13528,7 +13853,7 @@ function touchItem3(item) {
|
|
|
13528
13853
|
item.updatedAt = now;
|
|
13529
13854
|
}
|
|
13530
13855
|
function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
13531
|
-
const router =
|
|
13856
|
+
const router = Router14();
|
|
13532
13857
|
installRecordsInvalidation(router);
|
|
13533
13858
|
function broadcastUpdate() {
|
|
13534
13859
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -13545,6 +13870,19 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
13545
13870
|
next();
|
|
13546
13871
|
}
|
|
13547
13872
|
router.param("workspace", validateWorkspace);
|
|
13873
|
+
installTodoAttachmentRoutes(router, "/:workspace/:id", {
|
|
13874
|
+
resolveScope: (req) => ({
|
|
13875
|
+
todosDir: todosDir2,
|
|
13876
|
+
scopeId: getWorkspaceParam(req.params.workspace),
|
|
13877
|
+
todoId: getWorkspaceParam(req.params.id)
|
|
13878
|
+
}),
|
|
13879
|
+
withScopeLock: (req, fn) => wsLock(getWorkspaceParam(req.params.workspace), fn),
|
|
13880
|
+
todoExists: async (scope) => {
|
|
13881
|
+
const checklist = await readChecklist(scope.todosDir, scope.scopeId);
|
|
13882
|
+
return checklist.items.some((i) => i.id === scope.todoId);
|
|
13883
|
+
},
|
|
13884
|
+
onChange: () => broadcastUpdate()
|
|
13885
|
+
});
|
|
13548
13886
|
router.post("/promote-bulk", async (req, res) => {
|
|
13549
13887
|
try {
|
|
13550
13888
|
const { groups, mode, target, title, type, priority, keepSource } = req.body ?? {};
|
|
@@ -13649,17 +13987,18 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
13649
13987
|
router.get("/", async (_req, res) => {
|
|
13650
13988
|
try {
|
|
13651
13989
|
await ensureDir(todosDir2);
|
|
13652
|
-
const files = await
|
|
13990
|
+
const files = await readdir11(todosDir2).catch(() => []);
|
|
13653
13991
|
const workspaces = [];
|
|
13654
13992
|
for (const file of files) {
|
|
13655
13993
|
if (typeof file !== "string") continue;
|
|
13656
13994
|
if (!file.endsWith(".md") || file.endsWith("-log.md")) continue;
|
|
13657
13995
|
const workspace = file.replace(".md", "");
|
|
13658
13996
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
13997
|
+
const attachmentsByTodo = await readScopeAttachments(todosDir2, checklist.workspace);
|
|
13659
13998
|
workspaces.push({
|
|
13660
13999
|
workspace: checklist.workspace,
|
|
13661
14000
|
archiveInterval: checklist.archiveInterval,
|
|
13662
|
-
items: checklist.items,
|
|
14001
|
+
items: checklist.items.map((i) => ({ ...i, attachments: attachmentsByTodo[i.id] ?? [] })),
|
|
13663
14002
|
counts: computeCounts(checklist.items)
|
|
13664
14003
|
});
|
|
13665
14004
|
}
|
|
@@ -13672,10 +14011,11 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
13672
14011
|
try {
|
|
13673
14012
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
13674
14013
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
14014
|
+
const attachmentsByTodo = await readScopeAttachments(todosDir2, workspace);
|
|
13675
14015
|
res.json({
|
|
13676
14016
|
workspace: checklist.workspace,
|
|
13677
14017
|
archiveInterval: checklist.archiveInterval,
|
|
13678
|
-
items: checklist.items,
|
|
14018
|
+
items: checklist.items.map((i) => ({ ...i, attachments: attachmentsByTodo[i.id] ?? [] })),
|
|
13679
14019
|
counts: computeCounts(checklist.items)
|
|
13680
14020
|
});
|
|
13681
14021
|
} catch (error) {
|
|
@@ -13763,63 +14103,68 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
13763
14103
|
router.post("/:workspace/archive", async (req, res) => {
|
|
13764
14104
|
try {
|
|
13765
14105
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
13766
|
-
const { resolve:
|
|
14106
|
+
const { resolve: resolve30 } = await import("path");
|
|
13767
14107
|
const { readFile: readFile21 } = await import("fs/promises");
|
|
13768
14108
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
13769
14109
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
13770
|
-
const
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
13788
|
-
|
|
13789
|
-
|
|
14110
|
+
const outcome = await wsLock(workspace, async () => {
|
|
14111
|
+
const checklist = await readChecklist(todosDir2, workspace);
|
|
14112
|
+
const log = await readLog(todosDir2, workspace);
|
|
14113
|
+
const completedIds = new Set(
|
|
14114
|
+
checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
|
|
14115
|
+
);
|
|
14116
|
+
if (completedIds.size === 0) {
|
|
14117
|
+
return { archived: 0, message: "No completed items to archive" };
|
|
14118
|
+
}
|
|
14119
|
+
const toArchive = log.entries.filter(
|
|
14120
|
+
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
14121
|
+
);
|
|
14122
|
+
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
14123
|
+
await ensureDir(resolve30(todosDir2, "archive"));
|
|
14124
|
+
let archContent = "";
|
|
14125
|
+
if (await fileExists(archFile)) {
|
|
14126
|
+
archContent = await readFile21(archFile, "utf-8");
|
|
14127
|
+
archContent = archContent.trimEnd() + "\n\n";
|
|
14128
|
+
} else {
|
|
14129
|
+
archContent = `---
|
|
13790
14130
|
workspace: ${workspace}
|
|
13791
14131
|
---
|
|
13792
14132
|
|
|
13793
14133
|
# Archive
|
|
13794
14134
|
|
|
13795
14135
|
`;
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
14136
|
+
}
|
|
14137
|
+
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
14138
|
+
for (const item of completedItems) {
|
|
14139
|
+
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
13800
14140
|
`;
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
14141
|
+
}
|
|
14142
|
+
archContent += "\n";
|
|
14143
|
+
for (const entry of toArchive) {
|
|
14144
|
+
archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
|
|
13805
14145
|
`;
|
|
13806
|
-
|
|
14146
|
+
if (entry.items) archContent += `**Items:** ${entry.items}
|
|
13807
14147
|
`;
|
|
13808
|
-
|
|
14148
|
+
if (entry.session) archContent += `**Session:** ${entry.session}
|
|
13809
14149
|
`;
|
|
13810
|
-
|
|
14150
|
+
if (entry.branch) archContent += `**Branch:** ${entry.branch}
|
|
13811
14151
|
`;
|
|
13812
|
-
|
|
14152
|
+
if (entry.summary) archContent += `**Summary:** ${entry.summary}
|
|
13813
14153
|
`;
|
|
13814
|
-
|
|
14154
|
+
if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
|
|
13815
14155
|
`;
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
14156
|
+
archContent += "\n";
|
|
14157
|
+
}
|
|
14158
|
+
await writeFileForce2(archFile, archContent);
|
|
14159
|
+
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
14160
|
+
await writeChecklist(todosDir2, checklist);
|
|
14161
|
+
for (const id of completedIds) {
|
|
14162
|
+
await deleteAllAttachments(todosDir2, workspace, id);
|
|
14163
|
+
}
|
|
14164
|
+
return { archived: completedIds.size, logEntries: toArchive.length };
|
|
14165
|
+
});
|
|
14166
|
+
if (outcome.archived > 0) broadcastUpdate();
|
|
14167
|
+
res.json(outcome);
|
|
13823
14168
|
} catch (error) {
|
|
13824
14169
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to archive" });
|
|
13825
14170
|
}
|
|
@@ -13844,7 +14189,8 @@ workspace: ${workspace}
|
|
|
13844
14189
|
}
|
|
13845
14190
|
const log = await readLog(todosDir2, workspace);
|
|
13846
14191
|
const logEntries = log.entries.filter((e) => e.itemIds.includes(req.params.id));
|
|
13847
|
-
|
|
14192
|
+
const attachments = await listAttachments(todosDir2, workspace, item.id);
|
|
14193
|
+
res.json({ ...item, attachments, log: logEntries });
|
|
13848
14194
|
} catch (error) {
|
|
13849
14195
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
|
|
13850
14196
|
}
|
|
@@ -13881,6 +14227,7 @@ workspace: ${workspace}
|
|
|
13881
14227
|
if (idx === -1) return false;
|
|
13882
14228
|
checklist.items.splice(idx, 1);
|
|
13883
14229
|
await writeChecklist(todosDir2, checklist);
|
|
14230
|
+
await deleteAllAttachments(todosDir2, workspace, req.params.id);
|
|
13884
14231
|
return true;
|
|
13885
14232
|
});
|
|
13886
14233
|
if (!deleted) {
|
|
@@ -14192,15 +14539,22 @@ workspace: ${workspace}
|
|
|
14192
14539
|
return { status: 409, error: "id already exists in target" };
|
|
14193
14540
|
}
|
|
14194
14541
|
const item = sourceChecklist.items[idx];
|
|
14542
|
+
let newPlanDir = null;
|
|
14195
14543
|
if (item.planDir) {
|
|
14196
|
-
|
|
14544
|
+
newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
14197
14545
|
if (await fileExists(newPlanDir)) {
|
|
14198
14546
|
return { status: 409, error: "plan dir already exists in target" };
|
|
14199
14547
|
}
|
|
14200
|
-
|
|
14201
|
-
|
|
14548
|
+
}
|
|
14549
|
+
if (await attachmentMoveConflict(todosDir2, sourceWs, target.todosPath, target.id, id)) {
|
|
14550
|
+
return { status: 409, error: "attachments already exist in target" };
|
|
14551
|
+
}
|
|
14552
|
+
if (item.planDir && newPlanDir) {
|
|
14553
|
+
await mkdir3(dirname6(newPlanDir), { recursive: true });
|
|
14554
|
+
await rename6(item.planDir, newPlanDir);
|
|
14202
14555
|
item.planDir = newPlanDir;
|
|
14203
14556
|
}
|
|
14557
|
+
await moveAttachments(todosDir2, sourceWs, target.todosPath, target.id, id);
|
|
14204
14558
|
sourceChecklist.items.splice(idx, 1);
|
|
14205
14559
|
targetChecklist.items.push(item);
|
|
14206
14560
|
await writeChecklist(todosDir2, sourceChecklist);
|
|
@@ -14273,9 +14627,9 @@ init_parser2();
|
|
|
14273
14627
|
init_fs();
|
|
14274
14628
|
init_paths();
|
|
14275
14629
|
init_slug();
|
|
14276
|
-
import { Router as
|
|
14277
|
-
import { mkdir as
|
|
14278
|
-
import { resolve as
|
|
14630
|
+
import { Router as Router15 } from "express";
|
|
14631
|
+
import { mkdir as mkdir4, readFile as readFile18, rename as rename7 } from "fs/promises";
|
|
14632
|
+
import { resolve as resolve26, dirname as dirname7 } from "path";
|
|
14279
14633
|
init_api();
|
|
14280
14634
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
14281
14635
|
function touchItem4(item) {
|
|
@@ -14291,12 +14645,12 @@ function params(req) {
|
|
|
14291
14645
|
return req.params;
|
|
14292
14646
|
}
|
|
14293
14647
|
async function projectExists(projectsDir, slug) {
|
|
14294
|
-
return fileExists(
|
|
14648
|
+
return fileExists(resolve26(projectsDir, slug, "project.md"));
|
|
14295
14649
|
}
|
|
14296
14650
|
async function ensureProjectTodosDir(projectsDir, slug) {
|
|
14297
14651
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
14298
14652
|
try {
|
|
14299
|
-
await
|
|
14653
|
+
await mkdir4(todosDir2, { recursive: false });
|
|
14300
14654
|
} catch (err) {
|
|
14301
14655
|
const code = err.code;
|
|
14302
14656
|
if (code === "EEXIST") return;
|
|
@@ -14308,7 +14662,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
14308
14662
|
throw err;
|
|
14309
14663
|
}
|
|
14310
14664
|
try {
|
|
14311
|
-
await
|
|
14665
|
+
await mkdir4(resolve26(todosDir2, "archive"), { recursive: false });
|
|
14312
14666
|
} catch (err) {
|
|
14313
14667
|
const code = err.code;
|
|
14314
14668
|
if (code === "EEXIST") return;
|
|
@@ -14324,7 +14678,7 @@ function notFound(res, slug) {
|
|
|
14324
14678
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
14325
14679
|
}
|
|
14326
14680
|
function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
14327
|
-
const router =
|
|
14681
|
+
const router = Router15({ mergeParams: true });
|
|
14328
14682
|
installRecordsInvalidation(router);
|
|
14329
14683
|
function broadcastUpdate(projectSlug) {
|
|
14330
14684
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
@@ -14341,6 +14695,18 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
|
14341
14695
|
next();
|
|
14342
14696
|
}
|
|
14343
14697
|
router.use(validateProjectId);
|
|
14698
|
+
installTodoAttachmentRoutes(router, "/:id", {
|
|
14699
|
+
resolveScope: (req) => {
|
|
14700
|
+
const slug = getProjectIdParam(params(req).projectId);
|
|
14701
|
+
return { todosDir: projectTodosDir(projectsDir, slug), scopeId: slug, todoId: params(req).id ?? "" };
|
|
14702
|
+
},
|
|
14703
|
+
withScopeLock: (req, fn) => projLock(getProjectIdParam(params(req).projectId), fn),
|
|
14704
|
+
todoExists: async (scope) => {
|
|
14705
|
+
const checklist = await readChecklist(scope.todosDir, scope.scopeId);
|
|
14706
|
+
return checklist.items.some((i) => i.id === scope.todoId);
|
|
14707
|
+
},
|
|
14708
|
+
onChange: (req) => broadcastUpdate(getProjectIdParam(params(req).projectId))
|
|
14709
|
+
});
|
|
14344
14710
|
router.get("/", async (req, res) => {
|
|
14345
14711
|
try {
|
|
14346
14712
|
const slug = getProjectIdParam(params(req).projectId);
|
|
@@ -14350,10 +14716,11 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
|
14350
14716
|
}
|
|
14351
14717
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
14352
14718
|
const checklist = await readChecklist(todosDir2, slug);
|
|
14719
|
+
const attachmentsByTodo = await readScopeAttachments(todosDir2, slug);
|
|
14353
14720
|
res.json({
|
|
14354
14721
|
workspace: checklist.workspace,
|
|
14355
14722
|
archiveInterval: checklist.archiveInterval,
|
|
14356
|
-
items: checklist.items,
|
|
14723
|
+
items: checklist.items.map((i) => ({ ...i, attachments: attachmentsByTodo[i.id] ?? [] })),
|
|
14357
14724
|
counts: computeCounts(checklist.items)
|
|
14358
14725
|
});
|
|
14359
14726
|
} catch (error) {
|
|
@@ -14481,61 +14848,71 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
|
14481
14848
|
notFound(res, slug);
|
|
14482
14849
|
return;
|
|
14483
14850
|
}
|
|
14484
|
-
const
|
|
14485
|
-
|
|
14486
|
-
|
|
14487
|
-
|
|
14488
|
-
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14851
|
+
const outcome = await projLock(slug, async () => {
|
|
14852
|
+
if (!await projectExists(projectsDir, slug)) return "gone";
|
|
14853
|
+
await ensureProjectTodosDir(projectsDir, slug);
|
|
14854
|
+
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
14855
|
+
const checklist = await readChecklist(todosDir2, slug);
|
|
14856
|
+
const log = await readLog(todosDir2, slug);
|
|
14857
|
+
const completedIds = new Set(
|
|
14858
|
+
checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
|
|
14859
|
+
);
|
|
14860
|
+
if (completedIds.size === 0) {
|
|
14861
|
+
return { archived: 0, message: "No completed items to archive" };
|
|
14862
|
+
}
|
|
14863
|
+
const toArchive = log.entries.filter(
|
|
14864
|
+
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
14865
|
+
);
|
|
14866
|
+
const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
|
|
14867
|
+
let archContent = "";
|
|
14868
|
+
if (await fileExists(archFile)) {
|
|
14869
|
+
archContent = await readFile18(archFile, "utf-8");
|
|
14870
|
+
archContent = archContent.trimEnd() + "\n\n";
|
|
14871
|
+
} else {
|
|
14872
|
+
archContent = `---
|
|
14505
14873
|
workspace: ${slug}
|
|
14506
14874
|
---
|
|
14507
14875
|
|
|
14508
14876
|
# Archive
|
|
14509
14877
|
|
|
14510
14878
|
`;
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14879
|
+
}
|
|
14880
|
+
const completedItems = checklist.items.filter((i) => completedIds.has(i.id));
|
|
14881
|
+
for (const item of completedItems) {
|
|
14882
|
+
archContent += `- [x] ${item.description} ${item.tags.map((t) => `#${t}`).join(" ")} [t:${item.id}]
|
|
14515
14883
|
`;
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
|
|
14884
|
+
}
|
|
14885
|
+
archContent += "\n";
|
|
14886
|
+
for (const entry of toArchive) {
|
|
14887
|
+
archContent += `### ${entry.timestamp} \u2014 ${entry.itemIds.map((i) => `t:${i}`).join(", ")}
|
|
14520
14888
|
`;
|
|
14521
|
-
|
|
14889
|
+
if (entry.items) archContent += `**Items:** ${entry.items}
|
|
14522
14890
|
`;
|
|
14523
|
-
|
|
14891
|
+
if (entry.session) archContent += `**Session:** ${entry.session}
|
|
14524
14892
|
`;
|
|
14525
|
-
|
|
14893
|
+
if (entry.branch) archContent += `**Branch:** ${entry.branch}
|
|
14526
14894
|
`;
|
|
14527
|
-
|
|
14895
|
+
if (entry.summary) archContent += `**Summary:** ${entry.summary}
|
|
14528
14896
|
`;
|
|
14529
|
-
|
|
14897
|
+
if (entry.blockers) archContent += `**Blockers:** ${entry.blockers}
|
|
14530
14898
|
`;
|
|
14531
|
-
|
|
14899
|
+
archContent += "\n";
|
|
14900
|
+
}
|
|
14901
|
+
await writeFileForce(archFile, archContent);
|
|
14902
|
+
checklist.workspace = slug;
|
|
14903
|
+
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
14904
|
+
await writeChecklist(todosDir2, checklist);
|
|
14905
|
+
for (const id of completedIds) {
|
|
14906
|
+
await deleteAllAttachments(todosDir2, slug, id);
|
|
14907
|
+
}
|
|
14908
|
+
return { archived: completedIds.size, logEntries: toArchive.length };
|
|
14909
|
+
});
|
|
14910
|
+
if (outcome === "gone") {
|
|
14911
|
+
notFound(res, slug);
|
|
14912
|
+
return;
|
|
14532
14913
|
}
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
|
|
14536
|
-
await writeChecklist(todosDir2, checklist);
|
|
14537
|
-
broadcastUpdate(slug);
|
|
14538
|
-
res.json({ archived: completedIds.size, logEntries: toArchive.length });
|
|
14914
|
+
if (outcome.archived > 0) broadcastUpdate(slug);
|
|
14915
|
+
res.json(outcome);
|
|
14539
14916
|
} catch (error) {
|
|
14540
14917
|
if (error.code === "PROJECT_GONE") {
|
|
14541
14918
|
notFound(res, getProjectIdParam(params(req).projectId));
|
|
@@ -14575,7 +14952,8 @@ workspace: ${slug}
|
|
|
14575
14952
|
}
|
|
14576
14953
|
const log = await readLog(todosDir2, slug);
|
|
14577
14954
|
const logEntries = log.entries.filter((e) => e.itemIds.includes(params(req).id ?? ""));
|
|
14578
|
-
|
|
14955
|
+
const attachments = await listAttachments(todosDir2, slug, item.id);
|
|
14956
|
+
res.json({ ...item, attachments, log: logEntries });
|
|
14579
14957
|
} catch (error) {
|
|
14580
14958
|
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get todo" });
|
|
14581
14959
|
}
|
|
@@ -14631,11 +15009,13 @@ workspace: ${slug}
|
|
|
14631
15009
|
await ensureProjectTodosDir(projectsDir, slug);
|
|
14632
15010
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
14633
15011
|
const checklist = await readChecklist(todosDir2, slug);
|
|
14634
|
-
const
|
|
15012
|
+
const targetId = params(req).id ?? "";
|
|
15013
|
+
const idx = checklist.items.findIndex((i) => i.id === targetId);
|
|
14635
15014
|
if (idx === -1) return false;
|
|
14636
15015
|
checklist.items.splice(idx, 1);
|
|
14637
15016
|
checklist.workspace = slug;
|
|
14638
15017
|
await writeChecklist(todosDir2, checklist);
|
|
15018
|
+
await deleteAllAttachments(todosDir2, slug, targetId);
|
|
14639
15019
|
return true;
|
|
14640
15020
|
});
|
|
14641
15021
|
if (deleted === "gone") {
|
|
@@ -14945,15 +15325,15 @@ workspace: ${slug}
|
|
|
14945
15325
|
if (tg.includes("/")) {
|
|
14946
15326
|
const parts = tg.split("/");
|
|
14947
15327
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
14948
|
-
assignmentDir =
|
|
15328
|
+
assignmentDir = resolve26(projectsDir, parts[0], "assignments", parts[1]);
|
|
14949
15329
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
14950
15330
|
} 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)) {
|
|
14951
|
-
assignmentDir =
|
|
15331
|
+
assignmentDir = resolve26(assignmentsDirFn(), tg);
|
|
14952
15332
|
assignmentRef = tg;
|
|
14953
15333
|
} else {
|
|
14954
15334
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
14955
15335
|
}
|
|
14956
|
-
const assignmentMdPath =
|
|
15336
|
+
const assignmentMdPath = resolve26(assignmentDir, "assignment.md");
|
|
14957
15337
|
if (!await fileExists(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
14958
15338
|
let content = await readFile18(assignmentMdPath, "utf-8");
|
|
14959
15339
|
content = appendTodosToAssignmentBody2(
|
|
@@ -15083,15 +15463,22 @@ workspace: ${slug}
|
|
|
15083
15463
|
return { status: 409, error: "id already exists in target" };
|
|
15084
15464
|
}
|
|
15085
15465
|
const item = sourceChecklist.items[idx];
|
|
15466
|
+
let newPlanDir = null;
|
|
15086
15467
|
if (item.planDir) {
|
|
15087
|
-
|
|
15468
|
+
newPlanDir = todoPlanDir(target.todosPath, target.id, id);
|
|
15088
15469
|
if (await fileExists(newPlanDir)) {
|
|
15089
15470
|
return { status: 409, error: "plan dir already exists in target" };
|
|
15090
15471
|
}
|
|
15091
|
-
|
|
15092
|
-
|
|
15472
|
+
}
|
|
15473
|
+
if (await attachmentMoveConflict(sourceTodosDir, sourceSlug, target.todosPath, target.id, id)) {
|
|
15474
|
+
return { status: 409, error: "attachments already exist in target" };
|
|
15475
|
+
}
|
|
15476
|
+
if (item.planDir && newPlanDir) {
|
|
15477
|
+
await mkdir4(dirname7(newPlanDir), { recursive: true });
|
|
15478
|
+
await rename7(item.planDir, newPlanDir);
|
|
15093
15479
|
item.planDir = newPlanDir;
|
|
15094
15480
|
}
|
|
15481
|
+
await moveAttachments(sourceTodosDir, sourceSlug, target.todosPath, target.id, id);
|
|
15095
15482
|
sourceChecklist.items.splice(idx, 1);
|
|
15096
15483
|
targetChecklist.items.push(item);
|
|
15097
15484
|
sourceChecklist.workspace = sourceSlug;
|
|
@@ -15150,33 +15537,33 @@ workspace: ${slug}
|
|
|
15150
15537
|
}
|
|
15151
15538
|
|
|
15152
15539
|
// src/dashboard/api-bundles.ts
|
|
15153
|
-
import { Router as
|
|
15154
|
-
import { readdir as
|
|
15540
|
+
import { Router as Router16 } from "express";
|
|
15541
|
+
import { readdir as readdir12 } from "fs/promises";
|
|
15155
15542
|
|
|
15156
15543
|
// src/todos/bundle-parser.ts
|
|
15157
15544
|
init_parser();
|
|
15158
15545
|
init_fs();
|
|
15159
15546
|
init_paths();
|
|
15160
15547
|
init_parser2();
|
|
15161
|
-
import { randomBytes as
|
|
15548
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
15162
15549
|
import { readFile as readFile19 } from "fs/promises";
|
|
15163
15550
|
var BUNDLE_ID_REGEX = /^[a-f0-9]{4}$/;
|
|
15164
15551
|
var SCOPE_VALUES = /* @__PURE__ */ new Set(["workspace", "project", "global"]);
|
|
15165
15552
|
var SCOPE_ID_REGEX = /^[a-z0-9_][a-z0-9_-]*$/;
|
|
15166
15553
|
var BUNDLE_LINE_REGEX = /^- b:([a-f0-9]{4})\s+<([^>]*)>\s*$/;
|
|
15167
|
-
function parseScopeToken(
|
|
15168
|
-
const idx =
|
|
15554
|
+
function parseScopeToken(raw2) {
|
|
15555
|
+
const idx = raw2.indexOf(":");
|
|
15169
15556
|
if (idx < 0) return null;
|
|
15170
|
-
const scopeRaw =
|
|
15171
|
-
const scopeId =
|
|
15557
|
+
const scopeRaw = raw2.slice(0, idx);
|
|
15558
|
+
const scopeId = raw2.slice(idx + 1);
|
|
15172
15559
|
if (!SCOPE_VALUES.has(scopeRaw)) return null;
|
|
15173
15560
|
if (!scopeId) return null;
|
|
15174
15561
|
if (!SCOPE_ID_REGEX.test(scopeId)) return null;
|
|
15175
15562
|
return { scope: scopeRaw, scopeId };
|
|
15176
15563
|
}
|
|
15177
|
-
function parseTodosToken(
|
|
15178
|
-
if (!
|
|
15179
|
-
return
|
|
15564
|
+
function parseTodosToken(raw2) {
|
|
15565
|
+
if (!raw2) return [];
|
|
15566
|
+
return raw2.split(",").map((s) => s.trim()).filter((s) => BUNDLE_ID_REGEX.test(s));
|
|
15180
15567
|
}
|
|
15181
15568
|
function parseBundleLine(line) {
|
|
15182
15569
|
const match = line.match(BUNDLE_LINE_REGEX);
|
|
@@ -15261,7 +15648,7 @@ function annotate(bundle, items) {
|
|
|
15261
15648
|
}
|
|
15262
15649
|
function createBundlesRouter(todosDir2, broadcast) {
|
|
15263
15650
|
void broadcast;
|
|
15264
|
-
const router =
|
|
15651
|
+
const router = Router16();
|
|
15265
15652
|
function validateWorkspace(req, res, next) {
|
|
15266
15653
|
const workspace = getWorkspaceParam2(req.params.workspace);
|
|
15267
15654
|
if (workspace && !WORKSPACE_REGEX3.test(workspace)) {
|
|
@@ -15275,7 +15662,7 @@ function createBundlesRouter(todosDir2, broadcast) {
|
|
|
15275
15662
|
try {
|
|
15276
15663
|
await ensureDir(todosDir2);
|
|
15277
15664
|
const bundles = await readBundles(todosDir2);
|
|
15278
|
-
const workspaceFiles = await
|
|
15665
|
+
const workspaceFiles = await readdir12(todosDir2).catch(() => []);
|
|
15279
15666
|
const itemsByKey = /* @__PURE__ */ new Map();
|
|
15280
15667
|
for (const f of workspaceFiles) {
|
|
15281
15668
|
if (typeof f !== "string") continue;
|
|
@@ -15327,8 +15714,8 @@ function createBundlesRouter(todosDir2, broadcast) {
|
|
|
15327
15714
|
init_fs();
|
|
15328
15715
|
init_paths();
|
|
15329
15716
|
init_slug();
|
|
15330
|
-
import { Router as
|
|
15331
|
-
import { resolve as
|
|
15717
|
+
import { Router as Router17 } from "express";
|
|
15718
|
+
import { resolve as resolve27 } from "path";
|
|
15332
15719
|
init_parser2();
|
|
15333
15720
|
function deriveStatus2(bundle, items) {
|
|
15334
15721
|
const members = bundle.todoIds.map((id) => items.find((i) => i.id === id)).filter((i) => i !== void 0);
|
|
@@ -15357,7 +15744,7 @@ function notFound2(res, slug) {
|
|
|
15357
15744
|
}
|
|
15358
15745
|
function createProjectBundlesRouter(projectsDir, broadcast) {
|
|
15359
15746
|
void broadcast;
|
|
15360
|
-
const router =
|
|
15747
|
+
const router = Router17({ mergeParams: true });
|
|
15361
15748
|
function validateProjectId(req, res, next) {
|
|
15362
15749
|
const slug = getProjectIdParam2(req.params.projectId);
|
|
15363
15750
|
if (!slug || !isValidSlug(slug)) {
|
|
@@ -15370,7 +15757,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
|
|
|
15370
15757
|
router.get("/", async (req, res) => {
|
|
15371
15758
|
try {
|
|
15372
15759
|
const slug = getProjectIdParam2(req.params.projectId);
|
|
15373
|
-
const projectMd =
|
|
15760
|
+
const projectMd = resolve27(projectsDir, slug, "project.md");
|
|
15374
15761
|
if (!await fileExists(projectMd)) {
|
|
15375
15762
|
notFound2(res, slug);
|
|
15376
15763
|
return;
|
|
@@ -15391,7 +15778,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
|
|
|
15391
15778
|
init_config2();
|
|
15392
15779
|
init_api();
|
|
15393
15780
|
init_scanner();
|
|
15394
|
-
import { Router as
|
|
15781
|
+
import { Router as Router18 } from "express";
|
|
15395
15782
|
|
|
15396
15783
|
// src/utils/github-backup.ts
|
|
15397
15784
|
init_paths();
|
|
@@ -15399,8 +15786,8 @@ init_fs();
|
|
|
15399
15786
|
init_config2();
|
|
15400
15787
|
import { execFile as execFile2 } from "child_process";
|
|
15401
15788
|
import { promisify as promisify2 } from "util";
|
|
15402
|
-
import { cp, mkdtemp, rm as
|
|
15403
|
-
import { resolve as
|
|
15789
|
+
import { cp as cp2, mkdtemp, rm as rm4, readFile as readFile20, writeFile as writeFile6, unlink as unlink6, stat as stat2, open as open2, rename as rename8 } from "fs/promises";
|
|
15790
|
+
import { resolve as resolve28, join as join3 } from "path";
|
|
15404
15791
|
import { tmpdir } from "os";
|
|
15405
15792
|
var exec2 = promisify2(execFile2);
|
|
15406
15793
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -15440,7 +15827,7 @@ async function resolveCategoryPath(category) {
|
|
|
15440
15827
|
case "servers":
|
|
15441
15828
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
15442
15829
|
case "config":
|
|
15443
|
-
return { sourcePath:
|
|
15830
|
+
return { sourcePath: resolve28(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
15444
15831
|
}
|
|
15445
15832
|
}
|
|
15446
15833
|
async function checkGitInstalled() {
|
|
@@ -15451,7 +15838,7 @@ async function checkGitInstalled() {
|
|
|
15451
15838
|
}
|
|
15452
15839
|
}
|
|
15453
15840
|
async function acquireLock() {
|
|
15454
|
-
const lockPath =
|
|
15841
|
+
const lockPath = resolve28(syntaurRoot(), LOCK_FILE_NAME);
|
|
15455
15842
|
await ensureDir(syntaurRoot());
|
|
15456
15843
|
try {
|
|
15457
15844
|
const handle = await open2(lockPath, "wx");
|
|
@@ -15470,7 +15857,7 @@ async function acquireLock() {
|
|
|
15470
15857
|
}
|
|
15471
15858
|
async function releaseLock(lockPath) {
|
|
15472
15859
|
try {
|
|
15473
|
-
await
|
|
15860
|
+
await unlink6(lockPath);
|
|
15474
15861
|
} catch {
|
|
15475
15862
|
}
|
|
15476
15863
|
}
|
|
@@ -15493,13 +15880,13 @@ async function cloneOrInit(repoUrl, destDir) {
|
|
|
15493
15880
|
}
|
|
15494
15881
|
async function copyRecursive(src, dest) {
|
|
15495
15882
|
if (!await fileExists(src)) return;
|
|
15496
|
-
const s = await
|
|
15883
|
+
const s = await stat2(src);
|
|
15497
15884
|
if (s.isDirectory()) {
|
|
15498
15885
|
await ensureDir(dest);
|
|
15499
|
-
await
|
|
15886
|
+
await cp2(src, dest, { recursive: true, force: true });
|
|
15500
15887
|
} else {
|
|
15501
|
-
await ensureDir(
|
|
15502
|
-
await
|
|
15888
|
+
await ensureDir(resolve28(dest, ".."));
|
|
15889
|
+
await cp2(src, dest, { force: true });
|
|
15503
15890
|
}
|
|
15504
15891
|
}
|
|
15505
15892
|
function resolveCategoriesStrict(csv) {
|
|
@@ -15536,9 +15923,9 @@ async function backupToGithub(overrides) {
|
|
|
15536
15923
|
const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
|
|
15537
15924
|
const destPath = join3(tmpDir, repoPath);
|
|
15538
15925
|
if (isFile) {
|
|
15539
|
-
await
|
|
15926
|
+
await rm4(destPath, { force: true });
|
|
15540
15927
|
} else {
|
|
15541
|
-
await
|
|
15928
|
+
await rm4(destPath, { recursive: true, force: true });
|
|
15542
15929
|
}
|
|
15543
15930
|
if (!await fileExists(sourcePath)) {
|
|
15544
15931
|
console.warn(`Category "${category}": no local data at ${sourcePath}; backup will reflect deletion.`);
|
|
@@ -15546,8 +15933,8 @@ async function backupToGithub(overrides) {
|
|
|
15546
15933
|
}
|
|
15547
15934
|
if (category === "config") {
|
|
15548
15935
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
15549
|
-
await ensureDir(
|
|
15550
|
-
await
|
|
15936
|
+
await ensureDir(resolve28(destPath, ".."));
|
|
15937
|
+
await writeFile6(destPath, sanitized, "utf-8");
|
|
15551
15938
|
} else {
|
|
15552
15939
|
await copyRecursive(sourcePath, destPath);
|
|
15553
15940
|
}
|
|
@@ -15592,7 +15979,7 @@ async function backupToGithub(overrides) {
|
|
|
15592
15979
|
};
|
|
15593
15980
|
} finally {
|
|
15594
15981
|
if (tmpDir) {
|
|
15595
|
-
await
|
|
15982
|
+
await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
15596
15983
|
});
|
|
15597
15984
|
}
|
|
15598
15985
|
await releaseLock(lockPath);
|
|
@@ -15600,18 +15987,18 @@ async function backupToGithub(overrides) {
|
|
|
15600
15987
|
}
|
|
15601
15988
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
15602
15989
|
if (isFile) {
|
|
15603
|
-
await ensureDir(
|
|
15604
|
-
await
|
|
15990
|
+
await ensureDir(resolve28(localPath, ".."));
|
|
15991
|
+
await cp2(repoSrcPath, localPath, { force: true });
|
|
15605
15992
|
return;
|
|
15606
15993
|
}
|
|
15607
15994
|
const stagingPath = `${localPath}.syntaur-restore-staging`;
|
|
15608
15995
|
const backupPath = `${localPath}.syntaur-restore-backup`;
|
|
15609
|
-
await
|
|
15996
|
+
await rm4(stagingPath, { recursive: true, force: true });
|
|
15610
15997
|
const backupExistsBefore = await fileExists(backupPath);
|
|
15611
15998
|
const localExistsBefore = await fileExists(localPath);
|
|
15612
15999
|
if (backupExistsBefore) {
|
|
15613
16000
|
if (!localExistsBefore) {
|
|
15614
|
-
await
|
|
16001
|
+
await rename8(backupPath, localPath);
|
|
15615
16002
|
} else {
|
|
15616
16003
|
throw new Error(
|
|
15617
16004
|
`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.`
|
|
@@ -15620,21 +16007,21 @@ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
|
15620
16007
|
}
|
|
15621
16008
|
let localMovedAside = false;
|
|
15622
16009
|
try {
|
|
15623
|
-
await
|
|
16010
|
+
await cp2(repoSrcPath, stagingPath, { recursive: true, force: true });
|
|
15624
16011
|
const localExists = await fileExists(localPath);
|
|
15625
16012
|
if (localExists) {
|
|
15626
|
-
await
|
|
16013
|
+
await rename8(localPath, backupPath);
|
|
15627
16014
|
localMovedAside = true;
|
|
15628
16015
|
}
|
|
15629
|
-
await
|
|
15630
|
-
await
|
|
16016
|
+
await rename8(stagingPath, localPath);
|
|
16017
|
+
await rm4(backupPath, { recursive: true, force: true }).catch(() => {
|
|
15631
16018
|
});
|
|
15632
16019
|
} catch (err) {
|
|
15633
16020
|
if (localMovedAside && await fileExists(backupPath)) {
|
|
15634
|
-
await
|
|
16021
|
+
await rename8(backupPath, localPath).catch(() => {
|
|
15635
16022
|
});
|
|
15636
16023
|
}
|
|
15637
|
-
await
|
|
16024
|
+
await rm4(stagingPath, { recursive: true, force: true }).catch(() => {
|
|
15638
16025
|
});
|
|
15639
16026
|
throw err;
|
|
15640
16027
|
}
|
|
@@ -15693,7 +16080,7 @@ async function restoreFromGithub(overrides) {
|
|
|
15693
16080
|
};
|
|
15694
16081
|
} finally {
|
|
15695
16082
|
if (tmpDir) {
|
|
15696
|
-
await
|
|
16083
|
+
await rm4(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
15697
16084
|
});
|
|
15698
16085
|
}
|
|
15699
16086
|
await releaseLock(lockPath);
|
|
@@ -15701,7 +16088,7 @@ async function restoreFromGithub(overrides) {
|
|
|
15701
16088
|
}
|
|
15702
16089
|
async function getBackupStatus() {
|
|
15703
16090
|
const config = await readConfig();
|
|
15704
|
-
const lockPath =
|
|
16091
|
+
const lockPath = resolve28(syntaurRoot(), LOCK_FILE_NAME);
|
|
15705
16092
|
const locked = await fileExists(lockPath);
|
|
15706
16093
|
return {
|
|
15707
16094
|
repo: config.backup?.repo ?? null,
|
|
@@ -15714,7 +16101,7 @@ async function getBackupStatus() {
|
|
|
15714
16101
|
|
|
15715
16102
|
// src/dashboard/api-backup.ts
|
|
15716
16103
|
function createBackupRouter() {
|
|
15717
|
-
const router =
|
|
16104
|
+
const router = Router18();
|
|
15718
16105
|
router.get("/", async (_req, res) => {
|
|
15719
16106
|
try {
|
|
15720
16107
|
const status = await getBackupStatus();
|
|
@@ -16049,7 +16436,7 @@ function createDashboardServer(options) {
|
|
|
16049
16436
|
(async () => {
|
|
16050
16437
|
try {
|
|
16051
16438
|
const configResult = await migrateLegacyConfig(
|
|
16052
|
-
|
|
16439
|
+
resolve29(syntaurRoot(), "config.md")
|
|
16053
16440
|
);
|
|
16054
16441
|
const projectResult = await migrateLegacyProjectFiles(projectsDir);
|
|
16055
16442
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -16150,8 +16537,8 @@ function createDashboardServer(options) {
|
|
|
16150
16537
|
});
|
|
16151
16538
|
app.put("/api/config/hotkeys", async (req, res) => {
|
|
16152
16539
|
try {
|
|
16153
|
-
const
|
|
16154
|
-
const incoming =
|
|
16540
|
+
const raw2 = req.body && typeof req.body === "object" ? req.body : {};
|
|
16541
|
+
const incoming = raw2.bindings;
|
|
16155
16542
|
if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) {
|
|
16156
16543
|
res.status(400).json({ error: "bindings must be an object keyed by action kind" });
|
|
16157
16544
|
return;
|
|
@@ -16567,14 +16954,14 @@ function createDashboardServer(options) {
|
|
|
16567
16954
|
app.use("/api/backup", createBackupRouter());
|
|
16568
16955
|
if (serveStaticUi && dashboardDistPath) {
|
|
16569
16956
|
const sendOpts = { dotfiles: "allow" };
|
|
16570
|
-
app.use("/assets", express.static(
|
|
16957
|
+
app.use("/assets", express.static(resolve29(dashboardDistPath, "assets"), sendOpts));
|
|
16571
16958
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
16572
16959
|
app.get("{*path}", async (req, res) => {
|
|
16573
16960
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
16574
16961
|
res.status(404).json({ error: "Not Found" });
|
|
16575
16962
|
return;
|
|
16576
16963
|
}
|
|
16577
|
-
const indexPath =
|
|
16964
|
+
const indexPath = resolve29(dashboardDistPath, "index.html");
|
|
16578
16965
|
if (!await fileExists(indexPath)) {
|
|
16579
16966
|
res.status(503).send(
|
|
16580
16967
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -16598,7 +16985,7 @@ function createDashboardServer(options) {
|
|
|
16598
16985
|
serversDir: serversDir2,
|
|
16599
16986
|
playbooksDir: playbooksDir2,
|
|
16600
16987
|
todosDir: todosDir2,
|
|
16601
|
-
dbPath:
|
|
16988
|
+
dbPath: resolve29(syntaurRoot(), "syntaur.db"),
|
|
16602
16989
|
onMessage: broadcast
|
|
16603
16990
|
});
|
|
16604
16991
|
startAutodiscovery({ serversDir: serversDir2, projectsDir, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -16613,8 +17000,8 @@ function createDashboardServer(options) {
|
|
|
16613
17000
|
}
|
|
16614
17001
|
});
|
|
16615
17002
|
server.listen(port, () => {
|
|
16616
|
-
const portFile =
|
|
16617
|
-
|
|
17003
|
+
const portFile = resolve29(syntaurRoot(), "dashboard-port");
|
|
17004
|
+
writeFile7(portFile, String(port), "utf-8").catch(() => {
|
|
16618
17005
|
});
|
|
16619
17006
|
resolvePromise();
|
|
16620
17007
|
});
|
|
@@ -16632,8 +17019,8 @@ function createDashboardServer(options) {
|
|
|
16632
17019
|
client.terminate();
|
|
16633
17020
|
}
|
|
16634
17021
|
clients.clear();
|
|
16635
|
-
const portFile =
|
|
16636
|
-
await
|
|
17022
|
+
const portFile = resolve29(syntaurRoot(), "dashboard-port");
|
|
17023
|
+
await unlink7(portFile).catch(() => {
|
|
16637
17024
|
});
|
|
16638
17025
|
server.closeAllConnections?.();
|
|
16639
17026
|
return new Promise((resolvePromise) => {
|