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.
- package/dashboard/dist/assets/{_basePickBy-DXzhD14q.js → _basePickBy-eih-KlEh.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-gxypqvP5.js → _baseUniq-M21wg9ZQ.js} +1 -1
- package/dashboard/dist/assets/{arc-Ce7nYKSm.js → arc-uKZMelpQ.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-zX4f4_Mf.js → architectureDiagram-2XIMDMQ5-CpMG5exj.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-auOdy7nH.js → blockDiagram-WCTKOSBZ-BHnCCKl_.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-C2kkjPbW.js → c4Diagram-IC4MRINW-B-n3zU9i.js} +1 -1
- package/dashboard/dist/assets/channel-DVBgSlOI.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-B7dfpnbG.js → chunk-4BX2VUAB-ChD9Iuih.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-r1_jHZYp.js → chunk-55IACEB6-B3vP9Psg.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-5mMONjMK.js → chunk-FMBD7UC4-CIhWgxPS.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-CwKj-Es4.js → chunk-JSJVCQXG-DiGIV_cB.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-ByoW-HgN.js → chunk-KX2RTZJC-DnGsx5jo.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-D1olOovd.js → chunk-NQ4KR5QH-BFBu1fmg.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-CB8_FC8w.js → chunk-QZHKN3VN-DYtumHth.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-CFEqRrE1.js → chunk-WL4C6EOR-BzCrQPuw.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-B7dxBacd.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-B7dxBacd.js +1 -0
- package/dashboard/dist/assets/clone-DAOrHcCC.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-D6dGVXEI.js → cose-bilkent-S5V4N54A-Bl8mb5eY.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-Cvg9CgP-.js → dagre-KLK3FWXG-BHffcOgo.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-iCBudhZD.js → diagram-E7M64L7V-Ib83qzT_.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-BGniy7VQ.js → diagram-IFDJBPK2-hOdh63_T.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-B6Ie044E.js → diagram-P4PSJMXO-D4ocLmc5.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-BHvFRNhJ.js → erDiagram-INFDFZHY-CHJ6zqnJ.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CN86Zu3Q.js → flowDiagram-PKNHOUZH-DEz5g2Ye.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-D-1fKFjW.js → ganttDiagram-A5KZAMGK-BSftxDHA.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Dtf1A6KL.js → gitGraphDiagram-K3NZZRJ6-Cr3vGf07.js} +1 -1
- package/dashboard/dist/assets/{graph-B6H_kXSs.js → graph-D4us8trI.js} +1 -1
- package/dashboard/dist/assets/index-AXntWS_w.css +1 -0
- package/dashboard/dist/assets/index-CEMjexkj.js +460 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-R9wJj4JF.js → infoDiagram-LFFYTUFH-CH_jVfru.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CJmeR-bX.js → ishikawaDiagram-PHBUUO56-BdKLa5GC.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-FcUhyu8I.js → journeyDiagram-4ABVD52K-C_SMzNGF.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-C8UcTIwW.js → kanban-definition-K7BYSVSG-BeA-egRW.js} +1 -1
- package/dashboard/dist/assets/{layout-DzBy6alw.js → layout-B8tDmL4j.js} +1 -1
- package/dashboard/dist/assets/{linear-CZJCNOB9.js → linear-CeGJyrHS.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-fMQRe9Gq.js → mermaid.core-DyEs-LPd.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BFwp-LS-.js → mindmap-definition-YRQLILUH-DCxzAr8m.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CQLmPkkd.js → pieDiagram-SKSYHLDU-CEj5dRDi.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DAmi-dmD.js → quadrantDiagram-337W2JSQ-CKfvAEQg.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dcdts4kX.js → requirementDiagram-Z7DCOOCP-CTRqKPtJ.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-By8LRvM0.js → sankeyDiagram-WA2Y5GQK-BlYbz8UR.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BsvqgtTz.js → sequenceDiagram-2WXFIKYE-PT2t7ryQ.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DFNOD7cx.js → stateDiagram-RAJIS63D-eDX7IUuV.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO--yuSBnLh.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-CMcgJGjn.js → timeline-definition-YZTLITO2-By11B1Ow.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-BWsRNHwq.js → treemap-KZPCXAKY-rvdLeWWV.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-io7-2Tod.js → vennDiagram-LZ73GAT5-Br_oZ1wv.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-AVnh4fDS.js → xychartDiagram-JWTSCODW-D-MWVqrT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +624 -139
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +658 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/channel-PMR2DuGi.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-DmESf_RL.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-DmESf_RL.js +0 -1
- package/dashboard/dist/assets/clone-WlIeyha4.js +0 -1
- package/dashboard/dist/assets/index-BhuXD-Q5.js +0 -445
- package/dashboard/dist/assets/index-BnqH-RIk.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DVO-Epiz.js +0 -1
package/dist/index.js
CHANGED
|
@@ -93,10 +93,11 @@ onboarding:
|
|
|
93
93
|
agentDefaults:
|
|
94
94
|
trustLevel: medium
|
|
95
95
|
autoApprove: false
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
backup:
|
|
97
|
+
repo: null
|
|
98
|
+
categories: missions, playbooks, todos, servers, config
|
|
99
|
+
lastBackup: null
|
|
100
|
+
lastRestore: null
|
|
100
101
|
---
|
|
101
102
|
|
|
102
103
|
# Syntaur Configuration
|
|
@@ -516,6 +517,14 @@ function serializeIntegrationConfig(integrations) {
|
|
|
516
517
|
function serializeOnboardingConfig(onboarding) {
|
|
517
518
|
return ["onboarding:", ` completed: ${onboarding.completed ? "true" : "false"}`].join("\n");
|
|
518
519
|
}
|
|
520
|
+
function serializeBackupConfig(backup) {
|
|
521
|
+
const lines = ["backup:"];
|
|
522
|
+
lines.push(` repo: ${backup.repo ?? "null"}`);
|
|
523
|
+
lines.push(` categories: ${backup.categories}`);
|
|
524
|
+
lines.push(` lastBackup: ${backup.lastBackup ?? "null"}`);
|
|
525
|
+
lines.push(` lastRestore: ${backup.lastRestore ?? "null"}`);
|
|
526
|
+
return lines.join("\n");
|
|
527
|
+
}
|
|
519
528
|
function stripTopLevelBlock(fmBlock, key) {
|
|
520
529
|
const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
|
|
521
530
|
if (!blockStart) {
|
|
@@ -680,6 +689,40 @@ ${normalizedFm}
|
|
|
680
689
|
---${afterFrontmatter}`;
|
|
681
690
|
await writeFileForce(configPath, newContent);
|
|
682
691
|
}
|
|
692
|
+
async function updateBackupConfig(backup) {
|
|
693
|
+
const configPath = resolve4(syntaurRoot(), "config.md");
|
|
694
|
+
const current = (await readConfig()).backup;
|
|
695
|
+
const nextBackup = {
|
|
696
|
+
repo: current?.repo ?? null,
|
|
697
|
+
categories: current?.categories ?? "missions, playbooks, todos, servers, config",
|
|
698
|
+
lastBackup: current?.lastBackup ?? null,
|
|
699
|
+
lastRestore: current?.lastRestore ?? null,
|
|
700
|
+
...backup
|
|
701
|
+
};
|
|
702
|
+
const backupBlock = serializeBackupConfig(nextBackup);
|
|
703
|
+
const existing = await fileExists(configPath) ? await readFile3(configPath, "utf-8") : renderConfig({ defaultMissionDir: defaultMissionDir() });
|
|
704
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
705
|
+
if (!fmMatch) {
|
|
706
|
+
const content = `---
|
|
707
|
+
version: "1.0"
|
|
708
|
+
defaultMissionDir: ${defaultMissionDir()}
|
|
709
|
+
${backupBlock}
|
|
710
|
+
---
|
|
711
|
+
${existing}`;
|
|
712
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const fmBlock = fmMatch[2];
|
|
716
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
717
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "backup");
|
|
718
|
+
const newFm = `${cleanedFm}
|
|
719
|
+
${backupBlock}`.replace(/^\n+/, "");
|
|
720
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
721
|
+
const newContent = `---
|
|
722
|
+
${normalizedFm}
|
|
723
|
+
---${afterFrontmatter}`;
|
|
724
|
+
await writeFileForce(configPath, newContent);
|
|
725
|
+
}
|
|
683
726
|
async function readConfig() {
|
|
684
727
|
const configPath = resolve4(syntaurRoot(), "config.md");
|
|
685
728
|
if (!await fileExists(configPath)) {
|
|
@@ -722,6 +765,12 @@ async function readConfig() {
|
|
|
722
765
|
"integrations.codexMarketplacePath"
|
|
723
766
|
)
|
|
724
767
|
},
|
|
768
|
+
backup: fm["backup.repo"] || fm["backup.categories"] ? {
|
|
769
|
+
repo: fm["backup.repo"] && fm["backup.repo"] !== "null" ? fm["backup.repo"] : null,
|
|
770
|
+
categories: fm["backup.categories"] || "missions, playbooks, todos, servers, config",
|
|
771
|
+
lastBackup: fm["backup.lastBackup"] && fm["backup.lastBackup"] !== "null" ? fm["backup.lastBackup"] : null,
|
|
772
|
+
lastRestore: fm["backup.lastRestore"] && fm["backup.lastRestore"] !== "null" ? fm["backup.lastRestore"] : null
|
|
773
|
+
} : null,
|
|
725
774
|
statuses: parseStatusConfig(content)
|
|
726
775
|
};
|
|
727
776
|
}
|
|
@@ -747,6 +796,7 @@ var init_config2 = __esm({
|
|
|
747
796
|
codexPluginDir: null,
|
|
748
797
|
codexMarketplacePath: null
|
|
749
798
|
},
|
|
799
|
+
backup: null,
|
|
750
800
|
statuses: null
|
|
751
801
|
};
|
|
752
802
|
}
|
|
@@ -3768,8 +3818,8 @@ __export(launch_exports, {
|
|
|
3768
3818
|
launchAgent: () => launchAgent
|
|
3769
3819
|
});
|
|
3770
3820
|
import { spawn as spawn2 } from "child_process";
|
|
3771
|
-
import { mkdir as mkdir2, writeFile as
|
|
3772
|
-
import { resolve as
|
|
3821
|
+
import { mkdir as mkdir2, writeFile as writeFile6 } from "fs/promises";
|
|
3822
|
+
import { resolve as resolve33 } from "path";
|
|
3773
3823
|
async function launchAgent(options) {
|
|
3774
3824
|
const { missionsDir, missionSlug, assignmentSlug, agent } = options;
|
|
3775
3825
|
const command = AGENT_COMMANDS[agent];
|
|
@@ -3779,9 +3829,9 @@ async function launchAgent(options) {
|
|
|
3779
3829
|
process.exit(1);
|
|
3780
3830
|
}
|
|
3781
3831
|
const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
|
|
3782
|
-
const missionDir =
|
|
3783
|
-
const assignmentDir =
|
|
3784
|
-
const contextDir =
|
|
3832
|
+
const missionDir = resolve33(missionsDir, missionSlug);
|
|
3833
|
+
const assignmentDir = resolve33(missionDir, "assignments", assignmentSlug);
|
|
3834
|
+
const contextDir = resolve33(workspaceDir, ".syntaur");
|
|
3785
3835
|
await mkdir2(contextDir, { recursive: true });
|
|
3786
3836
|
const context = {
|
|
3787
3837
|
missionSlug,
|
|
@@ -3793,8 +3843,8 @@ async function launchAgent(options) {
|
|
|
3793
3843
|
branch: detail.workspace.branch ?? null,
|
|
3794
3844
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3795
3845
|
};
|
|
3796
|
-
await
|
|
3797
|
-
|
|
3846
|
+
await writeFile6(
|
|
3847
|
+
resolve33(contextDir, "context.json"),
|
|
3798
3848
|
JSON.stringify(context, null, 2) + "\n"
|
|
3799
3849
|
);
|
|
3800
3850
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -3829,7 +3879,7 @@ var init_launch = __esm({
|
|
|
3829
3879
|
});
|
|
3830
3880
|
|
|
3831
3881
|
// src/index.ts
|
|
3832
|
-
import { Command as
|
|
3882
|
+
import { Command as Command3 } from "commander";
|
|
3833
3883
|
|
|
3834
3884
|
// src/commands/init.ts
|
|
3835
3885
|
init_paths();
|
|
@@ -4907,16 +4957,16 @@ Use --slug to specify a different slug.`
|
|
|
4907
4957
|
init_config2();
|
|
4908
4958
|
import { spawn } from "child_process";
|
|
4909
4959
|
import { createServer as createNetServer } from "net";
|
|
4910
|
-
import { resolve as
|
|
4960
|
+
import { resolve as resolve19, dirname as dirname4 } from "path";
|
|
4911
4961
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4912
4962
|
|
|
4913
4963
|
// src/dashboard/server.ts
|
|
4914
4964
|
init_api();
|
|
4915
4965
|
import express from "express";
|
|
4916
4966
|
import { createServer } from "http";
|
|
4917
|
-
import { resolve as
|
|
4967
|
+
import { resolve as resolve18 } from "path";
|
|
4918
4968
|
import { homedir as homedir2 } from "os";
|
|
4919
|
-
import { writeFile as
|
|
4969
|
+
import { writeFile as writeFile4, unlink as unlink4 } from "fs/promises";
|
|
4920
4970
|
import { WebSocketServer, WebSocket } from "ws";
|
|
4921
4971
|
|
|
4922
4972
|
// src/dashboard/watcher.ts
|
|
@@ -6096,8 +6146,8 @@ async function migrateFromMarkdown(missionsDir) {
|
|
|
6096
6146
|
return allSessions.length;
|
|
6097
6147
|
}
|
|
6098
6148
|
async function parseMarkdownSessionsIndex(filePath, missionSlug) {
|
|
6099
|
-
const { readFile:
|
|
6100
|
-
const raw = await
|
|
6149
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
6150
|
+
const raw = await readFile16(filePath, "utf-8");
|
|
6101
6151
|
const sessions = [];
|
|
6102
6152
|
const lines = raw.split("\n");
|
|
6103
6153
|
let inTable = false;
|
|
@@ -6604,8 +6654,8 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
6604
6654
|
router.post("/:workspace/archive", async (req, res) => {
|
|
6605
6655
|
try {
|
|
6606
6656
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
6607
|
-
const { resolve:
|
|
6608
|
-
const { readFile:
|
|
6657
|
+
const { resolve: resolve37 } = await import("path");
|
|
6658
|
+
const { readFile: readFile16 } = await import("fs/promises");
|
|
6609
6659
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
6610
6660
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
6611
6661
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -6621,10 +6671,10 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
6621
6671
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
6622
6672
|
);
|
|
6623
6673
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
6624
|
-
await ensureDir(
|
|
6674
|
+
await ensureDir(resolve37(todosDir2, "archive"));
|
|
6625
6675
|
let archContent = "";
|
|
6626
6676
|
if (await fileExists(archFile)) {
|
|
6627
|
-
archContent = await
|
|
6677
|
+
archContent = await readFile16(archFile, "utf-8");
|
|
6628
6678
|
archContent = archContent.trimEnd() + "\n\n";
|
|
6629
6679
|
} else {
|
|
6630
6680
|
archContent = `---
|
|
@@ -6874,6 +6924,411 @@ workspace: ${workspace}
|
|
|
6874
6924
|
return router;
|
|
6875
6925
|
}
|
|
6876
6926
|
|
|
6927
|
+
// src/dashboard/api-backup.ts
|
|
6928
|
+
init_config2();
|
|
6929
|
+
import { Router as Router6 } from "express";
|
|
6930
|
+
|
|
6931
|
+
// src/utils/github-backup.ts
|
|
6932
|
+
init_paths();
|
|
6933
|
+
init_fs();
|
|
6934
|
+
init_config2();
|
|
6935
|
+
import { execFile as execFile2 } from "child_process";
|
|
6936
|
+
import { promisify as promisify2 } from "util";
|
|
6937
|
+
import { cp, mkdtemp, rm as rm2, readFile as readFile12, writeFile as writeFile3, unlink as unlink3, stat, open, rename as rename2 } from "fs/promises";
|
|
6938
|
+
import { resolve as resolve17, join as join2 } from "path";
|
|
6939
|
+
import { tmpdir } from "os";
|
|
6940
|
+
var exec2 = promisify2(execFile2);
|
|
6941
|
+
var VALID_CATEGORIES = ["missions", "playbooks", "todos", "servers", "config"];
|
|
6942
|
+
var LOCK_FILE_NAME = ".backup-lock";
|
|
6943
|
+
function parseCategoriesStrict(cats) {
|
|
6944
|
+
const unknown = [];
|
|
6945
|
+
const valid = [];
|
|
6946
|
+
for (const cat of cats) {
|
|
6947
|
+
if (VALID_CATEGORIES.includes(cat)) {
|
|
6948
|
+
valid.push(cat);
|
|
6949
|
+
} else {
|
|
6950
|
+
unknown.push(cat);
|
|
6951
|
+
}
|
|
6952
|
+
}
|
|
6953
|
+
if (unknown.length > 0) {
|
|
6954
|
+
throw new Error(
|
|
6955
|
+
`Unknown categor${unknown.length === 1 ? "y" : "ies"}: ${unknown.map((c) => `"${c}"`).join(", ")}. Valid: ${VALID_CATEGORIES.join(", ")}`
|
|
6956
|
+
);
|
|
6957
|
+
}
|
|
6958
|
+
return valid;
|
|
6959
|
+
}
|
|
6960
|
+
function validateRepoUrl(url) {
|
|
6961
|
+
if (!url || typeof url !== "string") return false;
|
|
6962
|
+
const trimmed = url.trim();
|
|
6963
|
+
return trimmed.startsWith("https://") || trimmed.startsWith("git@");
|
|
6964
|
+
}
|
|
6965
|
+
async function resolveCategoryPath(category) {
|
|
6966
|
+
switch (category) {
|
|
6967
|
+
case "missions": {
|
|
6968
|
+
const config = await readConfig();
|
|
6969
|
+
return { sourcePath: config.defaultMissionDir, repoPath: "missions", isFile: false };
|
|
6970
|
+
}
|
|
6971
|
+
case "playbooks":
|
|
6972
|
+
return { sourcePath: playbooksDir(), repoPath: "playbooks", isFile: false };
|
|
6973
|
+
case "todos":
|
|
6974
|
+
return { sourcePath: todosDir(), repoPath: "todos", isFile: false };
|
|
6975
|
+
case "servers":
|
|
6976
|
+
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
6977
|
+
case "config":
|
|
6978
|
+
return { sourcePath: resolve17(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6981
|
+
async function checkGitInstalled() {
|
|
6982
|
+
try {
|
|
6983
|
+
await exec2("git", ["--version"]);
|
|
6984
|
+
} catch {
|
|
6985
|
+
throw new Error("git is not installed or not on PATH. Install git and try again.");
|
|
6986
|
+
}
|
|
6987
|
+
}
|
|
6988
|
+
async function acquireLock() {
|
|
6989
|
+
const lockPath = resolve17(syntaurRoot(), LOCK_FILE_NAME);
|
|
6990
|
+
await ensureDir(syntaurRoot());
|
|
6991
|
+
try {
|
|
6992
|
+
const handle = await open(lockPath, "wx");
|
|
6993
|
+
await handle.write(String(process.pid));
|
|
6994
|
+
await handle.close();
|
|
6995
|
+
return lockPath;
|
|
6996
|
+
} catch (err) {
|
|
6997
|
+
if (err.code === "EEXIST") {
|
|
6998
|
+
const pid = await readFile12(lockPath, "utf-8").catch(() => "");
|
|
6999
|
+
throw new Error(
|
|
7000
|
+
`Backup operation already in progress (lock file at ${lockPath}, pid ${pid.trim() || "unknown"}). If stale, delete the file and retry.`
|
|
7001
|
+
);
|
|
7002
|
+
}
|
|
7003
|
+
throw err;
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
async function releaseLock(lockPath) {
|
|
7007
|
+
try {
|
|
7008
|
+
await unlink3(lockPath);
|
|
7009
|
+
} catch {
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
7012
|
+
async function runGit(args, cwd) {
|
|
7013
|
+
return exec2("git", args, { cwd });
|
|
7014
|
+
}
|
|
7015
|
+
async function cloneOrInit(repoUrl, destDir) {
|
|
7016
|
+
try {
|
|
7017
|
+
await exec2("git", ["clone", "--depth", "1", repoUrl, destDir]);
|
|
7018
|
+
} catch (error) {
|
|
7019
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7020
|
+
if (message.includes("Repository not found") || message.includes("does not appear to be a git repository")) {
|
|
7021
|
+
throw new Error(`Repository not found or inaccessible: ${repoUrl}. Check URL and credentials.`);
|
|
7022
|
+
}
|
|
7023
|
+
if (message.includes("Authentication failed") || message.includes("could not read Username")) {
|
|
7024
|
+
throw new Error(`Authentication failed for ${repoUrl}. Check SSH keys or credentials.`);
|
|
7025
|
+
}
|
|
7026
|
+
throw new Error(`git clone failed: ${message}`);
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
async function copyRecursive(src, dest) {
|
|
7030
|
+
if (!await fileExists(src)) return;
|
|
7031
|
+
const s = await stat(src);
|
|
7032
|
+
if (s.isDirectory()) {
|
|
7033
|
+
await ensureDir(dest);
|
|
7034
|
+
await cp(src, dest, { recursive: true, force: true });
|
|
7035
|
+
} else {
|
|
7036
|
+
await ensureDir(resolve17(dest, ".."));
|
|
7037
|
+
await cp(src, dest, { force: true });
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
function resolveCategoriesStrict(csv) {
|
|
7041
|
+
const parts = csv.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7042
|
+
return parseCategoriesStrict(parts);
|
|
7043
|
+
}
|
|
7044
|
+
async function readSanitizedConfig(configPath) {
|
|
7045
|
+
const content = await readFile12(configPath, "utf-8");
|
|
7046
|
+
return content.replace(/^(\s*lastBackup:\s*).*$/m, "$1null").replace(/^(\s*lastRestore:\s*).*$/m, "$1null");
|
|
7047
|
+
}
|
|
7048
|
+
async function backupToGithub(overrides) {
|
|
7049
|
+
await checkGitInstalled();
|
|
7050
|
+
const config = await readConfig();
|
|
7051
|
+
const rawRepo = overrides?.repo ?? config.backup?.repo ?? null;
|
|
7052
|
+
if (!rawRepo) {
|
|
7053
|
+
throw new Error("No backup repo configured. Set it via `syntaur backup config --repo <url>` or the dashboard.");
|
|
7054
|
+
}
|
|
7055
|
+
const repo = rawRepo.trim();
|
|
7056
|
+
if (!validateRepoUrl(repo)) {
|
|
7057
|
+
throw new Error(`Invalid repo URL: "${rawRepo}". Must start with https:// or git@.`);
|
|
7058
|
+
}
|
|
7059
|
+
const categoriesCsv = config.backup?.categories ?? "missions, playbooks, todos, servers, config";
|
|
7060
|
+
const categories = overrides?.categories ?? resolveCategoriesStrict(categoriesCsv);
|
|
7061
|
+
if (categories.length === 0) {
|
|
7062
|
+
throw new Error("No valid backup categories selected.");
|
|
7063
|
+
}
|
|
7064
|
+
const lockPath = await acquireLock();
|
|
7065
|
+
let tmpDir = null;
|
|
7066
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
7067
|
+
try {
|
|
7068
|
+
tmpDir = await mkdtemp(join2(tmpdir(), "syntaur-backup-"));
|
|
7069
|
+
await cloneOrInit(repo, tmpDir);
|
|
7070
|
+
for (const category of categories) {
|
|
7071
|
+
const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
|
|
7072
|
+
const destPath = join2(tmpDir, repoPath);
|
|
7073
|
+
if (isFile) {
|
|
7074
|
+
await rm2(destPath, { force: true });
|
|
7075
|
+
} else {
|
|
7076
|
+
await rm2(destPath, { recursive: true, force: true });
|
|
7077
|
+
}
|
|
7078
|
+
if (!await fileExists(sourcePath)) {
|
|
7079
|
+
console.warn(`Category "${category}": no local data at ${sourcePath}; backup will reflect deletion.`);
|
|
7080
|
+
continue;
|
|
7081
|
+
}
|
|
7082
|
+
if (category === "config") {
|
|
7083
|
+
const sanitized = await readSanitizedConfig(sourcePath);
|
|
7084
|
+
await ensureDir(resolve17(destPath, ".."));
|
|
7085
|
+
await writeFile3(destPath, sanitized, "utf-8");
|
|
7086
|
+
} else {
|
|
7087
|
+
await copyRecursive(sourcePath, destPath);
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
await runGit(["add", "-A"], tmpDir);
|
|
7091
|
+
const { stdout: status } = await runGit(["status", "--porcelain"], tmpDir);
|
|
7092
|
+
if (!status.trim()) {
|
|
7093
|
+
await updateBackupConfig({ lastBackup: timestamp }).catch(() => {
|
|
7094
|
+
});
|
|
7095
|
+
return {
|
|
7096
|
+
success: true,
|
|
7097
|
+
timestamp,
|
|
7098
|
+
message: "No changes to back up.",
|
|
7099
|
+
committed: false
|
|
7100
|
+
};
|
|
7101
|
+
}
|
|
7102
|
+
try {
|
|
7103
|
+
await runGit(["config", "user.email", "syntaur@local"], tmpDir);
|
|
7104
|
+
await runGit(["config", "user.name", "Syntaur Backup"], tmpDir);
|
|
7105
|
+
} catch {
|
|
7106
|
+
}
|
|
7107
|
+
await runGit(["commit", "-m", `Syntaur backup ${timestamp}`], tmpDir);
|
|
7108
|
+
try {
|
|
7109
|
+
await runGit(["push"], tmpDir);
|
|
7110
|
+
} catch (error) {
|
|
7111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7112
|
+
if (message.includes("non-fast-forward") || message.includes("rejected")) {
|
|
7113
|
+
throw new Error("Push rejected (non-fast-forward). Pull and resolve manually, or delete remote contents.");
|
|
7114
|
+
}
|
|
7115
|
+
if (message.includes("Authentication") || message.includes("could not read Username")) {
|
|
7116
|
+
throw new Error("Push authentication failed. Check SSH keys or credentials.");
|
|
7117
|
+
}
|
|
7118
|
+
throw new Error(`git push failed: ${message}`);
|
|
7119
|
+
}
|
|
7120
|
+
await updateBackupConfig({ lastBackup: timestamp }).catch(() => {
|
|
7121
|
+
});
|
|
7122
|
+
return {
|
|
7123
|
+
success: true,
|
|
7124
|
+
timestamp,
|
|
7125
|
+
message: `Backed up ${categories.length} categor${categories.length === 1 ? "y" : "ies"} to ${repo}.`,
|
|
7126
|
+
committed: true
|
|
7127
|
+
};
|
|
7128
|
+
} finally {
|
|
7129
|
+
if (tmpDir) {
|
|
7130
|
+
await rm2(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
7131
|
+
});
|
|
7132
|
+
}
|
|
7133
|
+
await releaseLock(lockPath);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
7137
|
+
if (isFile) {
|
|
7138
|
+
await ensureDir(resolve17(localPath, ".."));
|
|
7139
|
+
await cp(repoSrcPath, localPath, { force: true });
|
|
7140
|
+
return;
|
|
7141
|
+
}
|
|
7142
|
+
const stagingPath = `${localPath}.syntaur-restore-staging`;
|
|
7143
|
+
const backupPath = `${localPath}.syntaur-restore-backup`;
|
|
7144
|
+
await rm2(stagingPath, { recursive: true, force: true });
|
|
7145
|
+
const backupExistsBefore = await fileExists(backupPath);
|
|
7146
|
+
const localExistsBefore = await fileExists(localPath);
|
|
7147
|
+
if (backupExistsBefore) {
|
|
7148
|
+
if (!localExistsBefore) {
|
|
7149
|
+
await rename2(backupPath, localPath);
|
|
7150
|
+
} else {
|
|
7151
|
+
throw new Error(
|
|
7152
|
+
`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.`
|
|
7153
|
+
);
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7156
|
+
let localMovedAside = false;
|
|
7157
|
+
try {
|
|
7158
|
+
await cp(repoSrcPath, stagingPath, { recursive: true, force: true });
|
|
7159
|
+
const localExists = await fileExists(localPath);
|
|
7160
|
+
if (localExists) {
|
|
7161
|
+
await rename2(localPath, backupPath);
|
|
7162
|
+
localMovedAside = true;
|
|
7163
|
+
}
|
|
7164
|
+
await rename2(stagingPath, localPath);
|
|
7165
|
+
await rm2(backupPath, { recursive: true, force: true }).catch(() => {
|
|
7166
|
+
});
|
|
7167
|
+
} catch (err) {
|
|
7168
|
+
if (localMovedAside && await fileExists(backupPath)) {
|
|
7169
|
+
await rename2(backupPath, localPath).catch(() => {
|
|
7170
|
+
});
|
|
7171
|
+
}
|
|
7172
|
+
await rm2(stagingPath, { recursive: true, force: true }).catch(() => {
|
|
7173
|
+
});
|
|
7174
|
+
throw err;
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
async function restoreFromGithub(overrides) {
|
|
7178
|
+
await checkGitInstalled();
|
|
7179
|
+
const config = await readConfig();
|
|
7180
|
+
const rawRepo = overrides?.repo ?? config.backup?.repo ?? null;
|
|
7181
|
+
if (!rawRepo) {
|
|
7182
|
+
throw new Error("No backup repo configured.");
|
|
7183
|
+
}
|
|
7184
|
+
const repo = rawRepo.trim();
|
|
7185
|
+
if (!validateRepoUrl(repo)) {
|
|
7186
|
+
throw new Error(`Invalid repo URL: "${rawRepo}".`);
|
|
7187
|
+
}
|
|
7188
|
+
const categoriesCsv = config.backup?.categories ?? "missions, playbooks, todos, servers, config";
|
|
7189
|
+
const categories = overrides?.categories ?? resolveCategoriesStrict(categoriesCsv);
|
|
7190
|
+
if (categories.length === 0) {
|
|
7191
|
+
throw new Error("No valid restore categories selected.");
|
|
7192
|
+
}
|
|
7193
|
+
const lockPath = await acquireLock();
|
|
7194
|
+
let tmpDir = null;
|
|
7195
|
+
const restored = [];
|
|
7196
|
+
const failed = [];
|
|
7197
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
7198
|
+
try {
|
|
7199
|
+
await updateBackupConfig({ lastRestore: timestamp });
|
|
7200
|
+
tmpDir = await mkdtemp(join2(tmpdir(), "syntaur-restore-"));
|
|
7201
|
+
await cloneOrInit(repo, tmpDir);
|
|
7202
|
+
for (const category of categories) {
|
|
7203
|
+
if (category === "config") {
|
|
7204
|
+
console.warn('Skipping "config" on restore (would overwrite local backup settings).');
|
|
7205
|
+
continue;
|
|
7206
|
+
}
|
|
7207
|
+
try {
|
|
7208
|
+
const { sourcePath: localPath, repoPath, isFile } = await resolveCategoryPath(category);
|
|
7209
|
+
const repoSrcPath = join2(tmpDir, repoPath);
|
|
7210
|
+
if (!await fileExists(repoSrcPath)) {
|
|
7211
|
+
console.warn(`Category "${category}" not found in backup repo, skipping.`);
|
|
7212
|
+
continue;
|
|
7213
|
+
}
|
|
7214
|
+
await safeRestoreCategory(localPath, repoSrcPath, isFile);
|
|
7215
|
+
restored.push(category);
|
|
7216
|
+
} catch (error) {
|
|
7217
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
7218
|
+
console.error(`Failed to restore "${category}": ${msg}`);
|
|
7219
|
+
failed.push(category);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
const success = failed.length === 0;
|
|
7223
|
+
return {
|
|
7224
|
+
success,
|
|
7225
|
+
timestamp,
|
|
7226
|
+
message: success ? `Restored ${restored.length} categor${restored.length === 1 ? "y" : "ies"} from ${repo}.` : `Partial restore: ${restored.length} succeeded, ${failed.length} failed (${failed.join(", ")}).`,
|
|
7227
|
+
committed: false
|
|
7228
|
+
};
|
|
7229
|
+
} finally {
|
|
7230
|
+
if (tmpDir) {
|
|
7231
|
+
await rm2(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
7232
|
+
});
|
|
7233
|
+
}
|
|
7234
|
+
await releaseLock(lockPath);
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
async function getBackupStatus() {
|
|
7238
|
+
const config = await readConfig();
|
|
7239
|
+
const lockPath = resolve17(syntaurRoot(), LOCK_FILE_NAME);
|
|
7240
|
+
const locked = await fileExists(lockPath);
|
|
7241
|
+
return {
|
|
7242
|
+
repo: config.backup?.repo ?? null,
|
|
7243
|
+
categories: config.backup?.categories ?? "missions, playbooks, todos, servers, config",
|
|
7244
|
+
lastBackup: config.backup?.lastBackup ?? null,
|
|
7245
|
+
lastRestore: config.backup?.lastRestore ?? null,
|
|
7246
|
+
locked
|
|
7247
|
+
};
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
// src/dashboard/api-backup.ts
|
|
7251
|
+
function createBackupRouter() {
|
|
7252
|
+
const router = Router6();
|
|
7253
|
+
router.get("/", async (_req, res) => {
|
|
7254
|
+
try {
|
|
7255
|
+
const status = await getBackupStatus();
|
|
7256
|
+
res.json(status);
|
|
7257
|
+
} catch (error) {
|
|
7258
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
7259
|
+
}
|
|
7260
|
+
});
|
|
7261
|
+
router.put("/config", async (req, res) => {
|
|
7262
|
+
try {
|
|
7263
|
+
const body = req.body ?? {};
|
|
7264
|
+
const updates = {};
|
|
7265
|
+
if (body.repo !== void 0) {
|
|
7266
|
+
const trimmed = typeof body.repo === "string" ? body.repo.trim() : body.repo;
|
|
7267
|
+
if (trimmed !== null && trimmed !== "" && !validateRepoUrl(trimmed)) {
|
|
7268
|
+
return res.status(400).json({
|
|
7269
|
+
error: `Invalid repo URL. Must start with https:// or git@.`
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
7272
|
+
updates.repo = trimmed || null;
|
|
7273
|
+
}
|
|
7274
|
+
if (body.categories !== void 0) {
|
|
7275
|
+
let cats;
|
|
7276
|
+
if (Array.isArray(body.categories)) {
|
|
7277
|
+
cats = body.categories.map((s) => String(s).trim()).filter(Boolean);
|
|
7278
|
+
} else if (typeof body.categories === "string") {
|
|
7279
|
+
cats = body.categories.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7280
|
+
} else {
|
|
7281
|
+
return res.status(400).json({ error: "categories must be a string or array" });
|
|
7282
|
+
}
|
|
7283
|
+
if (cats.length === 0) {
|
|
7284
|
+
return res.status(400).json({
|
|
7285
|
+
error: `No categories provided. Valid: ${VALID_CATEGORIES.join(", ")}`
|
|
7286
|
+
});
|
|
7287
|
+
}
|
|
7288
|
+
try {
|
|
7289
|
+
const valid = parseCategoriesStrict(cats);
|
|
7290
|
+
updates.categories = valid.join(", ");
|
|
7291
|
+
} catch (err) {
|
|
7292
|
+
return res.status(400).json({
|
|
7293
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7294
|
+
});
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7297
|
+
if (Object.keys(updates).length === 0) {
|
|
7298
|
+
return res.status(400).json({ error: "No fields to update" });
|
|
7299
|
+
}
|
|
7300
|
+
await updateBackupConfig(updates);
|
|
7301
|
+
const status = await getBackupStatus();
|
|
7302
|
+
res.json(status);
|
|
7303
|
+
} catch (error) {
|
|
7304
|
+
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
7305
|
+
}
|
|
7306
|
+
});
|
|
7307
|
+
router.post("/push", async (_req, res) => {
|
|
7308
|
+
try {
|
|
7309
|
+
const result = await backupToGithub();
|
|
7310
|
+
res.json(result);
|
|
7311
|
+
} catch (error) {
|
|
7312
|
+
res.status(500).json({
|
|
7313
|
+
success: false,
|
|
7314
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7315
|
+
});
|
|
7316
|
+
}
|
|
7317
|
+
});
|
|
7318
|
+
router.post("/pull", async (_req, res) => {
|
|
7319
|
+
try {
|
|
7320
|
+
const result = await restoreFromGithub();
|
|
7321
|
+
res.json(result);
|
|
7322
|
+
} catch (error) {
|
|
7323
|
+
res.status(500).json({
|
|
7324
|
+
success: false,
|
|
7325
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7326
|
+
});
|
|
7327
|
+
}
|
|
7328
|
+
});
|
|
7329
|
+
return router;
|
|
7330
|
+
}
|
|
7331
|
+
|
|
6877
7332
|
// src/dashboard/autodiscovery.ts
|
|
6878
7333
|
init_scanner();
|
|
6879
7334
|
init_servers();
|
|
@@ -7299,10 +7754,11 @@ function createDashboardServer(options) {
|
|
|
7299
7754
|
app.use("/api/agent-sessions", createAgentSessionsRouter(missionsDir, broadcast));
|
|
7300
7755
|
app.use("/api/playbooks", createPlaybooksRouter(playbooksDir2));
|
|
7301
7756
|
app.use("/api/todos", createTodosRouter(todosDir2, broadcast));
|
|
7757
|
+
app.use("/api/backup", createBackupRouter());
|
|
7302
7758
|
if (serveStaticUi && dashboardDistPath) {
|
|
7303
7759
|
app.use(express.static(dashboardDistPath));
|
|
7304
7760
|
app.get("{*path}", async (_req, res) => {
|
|
7305
|
-
const indexPath =
|
|
7761
|
+
const indexPath = resolve18(dashboardDistPath, "index.html");
|
|
7306
7762
|
if (await fileExists(indexPath)) {
|
|
7307
7763
|
res.sendFile(indexPath);
|
|
7308
7764
|
} else {
|
|
@@ -7334,8 +7790,8 @@ function createDashboardServer(options) {
|
|
|
7334
7790
|
}
|
|
7335
7791
|
});
|
|
7336
7792
|
server.listen(port, () => {
|
|
7337
|
-
const portFile =
|
|
7338
|
-
|
|
7793
|
+
const portFile = resolve18(homedir2(), ".syntaur", "dashboard-port");
|
|
7794
|
+
writeFile4(portFile, String(port), "utf-8").catch(() => {
|
|
7339
7795
|
});
|
|
7340
7796
|
resolvePromise();
|
|
7341
7797
|
});
|
|
@@ -7351,8 +7807,8 @@ function createDashboardServer(options) {
|
|
|
7351
7807
|
client.close();
|
|
7352
7808
|
}
|
|
7353
7809
|
clients.clear();
|
|
7354
|
-
const portFile =
|
|
7355
|
-
await
|
|
7810
|
+
const portFile = resolve18(homedir2(), ".syntaur", "dashboard-port");
|
|
7811
|
+
await unlink4(portFile).catch(() => {
|
|
7356
7812
|
});
|
|
7357
7813
|
return new Promise((resolvePromise) => {
|
|
7358
7814
|
server.close(() => resolvePromise());
|
|
@@ -7430,8 +7886,8 @@ async function dashboardCommand(options) {
|
|
|
7430
7886
|
port = availablePort;
|
|
7431
7887
|
}
|
|
7432
7888
|
const thisFile = fileURLToPath2(import.meta.url);
|
|
7433
|
-
const packageRoot =
|
|
7434
|
-
const dashboardDist =
|
|
7889
|
+
const packageRoot = resolve19(dirname4(thisFile), "..");
|
|
7890
|
+
const dashboardDist = resolve19(packageRoot, "dashboard", "dist");
|
|
7435
7891
|
const server = createDashboardServer({
|
|
7436
7892
|
port,
|
|
7437
7893
|
missionsDir,
|
|
@@ -7444,8 +7900,8 @@ async function dashboardCommand(options) {
|
|
|
7444
7900
|
await server.start();
|
|
7445
7901
|
let viteProcess = null;
|
|
7446
7902
|
if (mode === "dev") {
|
|
7447
|
-
const dashboardDir =
|
|
7448
|
-
const viteBin =
|
|
7903
|
+
const dashboardDir = resolve19(packageRoot, "dashboard");
|
|
7904
|
+
const viteBin = resolve19(dashboardDir, "node_modules", ".bin", "vite");
|
|
7449
7905
|
if (!await fileExists(viteBin)) {
|
|
7450
7906
|
console.error(
|
|
7451
7907
|
'Vite not found. Run "npm ci --prefix dashboard" first, or use the default bundled dashboard mode.'
|
|
@@ -7497,7 +7953,7 @@ async function dashboardCommand(options) {
|
|
|
7497
7953
|
init_paths();
|
|
7498
7954
|
init_fs();
|
|
7499
7955
|
init_config2();
|
|
7500
|
-
import { resolve as
|
|
7956
|
+
import { resolve as resolve20 } from "path";
|
|
7501
7957
|
init_lifecycle();
|
|
7502
7958
|
async function assignCommand(assignment, options) {
|
|
7503
7959
|
if (!options.mission) {
|
|
@@ -7518,8 +7974,8 @@ async function assignCommand(assignment, options) {
|
|
|
7518
7974
|
}
|
|
7519
7975
|
const config = await readConfig();
|
|
7520
7976
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7521
|
-
const missionDir =
|
|
7522
|
-
const missionMdPath =
|
|
7977
|
+
const missionDir = resolve20(baseDir, options.mission);
|
|
7978
|
+
const missionMdPath = resolve20(missionDir, "mission.md");
|
|
7523
7979
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7524
7980
|
throw new Error(
|
|
7525
7981
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7536,7 +7992,7 @@ async function assignCommand(assignment, options) {
|
|
|
7536
7992
|
init_paths();
|
|
7537
7993
|
init_fs();
|
|
7538
7994
|
init_config2();
|
|
7539
|
-
import { resolve as
|
|
7995
|
+
import { resolve as resolve21 } from "path";
|
|
7540
7996
|
init_lifecycle();
|
|
7541
7997
|
async function startCommand(assignment, options) {
|
|
7542
7998
|
if (!options.mission) {
|
|
@@ -7554,8 +8010,8 @@ async function startCommand(assignment, options) {
|
|
|
7554
8010
|
}
|
|
7555
8011
|
const config = await readConfig();
|
|
7556
8012
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7557
|
-
const missionDir =
|
|
7558
|
-
const missionMdPath =
|
|
8013
|
+
const missionDir = resolve21(baseDir, options.mission);
|
|
8014
|
+
const missionMdPath = resolve21(missionDir, "mission.md");
|
|
7559
8015
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7560
8016
|
throw new Error(
|
|
7561
8017
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7579,7 +8035,7 @@ async function startCommand(assignment, options) {
|
|
|
7579
8035
|
init_paths();
|
|
7580
8036
|
init_fs();
|
|
7581
8037
|
init_config2();
|
|
7582
|
-
import { resolve as
|
|
8038
|
+
import { resolve as resolve22 } from "path";
|
|
7583
8039
|
init_lifecycle();
|
|
7584
8040
|
async function completeCommand(assignment, options) {
|
|
7585
8041
|
if (!options.mission) {
|
|
@@ -7597,8 +8053,8 @@ async function completeCommand(assignment, options) {
|
|
|
7597
8053
|
}
|
|
7598
8054
|
const config = await readConfig();
|
|
7599
8055
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7600
|
-
const missionDir =
|
|
7601
|
-
const missionMdPath =
|
|
8056
|
+
const missionDir = resolve22(baseDir, options.mission);
|
|
8057
|
+
const missionMdPath = resolve22(missionDir, "mission.md");
|
|
7602
8058
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7603
8059
|
throw new Error(
|
|
7604
8060
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7615,7 +8071,7 @@ async function completeCommand(assignment, options) {
|
|
|
7615
8071
|
init_paths();
|
|
7616
8072
|
init_fs();
|
|
7617
8073
|
init_config2();
|
|
7618
|
-
import { resolve as
|
|
8074
|
+
import { resolve as resolve23 } from "path";
|
|
7619
8075
|
init_lifecycle();
|
|
7620
8076
|
async function blockCommand(assignment, options) {
|
|
7621
8077
|
if (!options.mission) {
|
|
@@ -7633,8 +8089,8 @@ async function blockCommand(assignment, options) {
|
|
|
7633
8089
|
}
|
|
7634
8090
|
const config = await readConfig();
|
|
7635
8091
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7636
|
-
const missionDir =
|
|
7637
|
-
const missionMdPath =
|
|
8092
|
+
const missionDir = resolve23(baseDir, options.mission);
|
|
8093
|
+
const missionMdPath = resolve23(missionDir, "mission.md");
|
|
7638
8094
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7639
8095
|
throw new Error(
|
|
7640
8096
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7653,7 +8109,7 @@ async function blockCommand(assignment, options) {
|
|
|
7653
8109
|
init_paths();
|
|
7654
8110
|
init_fs();
|
|
7655
8111
|
init_config2();
|
|
7656
|
-
import { resolve as
|
|
8112
|
+
import { resolve as resolve24 } from "path";
|
|
7657
8113
|
init_lifecycle();
|
|
7658
8114
|
async function unblockCommand(assignment, options) {
|
|
7659
8115
|
if (!options.mission) {
|
|
@@ -7671,8 +8127,8 @@ async function unblockCommand(assignment, options) {
|
|
|
7671
8127
|
}
|
|
7672
8128
|
const config = await readConfig();
|
|
7673
8129
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7674
|
-
const missionDir =
|
|
7675
|
-
const missionMdPath =
|
|
8130
|
+
const missionDir = resolve24(baseDir, options.mission);
|
|
8131
|
+
const missionMdPath = resolve24(missionDir, "mission.md");
|
|
7676
8132
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7677
8133
|
throw new Error(
|
|
7678
8134
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7689,7 +8145,7 @@ async function unblockCommand(assignment, options) {
|
|
|
7689
8145
|
init_paths();
|
|
7690
8146
|
init_fs();
|
|
7691
8147
|
init_config2();
|
|
7692
|
-
import { resolve as
|
|
8148
|
+
import { resolve as resolve25 } from "path";
|
|
7693
8149
|
init_lifecycle();
|
|
7694
8150
|
async function reviewCommand(assignment, options) {
|
|
7695
8151
|
if (!options.mission) {
|
|
@@ -7707,8 +8163,8 @@ async function reviewCommand(assignment, options) {
|
|
|
7707
8163
|
}
|
|
7708
8164
|
const config = await readConfig();
|
|
7709
8165
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7710
|
-
const missionDir =
|
|
7711
|
-
const missionMdPath =
|
|
8166
|
+
const missionDir = resolve25(baseDir, options.mission);
|
|
8167
|
+
const missionMdPath = resolve25(missionDir, "mission.md");
|
|
7712
8168
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7713
8169
|
throw new Error(
|
|
7714
8170
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7725,7 +8181,7 @@ async function reviewCommand(assignment, options) {
|
|
|
7725
8181
|
init_paths();
|
|
7726
8182
|
init_fs();
|
|
7727
8183
|
init_config2();
|
|
7728
|
-
import { resolve as
|
|
8184
|
+
import { resolve as resolve26 } from "path";
|
|
7729
8185
|
init_lifecycle();
|
|
7730
8186
|
async function failCommand(assignment, options) {
|
|
7731
8187
|
if (!options.mission) {
|
|
@@ -7743,8 +8199,8 @@ async function failCommand(assignment, options) {
|
|
|
7743
8199
|
}
|
|
7744
8200
|
const config = await readConfig();
|
|
7745
8201
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7746
|
-
const missionDir =
|
|
7747
|
-
const missionMdPath =
|
|
8202
|
+
const missionDir = resolve26(baseDir, options.mission);
|
|
8203
|
+
const missionMdPath = resolve26(missionDir, "mission.md");
|
|
7748
8204
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7749
8205
|
throw new Error(
|
|
7750
8206
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7761,7 +8217,7 @@ async function failCommand(assignment, options) {
|
|
|
7761
8217
|
init_paths();
|
|
7762
8218
|
init_fs();
|
|
7763
8219
|
init_config2();
|
|
7764
|
-
import { resolve as
|
|
8220
|
+
import { resolve as resolve27 } from "path";
|
|
7765
8221
|
init_lifecycle();
|
|
7766
8222
|
async function reopenCommand(assignment, options) {
|
|
7767
8223
|
if (!options.mission) {
|
|
@@ -7779,8 +8235,8 @@ async function reopenCommand(assignment, options) {
|
|
|
7779
8235
|
}
|
|
7780
8236
|
const config = await readConfig();
|
|
7781
8237
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
7782
|
-
const missionDir =
|
|
7783
|
-
const missionMdPath =
|
|
8238
|
+
const missionDir = resolve27(baseDir, options.mission);
|
|
8239
|
+
const missionMdPath = resolve27(missionDir, "mission.md");
|
|
7784
8240
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
7785
8241
|
throw new Error(
|
|
7786
8242
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -7800,32 +8256,32 @@ init_config2();
|
|
|
7800
8256
|
init_config2();
|
|
7801
8257
|
init_fs();
|
|
7802
8258
|
import {
|
|
7803
|
-
cp,
|
|
8259
|
+
cp as cp2,
|
|
7804
8260
|
readdir as readdir8,
|
|
7805
8261
|
symlink,
|
|
7806
8262
|
lstat,
|
|
7807
|
-
readFile as
|
|
8263
|
+
readFile as readFile13,
|
|
7808
8264
|
readlink,
|
|
7809
|
-
rm as
|
|
7810
|
-
unlink as
|
|
7811
|
-
writeFile as
|
|
8265
|
+
rm as rm3,
|
|
8266
|
+
unlink as unlink5,
|
|
8267
|
+
writeFile as writeFile5
|
|
7812
8268
|
} from "fs/promises";
|
|
7813
8269
|
import { existsSync } from "fs";
|
|
7814
8270
|
import { homedir as homedir3 } from "os";
|
|
7815
|
-
import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as
|
|
8271
|
+
import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve29 } from "path";
|
|
7816
8272
|
|
|
7817
8273
|
// src/utils/package-root.ts
|
|
7818
8274
|
init_fs();
|
|
7819
|
-
import { dirname as dirname5, resolve as
|
|
8275
|
+
import { dirname as dirname5, resolve as resolve28 } from "path";
|
|
7820
8276
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7821
8277
|
async function findPackageRoot(expectedRelativePath) {
|
|
7822
8278
|
let currentDir = dirname5(fileURLToPath3(import.meta.url));
|
|
7823
8279
|
while (true) {
|
|
7824
|
-
const candidate =
|
|
8280
|
+
const candidate = resolve28(currentDir, expectedRelativePath);
|
|
7825
8281
|
if (await fileExists(candidate)) {
|
|
7826
8282
|
return currentDir;
|
|
7827
8283
|
}
|
|
7828
|
-
const parentDir =
|
|
8284
|
+
const parentDir = resolve28(currentDir, "..");
|
|
7829
8285
|
if (parentDir === currentDir) {
|
|
7830
8286
|
throw new Error(
|
|
7831
8287
|
`Could not locate package root containing ${expectedRelativePath}.`
|
|
@@ -7846,25 +8302,25 @@ function getPluginManifestRelativePath(pluginKind) {
|
|
|
7846
8302
|
}
|
|
7847
8303
|
function getDefaultPluginTargetDir(pluginKind) {
|
|
7848
8304
|
const home = homedir3();
|
|
7849
|
-
return pluginKind === "claude" ?
|
|
8305
|
+
return pluginKind === "claude" ? resolve29(home, ".claude", "plugins", "syntaur") : resolve29(home, "plugins", "syntaur");
|
|
7850
8306
|
}
|
|
7851
8307
|
function getDefaultMarketplacePath() {
|
|
7852
|
-
return
|
|
8308
|
+
return resolve29(homedir3(), ".agents", "plugins", "marketplace.json");
|
|
7853
8309
|
}
|
|
7854
8310
|
function getClaudeMarketplacesRoot() {
|
|
7855
|
-
return
|
|
8311
|
+
return resolve29(homedir3(), ".claude", "plugins", "marketplaces");
|
|
7856
8312
|
}
|
|
7857
8313
|
function getClaudeKnownMarketplacesPath() {
|
|
7858
|
-
return
|
|
8314
|
+
return resolve29(homedir3(), ".claude", "plugins", "known_marketplaces.json");
|
|
7859
8315
|
}
|
|
7860
8316
|
function getClaudeInstalledPluginsPath() {
|
|
7861
|
-
return
|
|
8317
|
+
return resolve29(homedir3(), ".claude", "plugins", "installed_plugins.json");
|
|
7862
8318
|
}
|
|
7863
8319
|
function getInstallMarkerPath(targetDir) {
|
|
7864
|
-
return
|
|
8320
|
+
return resolve29(targetDir, INSTALL_MARKER_FILENAME);
|
|
7865
8321
|
}
|
|
7866
8322
|
async function readPackageManifest(packageRoot) {
|
|
7867
|
-
const raw = await
|
|
8323
|
+
const raw = await readFile13(resolve29(packageRoot, "package.json"), "utf-8");
|
|
7868
8324
|
return JSON.parse(raw);
|
|
7869
8325
|
}
|
|
7870
8326
|
async function readJsonFileIfExists(pathValue) {
|
|
@@ -7872,7 +8328,7 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
7872
8328
|
return null;
|
|
7873
8329
|
}
|
|
7874
8330
|
try {
|
|
7875
|
-
const raw = await
|
|
8331
|
+
const raw = await readFile13(pathValue, "utf-8");
|
|
7876
8332
|
return JSON.parse(raw);
|
|
7877
8333
|
} catch {
|
|
7878
8334
|
return null;
|
|
@@ -7880,15 +8336,15 @@ async function readJsonFileIfExists(pathValue) {
|
|
|
7880
8336
|
}
|
|
7881
8337
|
async function readClaudePluginManifest(pluginDir) {
|
|
7882
8338
|
return await readJsonFileIfExists(
|
|
7883
|
-
|
|
8339
|
+
resolve29(pluginDir, ".claude-plugin", "plugin.json")
|
|
7884
8340
|
) ?? {};
|
|
7885
8341
|
}
|
|
7886
8342
|
async function readPluginManifestName(targetDir, pluginKind) {
|
|
7887
|
-
const manifestPath =
|
|
8343
|
+
const manifestPath = resolve29(targetDir, getPluginManifestRelativePath(pluginKind));
|
|
7888
8344
|
if (!await fileExists(manifestPath)) {
|
|
7889
8345
|
return void 0;
|
|
7890
8346
|
}
|
|
7891
|
-
const raw = await
|
|
8347
|
+
const raw = await readFile13(manifestPath, "utf-8");
|
|
7892
8348
|
const parsed = JSON.parse(raw);
|
|
7893
8349
|
return parsed.name;
|
|
7894
8350
|
}
|
|
@@ -7898,7 +8354,7 @@ async function readInstallMetadata(targetDir) {
|
|
|
7898
8354
|
return null;
|
|
7899
8355
|
}
|
|
7900
8356
|
try {
|
|
7901
|
-
const raw = await
|
|
8357
|
+
const raw = await readFile13(markerPath, "utf-8");
|
|
7902
8358
|
return JSON.parse(raw);
|
|
7903
8359
|
} catch {
|
|
7904
8360
|
return null;
|
|
@@ -7911,7 +8367,7 @@ async function getInstallStatus(targetDir, pluginKind) {
|
|
|
7911
8367
|
const info = await lstat(targetDir);
|
|
7912
8368
|
if (info.isSymbolicLink()) {
|
|
7913
8369
|
const symlinkTarget = await readlink(targetDir);
|
|
7914
|
-
const resolvedTarget =
|
|
8370
|
+
const resolvedTarget = resolve29(dirname6(targetDir), symlinkTarget);
|
|
7915
8371
|
const manifestName2 = await readPluginManifestName(resolvedTarget, pluginKind);
|
|
7916
8372
|
return {
|
|
7917
8373
|
exists: true,
|
|
@@ -7940,7 +8396,7 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
7940
8396
|
installMode,
|
|
7941
8397
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7942
8398
|
};
|
|
7943
|
-
await
|
|
8399
|
+
await writeFile5(
|
|
7944
8400
|
getInstallMarkerPath(targetDir),
|
|
7945
8401
|
`${JSON.stringify(metadata, null, 2)}
|
|
7946
8402
|
`,
|
|
@@ -7949,20 +8405,20 @@ async function writeInstallMetadata(targetDir, pluginKind, installMode, packageM
|
|
|
7949
8405
|
}
|
|
7950
8406
|
async function installCopy(paths, pluginKind) {
|
|
7951
8407
|
await ensureDir(dirname6(paths.targetDir));
|
|
7952
|
-
await
|
|
8408
|
+
await cp2(paths.sourceDir, paths.targetDir, { recursive: true });
|
|
7953
8409
|
const packageManifest = await readPackageManifest(paths.packageRoot);
|
|
7954
8410
|
await writeInstallMetadata(paths.targetDir, pluginKind, "copy", packageManifest);
|
|
7955
8411
|
}
|
|
7956
8412
|
async function installLink(paths) {
|
|
7957
8413
|
await ensureDir(dirname6(paths.targetDir));
|
|
7958
|
-
await
|
|
8414
|
+
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
7959
8415
|
await ensureDir(dirname6(paths.targetDir));
|
|
7960
|
-
await symlink(
|
|
8416
|
+
await symlink(resolve29(paths.sourceDir), paths.targetDir, "dir");
|
|
7961
8417
|
}
|
|
7962
8418
|
async function removeInstallMarker(targetDir) {
|
|
7963
8419
|
const markerPath = getInstallMarkerPath(targetDir);
|
|
7964
8420
|
if (await fileExists(markerPath)) {
|
|
7965
|
-
await
|
|
8421
|
+
await unlink5(markerPath).catch(() => {
|
|
7966
8422
|
});
|
|
7967
8423
|
}
|
|
7968
8424
|
}
|
|
@@ -7971,13 +8427,13 @@ function normalizeAbsoluteInstallPath(pathValue, label) {
|
|
|
7971
8427
|
if (!isAbsolute2(expanded)) {
|
|
7972
8428
|
throw new Error(`${label} must be an absolute path.`);
|
|
7973
8429
|
}
|
|
7974
|
-
return
|
|
8430
|
+
return resolve29(expanded);
|
|
7975
8431
|
}
|
|
7976
8432
|
async function resolvePluginPaths(pluginKind, targetDir) {
|
|
7977
8433
|
const packageRoot = await findPackageRoot(getPluginRelativePath(pluginKind));
|
|
7978
8434
|
return {
|
|
7979
8435
|
packageRoot,
|
|
7980
|
-
sourceDir:
|
|
8436
|
+
sourceDir: resolve29(packageRoot, getPluginRelativePath(pluginKind)),
|
|
7981
8437
|
targetDir: targetDir ?? getDefaultPluginTargetDir(pluginKind)
|
|
7982
8438
|
};
|
|
7983
8439
|
}
|
|
@@ -8012,7 +8468,7 @@ async function readClaudeMarketplaceFile(manifestPath) {
|
|
|
8012
8468
|
}
|
|
8013
8469
|
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
8014
8470
|
await ensureDir(dirname6(manifestPath));
|
|
8015
|
-
await
|
|
8471
|
+
await writeFile5(manifestPath, `${JSON.stringify(marketplace, null, 2)}
|
|
8016
8472
|
`, "utf-8");
|
|
8017
8473
|
}
|
|
8018
8474
|
function buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir) {
|
|
@@ -8081,8 +8537,8 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
8081
8537
|
if (!entry.isDirectory()) {
|
|
8082
8538
|
continue;
|
|
8083
8539
|
}
|
|
8084
|
-
const candidateRoot =
|
|
8085
|
-
const manifestPath =
|
|
8540
|
+
const candidateRoot = resolve29(rootDir, entry.name);
|
|
8541
|
+
const manifestPath = resolve29(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
8086
8542
|
if (!await fileExists(manifestPath)) {
|
|
8087
8543
|
continue;
|
|
8088
8544
|
}
|
|
@@ -8109,11 +8565,11 @@ async function listClaudeMarketplaceCandidates() {
|
|
|
8109
8565
|
if (!installLocation) {
|
|
8110
8566
|
continue;
|
|
8111
8567
|
}
|
|
8112
|
-
const candidateRoot =
|
|
8568
|
+
const candidateRoot = resolve29(expandHome(installLocation));
|
|
8113
8569
|
if (seen.has(candidateRoot)) {
|
|
8114
8570
|
continue;
|
|
8115
8571
|
}
|
|
8116
|
-
const manifestPath =
|
|
8572
|
+
const manifestPath = resolve29(candidateRoot, ".claude-plugin", "marketplace.json");
|
|
8117
8573
|
if (!await fileExists(manifestPath)) {
|
|
8118
8574
|
continue;
|
|
8119
8575
|
}
|
|
@@ -8141,7 +8597,7 @@ async function getPreferredClaudeMarketplace() {
|
|
|
8141
8597
|
name: candidate.name,
|
|
8142
8598
|
rootDir: candidate.rootDir,
|
|
8143
8599
|
manifestPath: candidate.manifestPath,
|
|
8144
|
-
targetDir:
|
|
8600
|
+
targetDir: resolve29(candidate.rootDir, "plugins", "syntaur")
|
|
8145
8601
|
};
|
|
8146
8602
|
}
|
|
8147
8603
|
async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
@@ -8151,7 +8607,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
8151
8607
|
return null;
|
|
8152
8608
|
}
|
|
8153
8609
|
const rootDir = dirname6(pluginsDir);
|
|
8154
|
-
const manifestPath =
|
|
8610
|
+
const manifestPath = resolve29(rootDir, ".claude-plugin", "marketplace.json");
|
|
8155
8611
|
if (!await fileExists(manifestPath)) {
|
|
8156
8612
|
return null;
|
|
8157
8613
|
}
|
|
@@ -8167,7 +8623,7 @@ async function detectClaudeMarketplaceForTarget(targetDir) {
|
|
|
8167
8623
|
async function findManagedClaudeMarketplacePluginDir() {
|
|
8168
8624
|
const marketplaces = await listClaudeMarketplaceCandidates();
|
|
8169
8625
|
for (const marketplace of marketplaces) {
|
|
8170
|
-
const targetDir =
|
|
8626
|
+
const targetDir = resolve29(marketplace.rootDir, "plugins", "syntaur");
|
|
8171
8627
|
const status = await getInstallStatus(targetDir, "claude");
|
|
8172
8628
|
if (status.exists && status.managed) {
|
|
8173
8629
|
return targetDir;
|
|
@@ -8292,7 +8748,7 @@ async function installManagedPlugin(options) {
|
|
|
8292
8748
|
`${paths.targetDir} already exists and is not a Syntaur-managed install. Remove it manually before installing Syntaur there.`
|
|
8293
8749
|
);
|
|
8294
8750
|
}
|
|
8295
|
-
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget ===
|
|
8751
|
+
if (desiredMode === "link" && existing.exists && existing.installMode === "link" && existing.symlinkTarget === resolve29(paths.sourceDir) && !force) {
|
|
8296
8752
|
return {
|
|
8297
8753
|
targetDir: paths.targetDir,
|
|
8298
8754
|
sourceDir: paths.sourceDir,
|
|
@@ -8301,7 +8757,7 @@ async function installManagedPlugin(options) {
|
|
|
8301
8757
|
};
|
|
8302
8758
|
}
|
|
8303
8759
|
if (existing.exists) {
|
|
8304
|
-
await
|
|
8760
|
+
await rm3(paths.targetDir, { recursive: true, force: true });
|
|
8305
8761
|
}
|
|
8306
8762
|
if (desiredMode === "link") {
|
|
8307
8763
|
await installLink(paths);
|
|
@@ -8344,7 +8800,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
8344
8800
|
plugins: []
|
|
8345
8801
|
};
|
|
8346
8802
|
}
|
|
8347
|
-
const raw = await
|
|
8803
|
+
const raw = await readFile13(marketplacePath, "utf-8");
|
|
8348
8804
|
const parsed = JSON.parse(raw);
|
|
8349
8805
|
return {
|
|
8350
8806
|
name: parsed.name ?? "local",
|
|
@@ -8354,7 +8810,7 @@ async function readMarketplaceFile(marketplacePath) {
|
|
|
8354
8810
|
}
|
|
8355
8811
|
async function writeMarketplaceFile(marketplacePath, marketplace) {
|
|
8356
8812
|
await ensureDir(dirname6(marketplacePath));
|
|
8357
|
-
await
|
|
8813
|
+
await writeFile5(marketplacePath, `${JSON.stringify(marketplace, null, 2)}
|
|
8358
8814
|
`, "utf-8");
|
|
8359
8815
|
}
|
|
8360
8816
|
async function hasAnySyntaurMarketplaceEntry(marketplacePath) {
|
|
@@ -8440,7 +8896,7 @@ async function removeMarketplaceEntry(options) {
|
|
|
8440
8896
|
return { marketplacePath, removed: false };
|
|
8441
8897
|
}
|
|
8442
8898
|
if (marketplace.plugins.length === 0 && isDefaultMarketplaceShell(marketplace)) {
|
|
8443
|
-
await
|
|
8899
|
+
await rm3(marketplacePath, { force: true });
|
|
8444
8900
|
return { marketplacePath, removed: true };
|
|
8445
8901
|
}
|
|
8446
8902
|
await writeMarketplaceFile(marketplacePath, marketplace);
|
|
@@ -8461,7 +8917,7 @@ async function uninstallManagedPlugin(pluginKind, targetDir = getDefaultPluginTa
|
|
|
8461
8917
|
);
|
|
8462
8918
|
}
|
|
8463
8919
|
await removeInstallMarker(normalizedTarget);
|
|
8464
|
-
await
|
|
8920
|
+
await rm3(normalizedTarget, { recursive: true, force: true });
|
|
8465
8921
|
return { removed: true, targetDir: normalizedTarget };
|
|
8466
8922
|
}
|
|
8467
8923
|
async function getConfiguredOrLegacyManagedPluginDir(pluginKind) {
|
|
@@ -8505,13 +8961,13 @@ async function recommendMarketplacePath() {
|
|
|
8505
8961
|
return configuredOrManaged ?? getDefaultMarketplacePath();
|
|
8506
8962
|
}
|
|
8507
8963
|
async function isSyntaurDataInstalled() {
|
|
8508
|
-
return fileExists(
|
|
8964
|
+
return fileExists(resolve29(syntaurRoot(), "config.md"));
|
|
8509
8965
|
}
|
|
8510
8966
|
async function removeSyntaurData() {
|
|
8511
|
-
await
|
|
8967
|
+
await rm3(syntaurRoot(), { recursive: true, force: true });
|
|
8512
8968
|
}
|
|
8513
8969
|
async function getConfiguredMissionDir() {
|
|
8514
|
-
if (!await fileExists(
|
|
8970
|
+
if (!await fileExists(resolve29(syntaurRoot(), "config.md"))) {
|
|
8515
8971
|
return null;
|
|
8516
8972
|
}
|
|
8517
8973
|
return (await readConfig()).defaultMissionDir;
|
|
@@ -8857,7 +9313,7 @@ async function setupCommand(options) {
|
|
|
8857
9313
|
}
|
|
8858
9314
|
|
|
8859
9315
|
// src/commands/uninstall.ts
|
|
8860
|
-
import { resolve as
|
|
9316
|
+
import { resolve as resolve30 } from "path";
|
|
8861
9317
|
init_paths();
|
|
8862
9318
|
function expandTargets(options) {
|
|
8863
9319
|
if (options.all) {
|
|
@@ -8937,7 +9393,7 @@ async function uninstallCommand(options) {
|
|
|
8937
9393
|
const configuredMissionDir = await getConfiguredMissionDir();
|
|
8938
9394
|
await removeSyntaurData();
|
|
8939
9395
|
console.log(`Removed ${syntaurRoot()}`);
|
|
8940
|
-
if (configuredMissionDir &&
|
|
9396
|
+
if (configuredMissionDir && resolve30(configuredMissionDir) !== resolve30(syntaurRoot(), "missions")) {
|
|
8941
9397
|
console.warn(
|
|
8942
9398
|
`Warning: config.md pointed to an external mission directory (${configuredMissionDir}). That directory was not removed automatically.`
|
|
8943
9399
|
);
|
|
@@ -8952,7 +9408,7 @@ async function uninstallCommand(options) {
|
|
|
8952
9408
|
init_paths();
|
|
8953
9409
|
init_fs();
|
|
8954
9410
|
init_config2();
|
|
8955
|
-
import { resolve as
|
|
9411
|
+
import { resolve as resolve31 } from "path";
|
|
8956
9412
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
8957
9413
|
async function setupAdapterCommand(framework, options) {
|
|
8958
9414
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -8978,19 +9434,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
8978
9434
|
}
|
|
8979
9435
|
const config = await readConfig();
|
|
8980
9436
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
8981
|
-
const missionDir =
|
|
8982
|
-
const assignmentDir =
|
|
9437
|
+
const missionDir = resolve31(baseDir, options.mission);
|
|
9438
|
+
const assignmentDir = resolve31(
|
|
8983
9439
|
missionDir,
|
|
8984
9440
|
"assignments",
|
|
8985
9441
|
options.assignment
|
|
8986
9442
|
);
|
|
8987
|
-
const missionMdPath =
|
|
9443
|
+
const missionMdPath = resolve31(missionDir, "mission.md");
|
|
8988
9444
|
if (!await fileExists(missionDir) || !await fileExists(missionMdPath)) {
|
|
8989
9445
|
throw new Error(
|
|
8990
9446
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
8991
9447
|
);
|
|
8992
9448
|
}
|
|
8993
|
-
const assignmentMdPath =
|
|
9449
|
+
const assignmentMdPath = resolve31(assignmentDir, "assignment.md");
|
|
8994
9450
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
8995
9451
|
throw new Error(
|
|
8996
9452
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -9018,15 +9474,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
9018
9474
|
}
|
|
9019
9475
|
}
|
|
9020
9476
|
if (framework === "cursor") {
|
|
9021
|
-
const protocolPath =
|
|
9022
|
-
const assignmentPath =
|
|
9477
|
+
const protocolPath = resolve31(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
9478
|
+
const assignmentPath = resolve31(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
9023
9479
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
9024
9480
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
9025
9481
|
} else if (framework === "codex" || framework === "opencode") {
|
|
9026
|
-
const agentsPath =
|
|
9482
|
+
const agentsPath = resolve31(cwd, "AGENTS.md");
|
|
9027
9483
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
9028
9484
|
if (framework === "opencode") {
|
|
9029
|
-
const configPath =
|
|
9485
|
+
const configPath = resolve31(cwd, "opencode.json");
|
|
9030
9486
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ missionDir }));
|
|
9031
9487
|
}
|
|
9032
9488
|
}
|
|
@@ -9051,7 +9507,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
9051
9507
|
init_paths();
|
|
9052
9508
|
init_fs();
|
|
9053
9509
|
init_config2();
|
|
9054
|
-
import { resolve as
|
|
9510
|
+
import { resolve as resolve32 } from "path";
|
|
9055
9511
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
9056
9512
|
async function trackSessionCommand(options) {
|
|
9057
9513
|
if (!options.agent) {
|
|
@@ -9060,7 +9516,7 @@ async function trackSessionCommand(options) {
|
|
|
9060
9516
|
if (options.mission) {
|
|
9061
9517
|
const config = await readConfig();
|
|
9062
9518
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultMissionDir;
|
|
9063
|
-
const missionDir =
|
|
9519
|
+
const missionDir = resolve32(baseDir, options.mission);
|
|
9064
9520
|
if (!await fileExists(missionDir)) {
|
|
9065
9521
|
throw new Error(
|
|
9066
9522
|
`Mission "${options.mission}" not found at ${missionDir}.`
|
|
@@ -9112,7 +9568,7 @@ async function browseCommand(options) {
|
|
|
9112
9568
|
}
|
|
9113
9569
|
|
|
9114
9570
|
// src/commands/create-playbook.ts
|
|
9115
|
-
import { resolve as
|
|
9571
|
+
import { resolve as resolve34 } from "path";
|
|
9116
9572
|
init_timestamp();
|
|
9117
9573
|
init_paths();
|
|
9118
9574
|
init_fs();
|
|
@@ -9128,7 +9584,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
9128
9584
|
}
|
|
9129
9585
|
const dir = playbooksDir();
|
|
9130
9586
|
await ensureDir(dir);
|
|
9131
|
-
const filePath =
|
|
9587
|
+
const filePath = resolve34(dir, `${slug}.md`);
|
|
9132
9588
|
if (await fileExists(filePath)) {
|
|
9133
9589
|
throw new Error(
|
|
9134
9590
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -9149,8 +9605,8 @@ Use --slug to specify a different slug.`
|
|
|
9149
9605
|
init_paths();
|
|
9150
9606
|
init_fs();
|
|
9151
9607
|
init_parser();
|
|
9152
|
-
import { readdir as readdir9, readFile as
|
|
9153
|
-
import { resolve as
|
|
9608
|
+
import { readdir as readdir9, readFile as readFile14 } from "fs/promises";
|
|
9609
|
+
import { resolve as resolve35 } from "path";
|
|
9154
9610
|
async function listPlaybooksCommand() {
|
|
9155
9611
|
const dir = playbooksDir();
|
|
9156
9612
|
if (!await fileExists(dir)) {
|
|
@@ -9168,8 +9624,8 @@ async function listPlaybooksCommand() {
|
|
|
9168
9624
|
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
9169
9625
|
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
9170
9626
|
for (const entry of mdFiles) {
|
|
9171
|
-
const filePath =
|
|
9172
|
-
const raw = await
|
|
9627
|
+
const filePath = resolve35(dir, entry.name);
|
|
9628
|
+
const raw = await readFile14(filePath, "utf-8");
|
|
9173
9629
|
const parsed = parsePlaybook(raw);
|
|
9174
9630
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
9175
9631
|
const name = parsed.name || slug;
|
|
@@ -9183,8 +9639,8 @@ init_paths();
|
|
|
9183
9639
|
init_parser2();
|
|
9184
9640
|
init_fs();
|
|
9185
9641
|
import { Command } from "commander";
|
|
9186
|
-
import { readFile as
|
|
9187
|
-
import { resolve as
|
|
9642
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
9643
|
+
import { resolve as resolve36 } from "path";
|
|
9188
9644
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
9189
9645
|
function resolveWorkspace(options) {
|
|
9190
9646
|
if (options.global) return "_global";
|
|
@@ -9465,10 +9921,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
9465
9921
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
9466
9922
|
);
|
|
9467
9923
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
9468
|
-
await ensureDir(
|
|
9924
|
+
await ensureDir(resolve36(todosPath, "archive"));
|
|
9469
9925
|
let archContent = "";
|
|
9470
9926
|
if (await fileExists(archFile)) {
|
|
9471
|
-
archContent = await
|
|
9927
|
+
archContent = await readFile15(archFile, "utf-8");
|
|
9472
9928
|
archContent = archContent.trimEnd() + "\n\n";
|
|
9473
9929
|
} else {
|
|
9474
9930
|
archContent = `---
|
|
@@ -9575,6 +10031,83 @@ todoCommand.command("promote").description("Promote a todo to a full assignment"
|
|
|
9575
10031
|
}
|
|
9576
10032
|
});
|
|
9577
10033
|
|
|
10034
|
+
// src/commands/backup.ts
|
|
10035
|
+
init_config2();
|
|
10036
|
+
import { Command as Command2 } from "commander";
|
|
10037
|
+
function parseCategoryOption(csv) {
|
|
10038
|
+
if (!csv) return void 0;
|
|
10039
|
+
const parts = csv.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10040
|
+
if (parts.length === 0) {
|
|
10041
|
+
throw new Error(`No categories provided. Valid: ${VALID_CATEGORIES.join(", ")}`);
|
|
10042
|
+
}
|
|
10043
|
+
return parseCategoriesStrict(parts);
|
|
10044
|
+
}
|
|
10045
|
+
var backupCommand = new Command2("backup").description("Back up Syntaur files to a GitHub repository");
|
|
10046
|
+
backupCommand.command("push").description("Push a backup to the configured GitHub repo").option("--repo <url>", "Override the configured repo URL").option("--categories <list>", "Comma-separated categories to back up (missions, playbooks, todos, servers, config)").action(async (options) => {
|
|
10047
|
+
try {
|
|
10048
|
+
const result = await backupToGithub({
|
|
10049
|
+
repo: options.repo,
|
|
10050
|
+
categories: parseCategoryOption(options.categories)
|
|
10051
|
+
});
|
|
10052
|
+
console.log(result.message);
|
|
10053
|
+
if (result.committed) {
|
|
10054
|
+
console.log(` timestamp: ${result.timestamp}`);
|
|
10055
|
+
}
|
|
10056
|
+
} catch (error) {
|
|
10057
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
10058
|
+
process.exit(1);
|
|
10059
|
+
}
|
|
10060
|
+
});
|
|
10061
|
+
backupCommand.command("pull").description("Restore Syntaur files from the configured GitHub repo").option("--repo <url>", "Override the configured repo URL").option("--categories <list>", "Comma-separated categories to restore").action(async (options) => {
|
|
10062
|
+
try {
|
|
10063
|
+
const result = await restoreFromGithub({
|
|
10064
|
+
repo: options.repo,
|
|
10065
|
+
categories: parseCategoryOption(options.categories)
|
|
10066
|
+
});
|
|
10067
|
+
console.log(result.message);
|
|
10068
|
+
console.log(` timestamp: ${result.timestamp}`);
|
|
10069
|
+
} catch (error) {
|
|
10070
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
10071
|
+
process.exit(1);
|
|
10072
|
+
}
|
|
10073
|
+
});
|
|
10074
|
+
backupCommand.command("config").description("Show or update backup configuration").option("--repo <url>", "Set the backup repo URL").option("--categories <list>", "Set the default categories (comma-separated)").action(async (options) => {
|
|
10075
|
+
try {
|
|
10076
|
+
const updates = {};
|
|
10077
|
+
if (options.repo !== void 0) {
|
|
10078
|
+
const trimmed = typeof options.repo === "string" ? options.repo.trim() : options.repo;
|
|
10079
|
+
if (!validateRepoUrl(trimmed)) {
|
|
10080
|
+
throw new Error(`Invalid repo URL: "${options.repo}". Must start with https:// or git@.`);
|
|
10081
|
+
}
|
|
10082
|
+
updates.repo = trimmed;
|
|
10083
|
+
}
|
|
10084
|
+
if (options.categories !== void 0) {
|
|
10085
|
+
const parts = options.categories.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10086
|
+
if (parts.length === 0) {
|
|
10087
|
+
throw new Error(`No categories provided. Valid: ${VALID_CATEGORIES.join(", ")}`);
|
|
10088
|
+
}
|
|
10089
|
+
const valid = parseCategoriesStrict(parts);
|
|
10090
|
+
updates.categories = valid.join(", ");
|
|
10091
|
+
}
|
|
10092
|
+
if (Object.keys(updates).length > 0) {
|
|
10093
|
+
await updateBackupConfig(updates);
|
|
10094
|
+
console.log("Backup configuration updated.");
|
|
10095
|
+
}
|
|
10096
|
+
const status = await getBackupStatus();
|
|
10097
|
+
console.log("\nBackup configuration:");
|
|
10098
|
+
console.log(` repo: ${status.repo ?? "(not set)"}`);
|
|
10099
|
+
console.log(` categories: ${status.categories}`);
|
|
10100
|
+
console.log(` lastBackup: ${status.lastBackup ?? "(never)"}`);
|
|
10101
|
+
console.log(` lastRestore: ${status.lastRestore ?? "(never)"}`);
|
|
10102
|
+
if (status.locked) {
|
|
10103
|
+
console.log(" \u26A0 locked: a backup operation is in progress or the lock is stale");
|
|
10104
|
+
}
|
|
10105
|
+
} catch (error) {
|
|
10106
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
10107
|
+
process.exit(1);
|
|
10108
|
+
}
|
|
10109
|
+
});
|
|
10110
|
+
|
|
9578
10111
|
// src/cli-default-command.ts
|
|
9579
10112
|
init_config2();
|
|
9580
10113
|
import { readdir as readdir10 } from "fs/promises";
|
|
@@ -9614,7 +10147,7 @@ async function getDefaultCommandName() {
|
|
|
9614
10147
|
}
|
|
9615
10148
|
|
|
9616
10149
|
// src/index.ts
|
|
9617
|
-
var program = new
|
|
10150
|
+
var program = new Command3();
|
|
9618
10151
|
program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version("0.1.0");
|
|
9619
10152
|
program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
|
|
9620
10153
|
try {
|
|
@@ -9856,6 +10389,7 @@ program.command("list-playbooks").description("List all playbooks").action(async
|
|
|
9856
10389
|
}
|
|
9857
10390
|
});
|
|
9858
10391
|
program.addCommand(todoCommand);
|
|
10392
|
+
program.addCommand(backupCommand);
|
|
9859
10393
|
if (process.argv.length <= 2) {
|
|
9860
10394
|
process.argv.push(await getDefaultCommandName());
|
|
9861
10395
|
}
|