syntaur 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/assets/{_basePickBy-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 +335 -8
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1256 -131
- 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);
|
|
@@ -10297,7 +10935,9 @@ function createDashboardServer(options) {
|
|
|
10297
10935
|
app.use("/api/projects/:projectId/todos", createProjectTodosRouter(projectsDir2, broadcast));
|
|
10298
10936
|
app.use("/api/backup", createBackupRouter());
|
|
10299
10937
|
if (serveStaticUi && dashboardDistPath) {
|
|
10300
|
-
|
|
10938
|
+
const sendOpts = { dotfiles: "allow" };
|
|
10939
|
+
app.use("/assets", express.static(resolve21(dashboardDistPath, "assets"), sendOpts));
|
|
10940
|
+
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
10301
10941
|
app.get("{*path}", async (req, res) => {
|
|
10302
10942
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
10303
10943
|
res.status(404).json({ error: "Not Found" });
|
|
@@ -10310,7 +10950,7 @@ function createDashboardServer(options) {
|
|
|
10310
10950
|
);
|
|
10311
10951
|
return;
|
|
10312
10952
|
}
|
|
10313
|
-
res.sendFile(indexPath, (err2) => {
|
|
10953
|
+
res.sendFile(indexPath, sendOpts, (err2) => {
|
|
10314
10954
|
if (err2) {
|
|
10315
10955
|
console.error("Error sending dashboard index.html:", err2);
|
|
10316
10956
|
if (!res.headersSent) res.status(500).send("Dashboard load error");
|
|
@@ -12673,10 +13313,29 @@ async function trackSessionCommand(options) {
|
|
|
12673
13313
|
|
|
12674
13314
|
// src/commands/browse.ts
|
|
12675
13315
|
init_config2();
|
|
13316
|
+
init_paths();
|
|
13317
|
+
init_fs();
|
|
13318
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
13319
|
+
import { resolve as resolve33, isAbsolute as isAbsolute4 } from "path";
|
|
13320
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
13321
|
+
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
12676
13322
|
async function browseCommand(options) {
|
|
12677
13323
|
const config = await readConfig();
|
|
12678
13324
|
const projectsDir2 = config.defaultProjectDir;
|
|
12679
|
-
const
|
|
13325
|
+
const agents = getAgents(config);
|
|
13326
|
+
if (agents.length === 0) {
|
|
13327
|
+
console.error(
|
|
13328
|
+
"No agents configured. Add one with `syntaur agents add --id <id> --label <label> --command <path>`."
|
|
13329
|
+
);
|
|
13330
|
+
process.exit(1);
|
|
13331
|
+
}
|
|
13332
|
+
const preSelectedAgent = options.agent ? agents.find((a) => a.id === options.agent) : void 0;
|
|
13333
|
+
if (options.agent && !preSelectedAgent) {
|
|
13334
|
+
console.error(
|
|
13335
|
+
`Unknown agent id "${options.agent}". Configured: ${agents.map((a) => a.id).join(", ")}`
|
|
13336
|
+
);
|
|
13337
|
+
process.exit(1);
|
|
13338
|
+
}
|
|
12680
13339
|
const { render } = await import("ink");
|
|
12681
13340
|
const React2 = await import("react");
|
|
12682
13341
|
const { App: App2 } = await Promise.resolve().then(() => (init_App(), App_exports));
|
|
@@ -12687,7 +13346,19 @@ async function browseCommand(options) {
|
|
|
12687
13346
|
unmount();
|
|
12688
13347
|
unmount = null;
|
|
12689
13348
|
}
|
|
12690
|
-
|
|
13349
|
+
const agent = preSelectedAgent ?? await pickAgent(agents);
|
|
13350
|
+
const cwdOverride = await ensureWorktree({
|
|
13351
|
+
projectsDir: launchOpts.projectsDir,
|
|
13352
|
+
projectSlug: launchOpts.projectSlug,
|
|
13353
|
+
assignmentSlug: launchOpts.assignmentSlug,
|
|
13354
|
+
worktreePromptEnabled: options.worktreePrompt !== false,
|
|
13355
|
+
autoCreateWorktree: config.agentDefaults.autoCreateWorktree
|
|
13356
|
+
});
|
|
13357
|
+
await launchAgent2({
|
|
13358
|
+
...launchOpts,
|
|
13359
|
+
agent,
|
|
13360
|
+
...cwdOverride ? { cwdOverride } : {}
|
|
13361
|
+
});
|
|
12691
13362
|
};
|
|
12692
13363
|
const instance = render(
|
|
12693
13364
|
React2.createElement(App2, { projectsDir: projectsDir2, onLaunch })
|
|
@@ -12695,9 +13366,164 @@ async function browseCommand(options) {
|
|
|
12695
13366
|
unmount = instance.unmount;
|
|
12696
13367
|
await instance.waitUntilExit();
|
|
12697
13368
|
}
|
|
13369
|
+
async function pickAgent(agents) {
|
|
13370
|
+
if (agents.length === 1) return agents[0];
|
|
13371
|
+
if (!isInteractiveTerminal()) {
|
|
13372
|
+
const fallback = agents.find((a) => a.default) ?? agents[0];
|
|
13373
|
+
console.warn(
|
|
13374
|
+
`syntaur: multiple agents configured but no TTY \u2014 using "${fallback.id}".`
|
|
13375
|
+
);
|
|
13376
|
+
return fallback;
|
|
13377
|
+
}
|
|
13378
|
+
const defaultAgent = agents.find((a) => a.default) ?? agents[0];
|
|
13379
|
+
const id = await select({
|
|
13380
|
+
message: "Launch which agent?",
|
|
13381
|
+
choices: agents.map((a) => ({ name: a.label, value: a.id, description: a.command })),
|
|
13382
|
+
default: defaultAgent.id
|
|
13383
|
+
});
|
|
13384
|
+
const picked = agents.find((a) => a.id === id);
|
|
13385
|
+
if (!picked) throw new Error(`Internal error: picker returned unknown agent id "${id}"`);
|
|
13386
|
+
return picked;
|
|
13387
|
+
}
|
|
13388
|
+
async function ensureWorktree(opts) {
|
|
13389
|
+
const assignmentPath = resolve33(
|
|
13390
|
+
opts.projectsDir,
|
|
13391
|
+
opts.projectSlug,
|
|
13392
|
+
"assignments",
|
|
13393
|
+
opts.assignmentSlug,
|
|
13394
|
+
"assignment.md"
|
|
13395
|
+
);
|
|
13396
|
+
if (!await fileExists(assignmentPath)) {
|
|
13397
|
+
return void 0;
|
|
13398
|
+
}
|
|
13399
|
+
const content = await readFile21(assignmentPath, "utf-8");
|
|
13400
|
+
const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
|
|
13401
|
+
const fm = parseAssignmentFrontmatter2(content);
|
|
13402
|
+
const { workspace } = fm;
|
|
13403
|
+
if (workspace.worktreePath && workspace.branch) {
|
|
13404
|
+
return void 0;
|
|
13405
|
+
}
|
|
13406
|
+
if (!opts.worktreePromptEnabled) {
|
|
13407
|
+
return void 0;
|
|
13408
|
+
}
|
|
13409
|
+
if (opts.autoCreateWorktree === "skip") {
|
|
13410
|
+
return void 0;
|
|
13411
|
+
}
|
|
13412
|
+
const defaults = computeWorktreeDefaults({
|
|
13413
|
+
projectSlug: opts.projectSlug,
|
|
13414
|
+
assignmentSlug: opts.assignmentSlug,
|
|
13415
|
+
existing: workspace
|
|
13416
|
+
});
|
|
13417
|
+
if (!defaults.repository) {
|
|
13418
|
+
console.warn(
|
|
13419
|
+
`syntaur: cannot infer repository for ${opts.assignmentSlug} \u2014 skipping worktree prompt`
|
|
13420
|
+
);
|
|
13421
|
+
return void 0;
|
|
13422
|
+
}
|
|
13423
|
+
if (opts.autoCreateWorktree === "always") {
|
|
13424
|
+
return await runCreate({
|
|
13425
|
+
assignmentPath,
|
|
13426
|
+
repository: defaults.repository,
|
|
13427
|
+
branch: defaults.branch,
|
|
13428
|
+
parentBranch: defaults.parentBranch,
|
|
13429
|
+
worktreePath: defaults.worktreePath
|
|
13430
|
+
});
|
|
13431
|
+
}
|
|
13432
|
+
if (!isInteractiveTerminal()) {
|
|
13433
|
+
return void 0;
|
|
13434
|
+
}
|
|
13435
|
+
const proceed = await confirm2({
|
|
13436
|
+
message: `This assignment has no git worktree/branch configured. Create one?`,
|
|
13437
|
+
default: true
|
|
13438
|
+
});
|
|
13439
|
+
if (!proceed) {
|
|
13440
|
+
return void 0;
|
|
13441
|
+
}
|
|
13442
|
+
const repository = await input3({
|
|
13443
|
+
message: "Repository path:",
|
|
13444
|
+
default: defaults.repository
|
|
13445
|
+
});
|
|
13446
|
+
const branch = await input3({
|
|
13447
|
+
message: "Branch name:",
|
|
13448
|
+
default: defaults.branch
|
|
13449
|
+
});
|
|
13450
|
+
const parentBranch = await input3({
|
|
13451
|
+
message: "Parent branch:",
|
|
13452
|
+
default: defaults.parentBranch
|
|
13453
|
+
});
|
|
13454
|
+
const worktreePath = await input3({
|
|
13455
|
+
message: "Worktree path:",
|
|
13456
|
+
default: defaults.worktreePath
|
|
13457
|
+
});
|
|
13458
|
+
return await runCreate({
|
|
13459
|
+
assignmentPath,
|
|
13460
|
+
repository,
|
|
13461
|
+
branch,
|
|
13462
|
+
parentBranch,
|
|
13463
|
+
worktreePath
|
|
13464
|
+
});
|
|
13465
|
+
}
|
|
13466
|
+
function computeWorktreeDefaults(opts) {
|
|
13467
|
+
const repository = opts.existing.repository ?? detectCurrentGitRoot();
|
|
13468
|
+
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
13469
|
+
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
|
|
13470
|
+
const worktreeBase = resolve33(
|
|
13471
|
+
syntaurRoot(),
|
|
13472
|
+
"worktrees",
|
|
13473
|
+
opts.projectSlug || "standalone",
|
|
13474
|
+
opts.assignmentSlug
|
|
13475
|
+
);
|
|
13476
|
+
return {
|
|
13477
|
+
...repository ? { repository } : {},
|
|
13478
|
+
branch,
|
|
13479
|
+
parentBranch,
|
|
13480
|
+
worktreePath: worktreeBase
|
|
13481
|
+
};
|
|
13482
|
+
}
|
|
13483
|
+
function detectCurrentGitRoot() {
|
|
13484
|
+
const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
13485
|
+
encoding: "utf-8"
|
|
13486
|
+
});
|
|
13487
|
+
if (result.status !== 0) return void 0;
|
|
13488
|
+
const out = result.stdout.trim();
|
|
13489
|
+
return out.length > 0 ? out : void 0;
|
|
13490
|
+
}
|
|
13491
|
+
function detectCurrentBranch() {
|
|
13492
|
+
const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
13493
|
+
encoding: "utf-8"
|
|
13494
|
+
});
|
|
13495
|
+
if (result.status !== 0) return void 0;
|
|
13496
|
+
const out = result.stdout.trim();
|
|
13497
|
+
if (!out || out === "HEAD") return void 0;
|
|
13498
|
+
return out;
|
|
13499
|
+
}
|
|
13500
|
+
async function runCreate(opts) {
|
|
13501
|
+
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
13502
|
+
const expandedWorktree = expandHome(opts.worktreePath);
|
|
13503
|
+
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve33(expandedWorktree);
|
|
13504
|
+
try {
|
|
13505
|
+
await createWorktreeAndRecord2({
|
|
13506
|
+
assignmentPath: opts.assignmentPath,
|
|
13507
|
+
repository: opts.repository,
|
|
13508
|
+
branch: opts.branch,
|
|
13509
|
+
worktreePath: absWorktree,
|
|
13510
|
+
parentBranch: opts.parentBranch
|
|
13511
|
+
});
|
|
13512
|
+
console.log(`syntaur: created worktree at ${absWorktree} on branch ${opts.branch}`);
|
|
13513
|
+
return absWorktree;
|
|
13514
|
+
} catch (err2) {
|
|
13515
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
13516
|
+
if (err2 instanceof GitWorktreeError2) {
|
|
13517
|
+
console.error(`syntaur: ${msg}`);
|
|
13518
|
+
} else {
|
|
13519
|
+
console.error(`syntaur: ${msg}`);
|
|
13520
|
+
}
|
|
13521
|
+
process.exit(1);
|
|
13522
|
+
}
|
|
13523
|
+
}
|
|
12698
13524
|
|
|
12699
13525
|
// src/commands/create-playbook.ts
|
|
12700
|
-
import { resolve as
|
|
13526
|
+
import { resolve as resolve34 } from "path";
|
|
12701
13527
|
init_timestamp();
|
|
12702
13528
|
init_paths();
|
|
12703
13529
|
init_fs();
|
|
@@ -12714,7 +13540,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
12714
13540
|
}
|
|
12715
13541
|
const dir = playbooksDir();
|
|
12716
13542
|
await ensureDir(dir);
|
|
12717
|
-
const filePath =
|
|
13543
|
+
const filePath = resolve34(dir, `${slug}.md`);
|
|
12718
13544
|
if (await fileExists(filePath)) {
|
|
12719
13545
|
throw new Error(
|
|
12720
13546
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -12736,8 +13562,8 @@ init_paths();
|
|
|
12736
13562
|
init_fs();
|
|
12737
13563
|
init_parser();
|
|
12738
13564
|
init_config2();
|
|
12739
|
-
import { readdir as readdir12, readFile as
|
|
12740
|
-
import { resolve as
|
|
13565
|
+
import { readdir as readdir12, readFile as readFile22 } from "fs/promises";
|
|
13566
|
+
import { resolve as resolve35 } from "path";
|
|
12741
13567
|
async function listPlaybooksCommand(options = {}) {
|
|
12742
13568
|
const dir = playbooksDir();
|
|
12743
13569
|
if (!await fileExists(dir)) {
|
|
@@ -12752,8 +13578,8 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
12752
13578
|
);
|
|
12753
13579
|
const rows = [];
|
|
12754
13580
|
for (const entry of mdFiles) {
|
|
12755
|
-
const filePath =
|
|
12756
|
-
const raw = await
|
|
13581
|
+
const filePath = resolve35(dir, entry.name);
|
|
13582
|
+
const raw = await readFile22(filePath, "utf-8");
|
|
12757
13583
|
const parsed = parsePlaybook(raw);
|
|
12758
13584
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
12759
13585
|
const disabled = disabledSet.has(slug);
|
|
@@ -12835,8 +13661,8 @@ init_parser2();
|
|
|
12835
13661
|
init_fs();
|
|
12836
13662
|
init_config2();
|
|
12837
13663
|
import { Command } from "commander";
|
|
12838
|
-
import { readFile as
|
|
12839
|
-
import { resolve as
|
|
13664
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
13665
|
+
import { resolve as resolve36 } from "path";
|
|
12840
13666
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
12841
13667
|
async function resolveScope(options) {
|
|
12842
13668
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
@@ -12848,7 +13674,7 @@ async function resolveScope(options) {
|
|
|
12848
13674
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
12849
13675
|
}
|
|
12850
13676
|
const config = await readConfig();
|
|
12851
|
-
const projectMd =
|
|
13677
|
+
const projectMd = resolve36(config.defaultProjectDir, options.project, "project.md");
|
|
12852
13678
|
if (!await fileExists(projectMd)) {
|
|
12853
13679
|
throw new Error(`Project "${options.project}" not found.`);
|
|
12854
13680
|
}
|
|
@@ -13142,10 +13968,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
13142
13968
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
13143
13969
|
);
|
|
13144
13970
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
13145
|
-
await ensureDir(
|
|
13971
|
+
await ensureDir(resolve36(todosPath, "archive"));
|
|
13146
13972
|
let archContent = "";
|
|
13147
13973
|
if (await fileExists(archFile)) {
|
|
13148
|
-
archContent = await
|
|
13974
|
+
archContent = await readFile23(archFile, "utf-8");
|
|
13149
13975
|
archContent = archContent.trimEnd() + "\n\n";
|
|
13150
13976
|
} else {
|
|
13151
13977
|
archContent = `---
|
|
@@ -13335,7 +14161,7 @@ import { Command as Command3 } from "commander";
|
|
|
13335
14161
|
|
|
13336
14162
|
// src/utils/doctor/index.ts
|
|
13337
14163
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
13338
|
-
import { readFile as
|
|
14164
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
13339
14165
|
import { dirname as dirname11, join as join5 } from "path";
|
|
13340
14166
|
|
|
13341
14167
|
// src/utils/doctor/context.ts
|
|
@@ -13343,11 +14169,11 @@ init_config2();
|
|
|
13343
14169
|
init_paths();
|
|
13344
14170
|
init_fs();
|
|
13345
14171
|
import Database2 from "better-sqlite3";
|
|
13346
|
-
import { resolve as
|
|
14172
|
+
import { resolve as resolve37 } from "path";
|
|
13347
14173
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
13348
14174
|
const config = await readConfig();
|
|
13349
14175
|
const root = syntaurRoot();
|
|
13350
|
-
const dbPath =
|
|
14176
|
+
const dbPath = resolve37(root, "syntaur.db");
|
|
13351
14177
|
let db2 = null;
|
|
13352
14178
|
let dbError = null;
|
|
13353
14179
|
if (await fileExists(dbPath)) {
|
|
@@ -13381,8 +14207,8 @@ function closeCheckContext(ctx) {
|
|
|
13381
14207
|
// src/utils/doctor/checks/env.ts
|
|
13382
14208
|
init_fs();
|
|
13383
14209
|
init_paths();
|
|
13384
|
-
import { resolve as
|
|
13385
|
-
import { readFile as
|
|
14210
|
+
import { resolve as resolve38, isAbsolute as isAbsolute5 } from "path";
|
|
14211
|
+
import { readFile as readFile24, stat as stat4 } from "fs/promises";
|
|
13386
14212
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
13387
14213
|
import { dirname as dirname10, join as join4 } from "path";
|
|
13388
14214
|
var CATEGORY = "env";
|
|
@@ -13422,7 +14248,7 @@ var configValid = {
|
|
|
13422
14248
|
category: CATEGORY,
|
|
13423
14249
|
title: "~/.syntaur/config.md is valid",
|
|
13424
14250
|
async run(ctx) {
|
|
13425
|
-
const configPath =
|
|
14251
|
+
const configPath = resolve38(ctx.syntaurRoot, "config.md");
|
|
13426
14252
|
if (!await fileExists(configPath)) {
|
|
13427
14253
|
return {
|
|
13428
14254
|
id: this.id,
|
|
@@ -13439,7 +14265,7 @@ var configValid = {
|
|
|
13439
14265
|
autoFixable: false
|
|
13440
14266
|
};
|
|
13441
14267
|
}
|
|
13442
|
-
const content = await
|
|
14268
|
+
const content = await readFile24(configPath, "utf-8");
|
|
13443
14269
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
13444
14270
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
13445
14271
|
return {
|
|
@@ -13476,7 +14302,7 @@ var configValid = {
|
|
|
13476
14302
|
};
|
|
13477
14303
|
}
|
|
13478
14304
|
const expanded = expandHome(rawProjectDir);
|
|
13479
|
-
if (!
|
|
14305
|
+
if (!isAbsolute5(expanded)) {
|
|
13480
14306
|
return {
|
|
13481
14307
|
id: this.id,
|
|
13482
14308
|
category: this.category,
|
|
@@ -13719,7 +14545,7 @@ async function readLocalPkg() {
|
|
|
13719
14545
|
for (let i = 0; i < 6; i++) {
|
|
13720
14546
|
const candidate = join4(dir, "package.json");
|
|
13721
14547
|
try {
|
|
13722
|
-
const text = await
|
|
14548
|
+
const text = await readFile24(candidate, "utf-8");
|
|
13723
14549
|
return JSON.parse(text);
|
|
13724
14550
|
} catch {
|
|
13725
14551
|
dir = dirname10(dir);
|
|
@@ -13771,7 +14597,7 @@ function versionGte(a, b) {
|
|
|
13771
14597
|
|
|
13772
14598
|
// src/utils/doctor/checks/structure.ts
|
|
13773
14599
|
init_fs();
|
|
13774
|
-
import { resolve as
|
|
14600
|
+
import { resolve as resolve39 } from "path";
|
|
13775
14601
|
import { readdir as readdir13, stat as stat5 } from "fs/promises";
|
|
13776
14602
|
var CATEGORY2 = "structure";
|
|
13777
14603
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
@@ -13791,7 +14617,7 @@ var projectsDir = {
|
|
|
13791
14617
|
category: CATEGORY2,
|
|
13792
14618
|
title: "projects/ directory exists",
|
|
13793
14619
|
async run(ctx) {
|
|
13794
|
-
const p =
|
|
14620
|
+
const p = resolve39(ctx.syntaurRoot, "projects");
|
|
13795
14621
|
if (!await fileExists(p)) {
|
|
13796
14622
|
return {
|
|
13797
14623
|
id: this.id,
|
|
@@ -13816,7 +14642,7 @@ var playbooksDir2 = {
|
|
|
13816
14642
|
category: CATEGORY2,
|
|
13817
14643
|
title: "playbooks/ directory exists",
|
|
13818
14644
|
async run(ctx) {
|
|
13819
|
-
const p =
|
|
14645
|
+
const p = resolve39(ctx.syntaurRoot, "playbooks");
|
|
13820
14646
|
if (!await fileExists(p)) {
|
|
13821
14647
|
return {
|
|
13822
14648
|
id: this.id,
|
|
@@ -13841,7 +14667,7 @@ var todosDirValid = {
|
|
|
13841
14667
|
category: CATEGORY2,
|
|
13842
14668
|
title: "todos/ directory is readable (if present)",
|
|
13843
14669
|
async run(ctx) {
|
|
13844
|
-
const p =
|
|
14670
|
+
const p = resolve39(ctx.syntaurRoot, "todos");
|
|
13845
14671
|
if (!await fileExists(p)) {
|
|
13846
14672
|
return {
|
|
13847
14673
|
id: this.id,
|
|
@@ -13872,7 +14698,7 @@ var serversDirValid = {
|
|
|
13872
14698
|
category: CATEGORY2,
|
|
13873
14699
|
title: "servers/ directory is readable (if present)",
|
|
13874
14700
|
async run(ctx) {
|
|
13875
|
-
const p =
|
|
14701
|
+
const p = resolve39(ctx.syntaurRoot, "servers");
|
|
13876
14702
|
if (!await fileExists(p)) {
|
|
13877
14703
|
return {
|
|
13878
14704
|
id: this.id,
|
|
@@ -13917,7 +14743,7 @@ var knownFilesRecognized = {
|
|
|
13917
14743
|
title: this.title,
|
|
13918
14744
|
status: "warn",
|
|
13919
14745
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
13920
|
-
affected: unexpected.map((n) =>
|
|
14746
|
+
affected: unexpected.map((n) => resolve39(ctx.syntaurRoot, n)),
|
|
13921
14747
|
remediation: {
|
|
13922
14748
|
kind: "manual",
|
|
13923
14749
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -13946,7 +14772,7 @@ function pass2(check) {
|
|
|
13946
14772
|
|
|
13947
14773
|
// src/utils/doctor/checks/project.ts
|
|
13948
14774
|
init_fs();
|
|
13949
|
-
import { resolve as
|
|
14775
|
+
import { resolve as resolve40 } from "path";
|
|
13950
14776
|
import { readdir as readdir14, stat as stat6 } from "fs/promises";
|
|
13951
14777
|
var CATEGORY3 = "project";
|
|
13952
14778
|
var REQUIRED_PROJECT_FILES = [
|
|
@@ -13976,10 +14802,10 @@ async function listProjects2(ctx) {
|
|
|
13976
14802
|
for (const e of entries) {
|
|
13977
14803
|
if (!e.isDirectory()) continue;
|
|
13978
14804
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
13979
|
-
const projectDir =
|
|
14805
|
+
const projectDir = resolve40(dir, e.name);
|
|
13980
14806
|
let looksLikeProject = false;
|
|
13981
14807
|
for (const marker of PROJECT_MARKERS) {
|
|
13982
|
-
if (await fileExists(
|
|
14808
|
+
if (await fileExists(resolve40(projectDir, marker))) {
|
|
13983
14809
|
looksLikeProject = true;
|
|
13984
14810
|
break;
|
|
13985
14811
|
}
|
|
@@ -13998,7 +14824,7 @@ var requiredFiles = {
|
|
|
13998
14824
|
for (const projectDir of projects) {
|
|
13999
14825
|
const missing = [];
|
|
14000
14826
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
14001
|
-
const p =
|
|
14827
|
+
const p = resolve40(projectDir, rel);
|
|
14002
14828
|
if (!await fileExists(p)) missing.push(rel);
|
|
14003
14829
|
}
|
|
14004
14830
|
if (missing.length === 0) continue;
|
|
@@ -14008,7 +14834,7 @@ var requiredFiles = {
|
|
|
14008
14834
|
title: this.title,
|
|
14009
14835
|
status: "error",
|
|
14010
14836
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
14011
|
-
affected: missing.map((m) =>
|
|
14837
|
+
affected: missing.map((m) => resolve40(projectDir, m)),
|
|
14012
14838
|
remediation: {
|
|
14013
14839
|
kind: "manual",
|
|
14014
14840
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -14031,7 +14857,7 @@ var manifestStale = {
|
|
|
14031
14857
|
const projects = await listProjects2(ctx);
|
|
14032
14858
|
const results = [];
|
|
14033
14859
|
for (const projectDir of projects) {
|
|
14034
|
-
const manifestPath =
|
|
14860
|
+
const manifestPath = resolve40(projectDir, "manifest.md");
|
|
14035
14861
|
if (!await fileExists(manifestPath)) continue;
|
|
14036
14862
|
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
14037
14863
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
@@ -14080,7 +14906,7 @@ var orphanFiles = {
|
|
|
14080
14906
|
title: this.title,
|
|
14081
14907
|
status: "warn",
|
|
14082
14908
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
14083
|
-
affected: orphans.map((o) =>
|
|
14909
|
+
affected: orphans.map((o) => resolve40(projectDir, o)),
|
|
14084
14910
|
autoFixable: false
|
|
14085
14911
|
});
|
|
14086
14912
|
}
|
|
@@ -14090,7 +14916,7 @@ var orphanFiles = {
|
|
|
14090
14916
|
};
|
|
14091
14917
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
14092
14918
|
async function newestAssignmentMtime(projectDir) {
|
|
14093
|
-
const assignmentsRoot =
|
|
14919
|
+
const assignmentsRoot = resolve40(projectDir, "assignments");
|
|
14094
14920
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
14095
14921
|
let newest = 0;
|
|
14096
14922
|
let entries;
|
|
@@ -14101,7 +14927,7 @@ async function newestAssignmentMtime(projectDir) {
|
|
|
14101
14927
|
}
|
|
14102
14928
|
for (const e of entries) {
|
|
14103
14929
|
if (!e.isDirectory()) continue;
|
|
14104
|
-
const assignmentMd =
|
|
14930
|
+
const assignmentMd = resolve40(assignmentsRoot, e.name, "assignment.md");
|
|
14105
14931
|
try {
|
|
14106
14932
|
const s = await stat6(assignmentMd);
|
|
14107
14933
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
@@ -14125,8 +14951,8 @@ init_fs();
|
|
|
14125
14951
|
init_parser();
|
|
14126
14952
|
init_types();
|
|
14127
14953
|
init_paths();
|
|
14128
|
-
import { resolve as
|
|
14129
|
-
import { readdir as readdir15, readFile as
|
|
14954
|
+
import { resolve as resolve41 } from "path";
|
|
14955
|
+
import { readdir as readdir15, readFile as readFile25 } from "fs/promises";
|
|
14130
14956
|
var CATEGORY4 = "assignment";
|
|
14131
14957
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
14132
14958
|
async function listAssignments(ctx) {
|
|
@@ -14137,16 +14963,16 @@ async function listAssignments(ctx) {
|
|
|
14137
14963
|
for (const m of projects) {
|
|
14138
14964
|
if (!m.isDirectory()) continue;
|
|
14139
14965
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
14140
|
-
const assignmentsDir2 =
|
|
14966
|
+
const assignmentsDir2 = resolve41(projectsDir2, m.name, "assignments");
|
|
14141
14967
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
14142
14968
|
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
14143
14969
|
for (const a of entries) {
|
|
14144
14970
|
if (!a.isDirectory()) continue;
|
|
14145
14971
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
14146
|
-
const assignmentDir =
|
|
14147
|
-
const assignmentMd =
|
|
14972
|
+
const assignmentDir = resolve41(assignmentsDir2, a.name);
|
|
14973
|
+
const assignmentMd = resolve41(assignmentDir, "assignment.md");
|
|
14148
14974
|
const entry = {
|
|
14149
|
-
projectDir:
|
|
14975
|
+
projectDir: resolve41(projectsDir2, m.name),
|
|
14150
14976
|
projectSlug: m.name,
|
|
14151
14977
|
assignmentDir,
|
|
14152
14978
|
assignmentSlug: a.name,
|
|
@@ -14166,8 +14992,8 @@ async function listAssignments(ctx) {
|
|
|
14166
14992
|
for (const a of entries) {
|
|
14167
14993
|
if (!a.isDirectory()) continue;
|
|
14168
14994
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
14169
|
-
const assignmentDir =
|
|
14170
|
-
const assignmentMd =
|
|
14995
|
+
const assignmentDir = resolve41(standaloneRoot, a.name);
|
|
14996
|
+
const assignmentMd = resolve41(assignmentDir, "assignment.md");
|
|
14171
14997
|
const entry = {
|
|
14172
14998
|
projectDir: standaloneRoot,
|
|
14173
14999
|
projectSlug: null,
|
|
@@ -14245,7 +15071,7 @@ var invalidStatus = {
|
|
|
14245
15071
|
const allowed = configuredStatuses(ctx);
|
|
14246
15072
|
const results = [];
|
|
14247
15073
|
for (const a of withAssignmentMd) {
|
|
14248
|
-
const path =
|
|
15074
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14249
15075
|
const parsed = await parseSafe(path);
|
|
14250
15076
|
if (!parsed) continue;
|
|
14251
15077
|
if (!allowed.has(parsed.status)) {
|
|
@@ -14278,7 +15104,7 @@ var workspaceMissing = {
|
|
|
14278
15104
|
const terminal = terminalStatuses(ctx);
|
|
14279
15105
|
const results = [];
|
|
14280
15106
|
for (const a of withAssignmentMd) {
|
|
14281
|
-
const path =
|
|
15107
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14282
15108
|
const parsed = await parseSafe(path);
|
|
14283
15109
|
if (!parsed) continue;
|
|
14284
15110
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -14325,12 +15151,12 @@ var requiredFilesByStatus = {
|
|
|
14325
15151
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
14326
15152
|
const results = [];
|
|
14327
15153
|
for (const a of withAssignmentMd) {
|
|
14328
|
-
const assignmentPath =
|
|
15154
|
+
const assignmentPath = resolve41(a.assignmentDir, "assignment.md");
|
|
14329
15155
|
const parsed = await parseSafe(assignmentPath);
|
|
14330
15156
|
if (!parsed) continue;
|
|
14331
15157
|
const missing = [];
|
|
14332
15158
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
14333
|
-
const handoffPath =
|
|
15159
|
+
const handoffPath = resolve41(a.assignmentDir, "handoff.md");
|
|
14334
15160
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
14335
15161
|
}
|
|
14336
15162
|
if (missing.length === 0) continue;
|
|
@@ -14340,7 +15166,7 @@ var requiredFilesByStatus = {
|
|
|
14340
15166
|
title: this.title,
|
|
14341
15167
|
status: "warn",
|
|
14342
15168
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
14343
|
-
affected: missing.map((m) =>
|
|
15169
|
+
affected: missing.map((m) => resolve41(a.assignmentDir, m)),
|
|
14344
15170
|
remediation: {
|
|
14345
15171
|
kind: "manual",
|
|
14346
15172
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -14363,7 +15189,7 @@ var companionFilesScaffolded = {
|
|
|
14363
15189
|
for (const a of withAssignmentMd) {
|
|
14364
15190
|
const missing = [];
|
|
14365
15191
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
14366
|
-
if (!await fileExists(
|
|
15192
|
+
if (!await fileExists(resolve41(a.assignmentDir, filename))) {
|
|
14367
15193
|
missing.push(filename);
|
|
14368
15194
|
}
|
|
14369
15195
|
}
|
|
@@ -14375,7 +15201,7 @@ var companionFilesScaffolded = {
|
|
|
14375
15201
|
title: this.title,
|
|
14376
15202
|
status: "warn",
|
|
14377
15203
|
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) =>
|
|
15204
|
+
affected: missing.map((m) => resolve41(a.assignmentDir, m)),
|
|
14379
15205
|
remediation: {
|
|
14380
15206
|
kind: "manual",
|
|
14381
15207
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -14408,7 +15234,7 @@ var typeDefinition = {
|
|
|
14408
15234
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
14409
15235
|
const results = [];
|
|
14410
15236
|
for (const a of withAssignmentMd) {
|
|
14411
|
-
const path =
|
|
15237
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14412
15238
|
const parsed = await parseSafe(path);
|
|
14413
15239
|
if (!parsed) continue;
|
|
14414
15240
|
if (!parsed.type) continue;
|
|
@@ -14442,7 +15268,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
14442
15268
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
14443
15269
|
const results = [];
|
|
14444
15270
|
for (const a of withAssignmentMd) {
|
|
14445
|
-
const path =
|
|
15271
|
+
const path = resolve41(a.assignmentDir, "assignment.md");
|
|
14446
15272
|
const parsed = await parseSafe(path);
|
|
14447
15273
|
if (!parsed) continue;
|
|
14448
15274
|
if (a.standalone) {
|
|
@@ -14497,7 +15323,7 @@ var assignmentChecks = [
|
|
|
14497
15323
|
];
|
|
14498
15324
|
async function parseSafe(path) {
|
|
14499
15325
|
try {
|
|
14500
|
-
const content = await
|
|
15326
|
+
const content = await readFile25(path, "utf-8");
|
|
14501
15327
|
return parseAssignmentFull(content);
|
|
14502
15328
|
} catch {
|
|
14503
15329
|
return null;
|
|
@@ -14516,7 +15342,7 @@ function pass4(check, detail) {
|
|
|
14516
15342
|
|
|
14517
15343
|
// src/utils/doctor/checks/dashboard.ts
|
|
14518
15344
|
init_fs();
|
|
14519
|
-
import { resolve as
|
|
15345
|
+
import { resolve as resolve42 } from "path";
|
|
14520
15346
|
var CATEGORY5 = "dashboard";
|
|
14521
15347
|
var dbReachable = {
|
|
14522
15348
|
id: "dashboard.db-reachable",
|
|
@@ -14530,7 +15356,7 @@ var dbReachable = {
|
|
|
14530
15356
|
title: this.title,
|
|
14531
15357
|
status: "error",
|
|
14532
15358
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
14533
|
-
affected: [
|
|
15359
|
+
affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
|
|
14534
15360
|
remediation: {
|
|
14535
15361
|
kind: "manual",
|
|
14536
15362
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -14548,7 +15374,7 @@ var dbReachable = {
|
|
|
14548
15374
|
title: this.title,
|
|
14549
15375
|
status: "error",
|
|
14550
15376
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
14551
|
-
affected: [
|
|
15377
|
+
affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
|
|
14552
15378
|
autoFixable: false
|
|
14553
15379
|
};
|
|
14554
15380
|
}
|
|
@@ -14560,7 +15386,7 @@ var dbReachable = {
|
|
|
14560
15386
|
title: this.title,
|
|
14561
15387
|
status: "error",
|
|
14562
15388
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
14563
|
-
affected: [
|
|
15389
|
+
affected: [resolve42(ctx.syntaurRoot, "syntaur.db")],
|
|
14564
15390
|
autoFixable: false
|
|
14565
15391
|
};
|
|
14566
15392
|
}
|
|
@@ -14586,7 +15412,7 @@ var ghostSessions = {
|
|
|
14586
15412
|
const results = [];
|
|
14587
15413
|
for (const row of rows) {
|
|
14588
15414
|
if (!row.project_slug) continue;
|
|
14589
|
-
const projectPath =
|
|
15415
|
+
const projectPath = resolve42(projectsDir2, row.project_slug, "project.md");
|
|
14590
15416
|
if (!await fileExists(projectPath)) {
|
|
14591
15417
|
results.push({
|
|
14592
15418
|
id: this.id,
|
|
@@ -14605,7 +15431,7 @@ var ghostSessions = {
|
|
|
14605
15431
|
continue;
|
|
14606
15432
|
}
|
|
14607
15433
|
if (row.assignment_slug) {
|
|
14608
|
-
const assignmentPath =
|
|
15434
|
+
const assignmentPath = resolve42(
|
|
14609
15435
|
projectsDir2,
|
|
14610
15436
|
row.project_slug,
|
|
14611
15437
|
"assignments",
|
|
@@ -14762,8 +15588,8 @@ function skipped2(check, reason) {
|
|
|
14762
15588
|
init_fs();
|
|
14763
15589
|
init_parser();
|
|
14764
15590
|
init_types();
|
|
14765
|
-
import { resolve as
|
|
14766
|
-
import { readFile as
|
|
15591
|
+
import { resolve as resolve43 } from "path";
|
|
15592
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
14767
15593
|
var CATEGORY7 = "workspace";
|
|
14768
15594
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
14769
15595
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -14775,12 +15601,12 @@ function isStandaloneSession(ctx) {
|
|
|
14775
15601
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
14776
15602
|
}
|
|
14777
15603
|
async function loadContext(ctx) {
|
|
14778
|
-
const path =
|
|
15604
|
+
const path = resolve43(ctx.cwd, ".syntaur", "context.json");
|
|
14779
15605
|
if (!await fileExists(path)) {
|
|
14780
15606
|
return { data: null, path, exists: false, parseError: null };
|
|
14781
15607
|
}
|
|
14782
15608
|
try {
|
|
14783
|
-
const raw = await
|
|
15609
|
+
const raw = await readFile26(path, "utf-8");
|
|
14784
15610
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
14785
15611
|
} catch (err2) {
|
|
14786
15612
|
return {
|
|
@@ -14855,7 +15681,7 @@ var contextAssignmentResolves = {
|
|
|
14855
15681
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
14856
15682
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
14857
15683
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
14858
|
-
const assignmentMd =
|
|
15684
|
+
const assignmentMd = resolve43(data.assignmentDir, "assignment.md");
|
|
14859
15685
|
if (!await fileExists(assignmentMd)) {
|
|
14860
15686
|
return {
|
|
14861
15687
|
id: this.id,
|
|
@@ -14884,10 +15710,10 @@ var contextTerminal = {
|
|
|
14884
15710
|
if (!exists) return skipped3(this, "no context to check");
|
|
14885
15711
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
14886
15712
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
14887
|
-
const assignmentMd =
|
|
15713
|
+
const assignmentMd = resolve43(data.assignmentDir, "assignment.md");
|
|
14888
15714
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
14889
15715
|
try {
|
|
14890
|
-
const content = await
|
|
15716
|
+
const content = await readFile26(assignmentMd, "utf-8");
|
|
14891
15717
|
const parsed = parseAssignmentFull(content);
|
|
14892
15718
|
const terminal = terminalStatuses2(ctx);
|
|
14893
15719
|
if (terminal.has(parsed.status)) {
|
|
@@ -14939,6 +15765,97 @@ function skipped3(check, reason) {
|
|
|
14939
15765
|
};
|
|
14940
15766
|
}
|
|
14941
15767
|
|
|
15768
|
+
// src/utils/doctor/checks/agents.ts
|
|
15769
|
+
init_config2();
|
|
15770
|
+
import { isAbsolute as isAbsolute6 } from "path";
|
|
15771
|
+
import { access as access2, constants as fsConstants } from "fs/promises";
|
|
15772
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
15773
|
+
var CATEGORY8 = "agents";
|
|
15774
|
+
var agentsResolvable = {
|
|
15775
|
+
id: "agents.commands-resolvable",
|
|
15776
|
+
category: CATEGORY8,
|
|
15777
|
+
title: "All configured agent commands resolve",
|
|
15778
|
+
async run(ctx) {
|
|
15779
|
+
const agents = getAgents(ctx.config);
|
|
15780
|
+
if (agents.length === 0) {
|
|
15781
|
+
return {
|
|
15782
|
+
id: this.id,
|
|
15783
|
+
category: this.category,
|
|
15784
|
+
title: this.title,
|
|
15785
|
+
status: "warn",
|
|
15786
|
+
detail: "No agents configured and no built-in defaults available",
|
|
15787
|
+
remediation: {
|
|
15788
|
+
kind: "manual",
|
|
15789
|
+
suggestion: "Run `syntaur agents add --id <id> --label <label> --command <path>`",
|
|
15790
|
+
command: null
|
|
15791
|
+
},
|
|
15792
|
+
autoFixable: false
|
|
15793
|
+
};
|
|
15794
|
+
}
|
|
15795
|
+
const results = [];
|
|
15796
|
+
for (const agent of agents) {
|
|
15797
|
+
results.push(await checkAgent(agent));
|
|
15798
|
+
}
|
|
15799
|
+
return results;
|
|
15800
|
+
}
|
|
15801
|
+
};
|
|
15802
|
+
async function checkAgent(agent) {
|
|
15803
|
+
const base = {
|
|
15804
|
+
id: `agents.resolvable.${agent.id}`,
|
|
15805
|
+
category: CATEGORY8,
|
|
15806
|
+
title: `Agent "${agent.id}" command resolves`
|
|
15807
|
+
};
|
|
15808
|
+
if (agent.resolveFromShellAliases) {
|
|
15809
|
+
return {
|
|
15810
|
+
...base,
|
|
15811
|
+
status: "pass",
|
|
15812
|
+
detail: `shell-alias resolution enabled for "${agent.command}" \u2014 will run via $SHELL -i -c`,
|
|
15813
|
+
autoFixable: false
|
|
15814
|
+
};
|
|
15815
|
+
}
|
|
15816
|
+
if (isAbsolute6(agent.command)) {
|
|
15817
|
+
try {
|
|
15818
|
+
await access2(agent.command, fsConstants.X_OK);
|
|
15819
|
+
return { ...base, status: "pass", autoFixable: false };
|
|
15820
|
+
} catch (err2) {
|
|
15821
|
+
const code = err2.code;
|
|
15822
|
+
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"})`;
|
|
15823
|
+
return {
|
|
15824
|
+
...base,
|
|
15825
|
+
status: "warn",
|
|
15826
|
+
detail,
|
|
15827
|
+
remediation: {
|
|
15828
|
+
kind: "manual",
|
|
15829
|
+
suggestion: `Update with \`syntaur agents set ${agent.id} --command <path>\` or fix the file permissions`,
|
|
15830
|
+
command: null
|
|
15831
|
+
},
|
|
15832
|
+
autoFixable: false
|
|
15833
|
+
};
|
|
15834
|
+
}
|
|
15835
|
+
}
|
|
15836
|
+
const result = spawnSync3("which", [agent.command], { encoding: "utf-8" });
|
|
15837
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
15838
|
+
return {
|
|
15839
|
+
...base,
|
|
15840
|
+
status: "pass",
|
|
15841
|
+
detail: `resolved "${agent.command}" \u2192 ${result.stdout.trim()}`,
|
|
15842
|
+
autoFixable: false
|
|
15843
|
+
};
|
|
15844
|
+
}
|
|
15845
|
+
return {
|
|
15846
|
+
...base,
|
|
15847
|
+
status: "warn",
|
|
15848
|
+
detail: `bare command "${agent.command}" not found on PATH`,
|
|
15849
|
+
remediation: {
|
|
15850
|
+
kind: "manual",
|
|
15851
|
+
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\``,
|
|
15852
|
+
command: null
|
|
15853
|
+
},
|
|
15854
|
+
autoFixable: false
|
|
15855
|
+
};
|
|
15856
|
+
}
|
|
15857
|
+
var agentChecks = [agentsResolvable];
|
|
15858
|
+
|
|
14942
15859
|
// src/utils/doctor/registry.ts
|
|
14943
15860
|
function allChecks() {
|
|
14944
15861
|
return [
|
|
@@ -14948,7 +15865,8 @@ function allChecks() {
|
|
|
14948
15865
|
...assignmentChecks,
|
|
14949
15866
|
...dashboardChecks,
|
|
14950
15867
|
...integrationChecks,
|
|
14951
|
-
...workspaceChecks
|
|
15868
|
+
...workspaceChecks,
|
|
15869
|
+
...agentChecks
|
|
14952
15870
|
];
|
|
14953
15871
|
}
|
|
14954
15872
|
|
|
@@ -15034,7 +15952,7 @@ async function readVersion() {
|
|
|
15034
15952
|
let dir = dirname11(here);
|
|
15035
15953
|
for (let i = 0; i < 6; i++) {
|
|
15036
15954
|
try {
|
|
15037
|
-
const raw = await
|
|
15955
|
+
const raw = await readFile27(join5(dir, "package.json"), "utf-8");
|
|
15038
15956
|
const parsed = JSON.parse(raw);
|
|
15039
15957
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
15040
15958
|
} catch {
|
|
@@ -15167,12 +16085,218 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
15167
16085
|
}
|
|
15168
16086
|
});
|
|
15169
16087
|
|
|
16088
|
+
// src/commands/agents.ts
|
|
16089
|
+
init_config2();
|
|
16090
|
+
import { Command as Command4 } from "commander";
|
|
16091
|
+
var agentsCommand = new Command4("agents").description(
|
|
16092
|
+
"Manage configurable agents used by `syntaur browse` and future launch flows"
|
|
16093
|
+
);
|
|
16094
|
+
agentsCommand.command("list").description("List configured agents (or built-in defaults if none are configured)").action(async () => {
|
|
16095
|
+
try {
|
|
16096
|
+
const config = await readConfig();
|
|
16097
|
+
const agents = getAgents(config);
|
|
16098
|
+
const source = config.agents ? "config" : "built-in defaults";
|
|
16099
|
+
console.log(`Agents (${source}):`);
|
|
16100
|
+
for (const agent of agents) {
|
|
16101
|
+
const flags = [];
|
|
16102
|
+
if (agent.default) flags.push("default");
|
|
16103
|
+
if (agent.resolveFromShellAliases) flags.push("shell-alias");
|
|
16104
|
+
if (agent.promptArgPosition) flags.push(`prompt=${agent.promptArgPosition}`);
|
|
16105
|
+
const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
16106
|
+
const args = agent.args && agent.args.length > 0 ? ` ${agent.args.join(" ")}` : "";
|
|
16107
|
+
console.log(` ${agent.id.padEnd(12)} ${agent.label.padEnd(20)} ${agent.command}${args}${flagStr}`);
|
|
16108
|
+
}
|
|
16109
|
+
} catch (error) {
|
|
16110
|
+
reportAndExit(error);
|
|
16111
|
+
}
|
|
16112
|
+
});
|
|
16113
|
+
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) => {
|
|
16114
|
+
try {
|
|
16115
|
+
const agent = buildAgentFromOptions(options, null);
|
|
16116
|
+
const mutation = {
|
|
16117
|
+
kind: "add",
|
|
16118
|
+
apply: (current) => {
|
|
16119
|
+
if (current.some((a) => a.id === agent.id)) {
|
|
16120
|
+
throw new AgentConfigError(`agent "${agent.id}" already exists`);
|
|
16121
|
+
}
|
|
16122
|
+
const next = agent.default ? current.map((a) => ({ ...a, default: false })) : [...current];
|
|
16123
|
+
return [...next, agent];
|
|
16124
|
+
}
|
|
16125
|
+
};
|
|
16126
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16127
|
+
dryRun: Boolean(options.dryRun)
|
|
16128
|
+
});
|
|
16129
|
+
reportMutation("add", result);
|
|
16130
|
+
} catch (error) {
|
|
16131
|
+
reportAndExit(error);
|
|
16132
|
+
}
|
|
16133
|
+
});
|
|
16134
|
+
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) => {
|
|
16135
|
+
try {
|
|
16136
|
+
const mutation = {
|
|
16137
|
+
kind: "remove",
|
|
16138
|
+
apply: (current) => {
|
|
16139
|
+
if (!current.some((a) => a.id === id)) {
|
|
16140
|
+
throw new AgentConfigError(`unknown agent id "${id}"`);
|
|
16141
|
+
}
|
|
16142
|
+
return current.filter((a) => a.id !== id);
|
|
16143
|
+
}
|
|
16144
|
+
};
|
|
16145
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16146
|
+
dryRun: Boolean(options.dryRun)
|
|
16147
|
+
});
|
|
16148
|
+
reportMutation("remove", result);
|
|
16149
|
+
} catch (error) {
|
|
16150
|
+
reportAndExit(error);
|
|
16151
|
+
}
|
|
16152
|
+
});
|
|
16153
|
+
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) => {
|
|
16154
|
+
try {
|
|
16155
|
+
const mutation = {
|
|
16156
|
+
kind: "set",
|
|
16157
|
+
apply: (current) => {
|
|
16158
|
+
const existing = current.find((a) => a.id === id);
|
|
16159
|
+
if (!existing) {
|
|
16160
|
+
throw new AgentConfigError(`unknown agent id "${id}"`);
|
|
16161
|
+
}
|
|
16162
|
+
const merged = mergeOptionsIntoAgent(existing, options);
|
|
16163
|
+
const defaultFlip = options.default === true;
|
|
16164
|
+
return current.map((a) => {
|
|
16165
|
+
if (a.id === id) return merged;
|
|
16166
|
+
if (defaultFlip) return { ...a, default: false };
|
|
16167
|
+
return a;
|
|
16168
|
+
});
|
|
16169
|
+
}
|
|
16170
|
+
};
|
|
16171
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16172
|
+
dryRun: Boolean(options.dryRun)
|
|
16173
|
+
});
|
|
16174
|
+
reportMutation("set", result);
|
|
16175
|
+
} catch (error) {
|
|
16176
|
+
reportAndExit(error);
|
|
16177
|
+
}
|
|
16178
|
+
});
|
|
16179
|
+
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) => {
|
|
16180
|
+
try {
|
|
16181
|
+
const newOrder = ids.split(",").map((s) => s.trim()).filter(Boolean);
|
|
16182
|
+
const mutation = {
|
|
16183
|
+
kind: "reorder",
|
|
16184
|
+
apply: (current) => {
|
|
16185
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16186
|
+
for (const id of newOrder) {
|
|
16187
|
+
if (seen.has(id)) {
|
|
16188
|
+
throw new AgentConfigError(`duplicate id "${id}" in reorder list`);
|
|
16189
|
+
}
|
|
16190
|
+
seen.add(id);
|
|
16191
|
+
}
|
|
16192
|
+
const currentIds = new Set(current.map((a) => a.id));
|
|
16193
|
+
const missing = current.filter((a) => !seen.has(a.id)).map((a) => a.id);
|
|
16194
|
+
const extra = newOrder.filter((id) => !currentIds.has(id));
|
|
16195
|
+
if (missing.length > 0 || extra.length > 0) {
|
|
16196
|
+
const parts = [];
|
|
16197
|
+
if (missing.length) parts.push(`missing: ${missing.join(", ")}`);
|
|
16198
|
+
if (extra.length) parts.push(`unknown: ${extra.join(", ")}`);
|
|
16199
|
+
throw new AgentConfigError(
|
|
16200
|
+
`reorder list does not match current agents (${parts.join("; ")})`
|
|
16201
|
+
);
|
|
16202
|
+
}
|
|
16203
|
+
return newOrder.map((id) => current.find((a) => a.id === id));
|
|
16204
|
+
}
|
|
16205
|
+
};
|
|
16206
|
+
const result = await updateAgentsConfig(mutation, {
|
|
16207
|
+
dryRun: Boolean(options.dryRun)
|
|
16208
|
+
});
|
|
16209
|
+
reportMutation("reorder", result);
|
|
16210
|
+
} catch (error) {
|
|
16211
|
+
reportAndExit(error);
|
|
16212
|
+
}
|
|
16213
|
+
});
|
|
16214
|
+
function buildAgentFromOptions(options, existing) {
|
|
16215
|
+
const agent = {
|
|
16216
|
+
id: options.id,
|
|
16217
|
+
label: options.label,
|
|
16218
|
+
command: parseAgentCommand(options.command, options.id)
|
|
16219
|
+
};
|
|
16220
|
+
const args = parseArgsCsv(options.args);
|
|
16221
|
+
if (args) agent.args = args;
|
|
16222
|
+
if (options.promptArgPosition) {
|
|
16223
|
+
agent.promptArgPosition = options.promptArgPosition;
|
|
16224
|
+
}
|
|
16225
|
+
if (options.default) agent.default = true;
|
|
16226
|
+
if (options.resolveFromShellAliases) agent.resolveFromShellAliases = true;
|
|
16227
|
+
validateAgentList([...existing ? [] : [], agent]);
|
|
16228
|
+
return agent;
|
|
16229
|
+
}
|
|
16230
|
+
function mergeOptionsIntoAgent(existing, options) {
|
|
16231
|
+
const merged = { ...existing };
|
|
16232
|
+
if (options.label !== void 0) merged.label = options.label;
|
|
16233
|
+
if (options.command !== void 0) {
|
|
16234
|
+
merged.command = parseAgentCommand(options.command, existing.id);
|
|
16235
|
+
}
|
|
16236
|
+
if (options.args !== void 0) {
|
|
16237
|
+
const parsed = parseArgsCsv(options.args);
|
|
16238
|
+
if (parsed) merged.args = parsed;
|
|
16239
|
+
else delete merged.args;
|
|
16240
|
+
}
|
|
16241
|
+
if (options.promptArgPosition !== void 0) {
|
|
16242
|
+
merged.promptArgPosition = options.promptArgPosition;
|
|
16243
|
+
}
|
|
16244
|
+
if (options.default === true) merged.default = true;
|
|
16245
|
+
if (options.default === false) delete merged.default;
|
|
16246
|
+
if (options.resolveFromShellAliases === true) merged.resolveFromShellAliases = true;
|
|
16247
|
+
if (options.resolveFromShellAliases === false) delete merged.resolveFromShellAliases;
|
|
16248
|
+
return merged;
|
|
16249
|
+
}
|
|
16250
|
+
function parseArgsCsv(csv) {
|
|
16251
|
+
if (csv === void 0) return null;
|
|
16252
|
+
const parts = csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16253
|
+
return parts.length > 0 ? parts : null;
|
|
16254
|
+
}
|
|
16255
|
+
function reportMutation(action, result) {
|
|
16256
|
+
const diff = renderDiff(result.previous, result.next);
|
|
16257
|
+
if (result.written) {
|
|
16258
|
+
console.log(`Agents updated (${action}):`);
|
|
16259
|
+
console.log(diff);
|
|
16260
|
+
} else {
|
|
16261
|
+
console.log(`Dry run (${action}) \u2014 no changes written:`);
|
|
16262
|
+
console.log(diff);
|
|
16263
|
+
}
|
|
16264
|
+
}
|
|
16265
|
+
function renderDiff(prev, next) {
|
|
16266
|
+
const prevLines = prev.map(formatAgentLine);
|
|
16267
|
+
const nextLines = next.map(formatAgentLine);
|
|
16268
|
+
const prevSet = new Set(prevLines);
|
|
16269
|
+
const nextSet = new Set(nextLines);
|
|
16270
|
+
const out = [];
|
|
16271
|
+
for (const line of prevLines) {
|
|
16272
|
+
if (!nextSet.has(line)) out.push(` - ${line}`);
|
|
16273
|
+
}
|
|
16274
|
+
for (const line of nextLines) {
|
|
16275
|
+
if (!prevSet.has(line)) out.push(` + ${line}`);
|
|
16276
|
+
}
|
|
16277
|
+
if (out.length === 0) out.push(" (no changes)");
|
|
16278
|
+
return out.join("\n");
|
|
16279
|
+
}
|
|
16280
|
+
function formatAgentLine(a) {
|
|
16281
|
+
const flags = [];
|
|
16282
|
+
if (a.default) flags.push("default");
|
|
16283
|
+
if (a.resolveFromShellAliases) flags.push("shell-alias");
|
|
16284
|
+
if (a.promptArgPosition) flags.push(`prompt=${a.promptArgPosition}`);
|
|
16285
|
+
if (a.args && a.args.length > 0) flags.push(`args=[${a.args.join(", ")}]`);
|
|
16286
|
+
const suffix = flags.length > 0 ? ` (${flags.join(", ")})` : "";
|
|
16287
|
+
return `${a.id}: ${a.label} \u2192 ${a.command}${suffix}`;
|
|
16288
|
+
}
|
|
16289
|
+
function reportAndExit(error) {
|
|
16290
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
16291
|
+
process.exit(1);
|
|
16292
|
+
}
|
|
16293
|
+
|
|
15170
16294
|
// src/commands/comment.ts
|
|
15171
16295
|
init_paths();
|
|
15172
16296
|
init_fs();
|
|
15173
16297
|
init_config2();
|
|
15174
|
-
import { resolve as
|
|
15175
|
-
import { readFile as
|
|
16298
|
+
import { resolve as resolve44 } from "path";
|
|
16299
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
15176
16300
|
init_timestamp();
|
|
15177
16301
|
init_assignment_resolver();
|
|
15178
16302
|
function shortId() {
|
|
@@ -15204,7 +16328,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
15204
16328
|
if (!isValidSlug(target)) {
|
|
15205
16329
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
15206
16330
|
}
|
|
15207
|
-
assignmentDir =
|
|
16331
|
+
assignmentDir = resolve44(baseDir, options.project, "assignments", target);
|
|
15208
16332
|
assignmentRef = target;
|
|
15209
16333
|
} else {
|
|
15210
16334
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -15214,13 +16338,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
15214
16338
|
assignmentDir = resolved.assignmentDir;
|
|
15215
16339
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
15216
16340
|
}
|
|
15217
|
-
const commentsPath =
|
|
16341
|
+
const commentsPath = resolve44(assignmentDir, "comments.md");
|
|
15218
16342
|
const timestamp = nowTimestamp();
|
|
15219
16343
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
15220
16344
|
let currentContent;
|
|
15221
16345
|
let currentCount = 0;
|
|
15222
16346
|
if (await fileExists(commentsPath)) {
|
|
15223
|
-
currentContent = await
|
|
16347
|
+
currentContent = await readFile28(commentsPath, "utf-8");
|
|
15224
16348
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
15225
16349
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
15226
16350
|
} else {
|
|
@@ -15257,8 +16381,8 @@ ${entry}`;
|
|
|
15257
16381
|
init_paths();
|
|
15258
16382
|
init_fs();
|
|
15259
16383
|
init_config2();
|
|
15260
|
-
import { resolve as
|
|
15261
|
-
import { readFile as
|
|
16384
|
+
import { resolve as resolve45 } from "path";
|
|
16385
|
+
import { readFile as readFile29 } from "fs/promises";
|
|
15262
16386
|
init_timestamp();
|
|
15263
16387
|
init_assignment_resolver();
|
|
15264
16388
|
function setTopLevelField3(content, key, value) {
|
|
@@ -15283,7 +16407,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
15283
16407
|
if (!isValidSlug(target)) {
|
|
15284
16408
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
15285
16409
|
}
|
|
15286
|
-
assignmentDir =
|
|
16410
|
+
assignmentDir = resolve45(baseDir, options.project, "assignments", target);
|
|
15287
16411
|
targetRef = target;
|
|
15288
16412
|
} else {
|
|
15289
16413
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -15293,12 +16417,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
15293
16417
|
assignmentDir = resolved.assignmentDir;
|
|
15294
16418
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
15295
16419
|
}
|
|
15296
|
-
const assignmentMdPath =
|
|
16420
|
+
const assignmentMdPath = resolve45(assignmentDir, "assignment.md");
|
|
15297
16421
|
if (!await fileExists(assignmentMdPath)) {
|
|
15298
16422
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
15299
16423
|
}
|
|
15300
16424
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
15301
|
-
let content = await
|
|
16425
|
+
let content = await readFile29(assignmentMdPath, "utf-8");
|
|
15302
16426
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
15303
16427
|
const todosHeading = /^## Todos\s*$/m;
|
|
15304
16428
|
if (todosHeading.test(content)) {
|
|
@@ -15366,20 +16490,20 @@ async function getDefaultCommandName() {
|
|
|
15366
16490
|
init_paths();
|
|
15367
16491
|
init_fs();
|
|
15368
16492
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
15369
|
-
import { readFile as
|
|
15370
|
-
import { dirname as dirname13, join as join7, resolve as
|
|
15371
|
-
import { spawn as
|
|
16493
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
16494
|
+
import { dirname as dirname13, join as join7, resolve as resolve46 } from "path";
|
|
16495
|
+
import { spawn as spawn4 } from "child_process";
|
|
15372
16496
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
15373
16497
|
|
|
15374
16498
|
// src/utils/version.ts
|
|
15375
16499
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
15376
|
-
import { readFile as
|
|
16500
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
15377
16501
|
import { dirname as dirname12, join as join6 } from "path";
|
|
15378
16502
|
async function readPackageVersion(scriptUrl) {
|
|
15379
16503
|
try {
|
|
15380
16504
|
const scriptPath = fileURLToPath8(scriptUrl);
|
|
15381
16505
|
const pkgRoot = dirname12(dirname12(scriptPath));
|
|
15382
|
-
const raw = await
|
|
16506
|
+
const raw = await readFile30(join6(pkgRoot, "package.json"), "utf-8");
|
|
15383
16507
|
const parsed = JSON.parse(raw);
|
|
15384
16508
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
15385
16509
|
} catch {
|
|
@@ -15388,7 +16512,7 @@ async function readPackageVersion(scriptUrl) {
|
|
|
15388
16512
|
}
|
|
15389
16513
|
|
|
15390
16514
|
// src/utils/npx-prompt.ts
|
|
15391
|
-
var STATE_FILE =
|
|
16515
|
+
var STATE_FILE = resolve46(syntaurRoot(), "npx-install.json");
|
|
15392
16516
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
15393
16517
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
15394
16518
|
function isRunningViaNpx(scriptUrl) {
|
|
@@ -15409,7 +16533,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
15409
16533
|
async function readState() {
|
|
15410
16534
|
if (!await fileExists(STATE_FILE)) return null;
|
|
15411
16535
|
try {
|
|
15412
|
-
const raw = await
|
|
16536
|
+
const raw = await readFile31(STATE_FILE, "utf-8");
|
|
15413
16537
|
return JSON.parse(raw);
|
|
15414
16538
|
} catch {
|
|
15415
16539
|
return null;
|
|
@@ -15432,7 +16556,7 @@ async function resolveNpmBin() {
|
|
|
15432
16556
|
async function installGlobally() {
|
|
15433
16557
|
const { cmd, shell } = await resolveNpmBin();
|
|
15434
16558
|
return new Promise((resolvePromise) => {
|
|
15435
|
-
const child =
|
|
16559
|
+
const child = spawn4(cmd, ["install", "-g", "syntaur"], {
|
|
15436
16560
|
stdio: "inherit",
|
|
15437
16561
|
shell
|
|
15438
16562
|
});
|
|
@@ -15443,7 +16567,7 @@ async function installGlobally() {
|
|
|
15443
16567
|
async function readGlobalVersion() {
|
|
15444
16568
|
const { cmd, shell } = await resolveNpmBin();
|
|
15445
16569
|
const rootPath = await new Promise((resolvePromise) => {
|
|
15446
|
-
const child =
|
|
16570
|
+
const child = spawn4(cmd, ["root", "-g"], {
|
|
15447
16571
|
shell,
|
|
15448
16572
|
stdio: ["ignore", "pipe", "ignore"]
|
|
15449
16573
|
});
|
|
@@ -15468,7 +16592,7 @@ async function readGlobalVersion() {
|
|
|
15468
16592
|
try {
|
|
15469
16593
|
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
15470
16594
|
if (!await fileExists(manifestPath)) return null;
|
|
15471
|
-
const raw = await
|
|
16595
|
+
const raw = await readFile31(manifestPath, "utf-8");
|
|
15472
16596
|
const parsed = JSON.parse(raw);
|
|
15473
16597
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
15474
16598
|
} catch {
|
|
@@ -15587,7 +16711,7 @@ async function maybePromptInstall(scriptUrl) {
|
|
|
15587
16711
|
|
|
15588
16712
|
// src/index.ts
|
|
15589
16713
|
await maybePromptInstall(import.meta.url);
|
|
15590
|
-
var program = new
|
|
16714
|
+
var program = new Command5();
|
|
15591
16715
|
var version = await readPackageVersion(import.meta.url) ?? "0.0.0";
|
|
15592
16716
|
program.name("syntaur").description("CLI scaffolding tool for the Syntaur protocol").version(version);
|
|
15593
16717
|
program.command("init").description("Initialize ~/.syntaur/ directory structure and config").option("--force", "Overwrite existing config file").action(async (options) => {
|
|
@@ -15878,7 +17002,7 @@ program.command("track-session").description("Register an agent session (optiona
|
|
|
15878
17002
|
process.exit(1);
|
|
15879
17003
|
}
|
|
15880
17004
|
});
|
|
15881
|
-
program.command("browse").description("Interactive TUI browser for projects and assignments").option("--agent <
|
|
17005
|
+
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
17006
|
try {
|
|
15883
17007
|
await browseCommand(options);
|
|
15884
17008
|
} catch (error) {
|
|
@@ -15936,6 +17060,7 @@ program.command("disable-playbook").description("Disable a playbook so agents no
|
|
|
15936
17060
|
program.addCommand(todoCommand);
|
|
15937
17061
|
program.addCommand(backupCommand);
|
|
15938
17062
|
program.addCommand(doctorCommand);
|
|
17063
|
+
program.addCommand(agentsCommand);
|
|
15939
17064
|
if (process.argv.length <= 2) {
|
|
15940
17065
|
process.argv.push(await getDefaultCommandName());
|
|
15941
17066
|
}
|