syntaur 0.16.0 → 0.16.2
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-B1S22H85.js → _basePickBy-DTuHiZCP.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-CJIRCCsh.js → _baseUniq-BjbISCNN.js} +1 -1
- package/dashboard/dist/assets/{arc-CdlNYTcd.js → arc-D9mCa8If.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-KVI0HWOy.js → architectureDiagram-2XIMDMQ5-CLWheiZu.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-ClnG7gaq.js → blockDiagram-WCTKOSBZ-BBxrVTWB.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BD9lkttL.js → c4Diagram-IC4MRINW-DnhDZ2W3.js} +1 -1
- package/dashboard/dist/assets/channel-CN5VmjNa.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-BM3DXZMG.js → chunk-4BX2VUAB-DeAfVeDa.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-DUIbFJ3r.js → chunk-55IACEB6-cKEeGDNI.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-B_cxCRkA.js → chunk-FMBD7UC4-7RuZ6HEq.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-2mLf1WoM.js → chunk-JSJVCQXG-B5dwG0bv.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-BMz3ND2R.js → chunk-KX2RTZJC-DeyQYDVj.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DiCxpw3L.js → chunk-NQ4KR5QH-NYo4hSqA.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BcaWPzd5.js → chunk-QZHKN3VN-CtJFICF8.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-BbsfhS6J.js → chunk-WL4C6EOR-DZ3WYdab.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-CqLb9GuU.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CqLb9GuU.js +1 -0
- package/dashboard/dist/assets/clone-DwvrjCQs.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-iuQzyyrA.js → cose-bilkent-S5V4N54A-DaYOsf-M.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-C0TB1zg5.js → dagre-KLK3FWXG-DzLc7ykF.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-Ck8tvxP2.js → diagram-E7M64L7V-kpjEdOqb.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-rQKIi-sc.js → diagram-IFDJBPK2-D3EHoh6j.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DS1RGSgx.js → diagram-P4PSJMXO-Cp6Un2ys.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BqWCybWW.js → erDiagram-INFDFZHY-DDjOUPWk.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-BCoQtyQ5.js → flowDiagram-PKNHOUZH-CfO51xge.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CD1BmicP.js → ganttDiagram-A5KZAMGK-BDzAtkcp.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-tGDYOZuO.js → gitGraphDiagram-K3NZZRJ6-JPMFN2zF.js} +1 -1
- package/dashboard/dist/assets/{graph-BT0s-094.js → graph-DXDryilu.js} +1 -1
- package/dashboard/dist/assets/index-D7UtkCYM.js +515 -0
- package/dashboard/dist/assets/{index-D2Yoi6DJ.css → index-DTXSWSzH.css} +1 -1
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-E4dVb8BD.js → infoDiagram-LFFYTUFH-CYi5kkvz.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CubuM_ev.js → ishikawaDiagram-PHBUUO56-DQl_IUe8.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DfyJbgMF.js → journeyDiagram-4ABVD52K-BXXGPcrS.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BXamajyZ.js → kanban-definition-K7BYSVSG-BqJestUY.js} +1 -1
- package/dashboard/dist/assets/{layout-6b_SlWD_.js → layout-D5VdYmWn.js} +1 -1
- package/dashboard/dist/assets/{linear-BE0O-66A.js → linear-dZA_O_GN.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-DM3qWr07.js → mermaid.core-B0Ixd1yP.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-puOtERrG.js → mindmap-definition-YRQLILUH-CSJYdSMG.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-cE6l8UBX.js → pieDiagram-SKSYHLDU-DmYrRZHN.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-CgDAyZQP.js → quadrantDiagram-337W2JSQ-La3ce5kE.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CJrai_8U.js → requirementDiagram-Z7DCOOCP-DPitIZQl.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-Bf72l7UW.js → sankeyDiagram-WA2Y5GQK-CAmCqGkr.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-0D8RN7ds.js → sequenceDiagram-2WXFIKYE-6lEzNTWG.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-B8LucUIt.js → stateDiagram-RAJIS63D-DyGKCS2C.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BOmRICoY.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DL3Hq8ii.js → timeline-definition-YZTLITO2-D9RxQE3j.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-wF1X4_uI.js → treemap-KZPCXAKY-TuXbGNN4.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DDAjoGTZ.js → vennDiagram-LZ73GAT5-Bly5knr-.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-T-YmzRW8.js → xychartDiagram-JWTSCODW-CA-5z7-4.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +820 -166
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1244 -774
- package/dist/index.js.map +1 -1
- package/dist/launch/index.d.ts +20 -2
- package/dist/launch/index.js +67 -20
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/install-macos-url-handler.mjs +40 -7
- package/dashboard/dist/assets/channel-Cm_FzKJq.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-Bdan2Dp5.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Bdan2Dp5.js +0 -1
- package/dashboard/dist/assets/clone-9PoPEM_s.js +0 -1
- package/dashboard/dist/assets/index-BdSkANCC.js +0 -510
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-D0dc7wxQ.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -294,6 +294,62 @@ function updateAssignmentFile(fileContent, updates) {
|
|
|
294
294
|
}
|
|
295
295
|
return result;
|
|
296
296
|
}
|
|
297
|
+
function findWorkspaceBlock(fmBlock) {
|
|
298
|
+
const headerMatch = fmBlock.match(/^workspace:\s*$/m);
|
|
299
|
+
if (!headerMatch) return null;
|
|
300
|
+
const headerStart = fmBlock.indexOf(headerMatch[0]);
|
|
301
|
+
const bodyStart = headerStart + headerMatch[0].length + 1;
|
|
302
|
+
const after = fmBlock.slice(bodyStart);
|
|
303
|
+
const lines = after.split("\n");
|
|
304
|
+
let consumed = 0;
|
|
305
|
+
for (let i = 0; i < lines.length; i++) {
|
|
306
|
+
const line = lines[i];
|
|
307
|
+
if (line.length === 0) {
|
|
308
|
+
consumed += line.length + 1;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (line[0] !== " ") break;
|
|
312
|
+
consumed += line.length + 1;
|
|
313
|
+
}
|
|
314
|
+
const bodyEnd = Math.min(bodyStart + consumed, fmBlock.length);
|
|
315
|
+
return { headerStart, bodyStart, bodyEnd };
|
|
316
|
+
}
|
|
317
|
+
function updateAssignmentWorkspace(fileContent, partial) {
|
|
318
|
+
const fmMatch = fileContent.match(/^(---\n)([\s\S]*?)(\n---)/);
|
|
319
|
+
if (!fmMatch) {
|
|
320
|
+
throw new Error("No frontmatter found in assignment file. Expected --- delimiters.");
|
|
321
|
+
}
|
|
322
|
+
const fmBlock = fmMatch[2];
|
|
323
|
+
const fields = ["repository", "worktreePath", "branch", "parentBranch"];
|
|
324
|
+
const block = findWorkspaceBlock(fmBlock);
|
|
325
|
+
let newFm = fmBlock;
|
|
326
|
+
if (block) {
|
|
327
|
+
let body = fmBlock.slice(block.bodyStart, block.bodyEnd);
|
|
328
|
+
for (const field of fields) {
|
|
329
|
+
if (!(field in partial)) continue;
|
|
330
|
+
const value = partial[field] ?? null;
|
|
331
|
+
const formatted = formatYamlValue(value);
|
|
332
|
+
const lineRegex = new RegExp(`^(\\s+${field}:)\\s*.*$`, "m");
|
|
333
|
+
if (lineRegex.test(body)) {
|
|
334
|
+
body = body.replace(lineRegex, `$1 ${formatted}`);
|
|
335
|
+
} else {
|
|
336
|
+
const trimmed = body.replace(/\n+$/, "");
|
|
337
|
+
body = `${trimmed}${trimmed.length > 0 ? "\n" : ""} ${field}: ${formatted}
|
|
338
|
+
`;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
newFm = fmBlock.slice(0, block.bodyStart) + body + fmBlock.slice(block.bodyEnd);
|
|
342
|
+
} else {
|
|
343
|
+
const lines = ["workspace:"];
|
|
344
|
+
for (const field of fields) {
|
|
345
|
+
const value = field in partial ? partial[field] ?? null : null;
|
|
346
|
+
lines.push(` ${field}: ${formatYamlValue(value)}`);
|
|
347
|
+
}
|
|
348
|
+
newFm = `${fmBlock.replace(/\n+$/, "")}
|
|
349
|
+
${lines.join("\n")}`;
|
|
350
|
+
}
|
|
351
|
+
return `${fmMatch[1]}${newFm}${fmMatch[3]}${fileContent.slice(fmMatch[0].length)}`;
|
|
352
|
+
}
|
|
297
353
|
var init_frontmatter = __esm({
|
|
298
354
|
"src/lifecycle/frontmatter.ts"() {
|
|
299
355
|
"use strict";
|
|
@@ -407,6 +463,12 @@ function parseListField(frontmatter, fieldName) {
|
|
|
407
463
|
}
|
|
408
464
|
return results;
|
|
409
465
|
}
|
|
466
|
+
function unquoteYamlString(value) {
|
|
467
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
468
|
+
return value.slice(1, -1);
|
|
469
|
+
}
|
|
470
|
+
return value;
|
|
471
|
+
}
|
|
410
472
|
function parseProject(fileContent) {
|
|
411
473
|
const [fm, body] = extractFrontmatter2(fileContent);
|
|
412
474
|
const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
|
|
@@ -422,6 +484,7 @@ function parseProject(fileContent) {
|
|
|
422
484
|
updated: getField(fm, "updated") ?? "",
|
|
423
485
|
tags: parseListField(fm, "tags"),
|
|
424
486
|
workspace: getField(fm, "workspace"),
|
|
487
|
+
repositories: parseListField(fm, "repositories").map(unquoteYamlString),
|
|
425
488
|
externalIds: parseExternalIds2(fm),
|
|
426
489
|
body
|
|
427
490
|
};
|
|
@@ -1319,7 +1382,6 @@ backup:
|
|
|
1319
1382
|
categories: projects, playbooks, todos, servers, config
|
|
1320
1383
|
lastBackup: null
|
|
1321
1384
|
lastRestore: null
|
|
1322
|
-
terminal: terminal-app
|
|
1323
1385
|
---
|
|
1324
1386
|
|
|
1325
1387
|
# Syntaur Configuration
|
|
@@ -1588,6 +1650,22 @@ var init_agents_schema = __esm({
|
|
|
1588
1650
|
}
|
|
1589
1651
|
});
|
|
1590
1652
|
|
|
1653
|
+
// src/utils/terminal-schema.ts
|
|
1654
|
+
var TERMINAL_CHOICES;
|
|
1655
|
+
var init_terminal_schema = __esm({
|
|
1656
|
+
"src/utils/terminal-schema.ts"() {
|
|
1657
|
+
"use strict";
|
|
1658
|
+
TERMINAL_CHOICES = [
|
|
1659
|
+
"terminal-app",
|
|
1660
|
+
"iterm",
|
|
1661
|
+
"ghostty",
|
|
1662
|
+
"alacritty",
|
|
1663
|
+
"warp",
|
|
1664
|
+
"kitty"
|
|
1665
|
+
];
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1591
1669
|
// src/utils/config.ts
|
|
1592
1670
|
var config_exports = {};
|
|
1593
1671
|
__export(config_exports, {
|
|
@@ -1601,6 +1679,7 @@ __export(config_exports, {
|
|
|
1601
1679
|
deleteAgentsConfig: () => deleteAgentsConfig,
|
|
1602
1680
|
deleteHotkeyBindingsConfig: () => deleteHotkeyBindingsConfig,
|
|
1603
1681
|
deleteStatusConfig: () => deleteStatusConfig,
|
|
1682
|
+
deleteTerminalConfig: () => deleteTerminalConfig,
|
|
1604
1683
|
deleteThemeConfig: () => deleteThemeConfig,
|
|
1605
1684
|
getAgents: () => getAgents,
|
|
1606
1685
|
getAssignmentTypes: () => getAssignmentTypes,
|
|
@@ -1617,9 +1696,11 @@ __export(config_exports, {
|
|
|
1617
1696
|
writeAgentsConfig: () => writeAgentsConfig,
|
|
1618
1697
|
writeHotkeyBindingsConfig: () => writeHotkeyBindingsConfig,
|
|
1619
1698
|
writeStatusConfig: () => writeStatusConfig,
|
|
1699
|
+
writeTerminalConfig: () => writeTerminalConfig,
|
|
1620
1700
|
writeThemeConfig: () => writeThemeConfig
|
|
1621
1701
|
});
|
|
1622
1702
|
import { readFile as readFile4 } from "fs/promises";
|
|
1703
|
+
import { spawnSync } from "child_process";
|
|
1623
1704
|
import { resolve as resolve6, isAbsolute } from "path";
|
|
1624
1705
|
function parseAgentCommand(value, agentId) {
|
|
1625
1706
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -2032,6 +2113,52 @@ ${cleanedFm}
|
|
|
2032
2113
|
---${afterFrontmatter}`;
|
|
2033
2114
|
await writeFileForce(configPath, newContent);
|
|
2034
2115
|
}
|
|
2116
|
+
function stripTopLevelScalar(fmBlock, key) {
|
|
2117
|
+
const lines = fmBlock.split("\n");
|
|
2118
|
+
const keyRegex = new RegExp(`^${key}:\\s*\\S`);
|
|
2119
|
+
const filtered = lines.filter((line) => !keyRegex.test(line));
|
|
2120
|
+
return filtered.join("\n").replace(/\n+$/, "");
|
|
2121
|
+
}
|
|
2122
|
+
async function writeTerminalConfig(terminal) {
|
|
2123
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2124
|
+
const terminalLine = `terminal: ${terminal}`;
|
|
2125
|
+
const existing = await fileExists(configPath) ? await readFile4(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
2126
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2127
|
+
if (!fmMatch) {
|
|
2128
|
+
const content = `---
|
|
2129
|
+
version: "2.0"
|
|
2130
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
2131
|
+
${terminalLine}
|
|
2132
|
+
---
|
|
2133
|
+
${existing}`;
|
|
2134
|
+
await writeFileForce(configPath, content);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
const fmBlock = fmMatch[2];
|
|
2138
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2139
|
+
const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
|
|
2140
|
+
const newFm = `${cleanedFm}
|
|
2141
|
+
${terminalLine}`.replace(/^\n+/, "");
|
|
2142
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
2143
|
+
const newContent = `---
|
|
2144
|
+
${normalizedFm}
|
|
2145
|
+
---${afterFrontmatter}`;
|
|
2146
|
+
await writeFileForce(configPath, newContent);
|
|
2147
|
+
}
|
|
2148
|
+
async function deleteTerminalConfig() {
|
|
2149
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
2150
|
+
if (!await fileExists(configPath)) return;
|
|
2151
|
+
const existing = await readFile4(configPath, "utf-8");
|
|
2152
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
2153
|
+
if (!fmMatch) return;
|
|
2154
|
+
const fmBlock = fmMatch[2];
|
|
2155
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
2156
|
+
const cleanedFm = stripTopLevelScalar(fmBlock, "terminal");
|
|
2157
|
+
const newContent = `---
|
|
2158
|
+
${cleanedFm}
|
|
2159
|
+
---${afterFrontmatter}`;
|
|
2160
|
+
await writeFileForce(configPath, newContent);
|
|
2161
|
+
}
|
|
2035
2162
|
function parseHotkeyBindingsConfig(content) {
|
|
2036
2163
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
2037
2164
|
if (!match) return null;
|
|
@@ -2727,7 +2854,18 @@ function parseTerminalConfig(value) {
|
|
|
2727
2854
|
return trimmed;
|
|
2728
2855
|
}
|
|
2729
2856
|
function getTerminal(config) {
|
|
2730
|
-
|
|
2857
|
+
if (config.terminal) return config.terminal;
|
|
2858
|
+
if (process.platform === "darwin") return "terminal-app";
|
|
2859
|
+
if (process.platform === "linux") {
|
|
2860
|
+
const order = ["kitty", "alacritty", "warp"];
|
|
2861
|
+
for (const candidate of order) {
|
|
2862
|
+
const result = spawnSync("which", [candidate], { encoding: "utf-8" });
|
|
2863
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
2864
|
+
return candidate;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
return "terminal-app";
|
|
2731
2869
|
}
|
|
2732
2870
|
async function updateAgentsConfig(mutation, options = {}) {
|
|
2733
2871
|
const config = await readConfig();
|
|
@@ -2740,7 +2878,7 @@ async function updateAgentsConfig(mutation, options = {}) {
|
|
|
2740
2878
|
await writeAgentsConfig(next);
|
|
2741
2879
|
return { previous, next, written: true };
|
|
2742
2880
|
}
|
|
2743
|
-
var DEFAULT_ASSIGNMENT_TYPES,
|
|
2881
|
+
var DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
2744
2882
|
var init_config2 = __esm({
|
|
2745
2883
|
"src/utils/config.ts"() {
|
|
2746
2884
|
"use strict";
|
|
@@ -2750,6 +2888,7 @@ var init_config2 = __esm({
|
|
|
2750
2888
|
init_fs_migration();
|
|
2751
2889
|
init_hotkeysCatalog();
|
|
2752
2890
|
init_agents_schema();
|
|
2891
|
+
init_terminal_schema();
|
|
2753
2892
|
DEFAULT_ASSIGNMENT_TYPES = {
|
|
2754
2893
|
definitions: [
|
|
2755
2894
|
{ id: "feature", label: "Feature" },
|
|
@@ -2760,14 +2899,6 @@ var init_config2 = __esm({
|
|
|
2760
2899
|
],
|
|
2761
2900
|
default: "feature"
|
|
2762
2901
|
};
|
|
2763
|
-
TERMINAL_CHOICES = [
|
|
2764
|
-
"terminal-app",
|
|
2765
|
-
"iterm",
|
|
2766
|
-
"ghostty",
|
|
2767
|
-
"alacritty",
|
|
2768
|
-
"warp",
|
|
2769
|
-
"kitty"
|
|
2770
|
-
];
|
|
2771
2902
|
DEFAULT_CONFIG = {
|
|
2772
2903
|
version: "2.0",
|
|
2773
2904
|
defaultProjectDir: defaultProjectDir(),
|
|
@@ -3705,8 +3836,8 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
3705
3836
|
return allSessions.length;
|
|
3706
3837
|
}
|
|
3707
3838
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
3708
|
-
const { readFile:
|
|
3709
|
-
const raw = await
|
|
3839
|
+
const { readFile: readFile18 } = await import("fs/promises");
|
|
3840
|
+
const raw = await readFile18(filePath, "utf-8");
|
|
3710
3841
|
const sessions = [];
|
|
3711
3842
|
const lines = raw.split("\n");
|
|
3712
3843
|
let inTable = false;
|
|
@@ -4895,7 +5026,8 @@ async function getProjectDetail(projectsDir, slug) {
|
|
|
4895
5026
|
resources,
|
|
4896
5027
|
memories,
|
|
4897
5028
|
dependencyGraph,
|
|
4898
|
-
workspace: project.workspace
|
|
5029
|
+
workspace: project.workspace,
|
|
5030
|
+
repositories: project.repositories
|
|
4899
5031
|
};
|
|
4900
5032
|
}
|
|
4901
5033
|
async function getAssignmentDetail(projectsDir, projectSlug, assignmentSlug) {
|
|
@@ -6175,7 +6307,7 @@ init_assignment_resolver();
|
|
|
6175
6307
|
init_agent_sessions();
|
|
6176
6308
|
import express from "express";
|
|
6177
6309
|
import { createServer } from "http";
|
|
6178
|
-
import { resolve as
|
|
6310
|
+
import { resolve as resolve24 } from "path";
|
|
6179
6311
|
import { writeFile as writeFile5, unlink as unlink5 } from "fs/promises";
|
|
6180
6312
|
import { WebSocketServer, WebSocket } from "ws";
|
|
6181
6313
|
|
|
@@ -6708,8 +6840,9 @@ function withTwoLocks(keyA, keyB, fn) {
|
|
|
6708
6840
|
init_lifecycle();
|
|
6709
6841
|
init_slug();
|
|
6710
6842
|
import { Router } from "express";
|
|
6711
|
-
import { resolve as
|
|
6712
|
-
import { rm, readFile as
|
|
6843
|
+
import { resolve as resolve17, basename as basename3, isAbsolute as isAbsolute2 } from "path";
|
|
6844
|
+
import { rm, readFile as readFile14, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
|
|
6845
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
6713
6846
|
|
|
6714
6847
|
// src/utils/uuid.ts
|
|
6715
6848
|
import { randomUUID } from "crypto";
|
|
@@ -6720,6 +6853,203 @@ function generateId() {
|
|
|
6720
6853
|
// src/dashboard/api-write.ts
|
|
6721
6854
|
init_timestamp();
|
|
6722
6855
|
init_fs();
|
|
6856
|
+
|
|
6857
|
+
// src/utils/git-worktree.ts
|
|
6858
|
+
init_frontmatter();
|
|
6859
|
+
init_fs();
|
|
6860
|
+
import { spawn } from "child_process";
|
|
6861
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
6862
|
+
function run(command, args, cwd) {
|
|
6863
|
+
return new Promise((resolvePromise) => {
|
|
6864
|
+
const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
6865
|
+
let stdout = "";
|
|
6866
|
+
let stderr = "";
|
|
6867
|
+
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
6868
|
+
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
6869
|
+
child.on("error", (err) => {
|
|
6870
|
+
resolvePromise({ code: -1, stdout, stderr: stderr + String(err) });
|
|
6871
|
+
});
|
|
6872
|
+
child.on("close", (code) => {
|
|
6873
|
+
resolvePromise({ code: code ?? -1, stdout, stderr });
|
|
6874
|
+
});
|
|
6875
|
+
});
|
|
6876
|
+
}
|
|
6877
|
+
var GitWorktreeError = class extends Error {
|
|
6878
|
+
constructor(message, stderr) {
|
|
6879
|
+
super(message);
|
|
6880
|
+
this.stderr = stderr;
|
|
6881
|
+
}
|
|
6882
|
+
};
|
|
6883
|
+
async function createWorktree(opts) {
|
|
6884
|
+
const { repository, branch, worktreePath, parentBranch } = opts;
|
|
6885
|
+
const result = await run(
|
|
6886
|
+
"git",
|
|
6887
|
+
["-C", repository, "worktree", "add", "-b", branch, worktreePath, parentBranch]
|
|
6888
|
+
);
|
|
6889
|
+
if (result.code !== 0) {
|
|
6890
|
+
throw new GitWorktreeError(
|
|
6891
|
+
`git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
|
|
6892
|
+
result.stderr
|
|
6893
|
+
);
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
async function removeWorktree(repository, worktreePath) {
|
|
6897
|
+
const result = await run(
|
|
6898
|
+
"git",
|
|
6899
|
+
["-C", repository, "worktree", "remove", "--force", worktreePath]
|
|
6900
|
+
);
|
|
6901
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
6902
|
+
}
|
|
6903
|
+
async function deleteBranch(repository, branch) {
|
|
6904
|
+
const result = await run("git", ["-C", repository, "branch", "-D", branch]);
|
|
6905
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
6906
|
+
}
|
|
6907
|
+
async function createWorktreeAndRecord(opts) {
|
|
6908
|
+
const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
|
|
6909
|
+
await createWorktree({ repository, branch, worktreePath, parentBranch });
|
|
6910
|
+
try {
|
|
6911
|
+
const content = await readFile12(assignmentPath, "utf-8");
|
|
6912
|
+
const updated = updateAssignmentWorkspace(content, {
|
|
6913
|
+
repository,
|
|
6914
|
+
worktreePath,
|
|
6915
|
+
branch,
|
|
6916
|
+
parentBranch
|
|
6917
|
+
});
|
|
6918
|
+
await writeFileForce(assignmentPath, updated);
|
|
6919
|
+
} catch (writeErr) {
|
|
6920
|
+
const cleanup = await removeWorktree(repository, worktreePath);
|
|
6921
|
+
const branchCleanup = await deleteBranch(repository, branch);
|
|
6922
|
+
const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
|
|
6923
|
+
throw new Error(
|
|
6924
|
+
formatRollbackError({
|
|
6925
|
+
writeMsg,
|
|
6926
|
+
worktreePath,
|
|
6927
|
+
branch,
|
|
6928
|
+
worktreeCleanup: cleanup,
|
|
6929
|
+
branchCleanup
|
|
6930
|
+
})
|
|
6931
|
+
);
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
function formatRollbackError(opts) {
|
|
6935
|
+
const { writeMsg, worktreePath, branch, worktreeCleanup, branchCleanup } = opts;
|
|
6936
|
+
const wtMsg = worktreeCleanup.stderr.trim() || "(no stderr)";
|
|
6937
|
+
const brMsg = branchCleanup.stderr.trim() || "(no stderr)";
|
|
6938
|
+
if (!worktreeCleanup.ok && !branchCleanup.ok) {
|
|
6939
|
+
return `Failed to update assignment frontmatter AND failed to clean up both worktree and branch. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Branch cleanup error: ${brMsg}. Orphan worktree at ${worktreePath} and orphan branch "${branch}" \u2014 remove them manually.`;
|
|
6940
|
+
}
|
|
6941
|
+
if (!worktreeCleanup.ok) {
|
|
6942
|
+
return `Failed to update assignment frontmatter AND failed to clean up worktree. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Orphan worktree at ${worktreePath} \u2014 remove it manually.`;
|
|
6943
|
+
}
|
|
6944
|
+
if (!branchCleanup.ok) {
|
|
6945
|
+
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath}, but could not delete branch "${branch}": ${brMsg}. Remove the branch manually.`;
|
|
6946
|
+
}
|
|
6947
|
+
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath} and branch "${branch}".`;
|
|
6948
|
+
}
|
|
6949
|
+
|
|
6950
|
+
// src/utils/worktree-defaults.ts
|
|
6951
|
+
init_paths();
|
|
6952
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
6953
|
+
import { resolve as resolve15 } from "path";
|
|
6954
|
+
function computeWorktreeDefaults(opts) {
|
|
6955
|
+
const repository = opts.existing.repository ?? detectCurrentGitRoot(opts.cwd);
|
|
6956
|
+
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
6957
|
+
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch(opts.cwd) ?? "main";
|
|
6958
|
+
const worktreeBase = repository ? resolve15(repository, ".worktrees", branch) : resolve15(
|
|
6959
|
+
syntaurRoot(),
|
|
6960
|
+
"worktrees",
|
|
6961
|
+
opts.projectSlug || "standalone",
|
|
6962
|
+
opts.assignmentSlug
|
|
6963
|
+
);
|
|
6964
|
+
return {
|
|
6965
|
+
...repository ? { repository } : {},
|
|
6966
|
+
branch,
|
|
6967
|
+
parentBranch,
|
|
6968
|
+
worktreePath: worktreeBase
|
|
6969
|
+
};
|
|
6970
|
+
}
|
|
6971
|
+
function detectCurrentGitRoot(cwd) {
|
|
6972
|
+
const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
6973
|
+
cwd,
|
|
6974
|
+
encoding: "utf-8"
|
|
6975
|
+
});
|
|
6976
|
+
if (result.status !== 0) return void 0;
|
|
6977
|
+
const out = result.stdout.trim();
|
|
6978
|
+
return out.length > 0 ? out : void 0;
|
|
6979
|
+
}
|
|
6980
|
+
function detectCurrentBranch(cwd) {
|
|
6981
|
+
const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
6982
|
+
cwd,
|
|
6983
|
+
encoding: "utf-8"
|
|
6984
|
+
});
|
|
6985
|
+
if (result.status !== 0) return void 0;
|
|
6986
|
+
const out = result.stdout.trim();
|
|
6987
|
+
if (!out || out === "HEAD") return void 0;
|
|
6988
|
+
return out;
|
|
6989
|
+
}
|
|
6990
|
+
|
|
6991
|
+
// src/dashboard/repository-candidates.ts
|
|
6992
|
+
init_fs();
|
|
6993
|
+
init_parser();
|
|
6994
|
+
import { readdir as readdir9, readFile as readFile13 } from "fs/promises";
|
|
6995
|
+
import { resolve as resolve16 } from "path";
|
|
6996
|
+
async function getProjectRepositoryCandidates(projectsDir, projectSlug) {
|
|
6997
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6998
|
+
const out = [];
|
|
6999
|
+
const projectPath = resolve16(projectsDir, projectSlug, "project.md");
|
|
7000
|
+
if (await fileExists(projectPath)) {
|
|
7001
|
+
const project = parseProject(await readFile13(projectPath, "utf-8"));
|
|
7002
|
+
for (const raw of project.repositories) {
|
|
7003
|
+
const path = raw.trim();
|
|
7004
|
+
if (!path) continue;
|
|
7005
|
+
const abs = resolve16(path);
|
|
7006
|
+
if (seen.has(abs)) continue;
|
|
7007
|
+
seen.add(abs);
|
|
7008
|
+
out.push({ path: abs, source: "project", sourceAssignmentSlug: null });
|
|
7009
|
+
}
|
|
7010
|
+
}
|
|
7011
|
+
const assignmentsDir2 = resolve16(projectsDir, projectSlug, "assignments");
|
|
7012
|
+
if (await fileExists(assignmentsDir2)) {
|
|
7013
|
+
const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
|
|
7014
|
+
for (const entry of entries) {
|
|
7015
|
+
if (!entry.isDirectory()) continue;
|
|
7016
|
+
const assignmentMd = resolve16(assignmentsDir2, entry.name, "assignment.md");
|
|
7017
|
+
if (!await fileExists(assignmentMd)) continue;
|
|
7018
|
+
const parsed = parseAssignmentFull(await readFile13(assignmentMd, "utf-8"));
|
|
7019
|
+
const repo = parsed.workspace.repository?.trim();
|
|
7020
|
+
if (!repo) continue;
|
|
7021
|
+
const abs = resolve16(repo);
|
|
7022
|
+
if (seen.has(abs)) continue;
|
|
7023
|
+
seen.add(abs);
|
|
7024
|
+
out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
return out;
|
|
7028
|
+
}
|
|
7029
|
+
async function getStandaloneRepositoryCandidates(assignmentsDir2, excludeAssignmentId) {
|
|
7030
|
+
if (!await fileExists(assignmentsDir2)) {
|
|
7031
|
+
return [];
|
|
7032
|
+
}
|
|
7033
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7034
|
+
const out = [];
|
|
7035
|
+
const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
|
|
7036
|
+
for (const entry of entries) {
|
|
7037
|
+
if (!entry.isDirectory()) continue;
|
|
7038
|
+
if (entry.name === excludeAssignmentId) continue;
|
|
7039
|
+
const assignmentMd = resolve16(assignmentsDir2, entry.name, "assignment.md");
|
|
7040
|
+
if (!await fileExists(assignmentMd)) continue;
|
|
7041
|
+
const parsed = parseAssignmentFull(await readFile13(assignmentMd, "utf-8"));
|
|
7042
|
+
const repo = parsed.workspace.repository?.trim();
|
|
7043
|
+
if (!repo) continue;
|
|
7044
|
+
const abs = resolve16(repo);
|
|
7045
|
+
if (seen.has(abs)) continue;
|
|
7046
|
+
seen.add(abs);
|
|
7047
|
+
out.push({ path: abs, source: "sibling", sourceAssignmentSlug: parsed.slug });
|
|
7048
|
+
}
|
|
7049
|
+
return out;
|
|
7050
|
+
}
|
|
7051
|
+
|
|
7052
|
+
// src/dashboard/api-write.ts
|
|
6723
7053
|
init_parser();
|
|
6724
7054
|
|
|
6725
7055
|
// src/dashboard/acceptance-criteria.ts
|
|
@@ -6809,10 +7139,17 @@ function escapeYamlString(value) {
|
|
|
6809
7139
|
}
|
|
6810
7140
|
|
|
6811
7141
|
// src/templates/project.ts
|
|
7142
|
+
function renderRepositoriesBlock(repos) {
|
|
7143
|
+
if (!repos || repos.length === 0) {
|
|
7144
|
+
return "repositories: []";
|
|
7145
|
+
}
|
|
7146
|
+
return ["repositories:", ...repos.map((p) => ` - ${escapeYamlString(p)}`)].join("\n");
|
|
7147
|
+
}
|
|
6812
7148
|
function renderProject(params2) {
|
|
6813
7149
|
const safeTitle = escapeYamlString(params2.title);
|
|
6814
7150
|
const workspaceLine = params2.workspace ? `
|
|
6815
7151
|
workspace: ${params2.workspace}` : "";
|
|
7152
|
+
const repositoriesBlock = renderRepositoriesBlock(params2.repositories);
|
|
6816
7153
|
return `---
|
|
6817
7154
|
id: ${params2.id}
|
|
6818
7155
|
slug: ${params2.slug}
|
|
@@ -6823,7 +7160,8 @@ archivedReason: null
|
|
|
6823
7160
|
created: "${params2.timestamp}"
|
|
6824
7161
|
updated: "${params2.timestamp}"
|
|
6825
7162
|
externalIds: []
|
|
6826
|
-
tags: []
|
|
7163
|
+
tags: []
|
|
7164
|
+
${repositoriesBlock}${workspaceLine}
|
|
6827
7165
|
---
|
|
6828
7166
|
|
|
6829
7167
|
# ${params2.title}
|
|
@@ -7266,7 +7604,101 @@ async function readCurrentDocument(filePath) {
|
|
|
7266
7604
|
if (!await fileExists(filePath)) {
|
|
7267
7605
|
return null;
|
|
7268
7606
|
}
|
|
7269
|
-
return
|
|
7607
|
+
return readFile14(filePath, "utf-8");
|
|
7608
|
+
}
|
|
7609
|
+
async function handleWorktreeCreate(req, res, ctx) {
|
|
7610
|
+
if (!await fileExists(ctx.assignmentPath)) {
|
|
7611
|
+
res.status(404).json({ error: "Assignment not found" });
|
|
7612
|
+
return;
|
|
7613
|
+
}
|
|
7614
|
+
const parsed = parseAssignmentFull(await readFile14(ctx.assignmentPath, "utf-8"));
|
|
7615
|
+
if (parsed.workspace.worktreePath) {
|
|
7616
|
+
res.status(409).json({ error: "Worktree already configured for this assignment" });
|
|
7617
|
+
return;
|
|
7618
|
+
}
|
|
7619
|
+
const { repository, branch: bodyBranch, parentBranch: bodyParent } = req.body ?? {};
|
|
7620
|
+
if (typeof repository !== "string" || !repository.trim()) {
|
|
7621
|
+
res.status(400).json({ error: "`repository` is required." });
|
|
7622
|
+
return;
|
|
7623
|
+
}
|
|
7624
|
+
if (!isAbsolute2(repository)) {
|
|
7625
|
+
res.status(400).json({ error: "`repository` must be an absolute path." });
|
|
7626
|
+
return;
|
|
7627
|
+
}
|
|
7628
|
+
try {
|
|
7629
|
+
const st = await fsStat(repository);
|
|
7630
|
+
if (!st.isDirectory()) {
|
|
7631
|
+
res.status(400).json({ error: `Repository path is not a directory: ${repository}` });
|
|
7632
|
+
return;
|
|
7633
|
+
}
|
|
7634
|
+
} catch {
|
|
7635
|
+
res.status(400).json({ error: `Repository path does not exist: ${repository}` });
|
|
7636
|
+
return;
|
|
7637
|
+
}
|
|
7638
|
+
const topLevel = spawnSync3("git", ["-C", repository, "rev-parse", "--show-toplevel"], {
|
|
7639
|
+
encoding: "utf-8"
|
|
7640
|
+
});
|
|
7641
|
+
const topLevelOut = topLevel.stdout.trim();
|
|
7642
|
+
if (topLevel.status !== 0 || !topLevelOut) {
|
|
7643
|
+
res.status(400).json({ error: `Repository path is not a git working tree: ${repository}` });
|
|
7644
|
+
return;
|
|
7645
|
+
}
|
|
7646
|
+
const [requestReal, topLevelReal] = await Promise.all([
|
|
7647
|
+
fsRealpath(repository),
|
|
7648
|
+
fsRealpath(topLevelOut)
|
|
7649
|
+
]);
|
|
7650
|
+
if (requestReal !== topLevelReal) {
|
|
7651
|
+
res.status(400).json({
|
|
7652
|
+
error: `Repository path must be the git working-tree root. Got ${repository}; the enclosing repo root is ${topLevelOut}.`
|
|
7653
|
+
});
|
|
7654
|
+
return;
|
|
7655
|
+
}
|
|
7656
|
+
const defaults = computeWorktreeDefaults({
|
|
7657
|
+
projectSlug: ctx.projectSlug,
|
|
7658
|
+
assignmentSlug: ctx.assignmentSlug,
|
|
7659
|
+
existing: parsed.workspace,
|
|
7660
|
+
cwd: repository
|
|
7661
|
+
});
|
|
7662
|
+
const branch = typeof bodyBranch === "string" && bodyBranch.trim() ? bodyBranch.trim() : defaults.branch;
|
|
7663
|
+
const parentBranch = typeof bodyParent === "string" && bodyParent.trim() ? bodyParent.trim() : defaults.parentBranch;
|
|
7664
|
+
const worktreePath = resolve17(repository, ".worktrees", branch);
|
|
7665
|
+
try {
|
|
7666
|
+
await fsStat(worktreePath);
|
|
7667
|
+
res.status(409).json({
|
|
7668
|
+
error: `A file or directory already exists at ${worktreePath}. Remove it or choose a different branch.`
|
|
7669
|
+
});
|
|
7670
|
+
return;
|
|
7671
|
+
} catch {
|
|
7672
|
+
}
|
|
7673
|
+
const parentCheck = spawnSync3(
|
|
7674
|
+
"git",
|
|
7675
|
+
["-C", repository, "rev-parse", "--verify", "--quiet", parentBranch],
|
|
7676
|
+
{ encoding: "utf-8" }
|
|
7677
|
+
);
|
|
7678
|
+
if (parentCheck.status !== 0) {
|
|
7679
|
+
res.status(400).json({
|
|
7680
|
+
error: `Parent branch "${parentBranch}" does not exist in ${repository}.`
|
|
7681
|
+
});
|
|
7682
|
+
return;
|
|
7683
|
+
}
|
|
7684
|
+
try {
|
|
7685
|
+
await createWorktreeAndRecord({
|
|
7686
|
+
assignmentPath: ctx.assignmentPath,
|
|
7687
|
+
repository,
|
|
7688
|
+
branch,
|
|
7689
|
+
worktreePath,
|
|
7690
|
+
parentBranch
|
|
7691
|
+
});
|
|
7692
|
+
} catch (error) {
|
|
7693
|
+
if (error instanceof GitWorktreeError) {
|
|
7694
|
+
res.status(400).json({ error: error.message, stderr: error.stderr });
|
|
7695
|
+
return;
|
|
7696
|
+
}
|
|
7697
|
+
res.status(500).json({ error: error.message });
|
|
7698
|
+
return;
|
|
7699
|
+
}
|
|
7700
|
+
const assignment = await ctx.reload();
|
|
7701
|
+
res.json({ assignment });
|
|
7270
7702
|
}
|
|
7271
7703
|
function createWriteRouter(projectsDir, assignmentsDir2, todosDir2) {
|
|
7272
7704
|
const linkedTodosLookup = todosDir2 ? { todosDir: todosDir2, projectsDir } : void 0;
|
|
@@ -7495,9 +7927,9 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
7495
7927
|
});
|
|
7496
7928
|
return;
|
|
7497
7929
|
}
|
|
7498
|
-
const folderPath =
|
|
7930
|
+
const folderPath = resolve17(projectDir, folder);
|
|
7499
7931
|
await ensureDir(folderPath);
|
|
7500
|
-
const filePath =
|
|
7932
|
+
const filePath = resolve17(folderPath, `${requestedSlug}.md`);
|
|
7501
7933
|
const timestamp = nowTimestamp();
|
|
7502
7934
|
let content = renderItemStub(kind, {
|
|
7503
7935
|
slug: requestedSlug,
|
|
@@ -7541,14 +7973,14 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
7541
7973
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
7542
7974
|
return;
|
|
7543
7975
|
}
|
|
7544
|
-
const filePath =
|
|
7976
|
+
const filePath = resolve17(projectDir, folder, `${itemSlug}.md`);
|
|
7545
7977
|
if (!await fileExists(filePath)) {
|
|
7546
7978
|
res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
|
|
7547
7979
|
return;
|
|
7548
7980
|
}
|
|
7549
7981
|
const nextContentRaw = requireContent(req, res);
|
|
7550
7982
|
if (!nextContentRaw) return;
|
|
7551
|
-
const currentContent = await
|
|
7983
|
+
const currentContent = await readFile14(filePath, "utf-8");
|
|
7552
7984
|
const frontmatterBlock = extractFrontmatterBlock(currentContent);
|
|
7553
7985
|
if (!frontmatterBlock) {
|
|
7554
7986
|
res.status(500).json({ error: `${kind} file is malformed (no frontmatter)` });
|
|
@@ -7577,7 +8009,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7577
8009
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
7578
8010
|
return;
|
|
7579
8011
|
}
|
|
7580
|
-
const filePath =
|
|
8012
|
+
const filePath = resolve17(projectDir, folder, `${itemSlug}.md`);
|
|
7581
8013
|
if (!await fileExists(filePath)) {
|
|
7582
8014
|
res.status(404).json({ error: `${kind === "memory" ? "Memory" : "Resource"} not found` });
|
|
7583
8015
|
return;
|
|
@@ -7611,26 +8043,26 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7611
8043
|
res.status(400).json({ error: `Invalid slug "${slug}". Must be lowercase and hyphen-separated.` });
|
|
7612
8044
|
return;
|
|
7613
8045
|
}
|
|
7614
|
-
const projectDir =
|
|
8046
|
+
const projectDir = resolve17(projectsDir, slug);
|
|
7615
8047
|
if (await fileExists(projectDir)) {
|
|
7616
8048
|
res.status(409).json({ error: `Project "${slug}" already exists` });
|
|
7617
8049
|
return;
|
|
7618
8050
|
}
|
|
7619
8051
|
const title = fields.title;
|
|
7620
8052
|
const timestamp = fields.created || nowTimestamp();
|
|
7621
|
-
await ensureDir(
|
|
7622
|
-
await ensureDir(
|
|
7623
|
-
await ensureDir(
|
|
7624
|
-
await writeFileForce(
|
|
8053
|
+
await ensureDir(resolve17(projectDir, "assignments"));
|
|
8054
|
+
await ensureDir(resolve17(projectDir, "resources"));
|
|
8055
|
+
await ensureDir(resolve17(projectDir, "memories"));
|
|
8056
|
+
await writeFileForce(resolve17(projectDir, "project.md"), content);
|
|
7625
8057
|
try {
|
|
7626
8058
|
const companions = [
|
|
7627
|
-
[
|
|
7628
|
-
[
|
|
7629
|
-
[
|
|
7630
|
-
[
|
|
7631
|
-
[
|
|
7632
|
-
[
|
|
7633
|
-
[
|
|
8059
|
+
[resolve17(projectDir, "manifest.md"), renderManifest({ slug, timestamp })],
|
|
8060
|
+
[resolve17(projectDir, "_index-assignments.md"), renderIndexAssignments({ slug, title, timestamp })],
|
|
8061
|
+
[resolve17(projectDir, "_index-plans.md"), renderIndexPlans({ slug, title, timestamp })],
|
|
8062
|
+
[resolve17(projectDir, "_index-decisions.md"), renderIndexDecisions({ slug, title, timestamp })],
|
|
8063
|
+
[resolve17(projectDir, "_status.md"), renderStatus({ slug, title, timestamp })],
|
|
8064
|
+
[resolve17(projectDir, "resources", "_index.md"), renderResourcesIndex({ slug, title, timestamp })],
|
|
8065
|
+
[resolve17(projectDir, "memories", "_index.md"), renderMemoriesIndex({ slug, title, timestamp })]
|
|
7634
8066
|
];
|
|
7635
8067
|
for (const [filePath, fileContent] of companions) {
|
|
7636
8068
|
await writeFileForce(filePath, fileContent);
|
|
@@ -7651,8 +8083,8 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7651
8083
|
router.post("/api/projects/:slug/assignments", async (req, res) => {
|
|
7652
8084
|
try {
|
|
7653
8085
|
const projectSlug = getParam(req.params.slug);
|
|
7654
|
-
const projectDir =
|
|
7655
|
-
const projectMdPath =
|
|
8086
|
+
const projectDir = resolve17(projectsDir, projectSlug);
|
|
8087
|
+
const projectMdPath = resolve17(projectDir, "project.md");
|
|
7656
8088
|
if (!await fileExists(projectMdPath)) {
|
|
7657
8089
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
7658
8090
|
return;
|
|
@@ -7682,7 +8114,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7682
8114
|
res.status(400).json({ error: `Invalid priority "${priority}". Must be low, medium, high, or critical.` });
|
|
7683
8115
|
return;
|
|
7684
8116
|
}
|
|
7685
|
-
const assignmentDir =
|
|
8117
|
+
const assignmentDir = resolve17(projectDir, "assignments", assignmentSlug);
|
|
7686
8118
|
if (await fileExists(assignmentDir)) {
|
|
7687
8119
|
res.status(409).json({
|
|
7688
8120
|
error: `Assignment "${assignmentSlug}" already exists in project "${projectSlug}"`
|
|
@@ -7691,12 +8123,12 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7691
8123
|
}
|
|
7692
8124
|
const timestamp = fields.created || nowTimestamp();
|
|
7693
8125
|
await ensureDir(assignmentDir);
|
|
7694
|
-
await writeFileForce(
|
|
8126
|
+
await writeFileForce(resolve17(assignmentDir, "assignment.md"), content);
|
|
7695
8127
|
try {
|
|
7696
8128
|
const companions = [
|
|
7697
|
-
[
|
|
7698
|
-
[
|
|
7699
|
-
[
|
|
8129
|
+
[resolve17(assignmentDir, "scratchpad.md"), renderScratchpad({ assignmentSlug, timestamp })],
|
|
8130
|
+
[resolve17(assignmentDir, "handoff.md"), renderHandoff({ assignmentSlug, timestamp })],
|
|
8131
|
+
[resolve17(assignmentDir, "decision-record.md"), renderDecisionRecord({ assignmentSlug, timestamp })]
|
|
7700
8132
|
];
|
|
7701
8133
|
for (const [filePath, fileContent] of companions) {
|
|
7702
8134
|
await writeFileForce(filePath, fileContent);
|
|
@@ -7717,7 +8149,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7717
8149
|
router.patch("/api/projects/:slug", async (req, res) => {
|
|
7718
8150
|
try {
|
|
7719
8151
|
const projectSlug = getParam(req.params.slug);
|
|
7720
|
-
const projectPath =
|
|
8152
|
+
const projectPath = resolve17(projectsDir, projectSlug, "project.md");
|
|
7721
8153
|
const currentContent = await readCurrentDocument(projectPath);
|
|
7722
8154
|
if (!currentContent) {
|
|
7723
8155
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
@@ -7750,7 +8182,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7750
8182
|
try {
|
|
7751
8183
|
const projectSlug = getParam(req.params.slug);
|
|
7752
8184
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7753
|
-
const assignmentPath =
|
|
8185
|
+
const assignmentPath = resolve17(
|
|
7754
8186
|
projectsDir,
|
|
7755
8187
|
projectSlug,
|
|
7756
8188
|
"assignments",
|
|
@@ -7793,7 +8225,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7793
8225
|
try {
|
|
7794
8226
|
const projectSlug = getParam(req.params.slug);
|
|
7795
8227
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7796
|
-
const assignmentPath =
|
|
8228
|
+
const assignmentPath = resolve17(
|
|
7797
8229
|
projectsDir,
|
|
7798
8230
|
projectSlug,
|
|
7799
8231
|
"assignments",
|
|
@@ -7829,7 +8261,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7829
8261
|
try {
|
|
7830
8262
|
const projectSlug = getParam(req.params.slug);
|
|
7831
8263
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7832
|
-
const planPath =
|
|
8264
|
+
const planPath = resolve17(
|
|
7833
8265
|
projectsDir,
|
|
7834
8266
|
projectSlug,
|
|
7835
8267
|
"assignments",
|
|
@@ -7867,7 +8299,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7867
8299
|
try {
|
|
7868
8300
|
const projectSlug = getParam(req.params.slug);
|
|
7869
8301
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7870
|
-
const scratchpadPath =
|
|
8302
|
+
const scratchpadPath = resolve17(
|
|
7871
8303
|
projectsDir,
|
|
7872
8304
|
projectSlug,
|
|
7873
8305
|
"assignments",
|
|
@@ -7905,7 +8337,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7905
8337
|
try {
|
|
7906
8338
|
const projectSlug = getParam(req.params.slug);
|
|
7907
8339
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7908
|
-
const handoffPath =
|
|
8340
|
+
const handoffPath = resolve17(
|
|
7909
8341
|
projectsDir,
|
|
7910
8342
|
projectSlug,
|
|
7911
8343
|
"assignments",
|
|
@@ -7943,7 +8375,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7943
8375
|
try {
|
|
7944
8376
|
const projectSlug = getParam(req.params.slug);
|
|
7945
8377
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7946
|
-
const decisionPath =
|
|
8378
|
+
const decisionPath = resolve17(
|
|
7947
8379
|
projectsDir,
|
|
7948
8380
|
projectSlug,
|
|
7949
8381
|
"assignments",
|
|
@@ -7981,7 +8413,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7981
8413
|
try {
|
|
7982
8414
|
const projectSlug = getParam(req.params.slug);
|
|
7983
8415
|
const assignmentSlug = getParam(req.params.aslug);
|
|
7984
|
-
const commentsPath =
|
|
8416
|
+
const commentsPath = resolve17(
|
|
7985
8417
|
projectsDir,
|
|
7986
8418
|
projectSlug,
|
|
7987
8419
|
"assignments",
|
|
@@ -7999,7 +8431,7 @@ ${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
|
7999
8431
|
let currentContent;
|
|
8000
8432
|
let currentCount = 0;
|
|
8001
8433
|
if (await fileExists(commentsPath)) {
|
|
8002
|
-
currentContent = await
|
|
8434
|
+
currentContent = await readFile14(commentsPath, "utf-8");
|
|
8003
8435
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
8004
8436
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
8005
8437
|
} else {
|
|
@@ -8040,7 +8472,7 @@ ${entry}`;
|
|
|
8040
8472
|
const projectSlug = getParam(req.params.slug);
|
|
8041
8473
|
const assignmentSlug = getParam(req.params.aslug);
|
|
8042
8474
|
const commentId = getParam(req.params.commentId);
|
|
8043
|
-
const commentsPath =
|
|
8475
|
+
const commentsPath = resolve17(
|
|
8044
8476
|
projectsDir,
|
|
8045
8477
|
projectSlug,
|
|
8046
8478
|
"assignments",
|
|
@@ -8056,7 +8488,7 @@ ${entry}`;
|
|
|
8056
8488
|
res.status(400).json({ error: "resolved (boolean) is required" });
|
|
8057
8489
|
return;
|
|
8058
8490
|
}
|
|
8059
|
-
const content = await
|
|
8491
|
+
const content = await readFile14(commentsPath, "utf-8");
|
|
8060
8492
|
const parsed = parseComments(content);
|
|
8061
8493
|
const target = parsed.entries.find((e) => e.id === commentId);
|
|
8062
8494
|
if (!target) {
|
|
@@ -8091,7 +8523,7 @@ ${entry}`;
|
|
|
8091
8523
|
router.post("/api/projects/:slug/move-workspace", async (req, res) => {
|
|
8092
8524
|
try {
|
|
8093
8525
|
const projectSlug = getParam(req.params.slug);
|
|
8094
|
-
const projectPath =
|
|
8526
|
+
const projectPath = resolve17(projectsDir, projectSlug, "project.md");
|
|
8095
8527
|
if (!await fileExists(projectPath)) {
|
|
8096
8528
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
8097
8529
|
return;
|
|
@@ -8103,7 +8535,7 @@ ${entry}`;
|
|
|
8103
8535
|
});
|
|
8104
8536
|
return;
|
|
8105
8537
|
}
|
|
8106
|
-
let content = await
|
|
8538
|
+
let content = await readFile14(projectPath, "utf-8");
|
|
8107
8539
|
content = setTopLevelField(content, "workspace", workspace ?? null);
|
|
8108
8540
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8109
8541
|
await writeFileForce(projectPath, content);
|
|
@@ -8139,8 +8571,8 @@ ${entry}`;
|
|
|
8139
8571
|
});
|
|
8140
8572
|
return;
|
|
8141
8573
|
}
|
|
8142
|
-
const assignmentPath =
|
|
8143
|
-
let content = await
|
|
8574
|
+
const assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8575
|
+
let content = await readFile14(assignmentPath, "utf-8");
|
|
8144
8576
|
content = setTopLevelField(content, "workspaceGroup", workspaceGroup ?? null);
|
|
8145
8577
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8146
8578
|
await writeFileForce(assignmentPath, content);
|
|
@@ -8151,10 +8583,108 @@ ${entry}`;
|
|
|
8151
8583
|
res.status(500).json({ error: `Failed to move workspace: ${error.message}` });
|
|
8152
8584
|
}
|
|
8153
8585
|
});
|
|
8586
|
+
router.get(
|
|
8587
|
+
"/api/projects/:slug/repository-candidates",
|
|
8588
|
+
async (req, res) => {
|
|
8589
|
+
try {
|
|
8590
|
+
const projectSlug = getParam(req.params.slug);
|
|
8591
|
+
const projectPath = resolve17(projectsDir, projectSlug, "project.md");
|
|
8592
|
+
if (!await fileExists(projectPath)) {
|
|
8593
|
+
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
8594
|
+
return;
|
|
8595
|
+
}
|
|
8596
|
+
const candidates = await getProjectRepositoryCandidates(projectsDir, projectSlug);
|
|
8597
|
+
res.json({ candidates });
|
|
8598
|
+
} catch (error) {
|
|
8599
|
+
console.error("Error listing repository candidates:", error);
|
|
8600
|
+
res.status(500).json({
|
|
8601
|
+
error: `Failed to list repository candidates: ${error.message}`
|
|
8602
|
+
});
|
|
8603
|
+
}
|
|
8604
|
+
}
|
|
8605
|
+
);
|
|
8606
|
+
router.get(
|
|
8607
|
+
"/api/assignments/:id/repository-candidates",
|
|
8608
|
+
async (req, res) => {
|
|
8609
|
+
try {
|
|
8610
|
+
if (!assignmentsDir2) {
|
|
8611
|
+
res.status(501).json({ error: "Standalone assignments not configured on this server" });
|
|
8612
|
+
return;
|
|
8613
|
+
}
|
|
8614
|
+
const id = getParam(req.params.id);
|
|
8615
|
+
const resolved = await resolveAssignmentById(projectsDir, assignmentsDir2, id);
|
|
8616
|
+
if (!resolved) {
|
|
8617
|
+
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8618
|
+
return;
|
|
8619
|
+
}
|
|
8620
|
+
const candidates = resolved.standalone ? await getStandaloneRepositoryCandidates(assignmentsDir2, id) : await getProjectRepositoryCandidates(projectsDir, resolved.projectSlug);
|
|
8621
|
+
res.json({ candidates });
|
|
8622
|
+
} catch (error) {
|
|
8623
|
+
console.error("Error listing repository candidates:", error);
|
|
8624
|
+
res.status(500).json({
|
|
8625
|
+
error: `Failed to list repository candidates: ${error.message}`
|
|
8626
|
+
});
|
|
8627
|
+
}
|
|
8628
|
+
}
|
|
8629
|
+
);
|
|
8630
|
+
router.post(
|
|
8631
|
+
"/api/projects/:slug/assignments/:aslug/worktree",
|
|
8632
|
+
async (req, res) => {
|
|
8633
|
+
try {
|
|
8634
|
+
const projectSlug = getParam(req.params.slug);
|
|
8635
|
+
const assignmentSlug = getParam(req.params.aslug);
|
|
8636
|
+
const assignmentPath = resolve17(
|
|
8637
|
+
projectsDir,
|
|
8638
|
+
projectSlug,
|
|
8639
|
+
"assignments",
|
|
8640
|
+
assignmentSlug,
|
|
8641
|
+
"assignment.md"
|
|
8642
|
+
);
|
|
8643
|
+
await handleWorktreeCreate(req, res, {
|
|
8644
|
+
assignmentPath,
|
|
8645
|
+
projectSlug,
|
|
8646
|
+
assignmentSlug,
|
|
8647
|
+
reload: () => getAssignmentDetail(projectsDir, projectSlug, assignmentSlug)
|
|
8648
|
+
});
|
|
8649
|
+
} catch (error) {
|
|
8650
|
+
console.error("Error creating worktree:", error);
|
|
8651
|
+
res.status(500).json({ error: `Failed to create worktree: ${error.message}` });
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8654
|
+
);
|
|
8655
|
+
router.post(
|
|
8656
|
+
"/api/assignments/:id/worktree",
|
|
8657
|
+
async (req, res) => {
|
|
8658
|
+
try {
|
|
8659
|
+
if (!assignmentsDir2) {
|
|
8660
|
+
res.status(501).json({ error: "Standalone assignments not configured on this server" });
|
|
8661
|
+
return;
|
|
8662
|
+
}
|
|
8663
|
+
const id = getParam(req.params.id);
|
|
8664
|
+
const resolved = await resolveAssignmentById(projectsDir, assignmentsDir2, id);
|
|
8665
|
+
if (!resolved) {
|
|
8666
|
+
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8667
|
+
return;
|
|
8668
|
+
}
|
|
8669
|
+
const assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8670
|
+
const parsedForSlug = parseAssignmentFull(await readFile14(assignmentPath, "utf-8"));
|
|
8671
|
+
const assignmentSlugForBranch = parsedForSlug.slug || resolved.id;
|
|
8672
|
+
await handleWorktreeCreate(req, res, {
|
|
8673
|
+
assignmentPath,
|
|
8674
|
+
projectSlug: resolved.projectSlug ?? "",
|
|
8675
|
+
assignmentSlug: assignmentSlugForBranch,
|
|
8676
|
+
reload: () => getAssignmentDetailById(projectsDir, assignmentsDir2, id)
|
|
8677
|
+
});
|
|
8678
|
+
} catch (error) {
|
|
8679
|
+
console.error("Error creating worktree:", error);
|
|
8680
|
+
res.status(500).json({ error: `Failed to create worktree: ${error.message}` });
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
);
|
|
8154
8684
|
router.post("/api/projects/:slug/status-override", async (req, res) => {
|
|
8155
8685
|
try {
|
|
8156
8686
|
const projectSlug = getParam(req.params.slug);
|
|
8157
|
-
const projectPath =
|
|
8687
|
+
const projectPath = resolve17(projectsDir, projectSlug, "project.md");
|
|
8158
8688
|
if (!await fileExists(projectPath)) {
|
|
8159
8689
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
8160
8690
|
return;
|
|
@@ -8166,7 +8696,7 @@ ${entry}`;
|
|
|
8166
8696
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}, or null to clear.` });
|
|
8167
8697
|
return;
|
|
8168
8698
|
}
|
|
8169
|
-
let content = await
|
|
8699
|
+
let content = await readFile14(projectPath, "utf-8");
|
|
8170
8700
|
content = setTopLevelField(content, "statusOverride", status ?? null);
|
|
8171
8701
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8172
8702
|
await writeFileForce(projectPath, content);
|
|
@@ -8181,7 +8711,7 @@ ${entry}`;
|
|
|
8181
8711
|
try {
|
|
8182
8712
|
const projectSlug = getParam(req.params.slug);
|
|
8183
8713
|
const assignmentSlug = getParam(req.params.aslug);
|
|
8184
|
-
const assignmentPath =
|
|
8714
|
+
const assignmentPath = resolve17(
|
|
8185
8715
|
projectsDir,
|
|
8186
8716
|
projectSlug,
|
|
8187
8717
|
"assignments",
|
|
@@ -8199,7 +8729,7 @@ ${entry}`;
|
|
|
8199
8729
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
|
|
8200
8730
|
return;
|
|
8201
8731
|
}
|
|
8202
|
-
let content = await
|
|
8732
|
+
let content = await readFile14(assignmentPath, "utf-8");
|
|
8203
8733
|
content = setTopLevelField(content, "status", status);
|
|
8204
8734
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8205
8735
|
if (status !== "blocked") {
|
|
@@ -8217,7 +8747,7 @@ ${entry}`;
|
|
|
8217
8747
|
try {
|
|
8218
8748
|
const projectSlug = getParam(req.params.slug);
|
|
8219
8749
|
const assignmentSlug = getParam(req.params.aslug);
|
|
8220
|
-
const assignmentPath =
|
|
8750
|
+
const assignmentPath = resolve17(
|
|
8221
8751
|
projectsDir,
|
|
8222
8752
|
projectSlug,
|
|
8223
8753
|
"assignments",
|
|
@@ -8233,7 +8763,7 @@ ${entry}`;
|
|
|
8233
8763
|
res.status(400).json({ error: validation.error });
|
|
8234
8764
|
return;
|
|
8235
8765
|
}
|
|
8236
|
-
let content = await
|
|
8766
|
+
let content = await readFile14(assignmentPath, "utf-8");
|
|
8237
8767
|
content = setTopLevelField(content, "assignee", validation.value);
|
|
8238
8768
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8239
8769
|
await writeFileForce(assignmentPath, content);
|
|
@@ -8255,8 +8785,8 @@ ${entry}`;
|
|
|
8255
8785
|
res.status(400).json({ error: `Unsupported transition command "${req.params.command}"` });
|
|
8256
8786
|
return;
|
|
8257
8787
|
}
|
|
8258
|
-
const projectDir =
|
|
8259
|
-
const assignmentPath =
|
|
8788
|
+
const projectDir = resolve17(projectsDir, projectSlug);
|
|
8789
|
+
const assignmentPath = resolve17(projectDir, "assignments", assignmentSlug, "assignment.md");
|
|
8260
8790
|
if (!await fileExists(assignmentPath)) {
|
|
8261
8791
|
res.status(404).json({ error: "Assignment not found" });
|
|
8262
8792
|
return;
|
|
@@ -8283,8 +8813,8 @@ ${entry}`;
|
|
|
8283
8813
|
try {
|
|
8284
8814
|
const projectSlug = getParam(req.params.slug);
|
|
8285
8815
|
const assignmentSlug = getParam(req.params.aslug);
|
|
8286
|
-
const assignmentDir =
|
|
8287
|
-
const assignmentPath =
|
|
8816
|
+
const assignmentDir = resolve17(projectsDir, projectSlug, "assignments", assignmentSlug);
|
|
8817
|
+
const assignmentPath = resolve17(assignmentDir, "assignment.md");
|
|
8288
8818
|
if (!await fileExists(assignmentPath)) {
|
|
8289
8819
|
res.status(404).json({ error: `Assignment "${assignmentSlug}" not found in project "${projectSlug}"` });
|
|
8290
8820
|
return;
|
|
@@ -8339,7 +8869,7 @@ ${entry}`;
|
|
|
8339
8869
|
return;
|
|
8340
8870
|
}
|
|
8341
8871
|
const id2 = generateId();
|
|
8342
|
-
const assignmentDir2 =
|
|
8872
|
+
const assignmentDir2 = resolve17(assignmentsDir2, id2);
|
|
8343
8873
|
if (await fileExists(assignmentDir2)) {
|
|
8344
8874
|
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
8345
8875
|
return;
|
|
@@ -8347,25 +8877,25 @@ ${entry}`;
|
|
|
8347
8877
|
const timestamp2 = fields.created || nowTimestamp();
|
|
8348
8878
|
await ensureDir(assignmentDir2);
|
|
8349
8879
|
const normalizedContent = setTopLevelField(rawContent, "id", id2);
|
|
8350
|
-
await writeFileForce(
|
|
8880
|
+
await writeFileForce(resolve17(assignmentDir2, "assignment.md"), normalizedContent);
|
|
8351
8881
|
await writeFileForce(
|
|
8352
|
-
|
|
8882
|
+
resolve17(assignmentDir2, "scratchpad.md"),
|
|
8353
8883
|
renderScratchpad({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
8354
8884
|
);
|
|
8355
8885
|
await writeFileForce(
|
|
8356
|
-
|
|
8886
|
+
resolve17(assignmentDir2, "handoff.md"),
|
|
8357
8887
|
renderHandoff({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
8358
8888
|
);
|
|
8359
8889
|
await writeFileForce(
|
|
8360
|
-
|
|
8890
|
+
resolve17(assignmentDir2, "decision-record.md"),
|
|
8361
8891
|
renderDecisionRecord({ assignmentSlug: id2, timestamp: timestamp2 })
|
|
8362
8892
|
);
|
|
8363
8893
|
await writeFileForce(
|
|
8364
|
-
|
|
8894
|
+
resolve17(assignmentDir2, "progress.md"),
|
|
8365
8895
|
renderProgress({ assignment: id2, timestamp: timestamp2 })
|
|
8366
8896
|
);
|
|
8367
8897
|
await writeFileForce(
|
|
8368
|
-
|
|
8898
|
+
resolve17(assignmentDir2, "comments.md"),
|
|
8369
8899
|
renderComments({ assignment: id2, timestamp: timestamp2 })
|
|
8370
8900
|
);
|
|
8371
8901
|
const detail2 = await getAssignmentDetailById(projectsDir, assignmentsDir2, id2);
|
|
@@ -8383,7 +8913,7 @@ ${entry}`;
|
|
|
8383
8913
|
return;
|
|
8384
8914
|
}
|
|
8385
8915
|
const id = generateId();
|
|
8386
|
-
const assignmentDir =
|
|
8916
|
+
const assignmentDir = resolve17(assignmentsDir2, id);
|
|
8387
8917
|
if (await fileExists(assignmentDir)) {
|
|
8388
8918
|
res.status(500).json({ error: "UUID collision \u2014 try again" });
|
|
8389
8919
|
return;
|
|
@@ -8403,25 +8933,25 @@ ${entry}`;
|
|
|
8403
8933
|
project: null,
|
|
8404
8934
|
type: typeof type === "string" ? type : void 0
|
|
8405
8935
|
});
|
|
8406
|
-
await writeFileForce(
|
|
8936
|
+
await writeFileForce(resolve17(assignmentDir, "assignment.md"), assignmentContent);
|
|
8407
8937
|
await writeFileForce(
|
|
8408
|
-
|
|
8938
|
+
resolve17(assignmentDir, "scratchpad.md"),
|
|
8409
8939
|
renderScratchpad({ assignmentSlug: id, timestamp })
|
|
8410
8940
|
);
|
|
8411
8941
|
await writeFileForce(
|
|
8412
|
-
|
|
8942
|
+
resolve17(assignmentDir, "handoff.md"),
|
|
8413
8943
|
renderHandoff({ assignmentSlug: id, timestamp })
|
|
8414
8944
|
);
|
|
8415
8945
|
await writeFileForce(
|
|
8416
|
-
|
|
8946
|
+
resolve17(assignmentDir, "decision-record.md"),
|
|
8417
8947
|
renderDecisionRecord({ assignmentSlug: id, timestamp })
|
|
8418
8948
|
);
|
|
8419
8949
|
await writeFileForce(
|
|
8420
|
-
|
|
8950
|
+
resolve17(assignmentDir, "progress.md"),
|
|
8421
8951
|
renderProgress({ assignment: id, timestamp })
|
|
8422
8952
|
);
|
|
8423
8953
|
await writeFileForce(
|
|
8424
|
-
|
|
8954
|
+
resolve17(assignmentDir, "comments.md"),
|
|
8425
8955
|
renderComments({ assignment: id, timestamp })
|
|
8426
8956
|
);
|
|
8427
8957
|
const detail = await getAssignmentDetailById(projectsDir, assignmentsDir2, id);
|
|
@@ -8549,7 +9079,7 @@ ${entry}`;
|
|
|
8549
9079
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8550
9080
|
return;
|
|
8551
9081
|
}
|
|
8552
|
-
const assignmentPath =
|
|
9082
|
+
const assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8553
9083
|
const currentContent = await readCurrentDocument(assignmentPath);
|
|
8554
9084
|
if (!currentContent) {
|
|
8555
9085
|
res.status(404).json({ error: "Assignment not found" });
|
|
@@ -8591,7 +9121,7 @@ ${entry}`;
|
|
|
8591
9121
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8592
9122
|
return;
|
|
8593
9123
|
}
|
|
8594
|
-
const planPath =
|
|
9124
|
+
const planPath = resolve17(resolved.assignmentDir, "plan.md");
|
|
8595
9125
|
const currentContent = await readCurrentDocument(planPath);
|
|
8596
9126
|
if (!currentContent) {
|
|
8597
9127
|
res.status(404).json({ error: "Plan not found" });
|
|
@@ -8625,7 +9155,7 @@ ${entry}`;
|
|
|
8625
9155
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8626
9156
|
return;
|
|
8627
9157
|
}
|
|
8628
|
-
const scratchpadPath =
|
|
9158
|
+
const scratchpadPath = resolve17(resolved.assignmentDir, "scratchpad.md");
|
|
8629
9159
|
const currentContent = await readCurrentDocument(scratchpadPath);
|
|
8630
9160
|
if (!currentContent) {
|
|
8631
9161
|
res.status(404).json({ error: "Scratchpad not found" });
|
|
@@ -8659,7 +9189,7 @@ ${entry}`;
|
|
|
8659
9189
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8660
9190
|
return;
|
|
8661
9191
|
}
|
|
8662
|
-
const handoffPath =
|
|
9192
|
+
const handoffPath = resolve17(resolved.assignmentDir, "handoff.md");
|
|
8663
9193
|
const currentContent = await readCurrentDocument(handoffPath);
|
|
8664
9194
|
if (!currentContent) {
|
|
8665
9195
|
res.status(404).json({ error: "Handoff log not found" });
|
|
@@ -8699,7 +9229,7 @@ ${entry}`;
|
|
|
8699
9229
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8700
9230
|
return;
|
|
8701
9231
|
}
|
|
8702
|
-
const decisionPath =
|
|
9232
|
+
const decisionPath = resolve17(resolved.assignmentDir, "decision-record.md");
|
|
8703
9233
|
const currentContent = await readCurrentDocument(decisionPath);
|
|
8704
9234
|
if (!currentContent) {
|
|
8705
9235
|
res.status(404).json({ error: "Decision record not found" });
|
|
@@ -8739,7 +9269,7 @@ ${entry}`;
|
|
|
8739
9269
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8740
9270
|
return;
|
|
8741
9271
|
}
|
|
8742
|
-
const assignmentPath =
|
|
9272
|
+
const assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8743
9273
|
if (!await fileExists(assignmentPath)) {
|
|
8744
9274
|
res.status(404).json({ error: "Assignment not found" });
|
|
8745
9275
|
return;
|
|
@@ -8751,7 +9281,7 @@ ${entry}`;
|
|
|
8751
9281
|
res.status(400).json({ error: `Invalid status. Must be one of: ${validStatuses.join(", ")}.` });
|
|
8752
9282
|
return;
|
|
8753
9283
|
}
|
|
8754
|
-
let content = await
|
|
9284
|
+
let content = await readFile14(assignmentPath, "utf-8");
|
|
8755
9285
|
content = setTopLevelField(content, "status", status);
|
|
8756
9286
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8757
9287
|
if (status !== "blocked") {
|
|
@@ -8777,7 +9307,7 @@ ${entry}`;
|
|
|
8777
9307
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8778
9308
|
return;
|
|
8779
9309
|
}
|
|
8780
|
-
const assignmentPath =
|
|
9310
|
+
const assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8781
9311
|
if (!await fileExists(assignmentPath)) {
|
|
8782
9312
|
res.status(404).json({ error: "Assignment not found" });
|
|
8783
9313
|
return;
|
|
@@ -8787,7 +9317,7 @@ ${entry}`;
|
|
|
8787
9317
|
res.status(400).json({ error: validation.error });
|
|
8788
9318
|
return;
|
|
8789
9319
|
}
|
|
8790
|
-
let content = await
|
|
9320
|
+
let content = await readFile14(assignmentPath, "utf-8");
|
|
8791
9321
|
content = setTopLevelField(content, "assignee", validation.value);
|
|
8792
9322
|
content = setTopLevelField(content, "updated", nowTimestamp());
|
|
8793
9323
|
await writeFileForce(assignmentPath, content);
|
|
@@ -8841,9 +9371,9 @@ ${entry}`;
|
|
|
8841
9371
|
if (!resolved) {
|
|
8842
9372
|
throw new Error(`assignment "${item.id}" not found`);
|
|
8843
9373
|
}
|
|
8844
|
-
assignmentPath =
|
|
9374
|
+
assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8845
9375
|
} else if (typeof item.projectSlug === "string" && typeof item.assignmentSlug === "string" && item.projectSlug && item.assignmentSlug) {
|
|
8846
|
-
assignmentPath =
|
|
9376
|
+
assignmentPath = resolve17(
|
|
8847
9377
|
projectsDir,
|
|
8848
9378
|
item.projectSlug,
|
|
8849
9379
|
"assignments",
|
|
@@ -8856,7 +9386,7 @@ ${entry}`;
|
|
|
8856
9386
|
if (!await fileExists(assignmentPath)) {
|
|
8857
9387
|
throw new Error("assignment file not found");
|
|
8858
9388
|
}
|
|
8859
|
-
let content = await
|
|
9389
|
+
let content = await readFile14(assignmentPath, "utf-8");
|
|
8860
9390
|
content = setTopLevelField(content, "status", status);
|
|
8861
9391
|
content = setTopLevelField(content, "updated", timestamp);
|
|
8862
9392
|
if (status !== "blocked") {
|
|
@@ -8888,7 +9418,7 @@ ${entry}`;
|
|
|
8888
9418
|
res.status(404).json({ error: `Assignment "${id}" not found` });
|
|
8889
9419
|
return;
|
|
8890
9420
|
}
|
|
8891
|
-
const assignmentPath =
|
|
9421
|
+
const assignmentPath = resolve17(resolved.assignmentDir, "assignment.md");
|
|
8892
9422
|
const currentContent = await readCurrentDocument(assignmentPath);
|
|
8893
9423
|
if (!currentContent) {
|
|
8894
9424
|
res.status(404).json({ error: "Assignment not found" });
|
|
@@ -8980,7 +9510,7 @@ function buildBulkItemKey(raw, index) {
|
|
|
8980
9510
|
return `#${index}`;
|
|
8981
9511
|
}
|
|
8982
9512
|
async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
|
|
8983
|
-
const commentsPath =
|
|
9513
|
+
const commentsPath = resolve17(assignmentDir, "comments.md");
|
|
8984
9514
|
const { body, author, type, replyTo } = req.body || {};
|
|
8985
9515
|
if (!body || typeof body !== "string" || !body.trim()) {
|
|
8986
9516
|
res.status(400).json({ error: "body is required" });
|
|
@@ -8992,7 +9522,7 @@ async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDet
|
|
|
8992
9522
|
let currentContent;
|
|
8993
9523
|
let currentCount = 0;
|
|
8994
9524
|
if (await fileExists(commentsPath)) {
|
|
8995
|
-
currentContent = await
|
|
9525
|
+
currentContent = await readFile14(commentsPath, "utf-8");
|
|
8996
9526
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
8997
9527
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
8998
9528
|
} else {
|
|
@@ -9022,7 +9552,7 @@ ${entry}`;
|
|
|
9022
9552
|
res.status(201).json({ assignment, comment: { id: comment.id } });
|
|
9023
9553
|
}
|
|
9024
9554
|
async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloadDetail) {
|
|
9025
|
-
const commentsPath =
|
|
9555
|
+
const commentsPath = resolve17(assignmentDir, "comments.md");
|
|
9026
9556
|
if (!await fileExists(commentsPath)) {
|
|
9027
9557
|
res.status(404).json({ error: "Comments file not found" });
|
|
9028
9558
|
return;
|
|
@@ -9032,7 +9562,7 @@ async function toggleCommentResolvedAt(assignmentDir, commentId, req, res, reloa
|
|
|
9032
9562
|
res.status(400).json({ error: "resolved (boolean) is required" });
|
|
9033
9563
|
return;
|
|
9034
9564
|
}
|
|
9035
|
-
const content = await
|
|
9565
|
+
const content = await readFile14(commentsPath, "utf-8");
|
|
9036
9566
|
const parsed = parseComments(content);
|
|
9037
9567
|
const target = parsed.entries.find((e) => e.id === commentId);
|
|
9038
9568
|
if (!target) {
|
|
@@ -9179,7 +9709,7 @@ function createServersRouter(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
9179
9709
|
init_agent_sessions();
|
|
9180
9710
|
init_fs();
|
|
9181
9711
|
import { Router as Router3 } from "express";
|
|
9182
|
-
import { resolve as
|
|
9712
|
+
import { resolve as resolve18 } from "path";
|
|
9183
9713
|
|
|
9184
9714
|
// src/utils/transcript.ts
|
|
9185
9715
|
import { open } from "fs/promises";
|
|
@@ -9277,7 +9807,7 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
9277
9807
|
try {
|
|
9278
9808
|
const { projectSlug } = req.params;
|
|
9279
9809
|
const assignment = req.query.assignment;
|
|
9280
|
-
const projectDir =
|
|
9810
|
+
const projectDir = resolve18(projectsDir, projectSlug);
|
|
9281
9811
|
if (!await fileExists(projectDir)) {
|
|
9282
9812
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9283
9813
|
return;
|
|
@@ -9307,7 +9837,7 @@ function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
|
9307
9837
|
return;
|
|
9308
9838
|
}
|
|
9309
9839
|
if (projectSlug) {
|
|
9310
|
-
const projectDir =
|
|
9840
|
+
const projectDir = resolve18(projectsDir, projectSlug);
|
|
9311
9841
|
if (!await fileExists(projectDir)) {
|
|
9312
9842
|
res.status(404).json({ error: `Project "${projectSlug}" not found` });
|
|
9313
9843
|
return;
|
|
@@ -9631,14 +10161,136 @@ function createAgentsRouter() {
|
|
|
9631
10161
|
return router;
|
|
9632
10162
|
}
|
|
9633
10163
|
|
|
9634
|
-
// src/dashboard/api-
|
|
10164
|
+
// src/dashboard/api-launch-preflight.ts
|
|
10165
|
+
init_config2();
|
|
9635
10166
|
import { Router as Router5 } from "express";
|
|
9636
10167
|
|
|
10168
|
+
// src/utils/terminal-probe.ts
|
|
10169
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
10170
|
+
var APP_BUNDLE_IDS = {
|
|
10171
|
+
"terminal-app": "com.apple.Terminal",
|
|
10172
|
+
iterm: "com.googlecode.iterm2",
|
|
10173
|
+
ghostty: "com.mitchellh.ghostty",
|
|
10174
|
+
warp: "dev.warp.Warp-Stable"
|
|
10175
|
+
};
|
|
10176
|
+
var CLI_NAMES = {
|
|
10177
|
+
alacritty: "alacritty",
|
|
10178
|
+
kitty: "kitty"
|
|
10179
|
+
};
|
|
10180
|
+
function probeTerminalInstalled(terminal) {
|
|
10181
|
+
const bundleId = APP_BUNDLE_IDS[terminal];
|
|
10182
|
+
if (bundleId) {
|
|
10183
|
+
const result = spawnSync4(
|
|
10184
|
+
"mdfind",
|
|
10185
|
+
[`kMDItemCFBundleIdentifier == '${bundleId}'`],
|
|
10186
|
+
{ encoding: "utf-8" }
|
|
10187
|
+
);
|
|
10188
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
10189
|
+
return { ok: true, foundPath: result.stdout.trim().split("\n")[0] };
|
|
10190
|
+
}
|
|
10191
|
+
return { ok: false, reason: "not-installed" };
|
|
10192
|
+
}
|
|
10193
|
+
const cliName = CLI_NAMES[terminal];
|
|
10194
|
+
if (cliName) {
|
|
10195
|
+
const result = spawnSync4("which", [cliName], { encoding: "utf-8" });
|
|
10196
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
10197
|
+
return { ok: true, foundPath: result.stdout.trim() };
|
|
10198
|
+
}
|
|
10199
|
+
return { ok: false, reason: "not-installed" };
|
|
10200
|
+
}
|
|
10201
|
+
return { ok: false, reason: "no-probe-available" };
|
|
10202
|
+
}
|
|
10203
|
+
|
|
10204
|
+
// src/dashboard/api-launch-preflight.ts
|
|
10205
|
+
function createLaunchPreflightRouter() {
|
|
10206
|
+
const router = Router5();
|
|
10207
|
+
router.post("/preflight", async (req, res) => {
|
|
10208
|
+
try {
|
|
10209
|
+
const body = req.body ?? {};
|
|
10210
|
+
if (body.terminal !== void 0 && (typeof body.terminal !== "string" || !TERMINAL_CHOICES.includes(body.terminal))) {
|
|
10211
|
+
res.status(400).json({
|
|
10212
|
+
error: `terminal must be one of: ${TERMINAL_CHOICES.join(", ")}`
|
|
10213
|
+
});
|
|
10214
|
+
return;
|
|
10215
|
+
}
|
|
10216
|
+
const config = await readConfig();
|
|
10217
|
+
const terminal = body.terminal ?? getTerminal(config);
|
|
10218
|
+
const probe = probeTerminalInstalled(terminal);
|
|
10219
|
+
if (probe.ok) {
|
|
10220
|
+
const response2 = { ok: true, terminal };
|
|
10221
|
+
res.json(response2);
|
|
10222
|
+
return;
|
|
10223
|
+
}
|
|
10224
|
+
const suggestedFallback = getTerminal({ ...config, terminal: null });
|
|
10225
|
+
const response = {
|
|
10226
|
+
ok: false,
|
|
10227
|
+
terminal,
|
|
10228
|
+
reason: "not-installed",
|
|
10229
|
+
suggestedFallback
|
|
10230
|
+
};
|
|
10231
|
+
res.json(response);
|
|
10232
|
+
} catch (error) {
|
|
10233
|
+
console.error("Error in launch preflight:", error);
|
|
10234
|
+
res.status(500).json({ error: "preflight failed" });
|
|
10235
|
+
}
|
|
10236
|
+
});
|
|
10237
|
+
return router;
|
|
10238
|
+
}
|
|
10239
|
+
|
|
10240
|
+
// src/dashboard/api-terminal-config.ts
|
|
10241
|
+
init_config2();
|
|
10242
|
+
import { Router as Router6 } from "express";
|
|
10243
|
+
function createTerminalConfigRouter() {
|
|
10244
|
+
const router = Router6();
|
|
10245
|
+
router.get("/", async (_req, res) => {
|
|
10246
|
+
try {
|
|
10247
|
+
const config = await readConfig();
|
|
10248
|
+
res.json({
|
|
10249
|
+
terminal: getTerminal(config),
|
|
10250
|
+
custom: config.terminal !== null
|
|
10251
|
+
});
|
|
10252
|
+
} catch (error) {
|
|
10253
|
+
console.error("Error getting terminal config:", error);
|
|
10254
|
+
res.status(500).json({ error: "Failed to get terminal config" });
|
|
10255
|
+
}
|
|
10256
|
+
});
|
|
10257
|
+
router.post("/", async (req, res) => {
|
|
10258
|
+
try {
|
|
10259
|
+
const { terminal } = req.body ?? {};
|
|
10260
|
+
if (typeof terminal !== "string" || !TERMINAL_CHOICES.includes(terminal)) {
|
|
10261
|
+
res.status(400).json({
|
|
10262
|
+
error: `terminal must be one of: ${TERMINAL_CHOICES.join(", ")}`
|
|
10263
|
+
});
|
|
10264
|
+
return;
|
|
10265
|
+
}
|
|
10266
|
+
await writeTerminalConfig(terminal);
|
|
10267
|
+
res.json({ terminal, custom: true });
|
|
10268
|
+
} catch (error) {
|
|
10269
|
+
console.error("Error saving terminal config:", error);
|
|
10270
|
+
res.status(500).json({ error: "Failed to save terminal config" });
|
|
10271
|
+
}
|
|
10272
|
+
});
|
|
10273
|
+
router.delete("/", async (_req, res) => {
|
|
10274
|
+
try {
|
|
10275
|
+
await deleteTerminalConfig();
|
|
10276
|
+
const config = await readConfig();
|
|
10277
|
+
res.json({ terminal: getTerminal(config), custom: false });
|
|
10278
|
+
} catch (error) {
|
|
10279
|
+
console.error("Error resetting terminal config:", error);
|
|
10280
|
+
res.status(500).json({ error: "Failed to reset terminal config" });
|
|
10281
|
+
}
|
|
10282
|
+
});
|
|
10283
|
+
return router;
|
|
10284
|
+
}
|
|
10285
|
+
|
|
10286
|
+
// src/dashboard/api-leases.ts
|
|
10287
|
+
import { Router as Router7 } from "express";
|
|
10288
|
+
|
|
9637
10289
|
// src/db/leases-db.ts
|
|
9638
10290
|
init_paths();
|
|
9639
10291
|
import Database2 from "better-sqlite3";
|
|
9640
10292
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9641
|
-
import { resolve as
|
|
10293
|
+
import { resolve as resolve19 } from "path";
|
|
9642
10294
|
var db2 = null;
|
|
9643
10295
|
var LEASE_SCHEMA_VERSION = "1";
|
|
9644
10296
|
var SCHEMA_SQL2 = `
|
|
@@ -9725,7 +10377,7 @@ function isBusyError(err) {
|
|
|
9725
10377
|
}
|
|
9726
10378
|
function initLeasesDb(dbPath) {
|
|
9727
10379
|
if (db2) return db2;
|
|
9728
|
-
const finalPath = dbPath ??
|
|
10380
|
+
const finalPath = dbPath ?? resolve19(syntaurRoot(), "syntaur.db");
|
|
9729
10381
|
db2 = new Database2(finalPath);
|
|
9730
10382
|
db2.pragma("journal_mode = WAL");
|
|
9731
10383
|
db2.pragma("busy_timeout = 5000");
|
|
@@ -9814,7 +10466,7 @@ function forceReleaseLease(lease_id) {
|
|
|
9814
10466
|
|
|
9815
10467
|
// src/dashboard/api-leases.ts
|
|
9816
10468
|
function createLeasesRouter(broadcast) {
|
|
9817
|
-
const router =
|
|
10469
|
+
const router = Router7();
|
|
9818
10470
|
router.get("/", (_req, res) => {
|
|
9819
10471
|
try {
|
|
9820
10472
|
initLeasesDb();
|
|
@@ -9874,9 +10526,9 @@ init_parser();
|
|
|
9874
10526
|
init_slug();
|
|
9875
10527
|
init_timestamp();
|
|
9876
10528
|
init_fs();
|
|
9877
|
-
import { Router as
|
|
9878
|
-
import { resolve as
|
|
9879
|
-
import { readFile as
|
|
10529
|
+
import { Router as Router8 } from "express";
|
|
10530
|
+
import { resolve as resolve20 } from "path";
|
|
10531
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
9880
10532
|
init_playbooks();
|
|
9881
10533
|
function statusForPlaybookError(code) {
|
|
9882
10534
|
switch (code) {
|
|
@@ -9891,7 +10543,7 @@ function statusForPlaybookError(code) {
|
|
|
9891
10543
|
}
|
|
9892
10544
|
}
|
|
9893
10545
|
function createPlaybooksRouter(playbooksDir2) {
|
|
9894
|
-
const router =
|
|
10546
|
+
const router = Router8();
|
|
9895
10547
|
router.get("/", async (_req, res) => {
|
|
9896
10548
|
try {
|
|
9897
10549
|
const playbooks = await listPlaybooks(playbooksDir2);
|
|
@@ -9958,8 +10610,8 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
9958
10610
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
9959
10611
|
return;
|
|
9960
10612
|
}
|
|
9961
|
-
const filePath =
|
|
9962
|
-
const content = await
|
|
10613
|
+
const filePath = resolve20(playbooksDir2, resolved.filename);
|
|
10614
|
+
const content = await readFile15(filePath, "utf-8");
|
|
9963
10615
|
res.json({
|
|
9964
10616
|
documentType: "playbook",
|
|
9965
10617
|
title: `Edit Playbook: ${resolved.slug}`,
|
|
@@ -9984,7 +10636,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
9984
10636
|
return;
|
|
9985
10637
|
}
|
|
9986
10638
|
await ensureDir(playbooksDir2);
|
|
9987
|
-
const filePath =
|
|
10639
|
+
const filePath = resolve20(playbooksDir2, `${slug}.md`);
|
|
9988
10640
|
if (await fileExists(filePath)) {
|
|
9989
10641
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
9990
10642
|
return;
|
|
@@ -10008,7 +10660,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
10008
10660
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
10009
10661
|
return;
|
|
10010
10662
|
}
|
|
10011
|
-
const filePath =
|
|
10663
|
+
const filePath = resolve20(playbooksDir2, resolved.filename);
|
|
10012
10664
|
await writeFileForce(filePath, content);
|
|
10013
10665
|
await rebuildPlaybookManifest(playbooksDir2);
|
|
10014
10666
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -10055,8 +10707,8 @@ init_fs_migration();
|
|
|
10055
10707
|
init_parser2();
|
|
10056
10708
|
init_fs();
|
|
10057
10709
|
init_paths();
|
|
10058
|
-
import { Router as
|
|
10059
|
-
import { readdir as
|
|
10710
|
+
import { Router as Router9 } from "express";
|
|
10711
|
+
import { readdir as readdir10 } from "fs/promises";
|
|
10060
10712
|
import { resolve as resolvePath, dirname as dirname4 } from "path";
|
|
10061
10713
|
import { rename as rename4, mkdir as mkdir2 } from "fs/promises";
|
|
10062
10714
|
init_slug();
|
|
@@ -10067,7 +10719,7 @@ init_parser2();
|
|
|
10067
10719
|
// src/commands/create-assignment.ts
|
|
10068
10720
|
init_slug();
|
|
10069
10721
|
init_timestamp();
|
|
10070
|
-
import { resolve as
|
|
10722
|
+
import { resolve as resolve21 } from "path";
|
|
10071
10723
|
init_paths();
|
|
10072
10724
|
init_fs();
|
|
10073
10725
|
init_config2();
|
|
@@ -10145,14 +10797,14 @@ async function createAssignmentCommand(title, options) {
|
|
|
10145
10797
|
if (options.oneOff) {
|
|
10146
10798
|
const standaloneRoot = assignmentsDir();
|
|
10147
10799
|
folderName = id;
|
|
10148
|
-
assignmentDir =
|
|
10800
|
+
assignmentDir = resolve21(standaloneRoot, folderName);
|
|
10149
10801
|
projectSlug = null;
|
|
10150
10802
|
await ensureDir(standaloneRoot);
|
|
10151
10803
|
} else {
|
|
10152
10804
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10153
10805
|
projectSlug = options.project;
|
|
10154
|
-
const projectDir =
|
|
10155
|
-
const projectMdPath =
|
|
10806
|
+
const projectDir = resolve21(baseDir, projectSlug);
|
|
10807
|
+
const projectMdPath = resolve21(projectDir, "project.md");
|
|
10156
10808
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
10157
10809
|
throw new Error(
|
|
10158
10810
|
`Project "${projectSlug}" not found at ${projectDir}.
|
|
@@ -10160,9 +10812,9 @@ Run 'syntaur create-project' first or use --one-off.`
|
|
|
10160
10812
|
);
|
|
10161
10813
|
}
|
|
10162
10814
|
if (dependsOn.length > 0) {
|
|
10163
|
-
const depDirBase =
|
|
10815
|
+
const depDirBase = resolve21(projectDir, "assignments");
|
|
10164
10816
|
for (const dep of dependsOn) {
|
|
10165
|
-
const depDir =
|
|
10817
|
+
const depDir = resolve21(depDirBase, dep);
|
|
10166
10818
|
if (!await fileExists(depDir)) {
|
|
10167
10819
|
console.warn(
|
|
10168
10820
|
`Warning: dependency "${dep}" does not exist in project "${projectSlug}" yet.`
|
|
@@ -10171,7 +10823,7 @@ Run 'syntaur create-project' first or use --one-off.`
|
|
|
10171
10823
|
}
|
|
10172
10824
|
}
|
|
10173
10825
|
folderName = assignmentSlug;
|
|
10174
|
-
assignmentDir =
|
|
10826
|
+
assignmentDir = resolve21(projectDir, "assignments", folderName);
|
|
10175
10827
|
}
|
|
10176
10828
|
if (await fileExists(assignmentDir)) {
|
|
10177
10829
|
throw new Error(
|
|
@@ -10183,7 +10835,7 @@ Use --slug to specify a different slug.`
|
|
|
10183
10835
|
const companionAssignmentRef = projectSlug === null ? id : assignmentSlug;
|
|
10184
10836
|
const files = [
|
|
10185
10837
|
[
|
|
10186
|
-
|
|
10838
|
+
resolve21(assignmentDir, "assignment.md"),
|
|
10187
10839
|
renderAssignment({
|
|
10188
10840
|
id,
|
|
10189
10841
|
slug: assignmentSlug,
|
|
@@ -10201,35 +10853,35 @@ Use --slug to specify a different slug.`
|
|
|
10201
10853
|
})
|
|
10202
10854
|
],
|
|
10203
10855
|
[
|
|
10204
|
-
|
|
10856
|
+
resolve21(assignmentDir, "scratchpad.md"),
|
|
10205
10857
|
renderScratchpad({
|
|
10206
10858
|
assignmentSlug: companionAssignmentRef,
|
|
10207
10859
|
timestamp
|
|
10208
10860
|
})
|
|
10209
10861
|
],
|
|
10210
10862
|
[
|
|
10211
|
-
|
|
10863
|
+
resolve21(assignmentDir, "handoff.md"),
|
|
10212
10864
|
renderHandoff({
|
|
10213
10865
|
assignmentSlug: companionAssignmentRef,
|
|
10214
10866
|
timestamp
|
|
10215
10867
|
})
|
|
10216
10868
|
],
|
|
10217
10869
|
[
|
|
10218
|
-
|
|
10870
|
+
resolve21(assignmentDir, "decision-record.md"),
|
|
10219
10871
|
renderDecisionRecord({
|
|
10220
10872
|
assignmentSlug: companionAssignmentRef,
|
|
10221
10873
|
timestamp
|
|
10222
10874
|
})
|
|
10223
10875
|
],
|
|
10224
10876
|
[
|
|
10225
|
-
|
|
10877
|
+
resolve21(assignmentDir, "progress.md"),
|
|
10226
10878
|
renderProgress({
|
|
10227
10879
|
assignment: companionAssignmentRef,
|
|
10228
10880
|
timestamp
|
|
10229
10881
|
})
|
|
10230
10882
|
],
|
|
10231
10883
|
[
|
|
10232
|
-
|
|
10884
|
+
resolve21(assignmentDir, "comments.md"),
|
|
10233
10885
|
renderComments({
|
|
10234
10886
|
assignment: companionAssignmentRef,
|
|
10235
10887
|
timestamp
|
|
@@ -10394,7 +11046,7 @@ function touchItem3(item) {
|
|
|
10394
11046
|
item.updatedAt = now;
|
|
10395
11047
|
}
|
|
10396
11048
|
function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
10397
|
-
const router =
|
|
11049
|
+
const router = Router9();
|
|
10398
11050
|
function broadcastUpdate() {
|
|
10399
11051
|
broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10400
11052
|
}
|
|
@@ -10510,7 +11162,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
10510
11162
|
router.get("/", async (_req, res) => {
|
|
10511
11163
|
try {
|
|
10512
11164
|
await ensureDir(todosDir2);
|
|
10513
|
-
const files = await
|
|
11165
|
+
const files = await readdir10(todosDir2).catch(() => []);
|
|
10514
11166
|
const workspaces = [];
|
|
10515
11167
|
for (const file of files) {
|
|
10516
11168
|
if (typeof file !== "string") continue;
|
|
@@ -10623,8 +11275,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
10623
11275
|
router.post("/:workspace/archive", async (req, res) => {
|
|
10624
11276
|
try {
|
|
10625
11277
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
10626
|
-
const { resolve:
|
|
10627
|
-
const { readFile:
|
|
11278
|
+
const { resolve: resolve25 } = await import("path");
|
|
11279
|
+
const { readFile: readFile18 } = await import("fs/promises");
|
|
10628
11280
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
10629
11281
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
10630
11282
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -10640,10 +11292,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
10640
11292
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
10641
11293
|
);
|
|
10642
11294
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
10643
|
-
await ensureDir(
|
|
11295
|
+
await ensureDir(resolve25(todosDir2, "archive"));
|
|
10644
11296
|
let archContent = "";
|
|
10645
11297
|
if (await fileExists(archFile)) {
|
|
10646
|
-
archContent = await
|
|
11298
|
+
archContent = await readFile18(archFile, "utf-8");
|
|
10647
11299
|
archContent = archContent.trimEnd() + "\n\n";
|
|
10648
11300
|
} else {
|
|
10649
11301
|
archContent = `---
|
|
@@ -10922,7 +11574,7 @@ workspace: ${workspace}
|
|
|
10922
11574
|
const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
10923
11575
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
10924
11576
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
10925
|
-
const { readFile:
|
|
11577
|
+
const { readFile: readFile18 } = await import("fs/promises");
|
|
10926
11578
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
10927
11579
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
10928
11580
|
let assignmentRef;
|
|
@@ -10943,7 +11595,7 @@ workspace: ${workspace}
|
|
|
10943
11595
|
}
|
|
10944
11596
|
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
10945
11597
|
if (!await fileExists2(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
10946
|
-
let content = await
|
|
11598
|
+
let content = await readFile18(assignmentMdPath, "utf-8");
|
|
10947
11599
|
content = appendTodosToAssignmentBody2(
|
|
10948
11600
|
content,
|
|
10949
11601
|
items.map((it) => ({
|
|
@@ -11126,9 +11778,9 @@ init_parser2();
|
|
|
11126
11778
|
init_fs();
|
|
11127
11779
|
init_paths();
|
|
11128
11780
|
init_slug();
|
|
11129
|
-
import { Router as
|
|
11130
|
-
import { mkdir as mkdir3, readFile as
|
|
11131
|
-
import { resolve as
|
|
11781
|
+
import { Router as Router10 } from "express";
|
|
11782
|
+
import { mkdir as mkdir3, readFile as readFile16, rename as rename5 } from "fs/promises";
|
|
11783
|
+
import { resolve as resolve22, dirname as dirname5 } from "path";
|
|
11132
11784
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
11133
11785
|
function touchItem4(item) {
|
|
11134
11786
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -11143,7 +11795,7 @@ function params(req) {
|
|
|
11143
11795
|
return req.params;
|
|
11144
11796
|
}
|
|
11145
11797
|
async function projectExists(projectsDir, slug) {
|
|
11146
|
-
return fileExists(
|
|
11798
|
+
return fileExists(resolve22(projectsDir, slug, "project.md"));
|
|
11147
11799
|
}
|
|
11148
11800
|
async function ensureProjectTodosDir(projectsDir, slug) {
|
|
11149
11801
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
@@ -11160,7 +11812,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
11160
11812
|
throw err;
|
|
11161
11813
|
}
|
|
11162
11814
|
try {
|
|
11163
|
-
await mkdir3(
|
|
11815
|
+
await mkdir3(resolve22(todosDir2, "archive"), { recursive: false });
|
|
11164
11816
|
} catch (err) {
|
|
11165
11817
|
const code = err.code;
|
|
11166
11818
|
if (code === "EEXIST") return;
|
|
@@ -11176,7 +11828,7 @@ function notFound(res, slug) {
|
|
|
11176
11828
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
11177
11829
|
}
|
|
11178
11830
|
function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
11179
|
-
const router =
|
|
11831
|
+
const router = Router10({ mergeParams: true });
|
|
11180
11832
|
function broadcastUpdate(projectSlug) {
|
|
11181
11833
|
broadcast({ type: "todos-updated", projectSlug, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11182
11834
|
}
|
|
@@ -11348,7 +12000,7 @@ function createProjectTodosRouter(projectsDir, broadcast, workspaceTodosDir) {
|
|
|
11348
12000
|
const archFile = archivePath(todosDir2, slug, checklist.archiveInterval);
|
|
11349
12001
|
let archContent = "";
|
|
11350
12002
|
if (await fileExists(archFile)) {
|
|
11351
|
-
archContent = await
|
|
12003
|
+
archContent = await readFile16(archFile, "utf-8");
|
|
11352
12004
|
archContent = archContent.trimEnd() + "\n\n";
|
|
11353
12005
|
} else {
|
|
11354
12006
|
archContent = `---
|
|
@@ -11792,17 +12444,17 @@ workspace: ${slug}
|
|
|
11792
12444
|
if (tg.includes("/")) {
|
|
11793
12445
|
const parts = tg.split("/");
|
|
11794
12446
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
11795
|
-
assignmentDir =
|
|
12447
|
+
assignmentDir = resolve22(projectsDir, parts[0], "assignments", parts[1]);
|
|
11796
12448
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
11797
12449
|
} 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)) {
|
|
11798
|
-
assignmentDir =
|
|
12450
|
+
assignmentDir = resolve22(assignmentsDirFn(), tg);
|
|
11799
12451
|
assignmentRef = tg;
|
|
11800
12452
|
} else {
|
|
11801
12453
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
11802
12454
|
}
|
|
11803
|
-
const assignmentMdPath =
|
|
12455
|
+
const assignmentMdPath = resolve22(assignmentDir, "assignment.md");
|
|
11804
12456
|
if (!await fileExists(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
11805
|
-
let content = await
|
|
12457
|
+
let content = await readFile16(assignmentMdPath, "utf-8");
|
|
11806
12458
|
content = appendTodosToAssignmentBody2(
|
|
11807
12459
|
content,
|
|
11808
12460
|
items.map((it) => ({
|
|
@@ -11994,7 +12646,7 @@ workspace: ${slug}
|
|
|
11994
12646
|
|
|
11995
12647
|
// src/dashboard/api-backup.ts
|
|
11996
12648
|
init_config2();
|
|
11997
|
-
import { Router as
|
|
12649
|
+
import { Router as Router11 } from "express";
|
|
11998
12650
|
|
|
11999
12651
|
// src/utils/github-backup.ts
|
|
12000
12652
|
init_paths();
|
|
@@ -12002,8 +12654,8 @@ init_fs();
|
|
|
12002
12654
|
init_config2();
|
|
12003
12655
|
import { execFile as execFile2 } from "child_process";
|
|
12004
12656
|
import { promisify as promisify2 } from "util";
|
|
12005
|
-
import { cp, mkdtemp, rm as rm2, readFile as
|
|
12006
|
-
import { resolve as
|
|
12657
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile17, writeFile as writeFile4, unlink as unlink4, stat, open as open2, rename as rename6 } from "fs/promises";
|
|
12658
|
+
import { resolve as resolve23, join as join2 } from "path";
|
|
12007
12659
|
import { tmpdir } from "os";
|
|
12008
12660
|
var exec2 = promisify2(execFile2);
|
|
12009
12661
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -12043,7 +12695,7 @@ async function resolveCategoryPath(category) {
|
|
|
12043
12695
|
case "servers":
|
|
12044
12696
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
12045
12697
|
case "config":
|
|
12046
|
-
return { sourcePath:
|
|
12698
|
+
return { sourcePath: resolve23(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
12047
12699
|
}
|
|
12048
12700
|
}
|
|
12049
12701
|
async function checkGitInstalled() {
|
|
@@ -12054,7 +12706,7 @@ async function checkGitInstalled() {
|
|
|
12054
12706
|
}
|
|
12055
12707
|
}
|
|
12056
12708
|
async function acquireLock() {
|
|
12057
|
-
const lockPath =
|
|
12709
|
+
const lockPath = resolve23(syntaurRoot(), LOCK_FILE_NAME);
|
|
12058
12710
|
await ensureDir(syntaurRoot());
|
|
12059
12711
|
try {
|
|
12060
12712
|
const handle = await open2(lockPath, "wx");
|
|
@@ -12063,7 +12715,7 @@ async function acquireLock() {
|
|
|
12063
12715
|
return lockPath;
|
|
12064
12716
|
} catch (err) {
|
|
12065
12717
|
if (err.code === "EEXIST") {
|
|
12066
|
-
const pid = await
|
|
12718
|
+
const pid = await readFile17(lockPath, "utf-8").catch(() => "");
|
|
12067
12719
|
throw new Error(
|
|
12068
12720
|
`Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
|
|
12069
12721
|
);
|
|
@@ -12101,7 +12753,7 @@ async function copyRecursive(src, dest) {
|
|
|
12101
12753
|
await ensureDir(dest);
|
|
12102
12754
|
await cp(src, dest, { recursive: true, force: true });
|
|
12103
12755
|
} else {
|
|
12104
|
-
await ensureDir(
|
|
12756
|
+
await ensureDir(resolve23(dest, ".."));
|
|
12105
12757
|
await cp(src, dest, { force: true });
|
|
12106
12758
|
}
|
|
12107
12759
|
}
|
|
@@ -12110,7 +12762,7 @@ function resolveCategoriesStrict(csv) {
|
|
|
12110
12762
|
return parseCategoriesStrict(parts);
|
|
12111
12763
|
}
|
|
12112
12764
|
async function readSanitizedConfig(configPath) {
|
|
12113
|
-
const content = await
|
|
12765
|
+
const content = await readFile17(configPath, "utf-8");
|
|
12114
12766
|
return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
|
|
12115
12767
|
}
|
|
12116
12768
|
async function backupToGithub(overrides) {
|
|
@@ -12149,7 +12801,7 @@ async function backupToGithub(overrides) {
|
|
|
12149
12801
|
}
|
|
12150
12802
|
if (category === "config") {
|
|
12151
12803
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
12152
|
-
await ensureDir(
|
|
12804
|
+
await ensureDir(resolve23(destPath, ".."));
|
|
12153
12805
|
await writeFile4(destPath, sanitized, "utf-8");
|
|
12154
12806
|
} else {
|
|
12155
12807
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -12203,7 +12855,7 @@ async function backupToGithub(overrides) {
|
|
|
12203
12855
|
}
|
|
12204
12856
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
12205
12857
|
if (isFile) {
|
|
12206
|
-
await ensureDir(
|
|
12858
|
+
await ensureDir(resolve23(localPath, ".."));
|
|
12207
12859
|
await cp(repoSrcPath, localPath, { force: true });
|
|
12208
12860
|
return;
|
|
12209
12861
|
}
|
|
@@ -12304,7 +12956,7 @@ async function restoreFromGithub(overrides) {
|
|
|
12304
12956
|
}
|
|
12305
12957
|
async function getBackupStatus() {
|
|
12306
12958
|
const config = await readConfig();
|
|
12307
|
-
const lockPath =
|
|
12959
|
+
const lockPath = resolve23(syntaurRoot(), LOCK_FILE_NAME);
|
|
12308
12960
|
const locked = await fileExists(lockPath);
|
|
12309
12961
|
return {
|
|
12310
12962
|
repo: config.backup?.repo ?? null,
|
|
@@ -12317,7 +12969,7 @@ async function getBackupStatus() {
|
|
|
12317
12969
|
|
|
12318
12970
|
// src/dashboard/api-backup.ts
|
|
12319
12971
|
function createBackupRouter() {
|
|
12320
|
-
const router =
|
|
12972
|
+
const router = Router11();
|
|
12321
12973
|
router.get("/", async (_req, res) => {
|
|
12322
12974
|
try {
|
|
12323
12975
|
const status = await getBackupStatus();
|
|
@@ -12645,7 +13297,7 @@ function createDashboardServer(options) {
|
|
|
12645
13297
|
(async () => {
|
|
12646
13298
|
try {
|
|
12647
13299
|
const configResult = await migrateLegacyConfig(
|
|
12648
|
-
|
|
13300
|
+
resolve24(syntaurRoot(), "config.md")
|
|
12649
13301
|
);
|
|
12650
13302
|
const projectResult = await migrateLegacyProjectFiles(projectsDir);
|
|
12651
13303
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -12768,6 +13420,7 @@ function createDashboardServer(options) {
|
|
|
12768
13420
|
res.status(500).json({ error: "Failed to reset theme config" });
|
|
12769
13421
|
}
|
|
12770
13422
|
});
|
|
13423
|
+
app.use("/api/config/terminal", createTerminalConfigRouter());
|
|
12771
13424
|
app.get("/api/config/hotkeys", async (_req, res) => {
|
|
12772
13425
|
try {
|
|
12773
13426
|
const config = await readConfig();
|
|
@@ -13153,6 +13806,7 @@ function createDashboardServer(options) {
|
|
|
13153
13806
|
app.use("/api/leases", createLeasesRouter(broadcast));
|
|
13154
13807
|
app.use("/api/agent-sessions", createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2));
|
|
13155
13808
|
app.use("/api/config/agents", createAgentsRouter());
|
|
13809
|
+
app.use("/api/launch", createLaunchPreflightRouter());
|
|
13156
13810
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
|
|
13157
13811
|
app.get("/api/memories", async (_req, res) => {
|
|
13158
13812
|
try {
|
|
@@ -13177,14 +13831,14 @@ function createDashboardServer(options) {
|
|
|
13177
13831
|
app.use("/api/backup", createBackupRouter());
|
|
13178
13832
|
if (serveStaticUi && dashboardDistPath) {
|
|
13179
13833
|
const sendOpts = { dotfiles: "allow" };
|
|
13180
|
-
app.use("/assets", express.static(
|
|
13834
|
+
app.use("/assets", express.static(resolve24(dashboardDistPath, "assets"), sendOpts));
|
|
13181
13835
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
13182
13836
|
app.get("{*path}", async (req, res) => {
|
|
13183
13837
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
13184
13838
|
res.status(404).json({ error: "Not Found" });
|
|
13185
13839
|
return;
|
|
13186
13840
|
}
|
|
13187
|
-
const indexPath =
|
|
13841
|
+
const indexPath = resolve24(dashboardDistPath, "index.html");
|
|
13188
13842
|
if (!await fileExists(indexPath)) {
|
|
13189
13843
|
res.status(503).send(
|
|
13190
13844
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -13208,7 +13862,7 @@ function createDashboardServer(options) {
|
|
|
13208
13862
|
serversDir: serversDir2,
|
|
13209
13863
|
playbooksDir: playbooksDir2,
|
|
13210
13864
|
todosDir: todosDir2,
|
|
13211
|
-
dbPath:
|
|
13865
|
+
dbPath: resolve24(syntaurRoot(), "syntaur.db"),
|
|
13212
13866
|
onMessage: broadcast
|
|
13213
13867
|
});
|
|
13214
13868
|
startAutodiscovery({ serversDir: serversDir2, projectsDir, assignmentsDir: assignmentsDir2, excludePids: /* @__PURE__ */ new Set([process.pid]) });
|
|
@@ -13223,7 +13877,7 @@ function createDashboardServer(options) {
|
|
|
13223
13877
|
}
|
|
13224
13878
|
});
|
|
13225
13879
|
server.listen(port, () => {
|
|
13226
|
-
const portFile =
|
|
13880
|
+
const portFile = resolve24(syntaurRoot(), "dashboard-port");
|
|
13227
13881
|
writeFile5(portFile, String(port), "utf-8").catch(() => {
|
|
13228
13882
|
});
|
|
13229
13883
|
resolvePromise();
|
|
@@ -13241,7 +13895,7 @@ function createDashboardServer(options) {
|
|
|
13241
13895
|
client.terminate();
|
|
13242
13896
|
}
|
|
13243
13897
|
clients.clear();
|
|
13244
|
-
const portFile =
|
|
13898
|
+
const portFile = resolve24(syntaurRoot(), "dashboard-port");
|
|
13245
13899
|
await unlink5(portFile).catch(() => {
|
|
13246
13900
|
});
|
|
13247
13901
|
server.closeAllConnections?.();
|