syntaur 0.1.13 → 0.1.14

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.
Files changed (62) hide show
  1. package/dashboard/dist/assets/{_basePickBy-DXzhD14q.js → _basePickBy-eih-KlEh.js} +1 -1
  2. package/dashboard/dist/assets/{_baseUniq-gxypqvP5.js → _baseUniq-M21wg9ZQ.js} +1 -1
  3. package/dashboard/dist/assets/{arc-Ce7nYKSm.js → arc-uKZMelpQ.js} +1 -1
  4. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-zX4f4_Mf.js → architectureDiagram-2XIMDMQ5-CpMG5exj.js} +1 -1
  5. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-auOdy7nH.js → blockDiagram-WCTKOSBZ-BHnCCKl_.js} +1 -1
  6. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-C2kkjPbW.js → c4Diagram-IC4MRINW-B-n3zU9i.js} +1 -1
  7. package/dashboard/dist/assets/channel-DVBgSlOI.js +1 -0
  8. package/dashboard/dist/assets/{chunk-4BX2VUAB-B7dfpnbG.js → chunk-4BX2VUAB-ChD9Iuih.js} +1 -1
  9. package/dashboard/dist/assets/{chunk-55IACEB6-r1_jHZYp.js → chunk-55IACEB6-B3vP9Psg.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-FMBD7UC4-5mMONjMK.js → chunk-FMBD7UC4-CIhWgxPS.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-JSJVCQXG-CwKj-Es4.js → chunk-JSJVCQXG-DiGIV_cB.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-KX2RTZJC-ByoW-HgN.js → chunk-KX2RTZJC-DnGsx5jo.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-NQ4KR5QH-D1olOovd.js → chunk-NQ4KR5QH-BFBu1fmg.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-QZHKN3VN-CB8_FC8w.js → chunk-QZHKN3VN-DYtumHth.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-WL4C6EOR-CFEqRrE1.js → chunk-WL4C6EOR-BzCrQPuw.js} +1 -1
  16. package/dashboard/dist/assets/classDiagram-VBA2DB6C-B7dxBacd.js +1 -0
  17. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-B7dxBacd.js +1 -0
  18. package/dashboard/dist/assets/clone-DAOrHcCC.js +1 -0
  19. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-D6dGVXEI.js → cose-bilkent-S5V4N54A-Bl8mb5eY.js} +1 -1
  20. package/dashboard/dist/assets/{dagre-KLK3FWXG-Cvg9CgP-.js → dagre-KLK3FWXG-BHffcOgo.js} +1 -1
  21. package/dashboard/dist/assets/{diagram-E7M64L7V-iCBudhZD.js → diagram-E7M64L7V-Ib83qzT_.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-IFDJBPK2-BGniy7VQ.js → diagram-IFDJBPK2-hOdh63_T.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-P4PSJMXO-B6Ie044E.js → diagram-P4PSJMXO-D4ocLmc5.js} +1 -1
  24. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BHvFRNhJ.js → erDiagram-INFDFZHY-CHJ6zqnJ.js} +1 -1
  25. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CN86Zu3Q.js → flowDiagram-PKNHOUZH-DEz5g2Ye.js} +1 -1
  26. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-D-1fKFjW.js → ganttDiagram-A5KZAMGK-BSftxDHA.js} +1 -1
  27. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Dtf1A6KL.js → gitGraphDiagram-K3NZZRJ6-Cr3vGf07.js} +1 -1
  28. package/dashboard/dist/assets/{graph-B6H_kXSs.js → graph-D4us8trI.js} +1 -1
  29. package/dashboard/dist/assets/index-AXntWS_w.css +1 -0
  30. package/dashboard/dist/assets/index-CEMjexkj.js +460 -0
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-R9wJj4JF.js → infoDiagram-LFFYTUFH-CH_jVfru.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CJmeR-bX.js → ishikawaDiagram-PHBUUO56-BdKLa5GC.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-FcUhyu8I.js → journeyDiagram-4ABVD52K-C_SMzNGF.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-C8UcTIwW.js → kanban-definition-K7BYSVSG-BeA-egRW.js} +1 -1
  35. package/dashboard/dist/assets/{layout-DzBy6alw.js → layout-B8tDmL4j.js} +1 -1
  36. package/dashboard/dist/assets/{linear-CZJCNOB9.js → linear-CeGJyrHS.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-fMQRe9Gq.js → mermaid.core-DyEs-LPd.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BFwp-LS-.js → mindmap-definition-YRQLILUH-DCxzAr8m.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CQLmPkkd.js → pieDiagram-SKSYHLDU-CEj5dRDi.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DAmi-dmD.js → quadrantDiagram-337W2JSQ-CKfvAEQg.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dcdts4kX.js → requirementDiagram-Z7DCOOCP-CTRqKPtJ.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-By8LRvM0.js → sankeyDiagram-WA2Y5GQK-BlYbz8UR.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BsvqgtTz.js → sequenceDiagram-2WXFIKYE-PT2t7ryQ.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DFNOD7cx.js → stateDiagram-RAJIS63D-eDX7IUuV.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO--yuSBnLh.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-CMcgJGjn.js → timeline-definition-YZTLITO2-By11B1Ow.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-BWsRNHwq.js → treemap-KZPCXAKY-rvdLeWWV.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-io7-2Tod.js → vennDiagram-LZ73GAT5-Br_oZ1wv.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-AVnh4fDS.js → xychartDiagram-JWTSCODW-D-MWVqrT.js} +1 -1
  50. package/dashboard/dist/index.html +2 -2
  51. package/dist/dashboard/server.js +624 -139
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +658 -124
  54. package/dist/index.js.map +1 -1
  55. package/package.json +1 -1
  56. package/dashboard/dist/assets/channel-PMR2DuGi.js +0 -1
  57. package/dashboard/dist/assets/classDiagram-VBA2DB6C-DmESf_RL.js +0 -1
  58. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-DmESf_RL.js +0 -1
  59. package/dashboard/dist/assets/clone-WlIeyha4.js +0 -1
  60. package/dashboard/dist/assets/index-BhuXD-Q5.js +0 -445
  61. package/dashboard/dist/assets/index-BnqH-RIk.css +0 -1
  62. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DVO-Epiz.js +0 -1
@@ -389,6 +389,15 @@ function syntaurRoot() {
389
389
  function defaultMissionDir() {
390
390
  return resolve2(syntaurRoot(), "missions");
391
391
  }
392
+ function serversDir() {
393
+ return resolve2(syntaurRoot(), "servers");
394
+ }
395
+ function playbooksDir() {
396
+ return resolve2(syntaurRoot(), "playbooks");
397
+ }
398
+ function todosDir() {
399
+ return resolve2(syntaurRoot(), "todos");
400
+ }
392
401
  var init_paths = __esm({
393
402
  "src/utils/paths.ts"() {
394
403
  "use strict";
@@ -396,6 +405,27 @@ var init_paths = __esm({
396
405
  });
397
406
 
398
407
  // src/templates/config.ts
408
+ function renderConfig(params) {
409
+ return `---
410
+ version: "1.0"
411
+ defaultMissionDir: ${params.defaultMissionDir}
412
+ onboarding:
413
+ completed: false
414
+ agentDefaults:
415
+ trustLevel: medium
416
+ autoApprove: false
417
+ backup:
418
+ repo: null
419
+ categories: missions, playbooks, todos, servers, config
420
+ lastBackup: null
421
+ lastRestore: null
422
+ ---
423
+
424
+ # Syntaur Configuration
425
+
426
+ Global configuration for the Syntaur CLI.
427
+ `;
428
+ }
399
429
  var init_config = __esm({
400
430
  "src/templates/config.ts"() {
401
431
  "use strict";
@@ -549,6 +579,14 @@ function serializeStatusConfig(statuses) {
549
579
  }
550
580
  return lines.join("\n");
551
581
  }
582
+ function serializeBackupConfig(backup) {
583
+ const lines = ["backup:"];
584
+ lines.push(` repo: ${backup.repo ?? "null"}`);
585
+ lines.push(` categories: ${backup.categories}`);
586
+ lines.push(` lastBackup: ${backup.lastBackup ?? "null"}`);
587
+ lines.push(` lastRestore: ${backup.lastRestore ?? "null"}`);
588
+ return lines.join("\n");
589
+ }
552
590
  function stripTopLevelBlock(fmBlock, key) {
553
591
  const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
554
592
  if (!blockStart) {
@@ -653,6 +691,40 @@ ${cleanedFm}
653
691
  ---${afterFrontmatter}`;
654
692
  await writeFileForce(configPath, newContent);
655
693
  }
694
+ async function updateBackupConfig(backup) {
695
+ const configPath = resolve3(syntaurRoot(), "config.md");
696
+ const current = (await readConfig()).backup;
697
+ const nextBackup = {
698
+ repo: current?.repo ?? null,
699
+ categories: current?.categories ?? "missions, playbooks, todos, servers, config",
700
+ lastBackup: current?.lastBackup ?? null,
701
+ lastRestore: current?.lastRestore ?? null,
702
+ ...backup
703
+ };
704
+ const backupBlock = serializeBackupConfig(nextBackup);
705
+ const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultMissionDir: defaultMissionDir() });
706
+ const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
707
+ if (!fmMatch) {
708
+ const content = `---
709
+ version: "1.0"
710
+ defaultMissionDir: ${defaultMissionDir()}
711
+ ${backupBlock}
712
+ ---
713
+ ${existing}`;
714
+ await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
715
+ return;
716
+ }
717
+ const fmBlock = fmMatch[2];
718
+ const afterFrontmatter = existing.slice(fmMatch[0].length);
719
+ const cleanedFm = stripTopLevelBlock(fmBlock, "backup");
720
+ const newFm = `${cleanedFm}
721
+ ${backupBlock}`.replace(/^\n+/, "");
722
+ const normalizedFm = newFm.replace(/\n+$/, "");
723
+ const newContent = `---
724
+ ${normalizedFm}
725
+ ---${afterFrontmatter}`;
726
+ await writeFileForce(configPath, newContent);
727
+ }
656
728
  async function readConfig() {
657
729
  const configPath = resolve3(syntaurRoot(), "config.md");
658
730
  if (!await fileExists(configPath)) {
@@ -695,6 +767,12 @@ async function readConfig() {
695
767
  "integrations.codexMarketplacePath"
696
768
  )
697
769
  },
770
+ backup: fm["backup.repo"] || fm["backup.categories"] ? {
771
+ repo: fm["backup.repo"] && fm["backup.repo"] !== "null" ? fm["backup.repo"] : null,
772
+ categories: fm["backup.categories"] || "missions, playbooks, todos, servers, config",
773
+ lastBackup: fm["backup.lastBackup"] && fm["backup.lastBackup"] !== "null" ? fm["backup.lastBackup"] : null,
774
+ lastRestore: fm["backup.lastRestore"] && fm["backup.lastRestore"] !== "null" ? fm["backup.lastRestore"] : null
775
+ } : null,
698
776
  statuses: parseStatusConfig(content)
699
777
  };
700
778
  }
@@ -720,6 +798,7 @@ var init_config2 = __esm({
720
798
  codexPluginDir: null,
721
799
  codexMarketplacePath: null
722
800
  },
801
+ backup: null,
723
802
  statuses: null
724
803
  };
725
804
  }
@@ -1863,17 +1942,17 @@ async function scanProcessSession(sessionData, lsofOutput, workspaceRecords) {
1863
1942
  windows: [{ index: 0, name: "process", panes: [pane] }]
1864
1943
  };
1865
1944
  }
1866
- async function scanAllSessions(serversDir, missionsDir, options) {
1945
+ async function scanAllSessions(serversDir2, missionsDir, options) {
1867
1946
  if (!options?.bypassCache && cache && Date.now() < cache.expiry) {
1868
1947
  return cache.data;
1869
1948
  }
1870
1949
  const tmuxAvailable = await checkTmuxAvailable();
1871
- const names = await listSessionFiles(serversDir);
1950
+ const names = await listSessionFiles(serversDir2);
1872
1951
  const lsofOutput = await getLsofOutput();
1873
1952
  const workspaceRecords = await loadWorkspaceRecords(missionsDir);
1874
1953
  const sessions = [];
1875
1954
  for (const name of names) {
1876
- const data = await readSessionFile(serversDir, name);
1955
+ const data = await readSessionFile(serversDir2, name);
1877
1956
  if (!data) continue;
1878
1957
  if (data.kind === "process") {
1879
1958
  sessions.push(await scanProcessSession(data, lsofOutput, workspaceRecords));
@@ -1885,8 +1964,8 @@ async function scanAllSessions(serversDir, missionsDir, options) {
1885
1964
  cache = { data: result, expiry: Date.now() + CACHE_TTL_MS };
1886
1965
  return result;
1887
1966
  }
1888
- async function scanSingleSession(serversDir, missionsDir, name) {
1889
- const data = await readSessionFile(serversDir, name);
1967
+ async function scanSingleSession(serversDir2, missionsDir, name) {
1968
+ const data = await readSessionFile(serversDir2, name);
1890
1969
  if (!data) return null;
1891
1970
  const lsofOutput = await getLsofOutput();
1892
1971
  const workspaceRecords = await loadWorkspaceRecords(missionsDir);
@@ -2014,15 +2093,15 @@ async function deleteWorkspace(missionsDir, name) {
2014
2093
  const filtered = registered.filter((w) => w !== name);
2015
2094
  await writeWorkspaceRegistry(missionsDir, filtered);
2016
2095
  }
2017
- async function getOverview(missionsDir, serversDir) {
2096
+ async function getOverview(missionsDir, serversDir2) {
2018
2097
  const missionRecords = await listMissionRecords(missionsDir);
2019
2098
  const attention = buildAttentionItems(missionRecords);
2020
2099
  const recentActivity = buildRecentActivity(missionRecords);
2021
2100
  let serverStats;
2022
- if (serversDir) {
2101
+ if (serversDir2) {
2023
2102
  try {
2024
2103
  const { scanAllSessions: scanAllSessions2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
2025
- const servers = await scanAllSessions2(serversDir, missionsDir);
2104
+ const servers = await scanAllSessions2(serversDir2, missionsDir);
2026
2105
  if (servers.tmuxAvailable) {
2027
2106
  const alive = servers.sessions.filter((s) => s.alive).length;
2028
2107
  const totalPorts = servers.sessions.reduce((sum, s) => sum + s.windows.reduce((ws, w) => ws + w.panes.reduce((ps, p) => ps + p.ports.length, 0), 0), 0);
@@ -2068,13 +2147,13 @@ async function getOverview(missionsDir, serversDir) {
2068
2147
  serverStats
2069
2148
  };
2070
2149
  }
2071
- async function getAttention(missionsDir, serversDir) {
2150
+ async function getAttention(missionsDir, serversDir2) {
2072
2151
  const missionRecords = await listMissionRecords(missionsDir);
2073
2152
  const items = buildAttentionItems(missionRecords);
2074
- if (serversDir) {
2153
+ if (serversDir2) {
2075
2154
  try {
2076
2155
  const { scanAllSessions: scanAllSessions2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
2077
- const servers = await scanAllSessions2(serversDir, missionsDir);
2156
+ const servers = await scanAllSessions2(serversDir2, missionsDir);
2078
2157
  for (const session of servers.sessions) {
2079
2158
  if (!session.alive) {
2080
2159
  items.push({
@@ -2745,13 +2824,13 @@ function getEditableDocumentTitle(documentType, missionSlug, assignmentSlug) {
2745
2824
  return missionSlug;
2746
2825
  }
2747
2826
  }
2748
- async function listPlaybooks(playbooksDir) {
2749
- if (!await fileExists(playbooksDir)) return [];
2750
- const entries = await readdir3(playbooksDir, { withFileTypes: true });
2827
+ async function listPlaybooks(playbooksDir2) {
2828
+ if (!await fileExists(playbooksDir2)) return [];
2829
+ const entries = await readdir3(playbooksDir2, { withFileTypes: true });
2751
2830
  const playbooks = [];
2752
2831
  for (const entry of entries) {
2753
2832
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
2754
- const filePath = resolve6(playbooksDir, entry.name);
2833
+ const filePath = resolve6(playbooksDir2, entry.name);
2755
2834
  const raw = await readFile5(filePath, "utf-8");
2756
2835
  const parsed = parsePlaybook(raw);
2757
2836
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
@@ -2767,8 +2846,8 @@ async function listPlaybooks(playbooksDir) {
2767
2846
  }
2768
2847
  return playbooks.sort((a, b) => (b.updated || b.created).localeCompare(a.updated || a.created));
2769
2848
  }
2770
- async function getPlaybookDetail(playbooksDir, slug) {
2771
- const filePath = resolve6(playbooksDir, `${slug}.md`);
2849
+ async function getPlaybookDetail(playbooksDir2, slug) {
2850
+ const filePath = resolve6(playbooksDir2, `${slug}.md`);
2772
2851
  if (!await fileExists(filePath)) return null;
2773
2852
  const raw = await readFile5(filePath, "utf-8");
2774
2853
  const parsed = parsePlaybook(raw);
@@ -3042,13 +3121,13 @@ function serializeLogEntry(entry) {
3042
3121
  if (entry.status) lines.push(`**Status:** ${entry.status}`);
3043
3122
  return lines.join("\n");
3044
3123
  }
3045
- function checklistPath(todosDir, workspace) {
3046
- return resolve13(todosDir, `${workspace}.md`);
3124
+ function checklistPath(todosDir2, workspace) {
3125
+ return resolve13(todosDir2, `${workspace}.md`);
3047
3126
  }
3048
- function logPath(todosDir, workspace) {
3049
- return resolve13(todosDir, `${workspace}-log.md`);
3127
+ function logPath(todosDir2, workspace) {
3128
+ return resolve13(todosDir2, `${workspace}-log.md`);
3050
3129
  }
3051
- function archivePath(todosDir, workspace, interval, now = /* @__PURE__ */ new Date()) {
3130
+ function archivePath(todosDir2, workspace, interval, now = /* @__PURE__ */ new Date()) {
3052
3131
  const year = now.getFullYear();
3053
3132
  const month = String(now.getMonth() + 1).padStart(2, "0");
3054
3133
  const day = String(now.getDate()).padStart(2, "0");
@@ -3070,32 +3149,32 @@ function archivePath(todosDir, workspace, interval, now = /* @__PURE__ */ new Da
3070
3149
  default:
3071
3150
  suffix = `${year}-${month}-${day}`;
3072
3151
  }
3073
- return resolve13(todosDir, "archive", `${workspace}-${suffix}.md`);
3152
+ return resolve13(todosDir2, "archive", `${workspace}-${suffix}.md`);
3074
3153
  }
3075
- async function readChecklist(todosDir, workspace) {
3076
- const path = checklistPath(todosDir, workspace);
3154
+ async function readChecklist(todosDir2, workspace) {
3155
+ const path = checklistPath(todosDir2, workspace);
3077
3156
  if (!await fileExists(path)) {
3078
3157
  return { workspace, archiveInterval: "weekly", items: [] };
3079
3158
  }
3080
3159
  const content = await readFile10(path, "utf-8");
3081
3160
  return parseChecklist(content);
3082
3161
  }
3083
- async function writeChecklist(todosDir, checklist) {
3084
- await ensureDir(todosDir);
3085
- const path = checklistPath(todosDir, checklist.workspace);
3162
+ async function writeChecklist(todosDir2, checklist) {
3163
+ await ensureDir(todosDir2);
3164
+ const path = checklistPath(todosDir2, checklist.workspace);
3086
3165
  await writeFileForce(path, serializeChecklist(checklist));
3087
3166
  }
3088
- async function readLog(todosDir, workspace) {
3089
- const path = logPath(todosDir, workspace);
3167
+ async function readLog(todosDir2, workspace) {
3168
+ const path = logPath(todosDir2, workspace);
3090
3169
  if (!await fileExists(path)) {
3091
3170
  return { workspace, entries: [] };
3092
3171
  }
3093
3172
  const content = await readFile10(path, "utf-8");
3094
3173
  return parseLog(content);
3095
3174
  }
3096
- async function appendLogEntry2(todosDir, workspace, entry) {
3097
- await ensureDir(todosDir);
3098
- const path = logPath(todosDir, workspace);
3175
+ async function appendLogEntry2(todosDir2, workspace, entry) {
3176
+ await ensureDir(todosDir2);
3177
+ const path = logPath(todosDir2, workspace);
3099
3178
  let content;
3100
3179
  if (await fileExists(path)) {
3101
3180
  content = await readFile10(path, "utf-8");
@@ -3135,16 +3214,16 @@ var init_parser2 = __esm({
3135
3214
  init_api();
3136
3215
  import express from "express";
3137
3216
  import { createServer } from "http";
3138
- import { resolve as resolve14 } from "path";
3217
+ import { resolve as resolve15 } from "path";
3139
3218
  import { homedir as homedir2 } from "os";
3140
- import { writeFile as writeFile3, unlink as unlink3 } from "fs/promises";
3219
+ import { writeFile as writeFile4, unlink as unlink4 } from "fs/promises";
3141
3220
  import { WebSocketServer, WebSocket } from "ws";
3142
3221
 
3143
3222
  // src/dashboard/watcher.ts
3144
3223
  import { watch } from "chokidar";
3145
3224
  import { relative, sep } from "path";
3146
3225
  function createWatcher(options) {
3147
- const { missionsDir, serversDir, playbooksDir, todosDir, onMessage, debounceMs = 300 } = options;
3226
+ const { missionsDir, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, onMessage, debounceMs = 300 } = options;
3148
3227
  const pendingEvents = /* @__PURE__ */ new Map();
3149
3228
  const missionsWatcher = watch(missionsDir, {
3150
3229
  ignoreInitial: true,
@@ -3183,7 +3262,7 @@ function createWatcher(options) {
3183
3262
  missionsWatcher.on("add", handleMissionChange);
3184
3263
  missionsWatcher.on("unlink", handleMissionChange);
3185
3264
  let serversWatcher = null;
3186
- if (serversDir) {
3265
+ if (serversDir2) {
3187
3266
  let handleServerChange2 = function() {
3188
3267
  const debounceKey = "__servers__";
3189
3268
  const existing = pendingEvents.get(debounceKey);
@@ -3201,7 +3280,7 @@ function createWatcher(options) {
3201
3280
  );
3202
3281
  };
3203
3282
  var handleServerChange = handleServerChange2;
3204
- serversWatcher = watch(serversDir, {
3283
+ serversWatcher = watch(serversDir2, {
3205
3284
  ignoreInitial: true,
3206
3285
  persistent: true,
3207
3286
  depth: 1,
@@ -3212,7 +3291,7 @@ function createWatcher(options) {
3212
3291
  serversWatcher.on("unlink", handleServerChange2);
3213
3292
  }
3214
3293
  let playbooksWatcher = null;
3215
- if (playbooksDir) {
3294
+ if (playbooksDir2) {
3216
3295
  let handlePlaybookChange2 = function() {
3217
3296
  const debounceKey = "__playbooks__";
3218
3297
  const existing = pendingEvents.get(debounceKey);
@@ -3230,7 +3309,7 @@ function createWatcher(options) {
3230
3309
  );
3231
3310
  };
3232
3311
  var handlePlaybookChange = handlePlaybookChange2;
3233
- playbooksWatcher = watch(playbooksDir, {
3312
+ playbooksWatcher = watch(playbooksDir2, {
3234
3313
  ignoreInitial: true,
3235
3314
  persistent: true,
3236
3315
  depth: 1,
@@ -3241,7 +3320,7 @@ function createWatcher(options) {
3241
3320
  playbooksWatcher.on("unlink", handlePlaybookChange2);
3242
3321
  }
3243
3322
  let todosWatcher = null;
3244
- if (todosDir) {
3323
+ if (todosDir2) {
3245
3324
  let handleTodoChange2 = function() {
3246
3325
  const debounceKey = "__todos__";
3247
3326
  const existing = pendingEvents.get(debounceKey);
@@ -3259,7 +3338,7 @@ function createWatcher(options) {
3259
3338
  );
3260
3339
  };
3261
3340
  var handleTodoChange = handleTodoChange2;
3262
- todosWatcher = watch(todosDir, {
3341
+ todosWatcher = watch(todosDir2, {
3263
3342
  ignoreInitial: true,
3264
3343
  persistent: true,
3265
3344
  depth: 1,
@@ -4470,11 +4549,11 @@ function createWriteRouter(missionsDir) {
4470
4549
  init_servers();
4471
4550
  init_scanner();
4472
4551
  import { Router as Router2 } from "express";
4473
- function createServersRouter(serversDir, missionsDir) {
4552
+ function createServersRouter(serversDir2, missionsDir) {
4474
4553
  const router = Router2();
4475
4554
  router.get("/", async (_req, res) => {
4476
4555
  try {
4477
- const result = await scanAllSessions(serversDir, missionsDir);
4556
+ const result = await scanAllSessions(serversDir2, missionsDir);
4478
4557
  res.json(result);
4479
4558
  } catch (error) {
4480
4559
  res.status(500).json({ error: error instanceof Error ? error.message : "Scan failed" });
@@ -4482,7 +4561,7 @@ function createServersRouter(serversDir, missionsDir) {
4482
4561
  });
4483
4562
  router.get("/:name", async (req, res) => {
4484
4563
  try {
4485
- const session = await scanSingleSession(serversDir, missionsDir, req.params.name);
4564
+ const session = await scanSingleSession(serversDir2, missionsDir, req.params.name);
4486
4565
  if (!session) {
4487
4566
  res.status(404).json({ error: "Session not found" });
4488
4567
  return;
@@ -4500,12 +4579,12 @@ function createServersRouter(serversDir, missionsDir) {
4500
4579
  return;
4501
4580
  }
4502
4581
  const sanitized = sanitizeSessionName(name);
4503
- const existing = await readSessionFile(serversDir, sanitized);
4582
+ const existing = await readSessionFile(serversDir2, sanitized);
4504
4583
  if (existing) {
4505
4584
  res.status(409).json({ error: `Session "${sanitized}" already registered` });
4506
4585
  return;
4507
4586
  }
4508
- await registerSession(serversDir, name);
4587
+ await registerSession(serversDir2, name);
4509
4588
  clearScanCache();
4510
4589
  res.status(201).json({ name: sanitized });
4511
4590
  } catch (error) {
@@ -4514,12 +4593,12 @@ function createServersRouter(serversDir, missionsDir) {
4514
4593
  });
4515
4594
  router.delete("/:name", async (req, res) => {
4516
4595
  try {
4517
- const data = await readSessionFile(serversDir, req.params.name);
4596
+ const data = await readSessionFile(serversDir2, req.params.name);
4518
4597
  if (!data) {
4519
4598
  res.status(404).json({ error: "Session not found" });
4520
4599
  return;
4521
4600
  }
4522
- await removeSession(serversDir, req.params.name);
4601
+ await removeSession(serversDir2, req.params.name);
4523
4602
  clearScanCache();
4524
4603
  res.json({ removed: req.params.name });
4525
4604
  } catch (error) {
@@ -4528,12 +4607,12 @@ function createServersRouter(serversDir, missionsDir) {
4528
4607
  });
4529
4608
  router.post("/refresh", async (_req, res) => {
4530
4609
  try {
4531
- const names = await listSessionFiles(serversDir);
4610
+ const names = await listSessionFiles(serversDir2);
4532
4611
  for (const name of names) {
4533
- await updateLastRefreshed(serversDir, name);
4612
+ await updateLastRefreshed(serversDir2, name);
4534
4613
  }
4535
4614
  clearScanCache();
4536
- const result = await scanAllSessions(serversDir, missionsDir, { bypassCache: true });
4615
+ const result = await scanAllSessions(serversDir2, missionsDir, { bypassCache: true });
4537
4616
  res.json(result);
4538
4617
  } catch (error) {
4539
4618
  res.status(500).json({ error: error instanceof Error ? error.message : "Refresh failed" });
@@ -4541,14 +4620,14 @@ function createServersRouter(serversDir, missionsDir) {
4541
4620
  });
4542
4621
  router.post("/:name/refresh", async (req, res) => {
4543
4622
  try {
4544
- const data = await readSessionFile(serversDir, req.params.name);
4623
+ const data = await readSessionFile(serversDir2, req.params.name);
4545
4624
  if (!data) {
4546
4625
  res.status(404).json({ error: "Session not found" });
4547
4626
  return;
4548
4627
  }
4549
- await updateLastRefreshed(serversDir, req.params.name);
4628
+ await updateLastRefreshed(serversDir2, req.params.name);
4550
4629
  clearScanCache();
4551
- const session = await scanSingleSession(serversDir, missionsDir, req.params.name);
4630
+ const session = await scanSingleSession(serversDir2, missionsDir, req.params.name);
4552
4631
  res.json(session);
4553
4632
  } catch (error) {
4554
4633
  res.status(500).json({ error: error instanceof Error ? error.message : "Refresh failed" });
@@ -4557,7 +4636,7 @@ function createServersRouter(serversDir, missionsDir) {
4557
4636
  router.patch("/:name/panes/:windowIndex/:paneIndex/assignment", async (req, res) => {
4558
4637
  try {
4559
4638
  const { name, windowIndex, paneIndex } = req.params;
4560
- const data = await readSessionFile(serversDir, name);
4639
+ const data = await readSessionFile(serversDir2, name);
4561
4640
  if (!data) {
4562
4641
  res.status(404).json({ error: "Session not found" });
4563
4642
  return;
@@ -4565,7 +4644,7 @@ function createServersRouter(serversDir, missionsDir) {
4565
4644
  const body = req.body;
4566
4645
  if (body === null || body && body.mission && body.assignment) {
4567
4646
  await setOverride(
4568
- serversDir,
4647
+ serversDir2,
4569
4648
  name,
4570
4649
  parseInt(windowIndex, 10),
4571
4650
  parseInt(paneIndex, 10),
@@ -4701,8 +4780,8 @@ async function migrateFromMarkdown(missionsDir) {
4701
4780
  return allSessions.length;
4702
4781
  }
4703
4782
  async function parseMarkdownSessionsIndex(filePath, missionSlug) {
4704
- const { readFile: readFile11 } = await import("fs/promises");
4705
- const raw = await readFile11(filePath, "utf-8");
4783
+ const { readFile: readFile12 } = await import("fs/promises");
4784
+ const raw = await readFile12(filePath, "utf-8");
4706
4785
  const sessions = [];
4707
4786
  const lines = raw.split("\n");
4708
4787
  let inTable = false;
@@ -4954,13 +5033,13 @@ init_parser();
4954
5033
  init_timestamp();
4955
5034
  import { resolve as resolve11 } from "path";
4956
5035
  import { readdir as readdir5, readFile as readFile8 } from "fs/promises";
4957
- async function rebuildPlaybookManifest(playbooksDir) {
4958
- if (!await fileExists(playbooksDir)) return;
4959
- const entries = await readdir5(playbooksDir, { withFileTypes: true });
5036
+ async function rebuildPlaybookManifest(playbooksDir2) {
5037
+ if (!await fileExists(playbooksDir2)) return;
5038
+ const entries = await readdir5(playbooksDir2, { withFileTypes: true });
4960
5039
  const rows = [];
4961
5040
  for (const entry of entries) {
4962
5041
  if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name.startsWith("_") || entry.name === "manifest.md") continue;
4963
- const raw = await readFile8(resolve11(playbooksDir, entry.name), "utf-8");
5042
+ const raw = await readFile8(resolve11(playbooksDir2, entry.name), "utf-8");
4964
5043
  const parsed = parsePlaybook(raw);
4965
5044
  const slug = parsed.slug || entry.name.replace(/\.md$/, "");
4966
5045
  rows.push({
@@ -4990,15 +5069,15 @@ async function rebuildPlaybookManifest(playbooksDir) {
4990
5069
  }
4991
5070
  }
4992
5071
  lines.push("");
4993
- await writeFileForce(resolve11(playbooksDir, "manifest.md"), lines.join("\n"));
5072
+ await writeFileForce(resolve11(playbooksDir2, "manifest.md"), lines.join("\n"));
4994
5073
  }
4995
5074
 
4996
5075
  // src/dashboard/api-playbooks.ts
4997
- function createPlaybooksRouter(playbooksDir) {
5076
+ function createPlaybooksRouter(playbooksDir2) {
4998
5077
  const router = Router4();
4999
5078
  router.get("/", async (_req, res) => {
5000
5079
  try {
5001
- const playbooks = await listPlaybooks(playbooksDir);
5080
+ const playbooks = await listPlaybooks(playbooksDir2);
5002
5081
  res.json({ playbooks, generatedAt: (/* @__PURE__ */ new Date()).toISOString() });
5003
5082
  } catch (error) {
5004
5083
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to list playbooks" });
@@ -5019,7 +5098,7 @@ function createPlaybooksRouter(playbooksDir) {
5019
5098
  });
5020
5099
  router.get("/:slug", async (req, res) => {
5021
5100
  try {
5022
- const detail = await getPlaybookDetail(playbooksDir, req.params.slug);
5101
+ const detail = await getPlaybookDetail(playbooksDir2, req.params.slug);
5023
5102
  if (!detail) {
5024
5103
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5025
5104
  return;
@@ -5031,7 +5110,7 @@ function createPlaybooksRouter(playbooksDir) {
5031
5110
  });
5032
5111
  router.get("/:slug/edit", async (req, res) => {
5033
5112
  try {
5034
- const filePath = resolve12(playbooksDir, `${req.params.slug}.md`);
5113
+ const filePath = resolve12(playbooksDir2, `${req.params.slug}.md`);
5035
5114
  if (!await fileExists(filePath)) {
5036
5115
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5037
5116
  return;
@@ -5060,14 +5139,14 @@ function createPlaybooksRouter(playbooksDir) {
5060
5139
  res.status(400).json({ error: `Invalid or missing slug: "${slug}"` });
5061
5140
  return;
5062
5141
  }
5063
- await ensureDir(playbooksDir);
5064
- const filePath = resolve12(playbooksDir, `${slug}.md`);
5142
+ await ensureDir(playbooksDir2);
5143
+ const filePath = resolve12(playbooksDir2, `${slug}.md`);
5065
5144
  if (await fileExists(filePath)) {
5066
5145
  res.status(409).json({ error: `Playbook "${slug}" already exists` });
5067
5146
  return;
5068
5147
  }
5069
5148
  await writeFileForce(filePath, content);
5070
- await rebuildPlaybookManifest(playbooksDir);
5149
+ await rebuildPlaybookManifest(playbooksDir2);
5071
5150
  res.status(201).json({ slug, path: filePath });
5072
5151
  } catch (error) {
5073
5152
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to create playbook" });
@@ -5080,13 +5159,13 @@ function createPlaybooksRouter(playbooksDir) {
5080
5159
  res.status(400).json({ error: "content is required" });
5081
5160
  return;
5082
5161
  }
5083
- const filePath = resolve12(playbooksDir, `${req.params.slug}.md`);
5162
+ const filePath = resolve12(playbooksDir2, `${req.params.slug}.md`);
5084
5163
  if (!await fileExists(filePath)) {
5085
5164
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5086
5165
  return;
5087
5166
  }
5088
5167
  await writeFileForce(filePath, content);
5089
- await rebuildPlaybookManifest(playbooksDir);
5168
+ await rebuildPlaybookManifest(playbooksDir2);
5090
5169
  res.json({ slug: req.params.slug, path: filePath });
5091
5170
  } catch (error) {
5092
5171
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to update playbook" });
@@ -5098,13 +5177,13 @@ function createPlaybooksRouter(playbooksDir) {
5098
5177
  res.status(403).json({ error: "The playbook manifest cannot be deleted" });
5099
5178
  return;
5100
5179
  }
5101
- const filePath = resolve12(playbooksDir, `${req.params.slug}.md`);
5180
+ const filePath = resolve12(playbooksDir2, `${req.params.slug}.md`);
5102
5181
  if (!await fileExists(filePath)) {
5103
5182
  res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
5104
5183
  return;
5105
5184
  }
5106
5185
  await unlink2(filePath);
5107
- await rebuildPlaybookManifest(playbooksDir);
5186
+ await rebuildPlaybookManifest(playbooksDir2);
5108
5187
  res.json({ deleted: req.params.slug });
5109
5188
  } catch (error) {
5110
5189
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to delete playbook" });
@@ -5134,7 +5213,7 @@ function withLock(workspace, fn) {
5134
5213
  }));
5135
5214
  return next;
5136
5215
  }
5137
- function createTodosRouter(todosDir, broadcast) {
5216
+ function createTodosRouter(todosDir2, broadcast) {
5138
5217
  const router = Router5();
5139
5218
  function broadcastUpdate() {
5140
5219
  broadcast({ type: "todos-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -5150,14 +5229,14 @@ function createTodosRouter(todosDir, broadcast) {
5150
5229
  router.param("workspace", validateWorkspace);
5151
5230
  router.get("/", async (_req, res) => {
5152
5231
  try {
5153
- await ensureDir(todosDir);
5154
- const files = await readdir6(todosDir).catch(() => []);
5232
+ await ensureDir(todosDir2);
5233
+ const files = await readdir6(todosDir2).catch(() => []);
5155
5234
  const workspaces = [];
5156
5235
  for (const file of files) {
5157
5236
  if (typeof file !== "string") continue;
5158
5237
  if (!file.endsWith(".md") || file.endsWith("-log.md")) continue;
5159
5238
  const workspace = file.replace(".md", "");
5160
- const checklist = await readChecklist(todosDir, workspace);
5239
+ const checklist = await readChecklist(todosDir2, workspace);
5161
5240
  workspaces.push({
5162
5241
  workspace: checklist.workspace,
5163
5242
  archiveInterval: checklist.archiveInterval,
@@ -5173,7 +5252,7 @@ function createTodosRouter(todosDir, broadcast) {
5173
5252
  router.get("/:workspace", async (req, res) => {
5174
5253
  try {
5175
5254
  const workspace = getWorkspaceParam(req.params.workspace);
5176
- const checklist = await readChecklist(todosDir, workspace);
5255
+ const checklist = await readChecklist(todosDir2, workspace);
5177
5256
  res.json({
5178
5257
  workspace: checklist.workspace,
5179
5258
  archiveInterval: checklist.archiveInterval,
@@ -5193,7 +5272,7 @@ function createTodosRouter(todosDir, broadcast) {
5193
5272
  return;
5194
5273
  }
5195
5274
  const item = await withLock(workspace, async () => {
5196
- const checklist = await readChecklist(todosDir, workspace);
5275
+ const checklist = await readChecklist(todosDir2, workspace);
5197
5276
  const existingIds = new Set(checklist.items.map((i) => i.id));
5198
5277
  const id = generateUniqueId(existingIds);
5199
5278
  const newItem = {
@@ -5204,7 +5283,7 @@ function createTodosRouter(todosDir, broadcast) {
5204
5283
  session: null
5205
5284
  };
5206
5285
  checklist.items.push(newItem);
5207
- await writeChecklist(todosDir, checklist);
5286
+ await writeChecklist(todosDir2, checklist);
5208
5287
  return newItem;
5209
5288
  });
5210
5289
  broadcastUpdate();
@@ -5222,7 +5301,7 @@ function createTodosRouter(todosDir, broadcast) {
5222
5301
  return;
5223
5302
  }
5224
5303
  const items = await withLock(workspace, async () => {
5225
- const checklist = await readChecklist(todosDir, workspace);
5304
+ const checklist = await readChecklist(todosDir2, workspace);
5226
5305
  const itemMap = new Map(checklist.items.map((i) => [i.id, i]));
5227
5306
  const reordered = [];
5228
5307
  for (const id of ids) {
@@ -5236,7 +5315,7 @@ function createTodosRouter(todosDir, broadcast) {
5236
5315
  reordered.push(item);
5237
5316
  }
5238
5317
  checklist.items = reordered;
5239
- await writeChecklist(todosDir, checklist);
5318
+ await writeChecklist(todosDir2, checklist);
5240
5319
  return reordered;
5241
5320
  });
5242
5321
  broadcastUpdate();
@@ -5247,7 +5326,7 @@ function createTodosRouter(todosDir, broadcast) {
5247
5326
  });
5248
5327
  router.get("/:workspace/log", async (req, res) => {
5249
5328
  try {
5250
- const log = await readLog(todosDir, getWorkspaceParam(req.params.workspace));
5329
+ const log = await readLog(todosDir2, getWorkspaceParam(req.params.workspace));
5251
5330
  res.json(log);
5252
5331
  } catch (error) {
5253
5332
  res.status(500).json({ error: error instanceof Error ? error.message : "Failed to get log" });
@@ -5256,12 +5335,12 @@ function createTodosRouter(todosDir, broadcast) {
5256
5335
  router.post("/:workspace/archive", async (req, res) => {
5257
5336
  try {
5258
5337
  const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
5259
- const { resolve: resolve15 } = await import("path");
5260
- const { readFile: readFile11 } = await import("fs/promises");
5338
+ const { resolve: resolve16 } = await import("path");
5339
+ const { readFile: readFile12 } = await import("fs/promises");
5261
5340
  const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
5262
5341
  const workspace = getWorkspaceParam(req.params.workspace);
5263
- const checklist = await readChecklist(todosDir, workspace);
5264
- const log = await readLog(todosDir, workspace);
5342
+ const checklist = await readChecklist(todosDir2, workspace);
5343
+ const log = await readLog(todosDir2, workspace);
5265
5344
  const completedIds = new Set(
5266
5345
  checklist.items.filter((i) => i.status === "completed").map((i) => i.id)
5267
5346
  );
@@ -5272,11 +5351,11 @@ function createTodosRouter(todosDir, broadcast) {
5272
5351
  const toArchive = log.entries.filter(
5273
5352
  (e) => e.itemIds.every((id) => completedIds.has(id))
5274
5353
  );
5275
- const archFile = archivePath2(todosDir, workspace, checklist.archiveInterval);
5276
- await ensureDir(resolve15(todosDir, "archive"));
5354
+ const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
5355
+ await ensureDir(resolve16(todosDir2, "archive"));
5277
5356
  let archContent = "";
5278
5357
  if (await fileExists(archFile)) {
5279
- archContent = await readFile11(archFile, "utf-8");
5358
+ archContent = await readFile12(archFile, "utf-8");
5280
5359
  archContent = archContent.trimEnd() + "\n\n";
5281
5360
  } else {
5282
5361
  archContent = `---
@@ -5310,7 +5389,7 @@ workspace: ${workspace}
5310
5389
  }
5311
5390
  await writeFileForce2(archFile, archContent);
5312
5391
  checklist.items = checklist.items.filter((i) => !completedIds.has(i.id));
5313
- await writeChecklist(todosDir, checklist);
5392
+ await writeChecklist(todosDir2, checklist);
5314
5393
  broadcastUpdate();
5315
5394
  res.json({ archived: completedIds.size, logEntries: toArchive.length });
5316
5395
  } catch (error) {
@@ -5319,7 +5398,7 @@ workspace: ${workspace}
5319
5398
  });
5320
5399
  router.get("/:workspace/log/:id", async (req, res) => {
5321
5400
  try {
5322
- const log = await readLog(todosDir, getWorkspaceParam(req.params.workspace));
5401
+ const log = await readLog(todosDir2, getWorkspaceParam(req.params.workspace));
5323
5402
  const entries = log.entries.filter((e) => e.itemIds.includes(req.params.id));
5324
5403
  res.json({ workspace: log.workspace, entries });
5325
5404
  } catch (error) {
@@ -5329,13 +5408,13 @@ workspace: ${workspace}
5329
5408
  router.get("/:workspace/:id", async (req, res) => {
5330
5409
  try {
5331
5410
  const workspace = getWorkspaceParam(req.params.workspace);
5332
- const checklist = await readChecklist(todosDir, workspace);
5411
+ const checklist = await readChecklist(todosDir2, workspace);
5333
5412
  const item = checklist.items.find((i) => i.id === req.params.id);
5334
5413
  if (!item) {
5335
5414
  res.status(404).json({ error: `Todo "${req.params.id}" not found` });
5336
5415
  return;
5337
5416
  }
5338
- const log = await readLog(todosDir, workspace);
5417
+ const log = await readLog(todosDir2, workspace);
5339
5418
  const logEntries = log.entries.filter((e) => e.itemIds.includes(req.params.id));
5340
5419
  res.json({ ...item, log: logEntries });
5341
5420
  } catch (error) {
@@ -5346,12 +5425,12 @@ workspace: ${workspace}
5346
5425
  try {
5347
5426
  const workspace = getWorkspaceParam(req.params.workspace);
5348
5427
  const result = await withLock(workspace, async () => {
5349
- const checklist = await readChecklist(todosDir, workspace);
5428
+ const checklist = await readChecklist(todosDir2, workspace);
5350
5429
  const item = checklist.items.find((i) => i.id === req.params.id);
5351
5430
  if (!item) return null;
5352
5431
  if (req.body.description !== void 0) item.description = req.body.description;
5353
5432
  if (Array.isArray(req.body.tags)) item.tags = req.body.tags;
5354
- await writeChecklist(todosDir, checklist);
5433
+ await writeChecklist(todosDir2, checklist);
5355
5434
  return { ...item };
5356
5435
  });
5357
5436
  if (!result) {
@@ -5368,11 +5447,11 @@ workspace: ${workspace}
5368
5447
  try {
5369
5448
  const workspace = getWorkspaceParam(req.params.workspace);
5370
5449
  const deleted = await withLock(workspace, async () => {
5371
- const checklist = await readChecklist(todosDir, workspace);
5450
+ const checklist = await readChecklist(todosDir2, workspace);
5372
5451
  const idx = checklist.items.findIndex((i) => i.id === req.params.id);
5373
5452
  if (idx === -1) return false;
5374
5453
  checklist.items.splice(idx, 1);
5375
- await writeChecklist(todosDir, checklist);
5454
+ await writeChecklist(todosDir2, checklist);
5376
5455
  return true;
5377
5456
  });
5378
5457
  if (!deleted) {
@@ -5389,13 +5468,13 @@ workspace: ${workspace}
5389
5468
  try {
5390
5469
  const workspace = getWorkspaceParam(req.params.workspace);
5391
5470
  const result = await withLock(workspace, async () => {
5392
- const checklist = await readChecklist(todosDir, workspace);
5471
+ const checklist = await readChecklist(todosDir2, workspace);
5393
5472
  const item = checklist.items.find((i) => i.id === req.params.id);
5394
5473
  if (!item) return { error: "not_found" };
5395
5474
  if (item.status === "in_progress") return { error: "conflict", session: item.session };
5396
5475
  item.status = "in_progress";
5397
5476
  item.session = req.body.session || null;
5398
- await writeChecklist(todosDir, checklist);
5477
+ await writeChecklist(todosDir2, checklist);
5399
5478
  return { item: { ...item } };
5400
5479
  });
5401
5480
  if ("error" in result) {
@@ -5416,12 +5495,12 @@ workspace: ${workspace}
5416
5495
  try {
5417
5496
  const workspace = getWorkspaceParam(req.params.workspace);
5418
5497
  const result = await withLock(workspace, async () => {
5419
- const checklist = await readChecklist(todosDir, workspace);
5498
+ const checklist = await readChecklist(todosDir2, workspace);
5420
5499
  const item = checklist.items.find((i) => i.id === req.params.id);
5421
5500
  if (!item) return null;
5422
5501
  item.status = "completed";
5423
5502
  item.session = null;
5424
- await writeChecklist(todosDir, checklist);
5503
+ await writeChecklist(todosDir2, checklist);
5425
5504
  const entry = {
5426
5505
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5427
5506
  itemIds: [item.id],
@@ -5432,7 +5511,7 @@ workspace: ${workspace}
5432
5511
  blockers: null,
5433
5512
  status: null
5434
5513
  };
5435
- await appendLogEntry2(todosDir, workspace, entry);
5514
+ await appendLogEntry2(todosDir2, workspace, entry);
5436
5515
  return { ...item };
5437
5516
  });
5438
5517
  if (!result) {
@@ -5450,12 +5529,12 @@ workspace: ${workspace}
5450
5529
  const reason = req.body.reason || null;
5451
5530
  const workspace = getWorkspaceParam(req.params.workspace);
5452
5531
  const result = await withLock(workspace, async () => {
5453
- const checklist = await readChecklist(todosDir, workspace);
5532
+ const checklist = await readChecklist(todosDir2, workspace);
5454
5533
  const item = checklist.items.find((i) => i.id === req.params.id);
5455
5534
  if (!item) return null;
5456
5535
  item.status = "blocked";
5457
5536
  item.session = null;
5458
- await writeChecklist(todosDir, checklist);
5537
+ await writeChecklist(todosDir2, checklist);
5459
5538
  const entry = {
5460
5539
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5461
5540
  itemIds: [item.id],
@@ -5466,7 +5545,7 @@ workspace: ${workspace}
5466
5545
  blockers: reason,
5467
5546
  status: "blocked"
5468
5547
  };
5469
- await appendLogEntry2(todosDir, workspace, entry);
5548
+ await appendLogEntry2(todosDir2, workspace, entry);
5470
5549
  return { ...item };
5471
5550
  });
5472
5551
  if (!result) {
@@ -5483,12 +5562,12 @@ workspace: ${workspace}
5483
5562
  try {
5484
5563
  const workspace = getWorkspaceParam(req.params.workspace);
5485
5564
  const result = await withLock(workspace, async () => {
5486
- const checklist = await readChecklist(todosDir, workspace);
5565
+ const checklist = await readChecklist(todosDir2, workspace);
5487
5566
  const item = checklist.items.find((i) => i.id === req.params.id);
5488
5567
  if (!item) return null;
5489
5568
  item.status = "open";
5490
5569
  item.session = null;
5491
- await writeChecklist(todosDir, checklist);
5570
+ await writeChecklist(todosDir2, checklist);
5492
5571
  return { ...item };
5493
5572
  });
5494
5573
  if (!result) {
@@ -5505,12 +5584,12 @@ workspace: ${workspace}
5505
5584
  try {
5506
5585
  const workspace = getWorkspaceParam(req.params.workspace);
5507
5586
  const result = await withLock(workspace, async () => {
5508
- const checklist = await readChecklist(todosDir, workspace);
5587
+ const checklist = await readChecklist(todosDir2, workspace);
5509
5588
  const item = checklist.items.find((i) => i.id === req.params.id);
5510
5589
  if (!item) return null;
5511
5590
  item.status = "open";
5512
5591
  item.session = null;
5513
- await writeChecklist(todosDir, checklist);
5592
+ await writeChecklist(todosDir2, checklist);
5514
5593
  return { ...item };
5515
5594
  });
5516
5595
  if (!result) {
@@ -5526,6 +5605,411 @@ workspace: ${workspace}
5526
5605
  return router;
5527
5606
  }
5528
5607
 
5608
+ // src/dashboard/api-backup.ts
5609
+ init_config2();
5610
+ import { Router as Router6 } from "express";
5611
+
5612
+ // src/utils/github-backup.ts
5613
+ init_paths();
5614
+ init_fs();
5615
+ init_config2();
5616
+ import { execFile as execFile2 } from "child_process";
5617
+ import { promisify as promisify2 } from "util";
5618
+ import { cp, mkdtemp, rm as rm2, readFile as readFile11, writeFile as writeFile3, unlink as unlink3, stat, open, rename as rename2 } from "fs/promises";
5619
+ import { resolve as resolve14, join as join2 } from "path";
5620
+ import { tmpdir } from "os";
5621
+ var exec2 = promisify2(execFile2);
5622
+ var VALID_CATEGORIES = ["missions", "playbooks", "todos", "servers", "config"];
5623
+ var LOCK_FILE_NAME = ".backup-lock";
5624
+ function parseCategoriesStrict(cats) {
5625
+ const unknown = [];
5626
+ const valid = [];
5627
+ for (const cat of cats) {
5628
+ if (VALID_CATEGORIES.includes(cat)) {
5629
+ valid.push(cat);
5630
+ } else {
5631
+ unknown.push(cat);
5632
+ }
5633
+ }
5634
+ if (unknown.length > 0) {
5635
+ throw new Error(
5636
+ `Unknown categor${unknown.length === 1 ? "y" : "ies"}: ${unknown.map((c) => `"${c}"`).join(", ")}. Valid: ${VALID_CATEGORIES.join(", ")}`
5637
+ );
5638
+ }
5639
+ return valid;
5640
+ }
5641
+ function validateRepoUrl(url) {
5642
+ if (!url || typeof url !== "string") return false;
5643
+ const trimmed = url.trim();
5644
+ return trimmed.startsWith("https://") || trimmed.startsWith("git@");
5645
+ }
5646
+ async function resolveCategoryPath(category) {
5647
+ switch (category) {
5648
+ case "missions": {
5649
+ const config = await readConfig();
5650
+ return { sourcePath: config.defaultMissionDir, repoPath: "missions", isFile: false };
5651
+ }
5652
+ case "playbooks":
5653
+ return { sourcePath: playbooksDir(), repoPath: "playbooks", isFile: false };
5654
+ case "todos":
5655
+ return { sourcePath: todosDir(), repoPath: "todos", isFile: false };
5656
+ case "servers":
5657
+ return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
5658
+ case "config":
5659
+ return { sourcePath: resolve14(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
5660
+ }
5661
+ }
5662
+ async function checkGitInstalled() {
5663
+ try {
5664
+ await exec2("git", ["--version"]);
5665
+ } catch {
5666
+ throw new Error("git is not installed or not on PATH. Install git and try again.");
5667
+ }
5668
+ }
5669
+ async function acquireLock() {
5670
+ const lockPath = resolve14(syntaurRoot(), LOCK_FILE_NAME);
5671
+ await ensureDir(syntaurRoot());
5672
+ try {
5673
+ const handle = await open(lockPath, "wx");
5674
+ await handle.write(String(process.pid));
5675
+ await handle.close();
5676
+ return lockPath;
5677
+ } catch (err) {
5678
+ if (err.code === "EEXIST") {
5679
+ const pid = await readFile11(lockPath, "utf-8").catch(() => "");
5680
+ throw new Error(
5681
+ `Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
5682
+ );
5683
+ }
5684
+ throw err;
5685
+ }
5686
+ }
5687
+ async function releaseLock(lockPath) {
5688
+ try {
5689
+ await unlink3(lockPath);
5690
+ } catch {
5691
+ }
5692
+ }
5693
+ async function runGit(args, cwd) {
5694
+ return exec2("git", args, { cwd });
5695
+ }
5696
+ async function cloneOrInit(repoUrl, destDir) {
5697
+ try {
5698
+ await exec2("git", ["clone", "--depth", "1", repoUrl, destDir]);
5699
+ } catch (error) {
5700
+ const message = error instanceof Error ? error.message : String(error);
5701
+ if (message.includes("Repository not found") || message.includes("does not appear to be a git repository")) {
5702
+ throw new Error(`Repository not found or inaccessible: ${repoUrl}. Check URL and credentials.`);
5703
+ }
5704
+ if (message.includes("Authentication failed") || message.includes("could not read Username")) {
5705
+ throw new Error(`Authentication failed for ${repoUrl}. Check SSH keys or credentials.`);
5706
+ }
5707
+ throw new Error(`git clone failed: ${message}`);
5708
+ }
5709
+ }
5710
+ async function copyRecursive(src, dest) {
5711
+ if (!await fileExists(src)) return;
5712
+ const s = await stat(src);
5713
+ if (s.isDirectory()) {
5714
+ await ensureDir(dest);
5715
+ await cp(src, dest, { recursive: true, force: true });
5716
+ } else {
5717
+ await ensureDir(resolve14(dest, ".."));
5718
+ await cp(src, dest, { force: true });
5719
+ }
5720
+ }
5721
+ function resolveCategoriesStrict(csv) {
5722
+ const parts = csv.split(",").map((s) => s.trim()).filter(Boolean);
5723
+ return parseCategoriesStrict(parts);
5724
+ }
5725
+ async function readSanitizedConfig(configPath) {
5726
+ const content = await readFile11(configPath, "utf-8");
5727
+ return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
5728
+ }
5729
+ async function backupToGithub(overrides) {
5730
+ await checkGitInstalled();
5731
+ const config = await readConfig();
5732
+ const rawRepo = overrides?.repo ?? config.backup?.repo ?? null;
5733
+ if (!rawRepo) {
5734
+ throw new Error("No backup repo configured. Set it via `syntaur backup config --repo <url>` or the dashboard.");
5735
+ }
5736
+ const repo = rawRepo.trim();
5737
+ if (!validateRepoUrl(repo)) {
5738
+ throw new Error(`Invalid repo URL: "${rawRepo}". Must start with https:// or git@.`);
5739
+ }
5740
+ const categoriesCsv = config.backup?.categories ?? "missions, playbooks, todos, servers, config";
5741
+ const categories = overrides?.categories ?? resolveCategoriesStrict(categoriesCsv);
5742
+ if (categories.length === 0) {
5743
+ throw new Error("No valid backup categories selected.");
5744
+ }
5745
+ const lockPath = await acquireLock();
5746
+ let tmpDir = null;
5747
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5748
+ try {
5749
+ tmpDir = await mkdtemp(join2(tmpdir(), "syntaur-backup-"));
5750
+ await cloneOrInit(repo, tmpDir);
5751
+ for (const category of categories) {
5752
+ const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
5753
+ const destPath = join2(tmpDir, repoPath);
5754
+ if (isFile) {
5755
+ await rm2(destPath, { force: true });
5756
+ } else {
5757
+ await rm2(destPath, { recursive: true, force: true });
5758
+ }
5759
+ if (!await fileExists(sourcePath)) {
5760
+ console.warn(`Category "${category}": no local data at ${sourcePath}; backup will reflect deletion.`);
5761
+ continue;
5762
+ }
5763
+ if (category === "config") {
5764
+ const sanitized = await readSanitizedConfig(sourcePath);
5765
+ await ensureDir(resolve14(destPath, ".."));
5766
+ await writeFile3(destPath, sanitized, "utf-8");
5767
+ } else {
5768
+ await copyRecursive(sourcePath, destPath);
5769
+ }
5770
+ }
5771
+ await runGit(["add", "-A"], tmpDir);
5772
+ const { stdout: status } = await runGit(["status", "--porcelain"], tmpDir);
5773
+ if (!status.trim()) {
5774
+ await updateBackupConfig({ lastBackup: timestamp }).catch(() => {
5775
+ });
5776
+ return {
5777
+ success: true,
5778
+ timestamp,
5779
+ message: "No changes to back up.",
5780
+ committed: false
5781
+ };
5782
+ }
5783
+ try {
5784
+ await runGit(["config", "user.email", "syntaur@local"], tmpDir);
5785
+ await runGit(["config", "user.name", "Syntaur Backup"], tmpDir);
5786
+ } catch {
5787
+ }
5788
+ await runGit(["commit", "-m", `Syntaur backup ${timestamp}`], tmpDir);
5789
+ try {
5790
+ await runGit(["push"], tmpDir);
5791
+ } catch (error) {
5792
+ const message = error instanceof Error ? error.message : String(error);
5793
+ if (message.includes("non-fast-forward") || message.includes("rejected")) {
5794
+ throw new Error("Push rejected (non-fast-forward). Pull and resolve manually, or delete remote contents.");
5795
+ }
5796
+ if (message.includes("Authentication") || message.includes("could not read Username")) {
5797
+ throw new Error("Push authentication failed. Check SSH keys or credentials.");
5798
+ }
5799
+ throw new Error(`git push failed: ${message}`);
5800
+ }
5801
+ await updateBackupConfig({ lastBackup: timestamp }).catch(() => {
5802
+ });
5803
+ return {
5804
+ success: true,
5805
+ timestamp,
5806
+ message: `Backed up ${categories.length} categor${categories.length === 1 ? "y" : "ies"} to ${repo}.`,
5807
+ committed: true
5808
+ };
5809
+ } finally {
5810
+ if (tmpDir) {
5811
+ await rm2(tmpDir, { recursive: true, force: true }).catch(() => {
5812
+ });
5813
+ }
5814
+ await releaseLock(lockPath);
5815
+ }
5816
+ }
5817
+ async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
5818
+ if (isFile) {
5819
+ await ensureDir(resolve14(localPath, ".."));
5820
+ await cp(repoSrcPath, localPath, { force: true });
5821
+ return;
5822
+ }
5823
+ const stagingPath = `${localPath}.syntaur-restore-staging`;
5824
+ const backupPath = `${localPath}.syntaur-restore-backup`;
5825
+ await rm2(stagingPath, { recursive: true, force: true });
5826
+ const backupExistsBefore = await fileExists(backupPath);
5827
+ const localExistsBefore = await fileExists(localPath);
5828
+ if (backupExistsBefore) {
5829
+ if (!localExistsBefore) {
5830
+ await rename2(backupPath, localPath);
5831
+ } else {
5832
+ throw new Error(
5833
+ `Cannot restore "${localPath}": a stale crash-recovery backup exists at ${backupPath} while the current path also exists. Inspect both and remove the one you don't need, then retry.`
5834
+ );
5835
+ }
5836
+ }
5837
+ let localMovedAside = false;
5838
+ try {
5839
+ await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
5840
+ const localExists = await fileExists(localPath);
5841
+ if (localExists) {
5842
+ await rename2(localPath, backupPath);
5843
+ localMovedAside = true;
5844
+ }
5845
+ await rename2(stagingPath, localPath);
5846
+ await rm2(backupPath, { recursive: true, force: true }).catch(() => {
5847
+ });
5848
+ } catch (err) {
5849
+ if (localMovedAside && await fileExists(backupPath)) {
5850
+ await rename2(backupPath, localPath).catch(() => {
5851
+ });
5852
+ }
5853
+ await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
5854
+ });
5855
+ throw err;
5856
+ }
5857
+ }
5858
+ async function restoreFromGithub(overrides) {
5859
+ await checkGitInstalled();
5860
+ const config = await readConfig();
5861
+ const rawRepo = overrides?.repo ?? config.backup?.repo ?? null;
5862
+ if (!rawRepo) {
5863
+ throw new Error("No backup repo configured.");
5864
+ }
5865
+ const repo = rawRepo.trim();
5866
+ if (!validateRepoUrl(repo)) {
5867
+ throw new Error(`Invalid repo URL: "${rawRepo}".`);
5868
+ }
5869
+ const categoriesCsv = config.backup?.categories ?? "missions, playbooks, todos, servers, config";
5870
+ const categories = overrides?.categories ?? resolveCategoriesStrict(categoriesCsv);
5871
+ if (categories.length === 0) {
5872
+ throw new Error("No valid restore categories selected.");
5873
+ }
5874
+ const lockPath = await acquireLock();
5875
+ let tmpDir = null;
5876
+ const restored = [];
5877
+ const failed = [];
5878
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5879
+ try {
5880
+ await updateBackupConfig({ lastRestore: timestamp });
5881
+ tmpDir = await mkdtemp(join2(tmpdir(), "syntaur-restore-"));
5882
+ await cloneOrInit(repo, tmpDir);
5883
+ for (const category of categories) {
5884
+ if (category === "config") {
5885
+ console.warn('Skipping "config" on restore (would overwrite local backup settings).');
5886
+ continue;
5887
+ }
5888
+ try {
5889
+ const { sourcePath: localPath, repoPath, isFile } = await resolveCategoryPath(category);
5890
+ const repoSrcPath = join2(tmpDir, repoPath);
5891
+ if (!await fileExists(repoSrcPath)) {
5892
+ console.warn(`Category "${category}" not found in backup repo, skipping.`);
5893
+ continue;
5894
+ }
5895
+ await safeRestoreCategory(localPath, repoSrcPath, isFile);
5896
+ restored.push(category);
5897
+ } catch (error) {
5898
+ const msg = error instanceof Error ? error.message : String(error);
5899
+ console.error(`Failed to restore "${category}": ${msg}`);
5900
+ failed.push(category);
5901
+ }
5902
+ }
5903
+ const success = failed.length === 0;
5904
+ return {
5905
+ success,
5906
+ timestamp,
5907
+ message: success ? `Restored ${restored.length} categor${restored.length === 1 ? "y" : "ies"} from ${repo}.` : `Partial restore: ${restored.length} succeeded, ${failed.length} failed (${failed.join(", ")}).`,
5908
+ committed: false
5909
+ };
5910
+ } finally {
5911
+ if (tmpDir) {
5912
+ await rm2(tmpDir, { recursive: true, force: true }).catch(() => {
5913
+ });
5914
+ }
5915
+ await releaseLock(lockPath);
5916
+ }
5917
+ }
5918
+ async function getBackupStatus() {
5919
+ const config = await readConfig();
5920
+ const lockPath = resolve14(syntaurRoot(), LOCK_FILE_NAME);
5921
+ const locked = await fileExists(lockPath);
5922
+ return {
5923
+ repo: config.backup?.repo ?? null,
5924
+ categories: config.backup?.categories ?? "missions, playbooks, todos, servers, config",
5925
+ lastBackup: config.backup?.lastBackup ?? null,
5926
+ lastRestore: config.backup?.lastRestore ?? null,
5927
+ locked
5928
+ };
5929
+ }
5930
+
5931
+ // src/dashboard/api-backup.ts
5932
+ function createBackupRouter() {
5933
+ const router = Router6();
5934
+ router.get("/", async (_req, res) => {
5935
+ try {
5936
+ const status = await getBackupStatus();
5937
+ res.json(status);
5938
+ } catch (error) {
5939
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
5940
+ }
5941
+ });
5942
+ router.put("/config", async (req, res) => {
5943
+ try {
5944
+ const body = req.body ?? {};
5945
+ const updates = {};
5946
+ if (body.repo !== void 0) {
5947
+ const trimmed = typeof body.repo === "string" ? body.repo.trim() : body.repo;
5948
+ if (trimmed !== null && trimmed !== "" && !validateRepoUrl(trimmed)) {
5949
+ return res.status(400).json({
5950
+ error: `Invalid repo URL. Must start with https:// or git@.`
5951
+ });
5952
+ }
5953
+ updates.repo = trimmed || null;
5954
+ }
5955
+ if (body.categories !== void 0) {
5956
+ let cats;
5957
+ if (Array.isArray(body.categories)) {
5958
+ cats = body.categories.map((s) => String(s).trim()).filter(Boolean);
5959
+ } else if (typeof body.categories === "string") {
5960
+ cats = body.categories.split(",").map((s) => s.trim()).filter(Boolean);
5961
+ } else {
5962
+ return res.status(400).json({ error: "categories must be a string or array" });
5963
+ }
5964
+ if (cats.length === 0) {
5965
+ return res.status(400).json({
5966
+ error: `No categories provided. Valid: ${VALID_CATEGORIES.join(", ")}`
5967
+ });
5968
+ }
5969
+ try {
5970
+ const valid = parseCategoriesStrict(cats);
5971
+ updates.categories = valid.join(", ");
5972
+ } catch (err) {
5973
+ return res.status(400).json({
5974
+ error: err instanceof Error ? err.message : String(err)
5975
+ });
5976
+ }
5977
+ }
5978
+ if (Object.keys(updates).length === 0) {
5979
+ return res.status(400).json({ error: "No fields to update" });
5980
+ }
5981
+ await updateBackupConfig(updates);
5982
+ const status = await getBackupStatus();
5983
+ res.json(status);
5984
+ } catch (error) {
5985
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
5986
+ }
5987
+ });
5988
+ router.post("/push", async (_req, res) => {
5989
+ try {
5990
+ const result = await backupToGithub();
5991
+ res.json(result);
5992
+ } catch (error) {
5993
+ res.status(500).json({
5994
+ success: false,
5995
+ error: error instanceof Error ? error.message : String(error)
5996
+ });
5997
+ }
5998
+ });
5999
+ router.post("/pull", async (_req, res) => {
6000
+ try {
6001
+ const result = await restoreFromGithub();
6002
+ res.json(result);
6003
+ } catch (error) {
6004
+ res.status(500).json({
6005
+ success: false,
6006
+ error: error instanceof Error ? error.message : String(error)
6007
+ });
6008
+ }
6009
+ });
6010
+ return router;
6011
+ }
6012
+
5529
6013
  // src/dashboard/autodiscovery.ts
5530
6014
  init_scanner();
5531
6015
  init_servers();
@@ -5599,7 +6083,7 @@ async function listAllTmuxSessions() {
5599
6083
  if (!output) return [];
5600
6084
  return output.split("\n").filter((line) => line.length > 0);
5601
6085
  }
5602
- async function discoverTmuxSessions(serversDir, missionsDir, existingNames) {
6086
+ async function discoverTmuxSessions(serversDir2, missionsDir, existingNames) {
5603
6087
  const tmuxAvailable = await checkTmuxAvailable();
5604
6088
  if (!tmuxAvailable) return false;
5605
6089
  const workspaceRecords = await loadWorkspaceRecords(missionsDir);
@@ -5627,7 +6111,7 @@ async function discoverTmuxSessions(serversDir, missionsDir, existingNames) {
5627
6111
  }
5628
6112
  }
5629
6113
  if (matched) {
5630
- await registerAutoSession(serversDir, sessionName, { kind: "tmux" });
6114
+ await registerAutoSession(serversDir2, sessionName, { kind: "tmux" });
5631
6115
  changed = true;
5632
6116
  }
5633
6117
  }
@@ -5643,7 +6127,7 @@ async function getProcessCwd(pid) {
5643
6127
  }
5644
6128
  return null;
5645
6129
  }
5646
- async function discoverProcesses(serversDir, missionsDir, existingFiles, excludePids) {
6130
+ async function discoverProcesses(serversDir2, missionsDir, existingFiles, excludePids) {
5647
6131
  const workspaceRecords = await loadWorkspaceRecords(missionsDir);
5648
6132
  if (workspaceRecords.length === 0) return false;
5649
6133
  const lsofOutput = await getLsofOutput();
@@ -5668,7 +6152,7 @@ async function discoverProcesses(serversDir, missionsDir, existingFiles, exclude
5668
6152
  const sanitized = sanitizeSessionName(sessionName);
5669
6153
  if (existingFiles.has(sanitized)) continue;
5670
6154
  const ports = parsePortsForPid(lsofOutput, proc.pid);
5671
- await registerAutoSession(serversDir, sessionName, {
6155
+ await registerAutoSession(serversDir2, sessionName, {
5672
6156
  kind: "process",
5673
6157
  pid: proc.pid,
5674
6158
  ports: ports.length > 0 ? ports : [proc.port],
@@ -5678,7 +6162,7 @@ async function discoverProcesses(serversDir, missionsDir, existingFiles, exclude
5678
6162
  }
5679
6163
  return changed;
5680
6164
  }
5681
- async function cleanupDeadAutoSessions(serversDir, existingFiles) {
6165
+ async function cleanupDeadAutoSessions(serversDir2, existingFiles) {
5682
6166
  let changed = false;
5683
6167
  const removedNames = /* @__PURE__ */ new Set();
5684
6168
  const tmuxAvailable = await checkTmuxAvailable();
@@ -5694,7 +6178,7 @@ async function cleanupDeadAutoSessions(serversDir, existingFiles) {
5694
6178
  continue;
5695
6179
  }
5696
6180
  if (!alive) {
5697
- await removeSession(serversDir, name);
6181
+ await removeSession(serversDir2, name);
5698
6182
  removedNames.add(name);
5699
6183
  changed = true;
5700
6184
  }
@@ -5709,20 +6193,20 @@ async function isProcessAlive(pid) {
5709
6193
  return false;
5710
6194
  }
5711
6195
  }
5712
- async function reconcile(serversDir, missionsDir, excludePids) {
5713
- const names = await listSessionFiles(serversDir);
6196
+ async function reconcile(serversDir2, missionsDir, excludePids) {
6197
+ const names = await listSessionFiles(serversDir2);
5714
6198
  const existingFiles = /* @__PURE__ */ new Map();
5715
6199
  for (const name of names) {
5716
- const data = await readSessionFile(serversDir, name);
6200
+ const data = await readSessionFile(serversDir2, name);
5717
6201
  if (data) existingFiles.set(name, data);
5718
6202
  }
5719
- const { changed: cleanupChanged, removedNames } = await cleanupDeadAutoSessions(serversDir, existingFiles);
6203
+ const { changed: cleanupChanged, removedNames } = await cleanupDeadAutoSessions(serversDir2, existingFiles);
5720
6204
  for (const name of removedNames) {
5721
6205
  existingFiles.delete(name);
5722
6206
  }
5723
6207
  const existingNames = new Set(existingFiles.keys());
5724
- const tmuxChanged = await discoverTmuxSessions(serversDir, missionsDir, existingNames);
5725
- const processChanged = await discoverProcesses(serversDir, missionsDir, existingFiles, excludePids);
6208
+ const tmuxChanged = await discoverTmuxSessions(serversDir2, missionsDir, existingNames);
6209
+ const processChanged = await discoverProcesses(serversDir2, missionsDir, existingFiles, excludePids);
5726
6210
  if (tmuxChanged || processChanged || cleanupChanged) {
5727
6211
  clearScanCache();
5728
6212
  }
@@ -5730,7 +6214,7 @@ async function reconcile(serversDir, missionsDir, excludePids) {
5730
6214
 
5731
6215
  // src/dashboard/server.ts
5732
6216
  function createDashboardServer(options) {
5733
- const { port, missionsDir, serversDir, playbooksDir, todosDir, serveStaticUi, dashboardDistPath } = options;
6217
+ const { port, missionsDir, serversDir: serversDir2, playbooksDir: playbooksDir2, todosDir: todosDir2, serveStaticUi, dashboardDistPath } = options;
5734
6218
  const app = express();
5735
6219
  const server = createServer(app);
5736
6220
  const wss = new WebSocketServer({ noServer: true });
@@ -5770,7 +6254,7 @@ function createDashboardServer(options) {
5770
6254
  app.use(express.json());
5771
6255
  app.get("/api/overview", async (_req, res) => {
5772
6256
  try {
5773
- const overview = await getOverview(missionsDir, serversDir);
6257
+ const overview = await getOverview(missionsDir, serversDir2);
5774
6258
  res.json(overview);
5775
6259
  } catch (error) {
5776
6260
  console.error("Error getting overview:", error);
@@ -5779,7 +6263,7 @@ function createDashboardServer(options) {
5779
6263
  });
5780
6264
  app.get("/api/attention", async (_req, res) => {
5781
6265
  try {
5782
- const attention = await getAttention(missionsDir, serversDir);
6266
+ const attention = await getAttention(missionsDir, serversDir2);
5783
6267
  res.json(attention);
5784
6268
  } catch (error) {
5785
6269
  console.error("Error getting attention queue:", error);
@@ -5947,14 +6431,15 @@ function createDashboardServer(options) {
5947
6431
  }
5948
6432
  });
5949
6433
  app.use(createWriteRouter(missionsDir));
5950
- app.use("/api/servers", createServersRouter(serversDir, missionsDir));
6434
+ app.use("/api/servers", createServersRouter(serversDir2, missionsDir));
5951
6435
  app.use("/api/agent-sessions", createAgentSessionsRouter(missionsDir, broadcast));
5952
- app.use("/api/playbooks", createPlaybooksRouter(playbooksDir));
5953
- app.use("/api/todos", createTodosRouter(todosDir, broadcast));
6436
+ app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
6437
+ app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
6438
+ app.use("/api/backup", createBackupRouter());
5954
6439
  if (serveStaticUi && dashboardDistPath) {
5955
6440
  app.use(express.static(dashboardDistPath));
5956
6441
  app.get("{*path}", async (_req, res) => {
5957
- const indexPath = resolve14(dashboardDistPath, "index.html");
6442
+ const indexPath = resolve15(dashboardDistPath, "index.html");
5958
6443
  if (await fileExists(indexPath)) {
5959
6444
  res.sendFile(indexPath);
5960
6445
  } else {
@@ -5969,12 +6454,12 @@ function createDashboardServer(options) {
5969
6454
  async start() {
5970
6455
  watcherHandle = createWatcher({
5971
6456
  missionsDir,
5972
- serversDir,
5973
- playbooksDir,
5974
- todosDir,
6457
+ serversDir: serversDir2,
6458
+ playbooksDir: playbooksDir2,
6459
+ todosDir: todosDir2,
5975
6460
  onMessage: broadcast
5976
6461
  });
5977
- startAutodiscovery({ serversDir, missionsDir, excludePids: /* @__PURE__ */ new Set([process.pid]) });
6462
+ startAutodiscovery({ serversDir: serversDir2, missionsDir, excludePids: /* @__PURE__ */ new Set([process.pid]) });
5978
6463
  return new Promise((resolvePromise, reject) => {
5979
6464
  server.on("error", (err) => {
5980
6465
  if (err.code === "EADDRINUSE") {
@@ -5986,8 +6471,8 @@ function createDashboardServer(options) {
5986
6471
  }
5987
6472
  });
5988
6473
  server.listen(port, () => {
5989
- const portFile = resolve14(homedir2(), ".syntaur", "dashboard-port");
5990
- writeFile3(portFile, String(port), "utf-8").catch(() => {
6474
+ const portFile = resolve15(homedir2(), ".syntaur", "dashboard-port");
6475
+ writeFile4(portFile, String(port), "utf-8").catch(() => {
5991
6476
  });
5992
6477
  resolvePromise();
5993
6478
  });
@@ -6003,8 +6488,8 @@ function createDashboardServer(options) {
6003
6488
  client.close();
6004
6489
  }
6005
6490
  clients.clear();
6006
- const portFile = resolve14(homedir2(), ".syntaur", "dashboard-port");
6007
- await unlink3(portFile).catch(() => {
6491
+ const portFile = resolve15(homedir2(), ".syntaur", "dashboard-port");
6492
+ await unlink4(portFile).catch(() => {
6008
6493
  });
6009
6494
  return new Promise((resolvePromise) => {
6010
6495
  server.close(() => resolvePromise());