syntaur 0.5.0 → 0.5.1
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-Bcut0btZ.js → _basePickBy-ij-Ukp6s.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-AQSP2JEk.js → _baseUniq-CZKk9gZR.js} +1 -1
- package/dashboard/dist/assets/{arc-BLTpY9lc.js → arc-C30UbJZB.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CJtwMY_X.js → architectureDiagram-2XIMDMQ5-BDVieGIr.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-Don-O7X7.js → blockDiagram-WCTKOSBZ-DZYY4t9w.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-C_M3yTTB.js → c4Diagram-IC4MRINW-B019MXol.js} +1 -1
- package/dashboard/dist/assets/channel-DH4gshIt.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-CGss0jXe.js → chunk-4BX2VUAB-DrkTwY15.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-BatoPJga.js → chunk-55IACEB6-mFTAE8DD.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-DxH4wO82.js → chunk-FMBD7UC4-VgDZaNoS.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-BL3izAFQ.js → chunk-JSJVCQXG-C_KXaq-c.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-GnqXwnge.js → chunk-KX2RTZJC-DI-P_pPL.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-gvCn4QMb.js → chunk-NQ4KR5QH-TgYAsxTk.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-CYGWogyi.js → chunk-QZHKN3VN-Drfv_VpM.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-D9mVTQ1F.js → chunk-WL4C6EOR-CpLwvo_U.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-emsfh8H4.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-emsfh8H4.js +1 -0
- package/dashboard/dist/assets/clone-gdeRwgBN.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CUWQCKt4.js → cose-bilkent-S5V4N54A-CkKtF37m.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CH3ijEvV.js → dagre-KLK3FWXG-BBlY_FL3.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-sq83lpV1.js → diagram-E7M64L7V-DLsFDLHm.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-BzQG_rtq.js → diagram-IFDJBPK2--sb7diMG.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-Dg0eZn0q.js → diagram-P4PSJMXO-D2LuEWVt.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-4b9eQ0uj.js → erDiagram-INFDFZHY-C1BEeili.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-C9fzKcsZ.js → flowDiagram-PKNHOUZH-BpbapQbU.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-Bzt6i9SH.js → ganttDiagram-A5KZAMGK-Io60qUuG.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-D0wFOagh.js → gitGraphDiagram-K3NZZRJ6-oemlGgRh.js} +1 -1
- package/dashboard/dist/assets/{graph-EEIGvqDh.js → graph-BZb-lGfH.js} +1 -1
- package/dashboard/dist/assets/index-BSVCsfvM.css +1 -0
- package/dashboard/dist/assets/index-CXWVuGs-.js +481 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-DLYMsj1D.js → infoDiagram-LFFYTUFH-Ca4mwnZF.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DVebKkzl.js → ishikawaDiagram-PHBUUO56-9zuQ8y8W.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-BsmgOWVw.js → journeyDiagram-4ABVD52K-OdeeOdMx.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BTnHf0ey.js → kanban-definition-K7BYSVSG-Cie4JtFn.js} +1 -1
- package/dashboard/dist/assets/{layout-BbM7HRvv.js → layout-Bmx2mvFv.js} +1 -1
- package/dashboard/dist/assets/{linear-C37bJKPO.js → linear-CW6K_-MX.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-MZ_JgnRL.js → mermaid.core-DmfO6BgK.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CgHS4hFo.js → mindmap-definition-YRQLILUH-L6b3vG79.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CmAgopJe.js → pieDiagram-SKSYHLDU-CkHTCIWg.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-BvzYUPR6.js → quadrantDiagram-337W2JSQ-B9MqhhIC.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Bs52VP7k.js → requirementDiagram-Z7DCOOCP-CyHAfXCK.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-aXvGPR1o.js → sankeyDiagram-WA2Y5GQK-DHNzGGyE.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-CzgcfU6K.js → sequenceDiagram-2WXFIKYE-BVvcJkrx.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-BXBJf9Hq.js → stateDiagram-RAJIS63D-CZ2cknh7.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-C4CPervD.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-BsXp26Ai.js → timeline-definition-YZTLITO2-BXUtlVyd.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-C3WbDii1.js → treemap-KZPCXAKY-Dgi-hMKM.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-B28LMHWd.js → vennDiagram-LZ73GAT5-C9zGrrUQ.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-C3Xwz8mS.js → xychartDiagram-JWTSCODW-Dq71BUtc.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/syntaur-logo.svg +14 -0
- package/dist/dashboard/server.js +332 -6
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1253 -129
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/channel-BfXmPwE5.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-D7_G1qy0.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-D7_G1qy0.js +0 -1
- package/dashboard/dist/assets/clone-BKG-N796.js +0 -1
- package/dashboard/dist/assets/index-Bu6ma6my.css +0 -1
- package/dashboard/dist/assets/index-C7f0ySJE.js +0 -481
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-QqOtsuOs.js +0 -1
package/dist/index.js
CHANGED
|
@@ -555,6 +555,53 @@ var init_fs_migration = __esm({
|
|
|
555
555
|
// src/utils/config.ts
|
|
556
556
|
import { readFile as readFile2 } from "fs/promises";
|
|
557
557
|
import { resolve as resolve3, isAbsolute } from "path";
|
|
558
|
+
function parseAgentCommand(value, agentId) {
|
|
559
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
560
|
+
throw new AgentConfigError(
|
|
561
|
+
`agent${agentId ? ` "${agentId}"` : ""} has empty command`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
const expanded = expandHome(value.trim());
|
|
565
|
+
if (isAbsolute(expanded)) {
|
|
566
|
+
return resolve3(expanded);
|
|
567
|
+
}
|
|
568
|
+
if (expanded.includes("/")) {
|
|
569
|
+
throw new AgentConfigError(
|
|
570
|
+
`agent${agentId ? ` "${agentId}"` : ""} command "${value}" is a relative path \u2014 use an absolute path or a bare binary name`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return expanded;
|
|
574
|
+
}
|
|
575
|
+
function validateAgentList(agents) {
|
|
576
|
+
const seen = /* @__PURE__ */ new Set();
|
|
577
|
+
let defaults = 0;
|
|
578
|
+
for (const agent of agents) {
|
|
579
|
+
if (!AGENT_ID_PATTERN.test(agent.id)) {
|
|
580
|
+
throw new AgentConfigError(
|
|
581
|
+
`agent id "${agent.id}" is invalid \u2014 must match /^[a-z0-9][a-z0-9_-]*$/`
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (seen.has(agent.id)) {
|
|
585
|
+
throw new AgentConfigError(`duplicate agent id "${agent.id}"`);
|
|
586
|
+
}
|
|
587
|
+
seen.add(agent.id);
|
|
588
|
+
if (!agent.label || agent.label.trim() === "") {
|
|
589
|
+
throw new AgentConfigError(`agent "${agent.id}" has empty label`);
|
|
590
|
+
}
|
|
591
|
+
parseAgentCommand(agent.command, agent.id);
|
|
592
|
+
if (agent.promptArgPosition !== void 0 && !PROMPT_ARG_POSITIONS.includes(agent.promptArgPosition)) {
|
|
593
|
+
throw new AgentConfigError(
|
|
594
|
+
`agent "${agent.id}" has invalid promptArgPosition "${agent.promptArgPosition}" \u2014 expected first|last|none`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (agent.default) defaults++;
|
|
598
|
+
}
|
|
599
|
+
if (defaults > 1) {
|
|
600
|
+
throw new AgentConfigError(
|
|
601
|
+
`more than one agent is marked default: true (only one is allowed)`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
558
605
|
function cloneDefaultConfig() {
|
|
559
606
|
return {
|
|
560
607
|
...DEFAULT_CONFIG,
|
|
@@ -571,9 +618,14 @@ function cloneDefaultConfig() {
|
|
|
571
618
|
definitions: DEFAULT_CONFIG.types.definitions.map((d) => ({ ...d })),
|
|
572
619
|
default: DEFAULT_CONFIG.types.default
|
|
573
620
|
} : null,
|
|
621
|
+
agents: DEFAULT_CONFIG.agents ? DEFAULT_CONFIG.agents.map((a) => ({
|
|
622
|
+
...a,
|
|
623
|
+
...a.args ? { args: [...a.args] } : {}
|
|
624
|
+
})) : null,
|
|
574
625
|
playbooks: {
|
|
575
626
|
disabled: [...DEFAULT_CONFIG.playbooks.disabled]
|
|
576
|
-
}
|
|
627
|
+
},
|
|
628
|
+
theme: DEFAULT_CONFIG.theme ? { ...DEFAULT_CONFIG.theme } : null
|
|
577
629
|
};
|
|
578
630
|
}
|
|
579
631
|
function parseFrontmatter(content) {
|
|
@@ -823,6 +875,71 @@ ${normalizedFm}
|
|
|
823
875
|
---${afterFrontmatter}`;
|
|
824
876
|
await writeFileForce(configPath, newContent);
|
|
825
877
|
}
|
|
878
|
+
function parseThemeConfig(content) {
|
|
879
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
880
|
+
if (!match) return null;
|
|
881
|
+
const fmBlock = match[1];
|
|
882
|
+
const blockStart = fmBlock.match(/^theme:\s*$/m);
|
|
883
|
+
if (!blockStart) return null;
|
|
884
|
+
const startIdx = fmBlock.indexOf(blockStart[0]) + blockStart[0].length;
|
|
885
|
+
const remaining = fmBlock.slice(startIdx).split("\n");
|
|
886
|
+
let preset = null;
|
|
887
|
+
for (const line of remaining) {
|
|
888
|
+
const trimmed = line.trimStart();
|
|
889
|
+
const indent = line.length - trimmed.length;
|
|
890
|
+
if (indent === 0 && trimmed.length > 0) break;
|
|
891
|
+
if (trimmed === "") continue;
|
|
892
|
+
if (indent === 2 && trimmed.startsWith("preset:")) {
|
|
893
|
+
const value = trimmed.slice("preset:".length).trim().replace(/^["']|["']$/g, "");
|
|
894
|
+
if (value.length > 0) preset = value;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (!preset) return null;
|
|
898
|
+
return { preset };
|
|
899
|
+
}
|
|
900
|
+
function serializeThemeConfig(theme) {
|
|
901
|
+
return ["theme:", ` preset: ${theme.preset}`].join("\n");
|
|
902
|
+
}
|
|
903
|
+
async function writeThemeConfig(theme) {
|
|
904
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
905
|
+
const themeBlock = serializeThemeConfig(theme);
|
|
906
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
907
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
908
|
+
if (!fmMatch) {
|
|
909
|
+
const content = `---
|
|
910
|
+
version: "2.0"
|
|
911
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
912
|
+
${themeBlock}
|
|
913
|
+
---
|
|
914
|
+
${existing}`;
|
|
915
|
+
await writeFileForce(configPath, content);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const fmBlock = fmMatch[2];
|
|
919
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
920
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "theme");
|
|
921
|
+
const newFm = `${cleanedFm}
|
|
922
|
+
${themeBlock}`.replace(/^\n+/, "");
|
|
923
|
+
const normalizedFm = newFm.replace(/\n+$/, "");
|
|
924
|
+
const newContent = `---
|
|
925
|
+
${normalizedFm}
|
|
926
|
+
---${afterFrontmatter}`;
|
|
927
|
+
await writeFileForce(configPath, newContent);
|
|
928
|
+
}
|
|
929
|
+
async function deleteThemeConfig() {
|
|
930
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
931
|
+
if (!await fileExists(configPath)) return;
|
|
932
|
+
const existing = await readFile2(configPath, "utf-8");
|
|
933
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
934
|
+
if (!fmMatch) return;
|
|
935
|
+
const fmBlock = fmMatch[2];
|
|
936
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
937
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "theme");
|
|
938
|
+
const newContent = `---
|
|
939
|
+
${cleanedFm}
|
|
940
|
+
---${afterFrontmatter}`;
|
|
941
|
+
await writeFileForce(configPath, newContent);
|
|
942
|
+
}
|
|
826
943
|
function stripTopLevelBlock(fmBlock, key) {
|
|
827
944
|
const blockStart = fmBlock.match(new RegExp(`^${key}:\\s*$`, "m"));
|
|
828
945
|
if (!blockStart) {
|
|
@@ -859,6 +976,226 @@ function parseOptionalAbsolutePath(value, fieldName) {
|
|
|
859
976
|
}
|
|
860
977
|
return resolve3(expanded);
|
|
861
978
|
}
|
|
979
|
+
function parseAgentsConfig(content) {
|
|
980
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
981
|
+
if (!match) return null;
|
|
982
|
+
const fmBlock = match[1];
|
|
983
|
+
const agentsStart = fmBlock.match(/^agents:\s*$/m);
|
|
984
|
+
if (!agentsStart) return null;
|
|
985
|
+
const startIdx = fmBlock.indexOf(agentsStart[0]) + agentsStart[0].length;
|
|
986
|
+
const remaining = fmBlock.slice(startIdx);
|
|
987
|
+
const lines = remaining.split("\n");
|
|
988
|
+
const agents = [];
|
|
989
|
+
let current = null;
|
|
990
|
+
let argsCapture = null;
|
|
991
|
+
let argsBaseIndent = 0;
|
|
992
|
+
function flushCurrent() {
|
|
993
|
+
if (!current) return;
|
|
994
|
+
if (!current.id || !current.command || !current.label) {
|
|
995
|
+
current = null;
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
agents.push({
|
|
999
|
+
id: current.id,
|
|
1000
|
+
label: current.label,
|
|
1001
|
+
command: current.command,
|
|
1002
|
+
...current.args && current.args.length > 0 ? { args: current.args } : {},
|
|
1003
|
+
...current.promptArgPosition ? { promptArgPosition: current.promptArgPosition } : {},
|
|
1004
|
+
...current.default ? { default: true } : {},
|
|
1005
|
+
...current.resolveFromShellAliases ? { resolveFromShellAliases: true } : {}
|
|
1006
|
+
});
|
|
1007
|
+
current = null;
|
|
1008
|
+
argsCapture = null;
|
|
1009
|
+
}
|
|
1010
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1011
|
+
const line = lines[i];
|
|
1012
|
+
const trimmed = line.trimStart();
|
|
1013
|
+
const indent = line.length - trimmed.length;
|
|
1014
|
+
if (indent === 0 && trimmed !== "" && !trimmed.startsWith("#")) {
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
if (argsCapture) {
|
|
1018
|
+
if (indent > argsBaseIndent && trimmed.startsWith("- ")) {
|
|
1019
|
+
argsCapture.push(decodeYamlScalar(trimmed.slice(2).trim()));
|
|
1020
|
+
continue;
|
|
1021
|
+
} else {
|
|
1022
|
+
argsCapture = null;
|
|
1023
|
+
if (current) current.args = current.args ?? [];
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (indent === 2 && trimmed.startsWith("- ")) {
|
|
1027
|
+
flushCurrent();
|
|
1028
|
+
current = {};
|
|
1029
|
+
const rest = trimmed.slice(2).trim();
|
|
1030
|
+
const colonIdx = rest.indexOf(":");
|
|
1031
|
+
if (colonIdx > 0) {
|
|
1032
|
+
const k = rest.slice(0, colonIdx).trim();
|
|
1033
|
+
const v = rest.slice(colonIdx + 1).trim();
|
|
1034
|
+
assignAgentField(current, k, v);
|
|
1035
|
+
}
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (indent >= 4 && current) {
|
|
1039
|
+
const colonIdx = trimmed.indexOf(":");
|
|
1040
|
+
if (colonIdx <= 0) continue;
|
|
1041
|
+
const k = trimmed.slice(0, colonIdx).trim();
|
|
1042
|
+
const v = trimmed.slice(colonIdx + 1).trim();
|
|
1043
|
+
if (k === "args" && v === "") {
|
|
1044
|
+
argsCapture = [];
|
|
1045
|
+
argsBaseIndent = indent;
|
|
1046
|
+
current.args = argsCapture;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
assignAgentField(current, k, v);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
flushCurrent();
|
|
1053
|
+
if (agents.length === 0) return [];
|
|
1054
|
+
return agents;
|
|
1055
|
+
}
|
|
1056
|
+
function normalizeAgentsFromConfig(agents) {
|
|
1057
|
+
if (agents === null) return null;
|
|
1058
|
+
try {
|
|
1059
|
+
const normalized = agents.map((agent) => ({
|
|
1060
|
+
...agent,
|
|
1061
|
+
command: parseAgentCommand(agent.command, agent.id)
|
|
1062
|
+
}));
|
|
1063
|
+
validateAgentList(normalized);
|
|
1064
|
+
return normalized;
|
|
1065
|
+
} catch (err2) {
|
|
1066
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
1067
|
+
console.warn(
|
|
1068
|
+
`Warning: ~/.syntaur/config.md agents block is invalid (${msg}) \u2014 using built-in defaults`
|
|
1069
|
+
);
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function decodeYamlScalar(value) {
|
|
1074
|
+
const trimmed = value.trim();
|
|
1075
|
+
if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
1076
|
+
const body = trimmed.slice(1, -1);
|
|
1077
|
+
let out = "";
|
|
1078
|
+
for (let i = 0; i < body.length; i++) {
|
|
1079
|
+
const ch = body[i];
|
|
1080
|
+
if (ch === "\\" && i + 1 < body.length) {
|
|
1081
|
+
const next = body[i + 1];
|
|
1082
|
+
switch (next) {
|
|
1083
|
+
case "\\":
|
|
1084
|
+
out += "\\";
|
|
1085
|
+
break;
|
|
1086
|
+
case '"':
|
|
1087
|
+
out += '"';
|
|
1088
|
+
break;
|
|
1089
|
+
case "n":
|
|
1090
|
+
out += "\n";
|
|
1091
|
+
break;
|
|
1092
|
+
case "t":
|
|
1093
|
+
out += " ";
|
|
1094
|
+
break;
|
|
1095
|
+
case "r":
|
|
1096
|
+
out += "\r";
|
|
1097
|
+
break;
|
|
1098
|
+
default:
|
|
1099
|
+
out += next;
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
i++;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
out += ch;
|
|
1106
|
+
}
|
|
1107
|
+
return out;
|
|
1108
|
+
}
|
|
1109
|
+
if (trimmed.length >= 2 && trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
1110
|
+
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
1111
|
+
}
|
|
1112
|
+
return trimmed;
|
|
1113
|
+
}
|
|
1114
|
+
function assignAgentField(target, key, rawValue) {
|
|
1115
|
+
const value = decodeYamlScalar(rawValue);
|
|
1116
|
+
switch (key) {
|
|
1117
|
+
case "id":
|
|
1118
|
+
target.id = value;
|
|
1119
|
+
break;
|
|
1120
|
+
case "label":
|
|
1121
|
+
target.label = value;
|
|
1122
|
+
break;
|
|
1123
|
+
case "command":
|
|
1124
|
+
target.command = value;
|
|
1125
|
+
break;
|
|
1126
|
+
case "promptArgPosition":
|
|
1127
|
+
target.promptArgPosition = value;
|
|
1128
|
+
break;
|
|
1129
|
+
case "default":
|
|
1130
|
+
target.default = value === "true";
|
|
1131
|
+
break;
|
|
1132
|
+
case "resolveFromShellAliases":
|
|
1133
|
+
target.resolveFromShellAliases = value === "true";
|
|
1134
|
+
break;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
function yamlQuoteScalar(value) {
|
|
1138
|
+
if (/[\r\n]/.test(value)) {
|
|
1139
|
+
throw new AgentConfigError(
|
|
1140
|
+
`value contains newlines, which the agents config serializer does not support: ${JSON.stringify(value)}`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (value === "" || /[:#{}[\],&*?|>!%@`"'\\\t]/.test(value) || /^\s|\s$/.test(value)) {
|
|
1144
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t");
|
|
1145
|
+
return `"${escaped}"`;
|
|
1146
|
+
}
|
|
1147
|
+
return value;
|
|
1148
|
+
}
|
|
1149
|
+
function serializeAgentsConfig(agents) {
|
|
1150
|
+
const lines = ["agents:"];
|
|
1151
|
+
for (const a of agents) {
|
|
1152
|
+
lines.push(` - id: ${yamlQuoteScalar(a.id)}`);
|
|
1153
|
+
lines.push(` label: ${yamlQuoteScalar(a.label)}`);
|
|
1154
|
+
lines.push(` command: ${yamlQuoteScalar(a.command)}`);
|
|
1155
|
+
if (a.args && a.args.length > 0) {
|
|
1156
|
+
lines.push(` args:`);
|
|
1157
|
+
for (const arg of a.args) {
|
|
1158
|
+
lines.push(` - ${yamlQuoteScalar(arg)}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
if (a.promptArgPosition && a.promptArgPosition !== "first") {
|
|
1162
|
+
lines.push(` promptArgPosition: ${a.promptArgPosition}`);
|
|
1163
|
+
}
|
|
1164
|
+
if (a.default) {
|
|
1165
|
+
lines.push(` default: true`);
|
|
1166
|
+
}
|
|
1167
|
+
if (a.resolveFromShellAliases) {
|
|
1168
|
+
lines.push(` resolveFromShellAliases: true`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return lines.join("\n");
|
|
1172
|
+
}
|
|
1173
|
+
async function writeAgentsConfig(agents) {
|
|
1174
|
+
validateAgentList(agents);
|
|
1175
|
+
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
1176
|
+
const agentsBlock = serializeAgentsConfig(agents);
|
|
1177
|
+
const existing = await fileExists(configPath) ? await readFile2(configPath, "utf-8") : renderConfig({ defaultProjectDir: defaultProjectDir() });
|
|
1178
|
+
const fmMatch = existing.match(/^(---\n)([\s\S]*?)\n(---)/);
|
|
1179
|
+
if (!fmMatch) {
|
|
1180
|
+
const content = `---
|
|
1181
|
+
version: "2.0"
|
|
1182
|
+
defaultProjectDir: ${defaultProjectDir()}
|
|
1183
|
+
${agentsBlock}
|
|
1184
|
+
---
|
|
1185
|
+
${existing}`;
|
|
1186
|
+
await writeFileForce(configPath, content.replace(/\n\n---/, "\n---"));
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
const fmBlock = fmMatch[2];
|
|
1190
|
+
const afterFrontmatter = existing.slice(fmMatch[0].length);
|
|
1191
|
+
const cleanedFm = stripTopLevelBlock(fmBlock, "agents");
|
|
1192
|
+
const newFm = `${cleanedFm}
|
|
1193
|
+
${agentsBlock}`.replace(/^\n+/, "").replace(/\n+$/, "");
|
|
1194
|
+
const newContent = `---
|
|
1195
|
+
${newFm}
|
|
1196
|
+
---${afterFrontmatter}`;
|
|
1197
|
+
await writeFileForce(configPath, newContent);
|
|
1198
|
+
}
|
|
862
1199
|
async function writeStatusConfig(statuses) {
|
|
863
1200
|
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
864
1201
|
const statusBlock = serializeStatusConfig(statuses);
|
|
@@ -1052,7 +1389,10 @@ async function readConfig() {
|
|
|
1052
1389
|
},
|
|
1053
1390
|
agentDefaults: {
|
|
1054
1391
|
trustLevel: fm["agentDefaults.trustLevel"] || DEFAULT_CONFIG.agentDefaults.trustLevel,
|
|
1055
|
-
autoApprove: fm["agentDefaults.autoApprove"] === "true" || DEFAULT_CONFIG.agentDefaults.autoApprove
|
|
1392
|
+
autoApprove: fm["agentDefaults.autoApprove"] === "true" || DEFAULT_CONFIG.agentDefaults.autoApprove,
|
|
1393
|
+
autoCreateWorktree: AUTO_CREATE_WORKTREE_VALUES.includes(
|
|
1394
|
+
fm["agentDefaults.autoCreateWorktree"]
|
|
1395
|
+
) ? fm["agentDefaults.autoCreateWorktree"] : DEFAULT_CONFIG.agentDefaults.autoCreateWorktree
|
|
1056
1396
|
},
|
|
1057
1397
|
integrations: {
|
|
1058
1398
|
claudePluginDir: parseOptionalAbsolutePath(
|
|
@@ -1076,10 +1416,26 @@ async function readConfig() {
|
|
|
1076
1416
|
} : null,
|
|
1077
1417
|
statuses: parseStatusConfig(content),
|
|
1078
1418
|
types: null,
|
|
1079
|
-
|
|
1419
|
+
agents: normalizeAgentsFromConfig(parseAgentsConfig(content)),
|
|
1420
|
+
playbooks: parsePlaybooksConfig(fmBlock),
|
|
1421
|
+
theme: parseThemeConfig(content)
|
|
1080
1422
|
};
|
|
1081
1423
|
}
|
|
1082
|
-
|
|
1424
|
+
function getAgents(config) {
|
|
1425
|
+
return config.agents ?? BUILTIN_AGENTS;
|
|
1426
|
+
}
|
|
1427
|
+
async function updateAgentsConfig(mutation, options = {}) {
|
|
1428
|
+
const config = await readConfig();
|
|
1429
|
+
const previous = config.agents ?? [...BUILTIN_AGENTS];
|
|
1430
|
+
const next = mutation.apply(previous);
|
|
1431
|
+
validateAgentList(next);
|
|
1432
|
+
if (options.dryRun) {
|
|
1433
|
+
return { previous, next, written: false };
|
|
1434
|
+
}
|
|
1435
|
+
await writeAgentsConfig(next);
|
|
1436
|
+
return { previous, next, written: true };
|
|
1437
|
+
}
|
|
1438
|
+
var DEFAULT_CONFIG, BUILTIN_AGENTS, AGENT_ID_PATTERN, PROMPT_ARG_POSITIONS, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, migratedConfigPaths;
|
|
1083
1439
|
var init_config2 = __esm({
|
|
1084
1440
|
"src/utils/config.ts"() {
|
|
1085
1441
|
"use strict";
|
|
@@ -1095,7 +1451,8 @@ var init_config2 = __esm({
|
|
|
1095
1451
|
},
|
|
1096
1452
|
agentDefaults: {
|
|
1097
1453
|
trustLevel: "medium",
|
|
1098
|
-
autoApprove: false
|
|
1454
|
+
autoApprove: false,
|
|
1455
|
+
autoCreateWorktree: "ask"
|
|
1099
1456
|
},
|
|
1100
1457
|
integrations: {
|
|
1101
1458
|
claudePluginDir: null,
|
|
@@ -1105,9 +1462,20 @@ var init_config2 = __esm({
|
|
|
1105
1462
|
backup: null,
|
|
1106
1463
|
statuses: null,
|
|
1107
1464
|
types: null,
|
|
1465
|
+
agents: null,
|
|
1108
1466
|
playbooks: {
|
|
1109
1467
|
disabled: []
|
|
1110
|
-
}
|
|
1468
|
+
},
|
|
1469
|
+
theme: null
|
|
1470
|
+
};
|
|
1471
|
+
BUILTIN_AGENTS = [
|
|
1472
|
+
{ id: "claude", label: "Claude", command: "claude", default: true },
|
|
1473
|
+
{ id: "codex", label: "Codex", command: "codex" }
|
|
1474
|
+
];
|
|
1475
|
+
AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
1476
|
+
PROMPT_ARG_POSITIONS = ["first", "last", "none"];
|
|
1477
|
+
AUTO_CREATE_WORKTREE_VALUES = ["skip", "ask", "always"];
|
|
1478
|
+
AgentConfigError = class extends Error {
|
|
1111
1479
|
};
|
|
1112
1480
|
migratedConfigPaths = /* @__PURE__ */ new Set();
|
|
1113
1481
|
}
|
|
@@ -1283,6 +1651,12 @@ var init_state_machine = __esm({
|
|
|
1283
1651
|
});
|
|
1284
1652
|
|
|
1285
1653
|
// src/lifecycle/frontmatter.ts
|
|
1654
|
+
var frontmatter_exports = {};
|
|
1655
|
+
__export(frontmatter_exports, {
|
|
1656
|
+
parseAssignmentFrontmatter: () => parseAssignmentFrontmatter,
|
|
1657
|
+
updateAssignmentFile: () => updateAssignmentFile,
|
|
1658
|
+
updateAssignmentWorkspace: () => updateAssignmentWorkspace
|
|
1659
|
+
});
|
|
1286
1660
|
function extractFrontmatter2(fileContent) {
|
|
1287
1661
|
const match = fileContent.match(/^---\n([\s\S]*?)\n---/);
|
|
1288
1662
|
if (!match) {
|
|
@@ -1435,6 +1809,62 @@ function updateAssignmentFile(fileContent, updates) {
|
|
|
1435
1809
|
}
|
|
1436
1810
|
return result;
|
|
1437
1811
|
}
|
|
1812
|
+
function findWorkspaceBlock(fmBlock) {
|
|
1813
|
+
const headerMatch = fmBlock.match(/^workspace:\s*$/m);
|
|
1814
|
+
if (!headerMatch) return null;
|
|
1815
|
+
const headerStart = fmBlock.indexOf(headerMatch[0]);
|
|
1816
|
+
const bodyStart = headerStart + headerMatch[0].length + 1;
|
|
1817
|
+
const after = fmBlock.slice(bodyStart);
|
|
1818
|
+
const lines = after.split("\n");
|
|
1819
|
+
let consumed = 0;
|
|
1820
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1821
|
+
const line = lines[i];
|
|
1822
|
+
if (line.length === 0) {
|
|
1823
|
+
consumed += line.length + 1;
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (line[0] !== " ") break;
|
|
1827
|
+
consumed += line.length + 1;
|
|
1828
|
+
}
|
|
1829
|
+
const bodyEnd = Math.min(bodyStart + consumed, fmBlock.length);
|
|
1830
|
+
return { headerStart, bodyStart, bodyEnd };
|
|
1831
|
+
}
|
|
1832
|
+
function updateAssignmentWorkspace(fileContent, partial) {
|
|
1833
|
+
const fmMatch = fileContent.match(/^(---\n)([\s\S]*?)(\n---)/);
|
|
1834
|
+
if (!fmMatch) {
|
|
1835
|
+
throw new Error("No frontmatter found in assignment file. Expected --- delimiters.");
|
|
1836
|
+
}
|
|
1837
|
+
const fmBlock = fmMatch[2];
|
|
1838
|
+
const fields = ["repository", "worktreePath", "branch", "parentBranch"];
|
|
1839
|
+
const block = findWorkspaceBlock(fmBlock);
|
|
1840
|
+
let newFm = fmBlock;
|
|
1841
|
+
if (block) {
|
|
1842
|
+
let body = fmBlock.slice(block.bodyStart, block.bodyEnd);
|
|
1843
|
+
for (const field of fields) {
|
|
1844
|
+
if (!(field in partial)) continue;
|
|
1845
|
+
const value = partial[field] ?? null;
|
|
1846
|
+
const formatted = formatYamlValue(value);
|
|
1847
|
+
const lineRegex = new RegExp(`^(\\s+${field}:)\\s*.*$`, "m");
|
|
1848
|
+
if (lineRegex.test(body)) {
|
|
1849
|
+
body = body.replace(lineRegex, `$1 ${formatted}`);
|
|
1850
|
+
} else {
|
|
1851
|
+
const trimmed = body.replace(/\n+$/, "");
|
|
1852
|
+
body = `${trimmed}${trimmed.length > 0 ? "\n" : ""} ${field}: ${formatted}
|
|
1853
|
+
`;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
newFm = fmBlock.slice(0, block.bodyStart) + body + fmBlock.slice(block.bodyEnd);
|
|
1857
|
+
} else {
|
|
1858
|
+
const lines = ["workspace:"];
|
|
1859
|
+
for (const field of fields) {
|
|
1860
|
+
const value = field in partial ? partial[field] ?? null : null;
|
|
1861
|
+
lines.push(` ${field}: ${formatYamlValue(value)}`);
|
|
1862
|
+
}
|
|
1863
|
+
newFm = `${fmBlock.replace(/\n+$/, "")}
|
|
1864
|
+
${lines.join("\n")}`;
|
|
1865
|
+
}
|
|
1866
|
+
return `${fmMatch[1]}${newFm}${fmMatch[3]}${fileContent.slice(fmMatch[0].length)}`;
|
|
1867
|
+
}
|
|
1438
1868
|
var init_frontmatter = __esm({
|
|
1439
1869
|
"src/lifecycle/frontmatter.ts"() {
|
|
1440
1870
|
"use strict";
|
|
@@ -4672,7 +5102,7 @@ function App({ projectsDir: projectsDir2, onLaunch }) {
|
|
|
4672
5102
|
collapseNode,
|
|
4673
5103
|
currentNode
|
|
4674
5104
|
} = useTreeState(nodes, filteredIds);
|
|
4675
|
-
useInput((
|
|
5105
|
+
useInput((input4, key) => {
|
|
4676
5106
|
if (searchActive) {
|
|
4677
5107
|
if (key.escape) {
|
|
4678
5108
|
setSearchActive(false);
|
|
@@ -4685,19 +5115,19 @@ function App({ projectsDir: projectsDir2, onLaunch }) {
|
|
|
4685
5115
|
}
|
|
4686
5116
|
return;
|
|
4687
5117
|
}
|
|
4688
|
-
if (
|
|
5118
|
+
if (input4 === "q" || key.escape) {
|
|
4689
5119
|
exit();
|
|
4690
5120
|
return;
|
|
4691
5121
|
}
|
|
4692
|
-
if (
|
|
5122
|
+
if (input4 === "/") {
|
|
4693
5123
|
setSearchActive(true);
|
|
4694
5124
|
return;
|
|
4695
5125
|
}
|
|
4696
|
-
if (key.upArrow ||
|
|
5126
|
+
if (key.upArrow || input4 === "k") {
|
|
4697
5127
|
moveUp();
|
|
4698
5128
|
return;
|
|
4699
5129
|
}
|
|
4700
|
-
if (key.downArrow ||
|
|
5130
|
+
if (key.downArrow || input4 === "j") {
|
|
4701
5131
|
moveDown();
|
|
4702
5132
|
return;
|
|
4703
5133
|
}
|
|
@@ -4786,22 +5216,71 @@ var init_App = __esm({
|
|
|
4786
5216
|
// src/tui/launch.ts
|
|
4787
5217
|
var launch_exports = {};
|
|
4788
5218
|
__export(launch_exports, {
|
|
4789
|
-
|
|
5219
|
+
INITIAL_PROMPT: () => INITIAL_PROMPT,
|
|
5220
|
+
buildAgentArgv: () => buildAgentArgv,
|
|
5221
|
+
formatFallbackCwdWarning: () => formatFallbackCwdWarning,
|
|
5222
|
+
launchAgent: () => launchAgent,
|
|
5223
|
+
shellQuote: () => shellQuote
|
|
4790
5224
|
});
|
|
4791
5225
|
import { spawn as spawn2 } from "child_process";
|
|
4792
5226
|
import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
|
|
4793
|
-
import { resolve as resolve32 } from "path";
|
|
5227
|
+
import { isAbsolute as isAbsolute3, resolve as resolve32 } from "path";
|
|
5228
|
+
function formatFallbackCwdWarning(opts) {
|
|
5229
|
+
const missing = [];
|
|
5230
|
+
if (!opts.worktreePath) missing.push("worktreePath");
|
|
5231
|
+
if (!opts.branch) missing.push("branch");
|
|
5232
|
+
if (missing.length === 0) return null;
|
|
5233
|
+
const fields = missing.map((m) => `workspace.${m}`).join(" and ");
|
|
5234
|
+
return `syntaur: ${fields} not set for ${opts.assignmentSlug} \u2014 launching in ${opts.workspaceDir}`;
|
|
5235
|
+
}
|
|
5236
|
+
function shellQuote(arg) {
|
|
5237
|
+
if (arg === "") return "''";
|
|
5238
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
5239
|
+
}
|
|
5240
|
+
function buildAgentArgv(agent, prompt, env = process.env) {
|
|
5241
|
+
const position = agent.promptArgPosition ?? "first";
|
|
5242
|
+
const baseArgs = [...agent.args ?? []];
|
|
5243
|
+
const agentArgs = position === "first" ? [prompt, ...baseArgs] : position === "last" ? [...baseArgs, prompt] : baseArgs;
|
|
5244
|
+
if (agent.resolveFromShellAliases) {
|
|
5245
|
+
const requested = env.SHELL;
|
|
5246
|
+
let shell = requested;
|
|
5247
|
+
let warning = null;
|
|
5248
|
+
if (!shell || !isAbsolute3(shell)) {
|
|
5249
|
+
warning = `syntaur: $SHELL ${requested ? `("${requested}") is not absolute` : "is unset"} \u2014 falling back to /bin/sh for shell-alias resolution`;
|
|
5250
|
+
shell = "/bin/sh";
|
|
5251
|
+
}
|
|
5252
|
+
const quoted = [agent.command, ...agentArgs].map(shellQuote).join(" ");
|
|
5253
|
+
return {
|
|
5254
|
+
argv: { command: shell, args: ["-i", "-c", quoted] },
|
|
5255
|
+
shellFallbackWarning: warning
|
|
5256
|
+
};
|
|
5257
|
+
}
|
|
5258
|
+
return {
|
|
5259
|
+
argv: { command: agent.command, args: agentArgs },
|
|
5260
|
+
shellFallbackWarning: null
|
|
5261
|
+
};
|
|
5262
|
+
}
|
|
4794
5263
|
async function launchAgent(options) {
|
|
4795
|
-
const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent } = options;
|
|
4796
|
-
const
|
|
5264
|
+
const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent, cwdOverride } = options;
|
|
5265
|
+
const exitWith = options.onExit ?? ((code) => process.exit(code));
|
|
4797
5266
|
const detail = await getAssignmentDetail(projectsDir2, projectSlug, assignmentSlug);
|
|
4798
5267
|
if (!detail) {
|
|
4799
5268
|
console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
|
|
4800
5269
|
process.exit(1);
|
|
4801
5270
|
}
|
|
4802
|
-
const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
|
|
4803
5271
|
const projectDir = resolve32(projectsDir2, projectSlug);
|
|
4804
5272
|
const assignmentDir = resolve32(projectDir, "assignments", assignmentSlug);
|
|
5273
|
+
const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
|
|
5274
|
+
const workspaceDir = resolvedFromWorkspace ?? process.cwd();
|
|
5275
|
+
if (!cwdOverride) {
|
|
5276
|
+
const warning = formatFallbackCwdWarning({
|
|
5277
|
+
assignmentSlug,
|
|
5278
|
+
workspaceDir,
|
|
5279
|
+
worktreePath: detail.workspace.worktreePath,
|
|
5280
|
+
branch: detail.workspace.branch
|
|
5281
|
+
});
|
|
5282
|
+
if (warning) console.warn(warning);
|
|
5283
|
+
}
|
|
4805
5284
|
const contextDir = resolve32(workspaceDir, ".syntaur");
|
|
4806
5285
|
await mkdir5(contextDir, { recursive: true });
|
|
4807
5286
|
const context = {
|
|
@@ -4818,39 +5297,161 @@ async function launchAgent(options) {
|
|
|
4818
5297
|
resolve32(contextDir, "context.json"),
|
|
4819
5298
|
JSON.stringify(context, null, 2) + "\n"
|
|
4820
5299
|
);
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
5300
|
+
const { argv, shellFallbackWarning } = buildAgentArgv(
|
|
5301
|
+
agent,
|
|
5302
|
+
INITIAL_PROMPT(assignmentDir)
|
|
5303
|
+
);
|
|
5304
|
+
if (shellFallbackWarning) {
|
|
5305
|
+
console.warn(shellFallbackWarning);
|
|
5306
|
+
}
|
|
5307
|
+
return new Promise((resolvePromise) => {
|
|
5308
|
+
const child = spawn2(argv.command, argv.args, {
|
|
4824
5309
|
cwd: workspaceDir,
|
|
4825
5310
|
stdio: "inherit"
|
|
4826
5311
|
});
|
|
4827
5312
|
child.on("error", (err2) => {
|
|
4828
|
-
|
|
4829
|
-
|
|
5313
|
+
const code = err2.code;
|
|
5314
|
+
if (code === "ENOENT") {
|
|
5315
|
+
console.error(
|
|
5316
|
+
`syntaur: agent "${agent.id}" command "${agent.command}" not found. If "${agent.command}" is a shell alias, set resolveFromShellAliases: true on this agent in ~/.syntaur/config.md.`
|
|
5317
|
+
);
|
|
5318
|
+
} else if (code === "EACCES") {
|
|
5319
|
+
console.error(
|
|
5320
|
+
`syntaur: agent "${agent.id}" command "${agent.command}" is not executable (EACCES). Check file permissions.`
|
|
5321
|
+
);
|
|
4830
5322
|
} else {
|
|
4831
|
-
console.error(
|
|
5323
|
+
console.error(
|
|
5324
|
+
`syntaur: failed to launch agent "${agent.id}" (${code ?? "unknown"}): ${err2.message}`
|
|
5325
|
+
);
|
|
4832
5326
|
}
|
|
4833
|
-
|
|
5327
|
+
resolvePromise();
|
|
5328
|
+
exitWith(1);
|
|
4834
5329
|
});
|
|
4835
5330
|
child.on("exit", (code) => {
|
|
4836
|
-
|
|
5331
|
+
resolvePromise();
|
|
5332
|
+
exitWith(code ?? 0);
|
|
4837
5333
|
});
|
|
4838
5334
|
});
|
|
4839
5335
|
}
|
|
4840
|
-
var
|
|
5336
|
+
var INITIAL_PROMPT;
|
|
4841
5337
|
var init_launch = __esm({
|
|
4842
5338
|
"src/tui/launch.ts"() {
|
|
4843
5339
|
"use strict";
|
|
4844
5340
|
init_api();
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
5341
|
+
INITIAL_PROMPT = (assignmentDir) => `Read the current Syntaur assignment at ${assignmentDir}/assignment.md and give me a brief summary: title, status, priority, objective, and acceptance criteria.`;
|
|
5342
|
+
}
|
|
5343
|
+
});
|
|
5344
|
+
|
|
5345
|
+
// src/utils/git-worktree.ts
|
|
5346
|
+
var git_worktree_exports = {};
|
|
5347
|
+
__export(git_worktree_exports, {
|
|
5348
|
+
GitWorktreeError: () => GitWorktreeError,
|
|
5349
|
+
createWorktree: () => createWorktree,
|
|
5350
|
+
createWorktreeAndRecord: () => createWorktreeAndRecord,
|
|
5351
|
+
deleteBranch: () => deleteBranch,
|
|
5352
|
+
formatRollbackError: () => formatRollbackError,
|
|
5353
|
+
removeWorktree: () => removeWorktree
|
|
5354
|
+
});
|
|
5355
|
+
import { spawn as spawn3 } from "child_process";
|
|
5356
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
5357
|
+
function run(command, args, cwd) {
|
|
5358
|
+
return new Promise((resolvePromise) => {
|
|
5359
|
+
const child = spawn3(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
5360
|
+
let stdout = "";
|
|
5361
|
+
let stderr = "";
|
|
5362
|
+
child.stdout.on("data", (chunk) => stdout += chunk.toString());
|
|
5363
|
+
child.stderr.on("data", (chunk) => stderr += chunk.toString());
|
|
5364
|
+
child.on("error", (err2) => {
|
|
5365
|
+
resolvePromise({ code: -1, stdout, stderr: stderr + String(err2) });
|
|
5366
|
+
});
|
|
5367
|
+
child.on("close", (code) => {
|
|
5368
|
+
resolvePromise({ code: code ?? -1, stdout, stderr });
|
|
5369
|
+
});
|
|
5370
|
+
});
|
|
5371
|
+
}
|
|
5372
|
+
async function createWorktree(opts) {
|
|
5373
|
+
const { repository, branch, worktreePath, parentBranch } = opts;
|
|
5374
|
+
const result = await run(
|
|
5375
|
+
"git",
|
|
5376
|
+
["-C", repository, "worktree", "add", "-b", branch, worktreePath, parentBranch]
|
|
5377
|
+
);
|
|
5378
|
+
if (result.code !== 0) {
|
|
5379
|
+
throw new GitWorktreeError(
|
|
5380
|
+
`git worktree add failed (exit ${result.code}): ${result.stderr.trim() || "(no stderr)"}`,
|
|
5381
|
+
result.stderr
|
|
5382
|
+
);
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
async function removeWorktree(repository, worktreePath) {
|
|
5386
|
+
const result = await run(
|
|
5387
|
+
"git",
|
|
5388
|
+
["-C", repository, "worktree", "remove", "--force", worktreePath]
|
|
5389
|
+
);
|
|
5390
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
5391
|
+
}
|
|
5392
|
+
async function deleteBranch(repository, branch) {
|
|
5393
|
+
const result = await run("git", ["-C", repository, "branch", "-D", branch]);
|
|
5394
|
+
return { ok: result.code === 0, stderr: result.stderr };
|
|
5395
|
+
}
|
|
5396
|
+
async function createWorktreeAndRecord(opts) {
|
|
5397
|
+
const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
|
|
5398
|
+
await createWorktree({ repository, branch, worktreePath, parentBranch });
|
|
5399
|
+
try {
|
|
5400
|
+
const content = await readFile20(assignmentPath, "utf-8");
|
|
5401
|
+
const updated = updateAssignmentWorkspace(content, {
|
|
5402
|
+
repository,
|
|
5403
|
+
worktreePath,
|
|
5404
|
+
branch,
|
|
5405
|
+
parentBranch
|
|
5406
|
+
});
|
|
5407
|
+
await writeFileForce(assignmentPath, updated);
|
|
5408
|
+
} catch (writeErr) {
|
|
5409
|
+
const cleanup = await removeWorktree(repository, worktreePath);
|
|
5410
|
+
const branchCleanup = await deleteBranch(repository, branch);
|
|
5411
|
+
const writeMsg = writeErr instanceof Error ? writeErr.message : String(writeErr);
|
|
5412
|
+
throw new Error(
|
|
5413
|
+
formatRollbackError({
|
|
5414
|
+
writeMsg,
|
|
5415
|
+
worktreePath,
|
|
5416
|
+
branch,
|
|
5417
|
+
worktreeCleanup: cleanup,
|
|
5418
|
+
branchCleanup
|
|
5419
|
+
})
|
|
5420
|
+
);
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
function formatRollbackError(opts) {
|
|
5424
|
+
const { writeMsg, worktreePath, branch, worktreeCleanup, branchCleanup } = opts;
|
|
5425
|
+
const wtMsg = worktreeCleanup.stderr.trim() || "(no stderr)";
|
|
5426
|
+
const brMsg = branchCleanup.stderr.trim() || "(no stderr)";
|
|
5427
|
+
if (!worktreeCleanup.ok && !branchCleanup.ok) {
|
|
5428
|
+
return `Failed to update assignment frontmatter AND failed to clean up both worktree and branch. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Branch cleanup error: ${brMsg}. Orphan worktree at ${worktreePath} and orphan branch "${branch}" \u2014 remove them manually.`;
|
|
5429
|
+
}
|
|
5430
|
+
if (!worktreeCleanup.ok) {
|
|
5431
|
+
return `Failed to update assignment frontmatter AND failed to clean up worktree. Write error: ${writeMsg}. Worktree cleanup error: ${wtMsg}. Orphan worktree at ${worktreePath} \u2014 remove it manually.`;
|
|
5432
|
+
}
|
|
5433
|
+
if (!branchCleanup.ok) {
|
|
5434
|
+
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath}, but could not delete branch "${branch}": ${brMsg}. Remove the branch manually.`;
|
|
5435
|
+
}
|
|
5436
|
+
return `Failed to update assignment frontmatter: ${writeMsg}. Rolled back git worktree at ${worktreePath} and branch "${branch}".`;
|
|
5437
|
+
}
|
|
5438
|
+
var GitWorktreeError;
|
|
5439
|
+
var init_git_worktree = __esm({
|
|
5440
|
+
"src/utils/git-worktree.ts"() {
|
|
5441
|
+
"use strict";
|
|
5442
|
+
init_frontmatter();
|
|
5443
|
+
init_fs();
|
|
5444
|
+
GitWorktreeError = class extends Error {
|
|
5445
|
+
constructor(message, stderr) {
|
|
5446
|
+
super(message);
|
|
5447
|
+
this.stderr = stderr;
|
|
5448
|
+
}
|
|
4848
5449
|
};
|
|
4849
5450
|
}
|
|
4850
5451
|
});
|
|
4851
5452
|
|
|
4852
5453
|
// src/index.ts
|
|
4853
|
-
import { Command as
|
|
5454
|
+
import { Command as Command5 } from "commander";
|
|
4854
5455
|
|
|
4855
5456
|
// src/commands/init.ts
|
|
4856
5457
|
init_paths();
|
|
@@ -6124,8 +6725,8 @@ async function migrateFromMarkdown(projectsDir2) {
|
|
|
6124
6725
|
return allSessions.length;
|
|
6125
6726
|
}
|
|
6126
6727
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
6127
|
-
const { readFile:
|
|
6128
|
-
const raw = await
|
|
6728
|
+
const { readFile: readFile32 } = await import("fs/promises");
|
|
6729
|
+
const raw = await readFile32(filePath, "utf-8");
|
|
6129
6730
|
const sessions = [];
|
|
6130
6731
|
const lines = raw.split("\n");
|
|
6131
6732
|
let inTable = false;
|
|
@@ -7939,8 +8540,8 @@ ${entry}`;
|
|
|
7939
8540
|
});
|
|
7940
8541
|
return router;
|
|
7941
8542
|
}
|
|
7942
|
-
function slugifyLocal(
|
|
7943
|
-
return
|
|
8543
|
+
function slugifyLocal(input4) {
|
|
8544
|
+
return input4.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled";
|
|
7944
8545
|
}
|
|
7945
8546
|
async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
|
|
7946
8547
|
const commentsPath = resolve15(assignmentDir, "comments.md");
|
|
@@ -8554,8 +9155,8 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8554
9155
|
router.post("/:workspace/archive", async (req, res) => {
|
|
8555
9156
|
try {
|
|
8556
9157
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
8557
|
-
const { resolve:
|
|
8558
|
-
const { readFile:
|
|
9158
|
+
const { resolve: resolve47 } = await import("path");
|
|
9159
|
+
const { readFile: readFile32 } = await import("fs/promises");
|
|
8559
9160
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8560
9161
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8561
9162
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -8571,10 +9172,10 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8571
9172
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
8572
9173
|
);
|
|
8573
9174
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
8574
|
-
await ensureDir(
|
|
9175
|
+
await ensureDir(resolve47(todosDir2, "archive"));
|
|
8575
9176
|
let archContent = "";
|
|
8576
9177
|
if (await fileExists(archFile)) {
|
|
8577
|
-
archContent = await
|
|
9178
|
+
archContent = await readFile32(archFile, "utf-8");
|
|
8578
9179
|
archContent = archContent.trimEnd() + "\n\n";
|
|
8579
9180
|
} else {
|
|
8580
9181
|
archContent = `---
|
|
@@ -10158,6 +10759,43 @@ function createDashboardServer(options) {
|
|
|
10158
10759
|
res.status(500).json({ error: "Failed to reset status config" });
|
|
10159
10760
|
}
|
|
10160
10761
|
});
|
|
10762
|
+
const THEME_PRESET_SLUGS = ["default", "ocean", "forest", "sunset"];
|
|
10763
|
+
const DEFAULT_THEME_PRESET = "default";
|
|
10764
|
+
app.get("/api/config/theme", async (_req, res) => {
|
|
10765
|
+
try {
|
|
10766
|
+
const config = await readConfig();
|
|
10767
|
+
const preset = config.theme?.preset ?? DEFAULT_THEME_PRESET;
|
|
10768
|
+
res.json({ preset, custom: config.theme !== null });
|
|
10769
|
+
} catch (error) {
|
|
10770
|
+
console.error("Error getting theme config:", error);
|
|
10771
|
+
res.status(500).json({ error: "Failed to get theme config" });
|
|
10772
|
+
}
|
|
10773
|
+
});
|
|
10774
|
+
app.post("/api/config/theme", async (req, res) => {
|
|
10775
|
+
try {
|
|
10776
|
+
const { preset } = req.body ?? {};
|
|
10777
|
+
if (typeof preset !== "string" || !THEME_PRESET_SLUGS.includes(preset)) {
|
|
10778
|
+
res.status(400).json({
|
|
10779
|
+
error: `preset must be one of: ${THEME_PRESET_SLUGS.join(", ")}`
|
|
10780
|
+
});
|
|
10781
|
+
return;
|
|
10782
|
+
}
|
|
10783
|
+
await writeThemeConfig({ preset });
|
|
10784
|
+
res.json({ preset, custom: true });
|
|
10785
|
+
} catch (error) {
|
|
10786
|
+
console.error("Error saving theme config:", error);
|
|
10787
|
+
res.status(500).json({ error: "Failed to save theme config" });
|
|
10788
|
+
}
|
|
10789
|
+
});
|
|
10790
|
+
app.delete("/api/config/theme", async (_req, res) => {
|
|
10791
|
+
try {
|
|
10792
|
+
await deleteThemeConfig();
|
|
10793
|
+
res.json({ preset: DEFAULT_THEME_PRESET, custom: false });
|
|
10794
|
+
} catch (error) {
|
|
10795
|
+
console.error("Error resetting theme config:", error);
|
|
10796
|
+
res.status(500).json({ error: "Failed to reset theme config" });
|
|
10797
|
+
}
|
|
10798
|
+
});
|
|
10161
10799
|
app.get("/api/projects", async (req, res) => {
|
|
10162
10800
|
try {
|
|
10163
10801
|
let projects = await listProjects(projectsDir2);
|
|
@@ -10298,6 +10936,7 @@ function createDashboardServer(options) {
|
|
|
10298
10936
|
app.use("/api/backup", createBackupRouter());
|
|
10299
10937
|
if (serveStaticUi && dashboardDistPath) {
|
|
10300
10938
|
app.use("/assets", express.static(resolve21(dashboardDistPath, "assets")));
|
|
10939
|
+
app.use(express.static(dashboardDistPath, { index: false, fallthrough: true }));
|
|
10301
10940
|
app.get("{*path}", async (req, res) => {
|
|
10302
10941
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
10303
10942
|
res.status(404).json({ error: "Not Found" });
|
|
@@ -12673,10 +13312,29 @@ async function trackSessionCommand(options) {
|
|
|
12673
13312
|
|
|
12674
13313
|
// src/commands/browse.ts
|
|
12675
13314
|
init_config2();
|
|
13315
|
+
init_paths();
|
|
13316
|
+
init_fs();
|
|
13317
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
13318
|
+
import { resolve as resolve33, isAbsolute as isAbsolute4 } from "path";
|
|
13319
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
13320
|
+
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
12676
13321
|
async function browseCommand(options) {
|
|
12677
13322
|
const config = await readConfig();
|
|
12678
13323
|
const projectsDir2 = config.defaultProjectDir;
|
|
12679
|
-
const
|
|
13324
|
+
const agents = getAgents(config);
|
|
13325
|
+
if (agents.length === 0) {
|
|
13326
|
+
console.error(
|
|
13327
|
+
"No agents configured. Add one with `syntaur agents add --id <id> --label <label> --command <path>`."
|
|
13328
|
+
);
|
|
13329
|
+
process.exit(1);
|
|
13330
|
+
}
|
|
13331
|
+
const preSelectedAgent = options.agent ? agents.find((a) => a.id === options.agent) : void 0;
|
|
13332
|
+
if (options.agent && !preSelectedAgent) {
|
|
13333
|
+
console.error(
|
|
13334
|
+
`Unknown agent id "${options.agent}". Configured: ${agents.map((a) => a.id).join(", ")}`
|
|
13335
|
+
);
|
|
13336
|
+
process.exit(1);
|
|
13337
|
+
}
|
|
12680
13338
|
const { render } = await import("ink");
|
|
12681
13339
|
const React2 = await import("react");
|
|
12682
13340
|
const { App: App2 } = await Promise.resolve().then(() => (init_App(), App_exports));
|
|
@@ -12687,7 +13345,19 @@ async function browseCommand(options) {
|
|
|
12687
13345
|
unmount();
|
|
12688
13346
|
unmount = null;
|
|
12689
13347
|
}
|
|
12690
|
-
|
|
13348
|
+
const agent = preSelectedAgent ?? await pickAgent(agents);
|
|
13349
|
+
const cwdOverride = await ensureWorktree({
|
|
13350
|
+
projectsDir: launchOpts.projectsDir,
|
|
13351
|
+
projectSlug: launchOpts.projectSlug,
|
|
13352
|
+
assignmentSlug: launchOpts.assignmentSlug,
|
|
13353
|
+
worktreePromptEnabled: options.worktreePrompt !== false,
|
|
13354
|
+
autoCreateWorktree: config.agentDefaults.autoCreateWorktree
|
|
13355
|
+
});
|
|
13356
|
+
await launchAgent2({
|
|
13357
|
+
...launchOpts,
|
|
13358
|
+
agent,
|
|
13359
|
+
...cwdOverride ? { cwdOverride } : {}
|
|
13360
|
+
});
|
|
12691
13361
|
};
|
|
12692
13362
|
const instance = render(
|
|
12693
13363
|
React2.createElement(App2, { projectsDir: projectsDir2, onLaunch })
|
|
@@ -12695,9 +13365,164 @@ async function browseCommand(options) {
|
|
|
12695
13365
|
unmount = instance.unmount;
|
|
12696
13366
|
await instance.waitUntilExit();
|
|
12697
13367
|
}
|
|
13368
|
+
async function pickAgent(agents) {
|
|
13369
|
+
if (agents.length === 1) return agents[0];
|
|
13370
|
+
if (!isInteractiveTerminal()) {
|
|
13371
|
+
const fallback = agents.find((a) => a.default) ?? agents[0];
|
|
13372
|
+
console.warn(
|
|
13373
|
+
`syntaur: multiple agents configured but no TTY \u2014 using "${fallback.id}".`
|
|
13374
|
+
);
|
|
13375
|
+
return fallback;
|
|
13376
|
+
}
|
|
13377
|
+
const defaultAgent = agents.find((a) => a.default) ?? agents[0];
|
|
13378
|
+
const id = await select({
|
|
13379
|
+
message: "Launch which agent?",
|
|
13380
|
+
choices: agents.map((a) => ({ name: a.label, value: a.id, description: a.command })),
|
|
13381
|
+
default: defaultAgent.id
|
|
13382
|
+
});
|
|
13383
|
+
const picked = agents.find((a) => a.id === id);
|
|
13384
|
+
if (!picked) throw new Error(`Internal error: picker returned unknown agent id "${id}"`);
|
|
13385
|
+
return picked;
|
|
13386
|
+
}
|
|
13387
|
+
async function ensureWorktree(opts) {
|
|
13388
|
+
const assignmentPath = resolve33(
|
|
13389
|
+
opts.projectsDir,
|
|
13390
|
+
opts.projectSlug,
|
|
13391
|
+
"assignments",
|
|
13392
|
+
opts.assignmentSlug,
|
|
13393
|
+
"assignment.md"
|
|
13394
|
+
);
|
|
13395
|
+
if (!await fileExists(assignmentPath)) {
|
|
13396
|
+
return void 0;
|
|
13397
|
+
}
|
|
13398
|
+
const content = await readFile21(assignmentPath, "utf-8");
|
|
13399
|
+
const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
|
|
13400
|
+
const fm = parseAssignmentFrontmatter2(content);
|
|
13401
|
+
const { workspace } = fm;
|
|
13402
|
+
if (workspace.worktreePath && workspace.branch) {
|
|
13403
|
+
return void 0;
|
|
13404
|
+
}
|
|
13405
|
+
if (!opts.worktreePromptEnabled) {
|
|
13406
|
+
return void 0;
|
|
13407
|
+
}
|
|
13408
|
+
if (opts.autoCreateWorktree === "skip") {
|
|
13409
|
+
return void 0;
|
|
13410
|
+
}
|
|
13411
|
+
const defaults = computeWorktreeDefaults({
|
|
13412
|
+
projectSlug: opts.projectSlug,
|
|
13413
|
+
assignmentSlug: opts.assignmentSlug,
|
|
13414
|
+
existing: workspace
|
|
13415
|
+
});
|
|
13416
|
+
if (!defaults.repository) {
|
|
13417
|
+
console.warn(
|
|
13418
|
+
`syntaur: cannot infer repository for ${opts.assignmentSlug} \u2014 skipping worktree prompt`
|
|
13419
|
+
);
|
|
13420
|
+
return void 0;
|
|
13421
|
+
}
|
|
13422
|
+
if (opts.autoCreateWorktree === "always") {
|
|
13423
|
+
return await runCreate({
|
|
13424
|
+
assignmentPath,
|
|
13425
|
+
repository: defaults.repository,
|
|
13426
|
+
branch: defaults.branch,
|
|
13427
|
+
parentBranch: defaults.parentBranch,
|
|
13428
|
+
worktreePath: defaults.worktreePath
|
|
13429
|
+
});
|
|
13430
|
+
}
|
|
13431
|
+
if (!isInteractiveTerminal()) {
|
|
13432
|
+
return void 0;
|
|
13433
|
+
}
|
|
13434
|
+
const proceed = await confirm2({
|
|
13435
|
+
message: `This assignment has no git worktree/branch configured. Create one?`,
|
|
13436
|
+
default: true
|
|
13437
|
+
});
|
|
13438
|
+
if (!proceed) {
|
|
13439
|
+
return void 0;
|
|
13440
|
+
}
|
|
13441
|
+
const repository = await input3({
|
|
13442
|
+
message: "Repository path:",
|
|
13443
|
+
default: defaults.repository
|
|
13444
|
+
});
|
|
13445
|
+
const branch = await input3({
|
|
13446
|
+
message: "Branch name:",
|
|
13447
|
+
default: defaults.branch
|
|
13448
|
+
});
|
|
13449
|
+
const parentBranch = await input3({
|
|
13450
|
+
message: "Parent branch:",
|
|
13451
|
+
default: defaults.parentBranch
|
|
13452
|
+
});
|
|
13453
|
+
const worktreePath = await input3({
|
|
13454
|
+
message: "Worktree path:",
|
|
13455
|
+
default: defaults.worktreePath
|
|
13456
|
+
});
|
|
13457
|
+
return await runCreate({
|
|
13458
|
+
assignmentPath,
|
|
13459
|
+
repository,
|
|
13460
|
+
branch,
|
|
13461
|
+
parentBranch,
|
|
13462
|
+
worktreePath
|
|
13463
|
+
});
|
|
13464
|
+
}
|
|
13465
|
+
function computeWorktreeDefaults(opts) {
|
|
13466
|
+
const repository = opts.existing.repository ?? detectCurrentGitRoot();
|
|
13467
|
+
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
13468
|
+
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
|
|
13469
|
+
const worktreeBase = resolve33(
|
|
13470
|
+
syntaurRoot(),
|
|
13471
|
+
"worktrees",
|
|
13472
|
+
opts.projectSlug || "standalone",
|
|
13473
|
+
opts.assignmentSlug
|
|
13474
|
+
);
|
|
13475
|
+
return {
|
|
13476
|
+
...repository ? { repository } : {},
|
|
13477
|
+
branch,
|
|
13478
|
+
parentBranch,
|
|
13479
|
+
worktreePath: worktreeBase
|
|
13480
|
+
};
|
|
13481
|
+
}
|
|
13482
|
+
function detectCurrentGitRoot() {
|
|
13483
|
+
const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
13484
|
+
encoding: "utf-8"
|
|
13485
|
+
});
|
|
13486
|
+
if (result.status !== 0) return void 0;
|
|
13487
|
+
const out = result.stdout.trim();
|
|
13488
|
+
return out.length > 0 ? out : void 0;
|
|
13489
|
+
}
|
|
13490
|
+
function detectCurrentBranch() {
|
|
13491
|
+
const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
13492
|
+
encoding: "utf-8"
|
|
13493
|
+
});
|
|
13494
|
+
if (result.status !== 0) return void 0;
|
|
13495
|
+
const out = result.stdout.trim();
|
|
13496
|
+
if (!out || out === "HEAD") return void 0;
|
|
13497
|
+
return out;
|
|
13498
|
+
}
|
|
13499
|
+
async function runCreate(opts) {
|
|
13500
|
+
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
13501
|
+
const expandedWorktree = expandHome(opts.worktreePath);
|
|
13502
|
+
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve33(expandedWorktree);
|
|
13503
|
+
try {
|
|
13504
|
+
await createWorktreeAndRecord2({
|
|
13505
|
+
assignmentPath: opts.assignmentPath,
|
|
13506
|
+
repository: opts.repository,
|
|
13507
|
+
branch: opts.branch,
|
|
13508
|
+
worktreePath: absWorktree,
|
|
13509
|
+
parentBranch: opts.parentBranch
|
|
13510
|
+
});
|
|
13511
|
+
console.log(`syntaur: created worktree at ${absWorktree} on branch ${opts.branch}`);
|
|
13512
|
+
return absWorktree;
|
|
13513
|
+
} catch (err2) {
|
|
13514
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
13515
|
+
if (err2 instanceof GitWorktreeError2) {
|
|
13516
|
+
console.error(`syntaur: ${msg}`);
|
|
13517
|
+
} else {
|
|
13518
|
+
console.error(`syntaur: ${msg}`);
|
|
13519
|
+
}
|
|
13520
|
+
process.exit(1);
|
|
13521
|
+
}
|
|
13522
|
+
}
|
|
12698
13523
|
|
|
12699
13524
|
// src/commands/create-playbook.ts
|
|
12700
|
-
import { resolve as
|
|
13525
|
+
import { resolve as resolve34 } from "path";
|
|
12701
13526
|
init_timestamp();
|
|
12702
13527
|
init_paths();
|
|
12703
13528
|
init_fs();
|
|
@@ -12714,7 +13539,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
12714
13539
|
}
|
|
12715
13540
|
const dir = playbooksDir();
|
|
12716
13541
|
await ensureDir(dir);
|
|
12717
|
-
const filePath =
|
|
13542
|
+
const filePath = resolve34(dir, `${slug}.md`);
|
|
12718
13543
|
if (await fileExists(filePath)) {
|
|
12719
13544
|
throw new Error(
|
|
12720
13545
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -12736,8 +13561,8 @@ init_paths();
|
|
|
12736
13561
|
init_fs();
|
|
12737
13562
|
init_parser();
|
|
12738
13563
|
init_config2();
|
|
12739
|
-
import { readdir as readdir12, readFile as
|
|
12740
|
-
import { resolve as
|
|
13564
|
+
import { readdir as readdir12, readFile as readFile22 } from "fs/promises";
|
|
13565
|
+
import { resolve as resolve35 } from "path";
|
|
12741
13566
|
async function listPlaybooksCommand(options = {}) {
|
|
12742
13567
|
const dir = playbooksDir();
|
|
12743
13568
|
if (!await fileExists(dir)) {
|
|
@@ -12752,8 +13577,8 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
12752
13577
|
);
|
|
12753
13578
|
const rows = [];
|
|
12754
13579
|
for (const entry of mdFiles) {
|
|
12755
|
-
const filePath =
|
|
12756
|
-
const raw = await
|
|
13580
|
+
const filePath = resolve35(dir, entry.name);
|
|
13581
|
+
const raw = await readFile22(filePath, "utf-8");
|
|
12757
13582
|
const parsed = parsePlaybook(raw);
|
|
12758
13583
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
12759
13584
|
const disabled = disabledSet.has(slug);
|
|
@@ -12835,8 +13660,8 @@ init_parser2();
|
|
|
12835
13660
|
init_fs();
|
|
12836
13661
|
init_config2();
|
|
12837
13662
|
import { Command } from "commander";
|
|
12838
|
-
import { readFile as
|
|
12839
|
-
import { resolve as
|
|
13663
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
13664
|
+
import { resolve as resolve36 } from "path";
|
|
12840
13665
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
12841
13666
|
async function resolveScope(options) {
|
|
12842
13667
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
@@ -12848,7 +13673,7 @@ async function resolveScope(options) {
|
|
|
12848
13673
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
12849
13674
|
}
|
|
12850
13675
|
const config = await readConfig();
|
|
12851
|
-
const projectMd =
|
|
13676
|
+
const projectMd = resolve36(config.defaultProjectDir, options.project, "project.md");
|
|
12852
13677
|
if (!await fileExists(projectMd)) {
|
|
12853
13678
|
throw new Error(`Project "${options.project}" not found.`);
|
|
12854
13679
|
}
|
|
@@ -13142,10 +13967,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
13142
13967
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
13143
13968
|
);
|
|
13144
13969
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
13145
|
-
await ensureDir(
|
|
13970
|
+
await ensureDir(resolve36(todosPath, "archive"));
|
|
13146
13971
|
let archContent = "";
|
|
13147
13972
|
if (await fileExists(archFile)) {
|
|
13148
|
-
archContent = await
|
|
13973
|
+
archContent = await readFile23(archFile, "utf-8");
|
|
13149
13974
|
archContent = archContent.trimEnd() + "\n\n";
|
|
13150
13975
|
} else {
|
|
13151
13976
|
archContent = `---
|
|
@@ -13335,7 +14160,7 @@ import { Command as Command3 } from "commander";
|
|
|
13335
14160
|
|
|
13336
14161
|
// src/utils/doctor/index.ts
|
|
13337
14162
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
13338
|
-
import { readFile as
|
|
14163
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
13339
14164
|
import { dirname as dirname11, join as join5 } from "path";
|
|
13340
14165
|
|
|
13341
14166
|
// src/utils/doctor/context.ts
|
|
@@ -13343,11 +14168,11 @@ init_config2();
|
|
|
13343
14168
|
init_paths();
|
|
13344
14169
|
init_fs();
|
|
13345
14170
|
import Database2 from "better-sqlite3";
|
|
13346
|
-
import { resolve as
|
|
14171
|
+
import { resolve as resolve37 } from "path";
|
|
13347
14172
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
13348
14173
|
const config = await readConfig();
|
|
13349
14174
|
const root = syntaurRoot();
|
|
13350
|
-
const dbPath =
|
|
14175
|
+
const dbPath = resolve37(root, "syntaur.db");
|
|
13351
14176
|
let db2 = null;
|
|
13352
14177
|
let dbError = null;
|
|
13353
14178
|
if (await fileExists(dbPath)) {
|
|
@@ -13381,8 +14206,8 @@ function closeCheckContext(ctx) {
|
|
|
13381
14206
|
// src/utils/doctor/checks/env.ts
|
|
13382
14207
|
init_fs();
|
|
13383
14208
|
init_paths();
|
|
13384
|
-
import { resolve as
|
|
13385
|
-
import { readFile as
|
|
14209
|
+
import { resolve as resolve38, isAbsolute as isAbsolute5 } from "path";
|
|
14210
|
+
import { readFile as readFile24, stat as stat4 } from "fs/promises";
|
|
13386
14211
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
13387
14212
|
import { dirname as dirname10, join as join4 } from "path";
|
|
13388
14213
|
var CATEGORY = "env";
|
|
@@ -13422,7 +14247,7 @@ var configValid = {
|
|
|
13422
14247
|
category: CATEGORY,
|
|
13423
14248
|
title: "~/.syntaur/config.md is valid",
|
|
13424
14249
|
async run(ctx) {
|
|
13425
|
-
const configPath =
|
|
14250
|
+
const configPath = resolve38(ctx.syntaurRoot, "config.md");
|
|
13426
14251
|
if (!await fileExists(configPath)) {
|
|
13427
14252
|
return {
|
|
13428
14253
|
id: this.id,
|
|
@@ -13439,7 +14264,7 @@ var configValid = {
|
|
|
13439
14264
|
autoFixable: false
|
|
13440
14265
|
};
|
|
13441
14266
|
}
|
|
13442
|
-
const content = await
|
|
14267
|
+
const content = await readFile24(configPath, "utf-8");
|
|
13443
14268
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
13444
14269
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
13445
14270
|
return {
|
|
@@ -13476,7 +14301,7 @@ var configValid = {
|
|
|
13476
14301
|
};
|
|
13477
14302
|
}
|
|
13478
14303
|
const expanded = expandHome(rawProjectDir);
|
|
13479
|
-
if (!
|
|
14304
|
+
if (!isAbsolute5(expanded)) {
|
|
13480
14305
|
return {
|
|
13481
14306
|
id: this.id,
|
|
13482
14307
|
category: this.category,
|
|
@@ -13719,7 +14544,7 @@ async function readLocalPkg() {
|
|
|
13719
14544
|
for (let i = 0; i < 6; i++) {
|
|
13720
14545
|
const candidate = join4(dir, "package.json");
|
|
13721
14546
|
try {
|
|
13722
|
-
const text = await
|
|
14547
|
+
const text = await readFile24(candidate, "utf-8");
|
|
13723
14548
|
return JSON.parse(text);
|
|
13724
14549
|
} catch {
|
|
13725
14550
|
dir = dirname10(dir);
|
|
@@ -13771,7 +14596,7 @@ function versionGte(a, b) {
|
|
|
13771
14596
|
|
|
13772
14597
|
// src/utils/doctor/checks/structure.ts
|
|
13773
14598
|
init_fs();
|
|
13774
|
-
import { resolve as
|
|
14599
|
+
import { resolve as resolve39 } from "path";
|
|
13775
14600
|
import { readdir as readdir13, stat as stat5 } from "fs/promises";
|
|
13776
14601
|
var CATEGORY2 = "structure";
|
|
13777
14602
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
@@ -13791,7 +14616,7 @@ var projectsDir = {
|
|
|
13791
14616
|
category: CATEGORY2,
|
|
13792
14617
|
title: "projects/ directory exists",
|
|
13793
14618
|
async run(ctx) {
|
|
13794
|
-
const p =
|
|
14619
|
+
const p = resolve39(ctx.syntaurRoot, "projects");
|
|
13795
14620
|
if (!await fileExists(p)) {
|
|
13796
14621
|
return {
|
|
13797
14622
|
id: this.id,
|
|
@@ -13816,7 +14641,7 @@ var playbooksDir2 = {
|
|
|
13816
14641
|
category: CATEGORY2,
|
|
13817
14642
|
title: "playbooks/ directory exists",
|
|
13818
14643
|
async run(ctx) {
|
|
13819
|
-
const p =
|
|
14644
|
+
const p = resolve39(ctx.syntaurRoot, "playbooks");
|
|
13820
14645
|
if (!await fileExists(p)) {
|
|
13821
14646
|
return {
|
|
13822
14647
|
id: this.id,
|
|
@@ -13841,7 +14666,7 @@ var todosDirValid = {
|
|
|
13841
14666
|
category: CATEGORY2,
|
|
13842
14667
|
title: "todos/ directory is readable (if present)",
|
|
13843
14668
|
async run(ctx) {
|
|
13844
|
-
const p =
|
|
14669
|
+
const p = resolve39(ctx.syntaurRoot, "todos");
|
|
13845
14670
|
if (!await fileExists(p)) {
|
|
13846
14671
|
return {
|
|
13847
14672
|
id: this.id,
|
|
@@ -13872,7 +14697,7 @@ var serversDirValid = {
|
|
|
13872
14697
|
category: CATEGORY2,
|
|
13873
14698
|
title: "servers/ directory is readable (if present)",
|
|
13874
14699
|
async run(ctx) {
|
|
13875
|
-
const p =
|
|
14700
|
+
const p = resolve39(ctx.syntaurRoot, "servers");
|
|
13876
14701
|
if (!await fileExists(p)) {
|
|
13877
14702
|
return {
|
|
13878
14703
|
id: this.id,
|
|
@@ -13917,7 +14742,7 @@ var knownFilesRecognized = {
|
|
|
13917
14742
|
title: this.title,
|
|
13918
14743
|
status: "warn",
|
|
13919
14744
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
13920
|
-
affected: unexpected.map((n) =>
|
|
14745
|
+
affected: unexpected.map((n) => resolve39(ctx.syntaurRoot, n)),
|
|
13921
14746
|
remediation: {
|
|
13922
14747
|
kind: "manual",
|
|
13923
14748
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -13946,7 +14771,7 @@ function pass2(check) {
|
|
|
13946
14771
|
|
|
13947
14772
|
// src/utils/doctor/checks/project.ts
|
|
13948
14773
|
init_fs();
|
|
13949
|
-
import { resolve as
|
|
14774
|
+
import { resolve as resolve40 } from "path";
|
|
13950
14775
|
import { readdir as readdir14, stat as stat6 } from "fs/promises";
|
|
13951
14776
|
var CATEGORY3 = "project";
|
|
13952
14777
|
var REQUIRED_PROJECT_FILES = [
|
|
@@ -13976,10 +14801,10 @@ async function listProjects2(ctx) {
|
|
|
13976
14801
|
for (const e of entries) {
|
|
13977
14802
|
if (!e.isDirectory()) continue;
|
|
13978
14803
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
13979
|
-
const projectDir =
|
|
14804
|
+
const projectDir = resolve40(dir, e.name);
|
|
13980
14805
|
let looksLikeProject = false;
|
|
13981
14806
|
for (const marker of PROJECT_MARKERS) {
|
|
13982
|
-
if (await fileExists(
|
|
14807
|
+
if (await fileExists(resolve40(projectDir, marker))) {
|
|
13983
14808
|
looksLikeProject = true;
|
|
13984
14809
|
break;
|
|
13985
14810
|
}
|
|
@@ -13998,7 +14823,7 @@ var requiredFiles = {
|
|
|
13998
14823
|
for (const projectDir of projects) {
|
|
13999
14824
|
const missing = [];
|
|
14000
14825
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
14001
|
-
const p =
|
|
14826
|
+
const p = resolve40(projectDir, rel);
|
|
14002
14827
|
if (!await fileExists(p)) missing.push(rel);
|
|
14003
14828
|
}
|
|
14004
14829
|
if (missing.length === 0) continue;
|
|
@@ -14008,7 +14833,7 @@ var requiredFiles = {
|
|
|
14008
14833
|
title: this.title,
|
|
14009
14834
|
status: "error",
|
|
14010
14835
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
14011
|
-
affected: missing.map((m) =>
|
|
14836
|
+
affected: missing.map((m) => resolve40(projectDir, m)),
|
|
14012
14837
|
remediation: {
|
|
14013
14838
|
kind: "manual",
|
|
14014
14839
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -14031,7 +14856,7 @@ var manifestStale = {
|
|
|
14031
14856
|
const projects = await listProjects2(ctx);
|
|
14032
14857
|
const results = [];
|
|
14033
14858
|
for (const projectDir of projects) {
|
|
14034
|
-
const manifestPath =
|
|
14859
|
+
const manifestPath = resolve40(projectDir, "manifest.md");
|
|
14035
14860
|
if (!await fileExists(manifestPath)) continue;
|
|
14036
14861
|
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
14037
14862
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -14080,7 +14905,7 @@ var orphanFiles = {
|
|
|
14080
14905
|
title: this.title,
|
|
14081
14906
|
status: "warn",
|
|
14082
14907
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
14083
|
-
affected: orphans.map((o) =>
|
|
14908
|
+
affected: orphans.map((o) => resolve40(projectDir, o)),
|
|
14084
14909
|
autoFixable: false
|
|
14085
14910
|
});
|
|
14086
14911
|
}
|
|
@@ -14090,7 +14915,7 @@ var orphanFiles = {
|
|
|
14090
14915
|
};
|
|
14091
14916
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
14092
14917
|
async function newestAssignmentMtime(projectDir) {
|
|
14093
|
-
const assignmentsRoot =
|
|
14918
|
+
const assignmentsRoot = resolve40(projectDir, "assignments");
|
|
14094
14919
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
14095
14920
|
let newest = 0;
|
|
14096
14921
|
let entries;
|
|
@@ -14101,7 +14926,7 @@ async function newestAssignmentMtime(projectDir) {
|
|
|
14101
14926
|
}
|
|
14102
14927
|
for (const e of entries) {
|
|
14103
14928
|
if (!e.isDirectory()) continue;
|
|
14104
|
-
const assignmentMd =
|
|
14929
|
+
const assignmentMd = resolve40(assignmentsRoot, e.name, "assignment.md");
|
|
14105
14930
|
try {
|
|
14106
14931
|
const s = await stat6(assignmentMd);
|
|
14107
14932
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -14125,8 +14950,8 @@ init_fs();
|
|
|
14125
14950
|
init_parser();
|
|
14126
14951
|
init_types();
|
|
14127
14952
|
init_paths();
|
|
14128
|
-
import { resolve as
|
|
14129
|
-
import { readdir as readdir15, readFile as
|
|
14953
|
+
import { resolve as resolve41 } from "path";
|
|
14954
|
+
import { readdir as readdir15, readFile as readFile25 } from "fs/promises";
|
|
14130
14955
|
var CATEGORY4 = "assignment";
|
|
14131
14956
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
14132
14957
|
async function listAssignments(ctx) {
|
|
@@ -14137,16 +14962,16 @@ async function listAssignments(ctx) {
|
|
|
14137
14962
|
for (const m of projects) {
|
|
14138
14963
|
if (!m.isDirectory()) continue;
|
|
14139
14964
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
14140
|
-
const assignmentsDir2 =
|
|
14965
|
+
const assignmentsDir2 = resolve41(projectsDir2, m.name, "assignments");
|
|
14141
14966
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
14142
14967
|
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
14143
14968
|
for (const a of entries) {
|
|
14144
14969
|
if (!a.isDirectory()) continue;
|
|
14145
14970
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
14146
|
-
const assignmentDir =
|
|
14147
|
-
const assignmentMd =
|
|
14971
|
+
const assignmentDir = resolve41(assignmentsDir2, a.name);
|
|
14972
|
+
const assignmentMd = resolve41(assignmentDir, "assignment.md");
|
|
14148
14973
|
const entry = {
|
|
14149
|
-
projectDir:
|
|
14974
|
+
projectDir: resolve41(projectsDir2, m.name),
|
|
14150
14975
|
projectSlug: m.name,
|
|
14151
14976
|
assignmentDir,
|
|
14152
14977
|
assignmentSlug: a.name,
|
|
@@ -14166,8 +14991,8 @@ async function listAssignments(ctx) {
|
|
|
14166
14991
|
for (const a of entries) {
|
|
14167
14992
|
if (!a.isDirectory()) continue;
|
|
14168
14993
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
14169
|
-
const assignmentDir =
|
|
14170
|
-
const assignmentMd =
|
|
14994
|
+
const assignmentDir = resolve41(standaloneRoot, a.name);
|
|
14995
|
+
const assignmentMd = resolve41(assignmentDir, "assignment.md");
|
|
14171
14996
|
const entry = {
|
|
14172
14997
|
projectDir: standaloneRoot,
|
|
14173
14998
|
projectSlug: null,
|
|
@@ -14245,7 +15070,7 @@ var invalidStatus = {
|
|
|
14245
15070
|
const allowed = configuredStatuses(ctx);
|
|
14246
15071
|
const results = [];
|
|
14247
15072
|
for (const a of withAssignmentMd) {
|
|
14248
|
-
const path =
|
|
15073
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14249
15074
|
const parsed = await parseSafe(path);
|
|
14250
15075
|
if (!parsed) continue;
|
|
14251
15076
|
if (!allowed.has(parsed.status)) {
|
|
@@ -14278,7 +15103,7 @@ var workspaceMissing = {
|
|
|
14278
15103
|
const terminal = terminalStatuses(ctx);
|
|
14279
15104
|
const results = [];
|
|
14280
15105
|
for (const a of withAssignmentMd) {
|
|
14281
|
-
const path =
|
|
15106
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14282
15107
|
const parsed = await parseSafe(path);
|
|
14283
15108
|
if (!parsed) continue;
|
|
14284
15109
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -14325,12 +15150,12 @@ var requiredFilesByStatus = {
|
|
|
14325
15150
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
14326
15151
|
const results = [];
|
|
14327
15152
|
for (const a of withAssignmentMd) {
|
|
14328
|
-
const assignmentPath =
|
|
15153
|
+
const assignmentPath = resolve41(a.assignmentDir, "assignment.md");
|
|
14329
15154
|
const parsed = await parseSafe(assignmentPath);
|
|
14330
15155
|
if (!parsed) continue;
|
|
14331
15156
|
const missing = [];
|
|
14332
15157
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
14333
|
-
const handoffPath =
|
|
15158
|
+
const handoffPath = resolve41(a.assignmentDir, "handoff.md");
|
|
14334
15159
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
14335
15160
|
}
|
|
14336
15161
|
if (missing.length === 0) continue;
|
|
@@ -14340,7 +15165,7 @@ var requiredFilesByStatus = {
|
|
|
14340
15165
|
title: this.title,
|
|
14341
15166
|
status: "warn",
|
|
14342
15167
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
14343
|
-
affected: missing.map((m) =>
|
|
15168
|
+
affected: missing.map((m) => resolve41(a.assignmentDir, m)),
|
|
14344
15169
|
remediation: {
|
|
14345
15170
|
kind: "manual",
|
|
14346
15171
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -14363,7 +15188,7 @@ var companionFilesScaffolded = {
|
|
|
14363
15188
|
for (const a of withAssignmentMd) {
|
|
14364
15189
|
const missing = [];
|
|
14365
15190
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
14366
|
-
if (!await fileExists(
|
|
15191
|
+
if (!await fileExists(resolve41(a.assignmentDir, filename))) {
|
|
14367
15192
|
missing.push(filename);
|
|
14368
15193
|
}
|
|
14369
15194
|
}
|
|
@@ -14375,7 +15200,7 @@ var companionFilesScaffolded = {
|
|
|
14375
15200
|
title: this.title,
|
|
14376
15201
|
status: "warn",
|
|
14377
15202
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
14378
|
-
affected: missing.map((m) =>
|
|
15203
|
+
affected: missing.map((m) => resolve41(a.assignmentDir, m)),
|
|
14379
15204
|
remediation: {
|
|
14380
15205
|
kind: "manual",
|
|
14381
15206
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -14408,7 +15233,7 @@ var typeDefinition = {
|
|
|
14408
15233
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
14409
15234
|
const results = [];
|
|
14410
15235
|
for (const a of withAssignmentMd) {
|
|
14411
|
-
const path =
|
|
15236
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14412
15237
|
const parsed = await parseSafe(path);
|
|
14413
15238
|
if (!parsed) continue;
|
|
14414
15239
|
if (!parsed.type) continue;
|
|
@@ -14442,7 +15267,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
14442
15267
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
14443
15268
|
const results = [];
|
|
14444
15269
|
for (const a of withAssignmentMd) {
|
|
14445
|
-
const path =
|
|
15270
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14446
15271
|
const parsed = await parseSafe(path);
|
|
14447
15272
|
if (!parsed) continue;
|
|
14448
15273
|
if (a.standalone) {
|
|
@@ -14497,7 +15322,7 @@ var assignmentChecks = [
|
|
|
14497
15322
|
];
|
|
14498
15323
|
async function parseSafe(path) {
|
|
14499
15324
|
try {
|
|
14500
|
-
const content = await
|
|
15325
|
+
const content = await readFile25(path, "utf-8");
|
|
14501
15326
|
return parseAssignmentFull(content);
|
|
14502
15327
|
} catch {
|
|
14503
15328
|
return null;
|
|
@@ -14516,7 +15341,7 @@ function pass4(check, detail) {
|
|
|
14516
15341
|
|
|
14517
15342
|
// src/utils/doctor/checks/dashboard.ts
|
|
14518
15343
|
init_fs();
|
|
14519
|
-
import { resolve as
|
|
15344
|
+
import { resolve as resolve42 } from "path";
|
|
14520
15345
|
var CATEGORY5 = "dashboard";
|
|
14521
15346
|
var dbReachable = {
|
|
14522
15347
|
id: "dashboard.db-reachable",
|
|
@@ -14530,7 +15355,7 @@ var dbReachable = {
|
|
|
14530
15355
|
title: this.title,
|
|
14531
15356
|
status: "error",
|
|
14532
15357
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
14533
|
-
affected: [
|
|
15358
|
+
affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
|
|
14534
15359
|
remediation: {
|
|
14535
15360
|
kind: "manual",
|
|
14536
15361
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -14548,7 +15373,7 @@ var dbReachable = {
|
|
|
14548
15373
|
title: this.title,
|
|
14549
15374
|
status: "error",
|
|
14550
15375
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
14551
|
-
affected: [
|
|
15376
|
+
affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
|
|
14552
15377
|
autoFixable: false
|
|
14553
15378
|
};
|
|
14554
15379
|
}
|
|
@@ -14560,7 +15385,7 @@ var dbReachable = {
|
|
|
14560
15385
|
title: this.title,
|
|
14561
15386
|
status: "error",
|
|
14562
15387
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
14563
|
-
affected: [
|
|
15388
|
+
affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
|
|
14564
15389
|
autoFixable: false
|
|
14565
15390
|
};
|
|
14566
15391
|
}
|
|
@@ -14586,7 +15411,7 @@ var ghostSessions = {
|
|
|
14586
15411
|
const results = [];
|
|
14587
15412
|
for (const row of rows) {
|
|
14588
15413
|
if (!row.project_slug) continue;
|
|
14589
|
-
const projectPath =
|
|
15414
|
+
const projectPath = resolve42(projectsDir2, row.project_slug, "project.md");
|
|
14590
15415
|
if (!await fileExists(projectPath)) {
|
|
14591
15416
|
results.push({
|
|
14592
15417
|
id: this.id,
|
|
@@ -14605,7 +15430,7 @@ var ghostSessions = {
|
|
|
14605
15430
|
continue;
|
|
14606
15431
|
}
|
|
14607
15432
|
if (row.assignment_slug) {
|
|
14608
|
-
const assignmentPath =
|
|
15433
|
+
const assignmentPath = resolve42(
|
|
14609
15434
|
projectsDir2,
|
|
14610
15435
|
row.project_slug,
|
|
14611
15436
|
"assignments",
|
|
@@ -14762,8 +15587,8 @@ function skipped2(check, reason) {
|
|
|
14762
15587
|
init_fs();
|
|
14763
15588
|
init_parser();
|
|
14764
15589
|
init_types();
|
|
14765
|
-
import { resolve as
|
|
14766
|
-
import { readFile as
|
|
15590
|
+
import { resolve as resolve43 } from "path";
|
|
15591
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
14767
15592
|
var CATEGORY7 = "workspace";
|
|
14768
15593
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
14769
15594
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -14775,12 +15600,12 @@ function isStandaloneSession(ctx) {
|
|
|
14775
15600
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
14776
15601
|
}
|
|
14777
15602
|
async function loadContext(ctx) {
|
|
14778
|
-
const path =
|
|
15603
|
+
const path = resolve43(ctx.cwd, ".syntaur", "context.json");
|
|
14779
15604
|
if (!await fileExists(path)) {
|
|
14780
15605
|
return { data: null, path, exists: false, parseError: null };
|
|
14781
15606
|
}
|
|
14782
15607
|
try {
|
|
14783
|
-
const raw = await
|
|
15608
|
+
const raw = await readFile26(path, "utf-8");
|
|
14784
15609
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
14785
15610
|
} catch (err2) {
|
|
14786
15611
|
return {
|
|
@@ -14855,7 +15680,7 @@ var contextAssignmentResolves = {
|
|
|
14855
15680
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
14856
15681
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
14857
15682
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
14858
|
-
const assignmentMd =
|
|
15683
|
+
const assignmentMd = resolve43(data.assignmentDir, "assignment.md");
|
|
14859
15684
|
if (!await fileExists(assignmentMd)) {
|
|
14860
15685
|
return {
|
|
14861
15686
|
id: this.id,
|
|
@@ -14884,10 +15709,10 @@ var contextTerminal = {
|
|
|
14884
15709
|
if (!exists) return skipped3(this, "no context to check");
|
|
14885
15710
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
14886
15711
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
14887
|
-
const assignmentMd =
|
|
15712
|
+
const assignmentMd = resolve43(data.assignmentDir, "assignment.md");
|
|
14888
15713
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
14889
15714
|
try {
|
|
14890
|
-
const content = await
|
|
15715
|
+
const content = await readFile26(assignmentMd, "utf-8");
|
|
14891
15716
|
const parsed = parseAssignmentFull(content);
|
|
14892
15717
|
const terminal = terminalStatuses2(ctx);
|
|
14893
15718
|
if (terminal.has(parsed.status)) {
|
|
@@ -14939,6 +15764,97 @@ function skipped3(check, reason) {
|
|
|
14939
15764
|
};
|
|
14940
15765
|
}
|
|
14941
15766
|
|
|
15767
|
+
// src/utils/doctor/checks/agents.ts
|
|
15768
|
+
init_config2();
|
|
15769
|
+
import { isAbsolute as isAbsolute6 } from "path";
|
|
15770
|
+
import { access as access2, constants as fsConstants } from "fs/promises";
|
|
15771
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
15772
|
+
var CATEGORY8 = "agents";
|
|
15773
|
+
var agentsResolvable = {
|
|
15774
|
+
id: "agents.commands-resolvable",
|
|
15775
|
+
category: CATEGORY8,
|
|
15776
|
+
title: "All configured agent commands resolve",
|
|
15777
|
+
async run(ctx) {
|
|
15778
|
+
const agents = getAgents(ctx.config);
|
|
15779
|
+
if (agents.length === 0) {
|
|
15780
|
+
return {
|
|
15781
|
+
id: this.id,
|
|
15782
|
+
category: this.category,
|
|
15783
|
+
title: this.title,
|
|
15784
|
+
status: "warn",
|
|
15785
|
+
detail: "No agents configured and no built-in defaults available",
|
|
15786
|
+
remediation: {
|
|
15787
|
+
kind: "manual",
|
|
15788
|
+
suggestion: "Run `syntaur agents add --id <id> --label <label> --command <path>`",
|
|
15789
|
+
command: null
|
|
15790
|
+
},
|
|
15791
|
+
autoFixable: false
|
|
15792
|
+
};
|
|
15793
|
+
}
|
|
15794
|
+
const results = [];
|
|
15795
|
+
for (const agent of agents) {
|
|
15796
|
+
results.push(await checkAgent(agent));
|
|
15797
|
+
}
|
|
15798
|
+
return results;
|
|
15799
|
+
}
|
|
15800
|
+
};
|
|
15801
|
+
async function checkAgent(agent) {
|
|
15802
|
+
const base = {
|
|
15803
|
+
id: `agents.resolvable.${agent.id}`,
|
|
15804
|
+
category: CATEGORY8,
|
|
15805
|
+
title: `Agent "${agent.id}" command resolves`
|
|
15806
|
+
};
|
|
15807
|
+
if (agent.resolveFromShellAliases) {
|
|
15808
|
+
return {
|
|
15809
|
+
...base,
|
|
15810
|
+
status: "pass",
|
|
15811
|
+
detail: `shell-alias resolution enabled for "${agent.command}" \u2014 will run via $SHELL -i -c`,
|
|
15812
|
+
autoFixable: false
|
|
15813
|
+
};
|
|
15814
|
+
}
|
|
15815
|
+
if (isAbsolute6(agent.command)) {
|
|
15816
|
+
try {
|
|
15817
|
+
await access2(agent.command, fsConstants.X_OK);
|
|
15818
|
+
return { ...base, status: "pass", autoFixable: false };
|
|
15819
|
+
} catch (err2) {
|
|
15820
|
+
const code = err2.code;
|
|
15821
|
+
const detail = code === "ENOENT" ? `absolute path "${agent.command}" does not exist` : code === "EACCES" ? `absolute path "${agent.command}" exists but is not executable (chmod +x?)` : `absolute path "${agent.command}" failed access check (${code ?? "unknown"})`;
|
|
15822
|
+
return {
|
|
15823
|
+
...base,
|
|
15824
|
+
status: "warn",
|
|
15825
|
+
detail,
|
|
15826
|
+
remediation: {
|
|
15827
|
+
kind: "manual",
|
|
15828
|
+
suggestion: `Update with \`syntaur agents set ${agent.id} --command <path>\` or fix the file permissions`,
|
|
15829
|
+
command: null
|
|
15830
|
+
},
|
|
15831
|
+
autoFixable: false
|
|
15832
|
+
};
|
|
15833
|
+
}
|
|
15834
|
+
}
|
|
15835
|
+
const result = spawnSync3("which", [agent.command], { encoding: "utf-8" });
|
|
15836
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
15837
|
+
return {
|
|
15838
|
+
...base,
|
|
15839
|
+
status: "pass",
|
|
15840
|
+
detail: `resolved "${agent.command}" \u2192 ${result.stdout.trim()}`,
|
|
15841
|
+
autoFixable: false
|
|
15842
|
+
};
|
|
15843
|
+
}
|
|
15844
|
+
return {
|
|
15845
|
+
...base,
|
|
15846
|
+
status: "warn",
|
|
15847
|
+
detail: `bare command "${agent.command}" not found on PATH`,
|
|
15848
|
+
remediation: {
|
|
15849
|
+
kind: "manual",
|
|
15850
|
+
suggestion: `Install the binary, point at an absolute path with \`syntaur agents set ${agent.id} --command <abs-path>\`, or enable shell-alias resolution with \`syntaur agents set ${agent.id} --resolve-from-shell-aliases\``,
|
|
15851
|
+
command: null
|
|
15852
|
+
},
|
|
15853
|
+
autoFixable: false
|
|
15854
|
+
};
|
|
15855
|
+
}
|
|
15856
|
+
var agentChecks = [agentsResolvable];
|
|
15857
|
+
|
|
14942
15858
|
// src/utils/doctor/registry.ts
|
|
14943
15859
|
function allChecks() {
|
|
14944
15860
|
return [
|
|
@@ -14948,7 +15864,8 @@ function allChecks() {
|
|
|
14948
15864
|
...assignmentChecks,
|
|
14949
15865
|
...dashboardChecks,
|
|
14950
15866
|
...integrationChecks,
|
|
14951
|
-
...workspaceChecks
|
|
15867
|
+
...workspaceChecks,
|
|
15868
|
+
...agentChecks
|
|
14952
15869
|
];
|
|
14953
15870
|
}
|
|
14954
15871
|
|
|
@@ -15034,7 +15951,7 @@ async function readVersion() {
|
|
|
15034
15951
|
let dir = dirname11(here);
|
|
15035
15952
|
for (let i = 0; i < 6; i++) {
|
|
15036
15953
|
try {
|
|
15037
|
-
const raw = await
|
|
15954
|
+
const raw = await readFile27(join5(dir, "package.json"), "utf-8");
|
|
15038
15955
|
const parsed = JSON.parse(raw);
|
|
15039
15956
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
15040
15957
|
} catch {
|
|
@@ -15167,12 +16084,218 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
15167
16084
|
}
|
|
15168
16085
|
});
|
|
15169
16086
|
|
|
16087
|
+
// src/commands/agents.ts
|
|
16088
|
+
init_config2();
|
|
16089
|
+
import { Command as Command4 } from "commander";
|
|
16090
|
+
var agentsCommand = new Command4("agents").description(
|
|
16091
|
+
"Manage configurable agents used by `syntaur browse` and future launch flows"
|
|
16092
|
+
);
|
|
16093
|
+
agentsCommand.command("list").description("List configured agents (or built-in defaults if none are configured)").action(async () => {
|
|
16094
|
+
try {
|
|
16095
|
+
const config = await readConfig();
|
|
16096
|
+
const agents = getAgents(config);
|
|
16097
|
+
const source = config.agents ? "config" : "built-in defaults";
|
|
16098
|
+
console.log(`Agents (${source}):`);
|
|
16099
|
+
for (const agent of agents) {
|
|
16100
|
+
const flags = [];
|
|
16101
|
+
if (agent.default) flags.push("default");
|
|
16102
|
+
if (agent.resolveFromShellAliases) flags.push("shell-alias");
|
|
16103
|
+
if (agent.promptArgPosition) flags.push(`prompt=${agent.promptArgPosition}`);
|
|
16104
|
+
const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
16105
|
+
const args = agent.args && agent.args.length > 0 ? ` ${agent.args.join(" ")}` : "";
|
|
16106
|
+
console.log(` ${agent.id.padEnd(12)} ${agent.label.padEnd(20)} ${agent.command}${args}${flagStr}`);
|
|
16107
|
+
}
|
|
16108
|
+
} catch (error) {
|
|
16109
|
+
reportAndExit(error);
|
|
16110
|
+
}
|
|
16111
|
+
});
|
|
16112
|
+
agentsCommand.command("add").description("Add a new agent to ~/.syntaur/config.md").requiredOption("--id <id>", "Agent id (slug)").requiredOption("--label <label>", "Display label").requiredOption("--command <command>", "Absolute path or bare binary name").option("--args <csv>", "Comma-separated default args").option("--prompt-arg-position <position>", "first | last | none").option("--default", "Mark this agent as the default launch target").option("--resolve-from-shell-aliases", "Run via $SHELL -i -c (for shell aliases)").option("--dry-run", "Validate and print the proposed config without writing").action(async (options) => {
|
|
16113
|
+
try {
|
|
16114
|
+
const agent = buildAgentFromOptions(options, null);
|
|
16115
|
+
const mutation = {
|
|
16116
|
+
kind: "add",
|
|
16117
|
+
apply: (current) => {
|
|
16118
|
+
if (current.some((a) => a.id === agent.id)) {
|
|
16119
|
+
throw new AgentConfigError(`agent "${agent.id}" already exists`);
|
|
16120
|
+
}
|
|
16121
|
+
const next = agent.default ? current.map((a) => ({ ...a, default: false })) : [...current];
|
|
16122
|
+
return [...next, agent];
|
|
16123
|
+
}
|
|
16124
|
+
};
|
|
16125
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16126
|
+
dryRun: Boolean(options.dryRun)
|
|
16127
|
+
});
|
|
16128
|
+
reportMutation("add", result);
|
|
16129
|
+
} catch (error) {
|
|
16130
|
+
reportAndExit(error);
|
|
16131
|
+
}
|
|
16132
|
+
});
|
|
16133
|
+
agentsCommand.command("remove <id>").description("Remove an agent from ~/.syntaur/config.md").option("--dry-run", "Validate and print the proposed config without writing").action(async (id, options) => {
|
|
16134
|
+
try {
|
|
16135
|
+
const mutation = {
|
|
16136
|
+
kind: "remove",
|
|
16137
|
+
apply: (current) => {
|
|
16138
|
+
if (!current.some((a) => a.id === id)) {
|
|
16139
|
+
throw new AgentConfigError(`unknown agent id "${id}"`);
|
|
16140
|
+
}
|
|
16141
|
+
return current.filter((a) => a.id !== id);
|
|
16142
|
+
}
|
|
16143
|
+
};
|
|
16144
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16145
|
+
dryRun: Boolean(options.dryRun)
|
|
16146
|
+
});
|
|
16147
|
+
reportMutation("remove", result);
|
|
16148
|
+
} catch (error) {
|
|
16149
|
+
reportAndExit(error);
|
|
16150
|
+
}
|
|
16151
|
+
});
|
|
16152
|
+
agentsCommand.command("set <id>").description("Update one or more fields on an existing agent").option("--label <label>", "Display label").option("--command <command>", "Absolute path or bare binary name").option("--args <csv>", "Comma-separated default args").option("--prompt-arg-position <position>", "first | last | none").option("--default", "Mark this agent as the default (clears any prior default)").option("--no-default", "Unset the default flag on this agent").option("--resolve-from-shell-aliases", "Run via $SHELL -i -c (for shell aliases)").option("--no-resolve-from-shell-aliases", "Disable shell-alias resolution for this agent").option("--dry-run", "Validate and print the proposed config without writing").action(async (id, options) => {
|
|
16153
|
+
try {
|
|
16154
|
+
const mutation = {
|
|
16155
|
+
kind: "set",
|
|
16156
|
+
apply: (current) => {
|
|
16157
|
+
const existing = current.find((a) => a.id === id);
|
|
16158
|
+
if (!existing) {
|
|
16159
|
+
throw new AgentConfigError(`unknown agent id "${id}"`);
|
|
16160
|
+
}
|
|
16161
|
+
const merged = mergeOptionsIntoAgent(existing, options);
|
|
16162
|
+
const defaultFlip = options.default === true;
|
|
16163
|
+
return current.map((a) => {
|
|
16164
|
+
if (a.id === id) return merged;
|
|
16165
|
+
if (defaultFlip) return { ...a, default: false };
|
|
16166
|
+
return a;
|
|
16167
|
+
});
|
|
16168
|
+
}
|
|
16169
|
+
};
|
|
16170
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16171
|
+
dryRun: Boolean(options.dryRun)
|
|
16172
|
+
});
|
|
16173
|
+
reportMutation("set", result);
|
|
16174
|
+
} catch (error) {
|
|
16175
|
+
reportAndExit(error);
|
|
16176
|
+
}
|
|
16177
|
+
});
|
|
16178
|
+
agentsCommand.command("reorder <ids>").description("Reorder agents (comma-separated ids, must cover every configured agent exactly once)").option("--dry-run", "Validate and print the proposed config without writing").action(async (ids, options) => {
|
|
16179
|
+
try {
|
|
16180
|
+
const newOrder = ids.split(",").map((s) => s.trim()).filter(Boolean);
|
|
16181
|
+
const mutation = {
|
|
16182
|
+
kind: "reorder",
|
|
16183
|
+
apply: (current) => {
|
|
16184
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16185
|
+
for (const id of newOrder) {
|
|
16186
|
+
if (seen.has(id)) {
|
|
16187
|
+
throw new AgentConfigError(`duplicate id "${id}" in reorder list`);
|
|
16188
|
+
}
|
|
16189
|
+
seen.add(id);
|
|
16190
|
+
}
|
|
16191
|
+
const currentIds = new Set(current.map((a) => a.id));
|
|
16192
|
+
const missing = current.filter((a) => !seen.has(a.id)).map((a) => a.id);
|
|
16193
|
+
const extra = newOrder.filter((id) => !currentIds.has(id));
|
|
16194
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
16195
|
+
const parts = [];
|
|
16196
|
+
if (missing.length) parts.push(`missing: ${missing.join(", ")}`);
|
|
16197
|
+
if (extra.length) parts.push(`unknown: ${extra.join(", ")}`);
|
|
16198
|
+
throw new AgentConfigError(
|
|
16199
|
+
`reorder list does not match current agents (${parts.join("; ")})`
|
|
16200
|
+
);
|
|
16201
|
+
}
|
|
16202
|
+
return newOrder.map((id) => current.find((a) => a.id === id));
|
|
16203
|
+
}
|
|
16204
|
+
};
|
|
16205
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16206
|
+
dryRun: Boolean(options.dryRun)
|
|
16207
|
+
});
|
|
16208
|
+
reportMutation("reorder", result);
|
|
16209
|
+
} catch (error) {
|
|
16210
|
+
reportAndExit(error);
|
|
16211
|
+
}
|
|
16212
|
+
});
|
|
16213
|
+
function buildAgentFromOptions(options, existing) {
|
|
16214
|
+
const agent = {
|
|
16215
|
+
id: options.id,
|
|
16216
|
+
label: options.label,
|
|
16217
|
+
command: parseAgentCommand(options.command, options.id)
|
|
16218
|
+
};
|
|
16219
|
+
const args = parseArgsCsv(options.args);
|
|
16220
|
+
if (args) agent.args = args;
|
|
16221
|
+
if (options.promptArgPosition) {
|
|
16222
|
+
agent.promptArgPosition = options.promptArgPosition;
|
|
16223
|
+
}
|
|
16224
|
+
if (options.default) agent.default = true;
|
|
16225
|
+
if (options.resolveFromShellAliases) agent.resolveFromShellAliases = true;
|
|
16226
|
+
validateAgentList([...existing ? [] : [], agent]);
|
|
16227
|
+
return agent;
|
|
16228
|
+
}
|
|
16229
|
+
function mergeOptionsIntoAgent(existing, options) {
|
|
16230
|
+
const merged = { ...existing };
|
|
16231
|
+
if (options.label !== void 0) merged.label = options.label;
|
|
16232
|
+
if (options.command !== void 0) {
|
|
16233
|
+
merged.command = parseAgentCommand(options.command, existing.id);
|
|
16234
|
+
}
|
|
16235
|
+
if (options.args !== void 0) {
|
|
16236
|
+
const parsed = parseArgsCsv(options.args);
|
|
16237
|
+
if (parsed) merged.args = parsed;
|
|
16238
|
+
else delete merged.args;
|
|
16239
|
+
}
|
|
16240
|
+
if (options.promptArgPosition !== void 0) {
|
|
16241
|
+
merged.promptArgPosition = options.promptArgPosition;
|
|
16242
|
+
}
|
|
16243
|
+
if (options.default === true) merged.default = true;
|
|
16244
|
+
if (options.default === false) delete merged.default;
|
|
16245
|
+
if (options.resolveFromShellAliases === true) merged.resolveFromShellAliases = true;
|
|
16246
|
+
if (options.resolveFromShellAliases === false) delete merged.resolveFromShellAliases;
|
|
16247
|
+
return merged;
|
|
16248
|
+
}
|
|
16249
|
+
function parseArgsCsv(csv) {
|
|
16250
|
+
if (csv === void 0) return null;
|
|
16251
|
+
const parts = csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16252
|
+
return parts.length > 0 ? parts : null;
|
|
16253
|
+
}
|
|
16254
|
+
function reportMutation(action, result) {
|
|
16255
|
+
const diff = renderDiff(result.previous, result.next);
|
|
16256
|
+
if (result.written) {
|
|
16257
|
+
console.log(`Agents updated (${action}):`);
|
|
16258
|
+
console.log(diff);
|
|
16259
|
+
} else {
|
|
16260
|
+
console.log(`Dry run (${action}) \u2014 no changes written:`);
|
|
16261
|
+
console.log(diff);
|
|
16262
|
+
}
|
|
16263
|
+
}
|
|
16264
|
+
function renderDiff(prev, next) {
|
|
16265
|
+
const prevLines = prev.map(formatAgentLine);
|
|
16266
|
+
const nextLines = next.map(formatAgentLine);
|
|
16267
|
+
const prevSet = new Set(prevLines);
|
|
16268
|
+
const nextSet = new Set(nextLines);
|
|
16269
|
+
const out = [];
|
|
16270
|
+
for (const line of prevLines) {
|
|
16271
|
+
if (!nextSet.has(line)) out.push(` - ${line}`);
|
|
16272
|
+
}
|
|
16273
|
+
for (const line of nextLines) {
|
|
16274
|
+
if (!prevSet.has(line)) out.push(` + ${line}`);
|
|
16275
|
+
}
|
|
16276
|
+
if (out.length === 0) out.push(" (no changes)");
|
|
16277
|
+
return out.join("\n");
|
|
16278
|
+
}
|
|
16279
|
+
function formatAgentLine(a) {
|
|
16280
|
+
const flags = [];
|
|
16281
|
+
if (a.default) flags.push("default");
|
|
16282
|
+
if (a.resolveFromShellAliases) flags.push("shell-alias");
|
|
16283
|
+
if (a.promptArgPosition) flags.push(`prompt=${a.promptArgPosition}`);
|
|
16284
|
+
if (a.args && a.args.length > 0) flags.push(`args=[${a.args.join(", ")}]`);
|
|
16285
|
+
const suffix = flags.length > 0 ? ` (${flags.join(", ")})` : "";
|
|
16286
|
+
return `${a.id}: ${a.label} \u2192 ${a.command}${suffix}`;
|
|
16287
|
+
}
|
|
16288
|
+
function reportAndExit(error) {
|
|
16289
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
16290
|
+
process.exit(1);
|
|
16291
|
+
}
|
|
16292
|
+
|
|
15170
16293
|
// src/commands/comment.ts
|
|
15171
16294
|
init_paths();
|
|
15172
16295
|
init_fs();
|
|
15173
16296
|
init_config2();
|
|
15174
|
-
import { resolve as
|
|
15175
|
-
import { readFile as
|
|
16297
|
+
import { resolve as resolve44 } from "path";
|
|
16298
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
15176
16299
|
init_timestamp();
|
|
15177
16300
|
init_assignment_resolver();
|
|
15178
16301
|
function shortId() {
|
|
@@ -15204,7 +16327,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
15204
16327
|
if (!isValidSlug(target)) {
|
|
15205
16328
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
15206
16329
|
}
|
|
15207
|
-
assignmentDir =
|
|
16330
|
+
assignmentDir = resolve44(baseDir, options.project, "assignments", target);
|
|
15208
16331
|
assignmentRef = target;
|
|
15209
16332
|
} else {
|
|
15210
16333
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -15214,13 +16337,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
15214
16337
|
assignmentDir = resolved.assignmentDir;
|
|
15215
16338
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
15216
16339
|
}
|
|
15217
|
-
const commentsPath =
|
|
16340
|
+
const commentsPath = resolve44(assignmentDir, "comments.md");
|
|
15218
16341
|
const timestamp = nowTimestamp();
|
|
15219
16342
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
15220
16343
|
let currentContent;
|
|
15221
16344
|
let currentCount = 0;
|
|
15222
16345
|
if (await fileExists(commentsPath)) {
|
|
15223
|
-
currentContent = await
|
|
16346
|
+
currentContent = await readFile28(commentsPath, "utf-8");
|
|
15224
16347
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
15225
16348
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
15226
16349
|
} else {
|
|
@@ -15257,8 +16380,8 @@ ${entry}`;
|
|
|
15257
16380
|
init_paths();
|
|
15258
16381
|
init_fs();
|
|
15259
16382
|
init_config2();
|
|
15260
|
-
import { resolve as
|
|
15261
|
-
import { readFile as
|
|
16383
|
+
import { resolve as resolve45 } from "path";
|
|
16384
|
+
import { readFile as readFile29 } from "fs/promises";
|
|
15262
16385
|
init_timestamp();
|
|
15263
16386
|
init_assignment_resolver();
|
|
15264
16387
|
function setTopLevelField3(content, key, value) {
|
|
@@ -15283,7 +16406,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
15283
16406
|
if (!isValidSlug(target)) {
|
|
15284
16407
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
15285
16408
|
}
|
|
15286
|
-
assignmentDir =
|
|
16409
|
+
assignmentDir = resolve45(baseDir, options.project, "assignments", target);
|
|
15287
16410
|
targetRef = target;
|
|
15288
16411
|
} else {
|
|
15289
16412
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -15293,12 +16416,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
15293
16416
|
assignmentDir = resolved.assignmentDir;
|
|
15294
16417
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
15295
16418
|
}
|
|
15296
|
-
const assignmentMdPath =
|
|
16419
|
+
const assignmentMdPath = resolve45(assignmentDir, "assignment.md");
|
|
15297
16420
|
if (!await fileExists(assignmentMdPath)) {
|
|
15298
16421
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
15299
16422
|
}
|
|
15300
16423
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
15301
|
-
let content = await
|
|
16424
|
+
let content = await readFile29(assignmentMdPath, "utf-8");
|
|
15302
16425
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
15303
16426
|
const todosHeading = /^## Todos\s*$/m;
|
|
15304
16427
|
if (todosHeading.test(content)) {
|
|
@@ -15366,20 +16489,20 @@ async function getDefaultCommandName() {
|
|
|
15366
16489
|
init_paths();
|
|
15367
16490
|
init_fs();
|
|
15368
16491
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
15369
|
-
import { readFile as
|
|
15370
|
-
import { dirname as dirname13, join as join7, resolve as
|
|
15371
|
-
import { spawn as
|
|
16492
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
16493
|
+
import { dirname as dirname13, join as join7, resolve as resolve46 } from "path";
|
|
16494
|
+
import { spawn as spawn4 } from "child_process";
|
|
15372
16495
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
15373
16496
|
|
|
15374
16497
|
// src/utils/version.ts
|
|
15375
16498
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
15376
|
-
import { readFile as
|
|
16499
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
15377
16500
|
import { dirname as dirname12, join as join6 } from "path";
|
|
15378
16501
|
async function readPackageVersion(scriptUrl) {
|
|
15379
16502
|
try {
|
|
15380
16503
|
const scriptPath = fileURLToPath8(scriptUrl);
|
|
15381
16504
|
const pkgRoot = dirname12(dirname12(scriptPath));
|
|
15382
|
-
const raw = await
|
|
16505
|
+
const raw = await readFile30(join6(pkgRoot, "package.json"), "utf-8");
|
|
15383
16506
|
const parsed = JSON.parse(raw);
|
|
15384
16507
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
15385
16508
|
} catch {
|
|
@@ -15388,7 +16511,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
15388
16511
|
}
|
|
15389
16512
|
|
|
15390
16513
|
// src/utils/npx-prompt.ts
|
|
15391
|
-
var STATE_FILE =
|
|
16514
|
+
var STATE_FILE = resolve46(syntaurRoot(), "npx-install.json");
|
|
15392
16515
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
15393
16516
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
15394
16517
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -15409,7 +16532,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
15409
16532
|
async function readState() {
|
|
15410
16533
|
if (!await fileExists(STATE_FILE)) return null;
|
|
15411
16534
|
try {
|
|
15412
|
-
const raw = await
|
|
16535
|
+
const raw = await readFile31(STATE_FILE, "utf-8");
|
|
15413
16536
|
return JSON.parse(raw);
|
|
15414
16537
|
} catch {
|
|
15415
16538
|
return null;
|
|
@@ -15432,7 +16555,7 @@ async function resolveNpmBin() {
|
|
|
15432
16555
|
async function installGlobally() {
|
|
15433
16556
|
const { cmd, shell } = await resolveNpmBin();
|
|
15434
16557
|
return new Promise((resolvePromise) => {
|
|
15435
|
-
const child =
|
|
16558
|
+
const child = spawn4(cmd, ["install", "-g", "syntaur"], {
|
|
15436
16559
|
stdio: "inherit",
|
|
15437
16560
|
shell
|
|
15438
16561
|
});
|
|
@@ -15443,7 +16566,7 @@ async function installGlobally() {
|
|
|
15443
16566
|
async function readGlobalVersion() {
|
|
15444
16567
|
const { cmd, shell } = await resolveNpmBin();
|
|
15445
16568
|
const rootPath = await new Promise((resolvePromise) => {
|
|
15446
|
-
const child =
|
|
16569
|
+
const child = spawn4(cmd, ["root", "-g"], {
|
|
15447
16570
|
shell,
|
|
15448
16571
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15449
16572
|
});
|
|
@@ -15468,7 +16591,7 @@ async function readGlobalVersion() {
|
|
|
15468
16591
|
try {
|
|
15469
16592
|
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
15470
16593
|
if (!await fileExists(manifestPath)) return null;
|
|
15471
|
-
const raw = await
|
|
16594
|
+
const raw = await readFile31(manifestPath, "utf-8");
|
|
15472
16595
|
const parsed = JSON.parse(raw);
|
|
15473
16596
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
15474
16597
|
} catch {
|
|
@@ -15587,7 +16710,7 @@ async function maybePromptInstall(scriptUrl) {
|
|
|
15587
16710
|
|
|
15588
16711
|
// src/index.ts
|
|
15589
16712
|
await maybePromptInstall(import.meta.url);
|
|
15590
|
-
var program = new
|
|
16713
|
+
var program = new Command5();
|
|
15591
16714
|
var version = await readPackageVersion(import.meta.url) ?? "0.0.0";
|
|
15592
16715
|
program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version(version);
|
|
15593
16716
|
program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
|
|
@@ -15878,7 +17001,7 @@ program.command("track-session").description("Register an agent session (optiona
|
|
|
15878
17001
|
process.exit(1);
|
|
15879
17002
|
}
|
|
15880
17003
|
});
|
|
15881
|
-
program.command("browse").description("Interactive TUI browser for projects and assignments").option("--agent <
|
|
17004
|
+
program.command("browse").description("Interactive TUI browser for projects and assignments").option("--agent <id>", "Bypass the agent picker and launch the given configured agent id").option("--no-worktree-prompt", "Skip the prompt to create a worktree when one is missing").action(async (options) => {
|
|
15882
17005
|
try {
|
|
15883
17006
|
await browseCommand(options);
|
|
15884
17007
|
} catch (error) {
|
|
@@ -15936,6 +17059,7 @@ program.command("disable-playbook").description("Disable a playbook so agents no
|
|
|
15936
17059
|
program.addCommand(todoCommand);
|
|
15937
17060
|
program.addCommand(backupCommand);
|
|
15938
17061
|
program.addCommand(doctorCommand);
|
|
17062
|
+
program.addCommand(agentsCommand);
|
|
15939
17063
|
if (process.argv.length <= 2) {
|
|
15940
17064
|
process.argv.push(await getDefaultCommandName());
|
|
15941
17065
|
}
|