syntaur 0.3.3 → 0.4.0
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/README.md +26 -3
- package/dist/index.js +650 -155
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -5
- package/platforms/claude-code/agents/syntaur-expert.md +30 -18
- package/platforms/claude-code/commands/complete-assignment/complete-assignment.md +20 -0
- package/platforms/claude-code/commands/create-assignment/create-assignment.md +20 -0
- package/platforms/claude-code/commands/create-project/create-project.md +20 -0
- package/platforms/claude-code/commands/grab-assignment/grab-assignment.md +20 -0
- package/platforms/claude-code/commands/plan-assignment/plan-assignment.md +20 -0
- package/statusline/statusline.sh +133 -0
- package/vendor/syntaur-skills/LICENSE +21 -0
- package/vendor/syntaur-skills/README.md +43 -0
- package/vendor/syntaur-skills/skills/complete-assignment/SKILL.md +146 -0
- package/vendor/syntaur-skills/skills/create-assignment/SKILL.md +72 -0
- package/vendor/syntaur-skills/skills/create-project/SKILL.md +56 -0
- package/vendor/syntaur-skills/skills/grab-assignment/SKILL.md +158 -0
- package/vendor/syntaur-skills/skills/plan-assignment/SKILL.md +137 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/SKILL.md +119 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/file-ownership.md +67 -0
- package/vendor/syntaur-skills/skills/syntaur-protocol/references/protocol-summary.md +82 -0
- package/platforms/claude-code/hooks/statusline.sh +0 -110
- package/platforms/claude-code/skills/complete-assignment/SKILL.md +0 -155
- package/platforms/claude-code/skills/create-assignment/SKILL.md +0 -67
- package/platforms/claude-code/skills/grab-assignment/SKILL.md +0 -202
- package/platforms/claude-code/skills/plan-assignment/SKILL.md +0 -156
- package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +0 -86
- package/platforms/codex/skills/complete-assignment/SKILL.md +0 -64
- package/platforms/codex/skills/create-assignment/SKILL.md +0 -49
- package/platforms/codex/skills/grab-assignment/SKILL.md +0 -73
- package/platforms/codex/skills/plan-assignment/SKILL.md +0 -61
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +0 -102
package/dist/index.js
CHANGED
|
@@ -4546,8 +4546,8 @@ __export(launch_exports, {
|
|
|
4546
4546
|
launchAgent: () => launchAgent
|
|
4547
4547
|
});
|
|
4548
4548
|
import { spawn as spawn2 } from "child_process";
|
|
4549
|
-
import { mkdir as
|
|
4550
|
-
import { resolve as
|
|
4549
|
+
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
4550
|
+
import { resolve as resolve30 } from "path";
|
|
4551
4551
|
async function launchAgent(options) {
|
|
4552
4552
|
const { projectsDir: projectsDir2, projectSlug, assignmentSlug, agent } = options;
|
|
4553
4553
|
const command = AGENT_COMMANDS[agent];
|
|
@@ -4557,10 +4557,10 @@ async function launchAgent(options) {
|
|
|
4557
4557
|
process.exit(1);
|
|
4558
4558
|
}
|
|
4559
4559
|
const workspaceDir = detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null) ?? process.cwd();
|
|
4560
|
-
const projectDir =
|
|
4561
|
-
const assignmentDir =
|
|
4562
|
-
const contextDir =
|
|
4563
|
-
await
|
|
4560
|
+
const projectDir = resolve30(projectsDir2, projectSlug);
|
|
4561
|
+
const assignmentDir = resolve30(projectDir, "assignments", assignmentSlug);
|
|
4562
|
+
const contextDir = resolve30(workspaceDir, ".syntaur");
|
|
4563
|
+
await mkdir4(contextDir, { recursive: true });
|
|
4564
4564
|
const context = {
|
|
4565
4565
|
projectSlug,
|
|
4566
4566
|
assignmentSlug,
|
|
@@ -4571,8 +4571,8 @@ async function launchAgent(options) {
|
|
|
4571
4571
|
branch: detail.workspace.branch ?? null,
|
|
4572
4572
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4573
4573
|
};
|
|
4574
|
-
await
|
|
4575
|
-
|
|
4574
|
+
await writeFile8(
|
|
4575
|
+
resolve30(contextDir, "context.json"),
|
|
4576
4576
|
JSON.stringify(context, null, 2) + "\n"
|
|
4577
4577
|
);
|
|
4578
4578
|
return new Promise((resolvePromise, reject) => {
|
|
@@ -5909,8 +5909,8 @@ async function migrateFromMarkdown(projectsDir2) {
|
|
|
5909
5909
|
return allSessions.length;
|
|
5910
5910
|
}
|
|
5911
5911
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
5912
|
-
const { readFile:
|
|
5913
|
-
const raw = await
|
|
5912
|
+
const { readFile: readFile28 } = await import("fs/promises");
|
|
5913
|
+
const raw = await readFile28(filePath, "utf-8");
|
|
5914
5914
|
const sessions = [];
|
|
5915
5915
|
const lines = raw.split("\n");
|
|
5916
5916
|
let inTable = false;
|
|
@@ -8218,8 +8218,8 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8218
8218
|
router.post("/:workspace/archive", async (req, res) => {
|
|
8219
8219
|
try {
|
|
8220
8220
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
8221
|
-
const { resolve:
|
|
8222
|
-
const { readFile:
|
|
8221
|
+
const { resolve: resolve44 } = await import("path");
|
|
8222
|
+
const { readFile: readFile28 } = await import("fs/promises");
|
|
8223
8223
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8224
8224
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
8225
8225
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -8235,10 +8235,10 @@ function createTodosRouter(todosDir2, broadcast) {
|
|
|
8235
8235
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
8236
8236
|
);
|
|
8237
8237
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
8238
|
-
await ensureDir(
|
|
8238
|
+
await ensureDir(resolve44(todosDir2, "archive"));
|
|
8239
8239
|
let archContent = "";
|
|
8240
8240
|
if (await fileExists(archFile)) {
|
|
8241
|
-
archContent = await
|
|
8241
|
+
archContent = await readFile28(archFile, "utf-8");
|
|
8242
8242
|
archContent = archContent.trimEnd() + "\n\n";
|
|
8243
8243
|
} else {
|
|
8244
8244
|
archContent = `---
|
|
@@ -10489,6 +10489,165 @@ async function textPrompt(question, defaultValue) {
|
|
|
10489
10489
|
}
|
|
10490
10490
|
}
|
|
10491
10491
|
|
|
10492
|
+
// src/utils/install-skills.ts
|
|
10493
|
+
init_fs();
|
|
10494
|
+
import { readFile as readFile16, readdir as readdir11, mkdir as mkdir2, copyFile, rm as rm4 } from "fs/promises";
|
|
10495
|
+
import { dirname as dirname7, resolve as resolve25, relative as relative3, join as join3 } from "path";
|
|
10496
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10497
|
+
import { homedir as homedir3 } from "os";
|
|
10498
|
+
var REQUIRED_SKILLS = [
|
|
10499
|
+
"syntaur-protocol",
|
|
10500
|
+
"grab-assignment",
|
|
10501
|
+
"plan-assignment",
|
|
10502
|
+
"complete-assignment",
|
|
10503
|
+
"create-assignment",
|
|
10504
|
+
"create-project"
|
|
10505
|
+
];
|
|
10506
|
+
function getVendoredSkillsDir() {
|
|
10507
|
+
const here = dirname7(fileURLToPath4(import.meta.url));
|
|
10508
|
+
return resolve25(here, "..", "vendor", "syntaur-skills", "skills");
|
|
10509
|
+
}
|
|
10510
|
+
function defaultSkillTargetDir(target) {
|
|
10511
|
+
if (target === "claude") return resolve25(homedir3(), ".claude", "skills");
|
|
10512
|
+
return resolve25(homedir3(), ".codex", "skills");
|
|
10513
|
+
}
|
|
10514
|
+
async function walkFiles(root) {
|
|
10515
|
+
const out = [];
|
|
10516
|
+
async function walk(dir) {
|
|
10517
|
+
const entries = await readdir11(dir, { withFileTypes: true });
|
|
10518
|
+
for (const entry of entries) {
|
|
10519
|
+
const full = join3(dir, entry.name);
|
|
10520
|
+
if (entry.isDirectory()) {
|
|
10521
|
+
await walk(full);
|
|
10522
|
+
} else if (entry.isFile()) {
|
|
10523
|
+
out.push(full);
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
await walk(root);
|
|
10528
|
+
return out.sort();
|
|
10529
|
+
}
|
|
10530
|
+
async function filesEqual(a, b) {
|
|
10531
|
+
try {
|
|
10532
|
+
const [ba, bb] = await Promise.all([readFile16(a), readFile16(b)]);
|
|
10533
|
+
if (ba.length !== bb.length) return false;
|
|
10534
|
+
return ba.equals(bb);
|
|
10535
|
+
} catch {
|
|
10536
|
+
return false;
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
async function copyDir(srcDir, destDir) {
|
|
10540
|
+
await mkdir2(destDir, { recursive: true });
|
|
10541
|
+
const entries = await readdir11(srcDir, { withFileTypes: true });
|
|
10542
|
+
for (const entry of entries) {
|
|
10543
|
+
const src = join3(srcDir, entry.name);
|
|
10544
|
+
const dest = join3(destDir, entry.name);
|
|
10545
|
+
if (entry.isDirectory()) {
|
|
10546
|
+
await copyDir(src, dest);
|
|
10547
|
+
} else if (entry.isFile()) {
|
|
10548
|
+
await copyFile(src, dest);
|
|
10549
|
+
}
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10552
|
+
async function skillMatches(srcDir, destDir) {
|
|
10553
|
+
if (!await fileExists(destDir)) return false;
|
|
10554
|
+
const srcFiles = await walkFiles(srcDir);
|
|
10555
|
+
for (const srcFile of srcFiles) {
|
|
10556
|
+
const rel = relative3(srcDir, srcFile);
|
|
10557
|
+
const destFile = join3(destDir, rel);
|
|
10558
|
+
if (!await filesEqual(srcFile, destFile)) return false;
|
|
10559
|
+
}
|
|
10560
|
+
const destFiles = await walkFiles(destDir);
|
|
10561
|
+
if (destFiles.length !== srcFiles.length) return false;
|
|
10562
|
+
return true;
|
|
10563
|
+
}
|
|
10564
|
+
async function installSkills(options) {
|
|
10565
|
+
const source = options.sourceDir ?? getVendoredSkillsDir();
|
|
10566
|
+
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
10567
|
+
const force = options.force ?? false;
|
|
10568
|
+
if (!await fileExists(source)) {
|
|
10569
|
+
throw new Error(
|
|
10570
|
+
`Vendored skills not found at ${source}. Reinstall syntaur: npm install -g syntaur@latest`
|
|
10571
|
+
);
|
|
10572
|
+
}
|
|
10573
|
+
const results = [];
|
|
10574
|
+
await mkdir2(targetRoot, { recursive: true });
|
|
10575
|
+
for (const skill of REQUIRED_SKILLS) {
|
|
10576
|
+
const srcDir = join3(source, skill);
|
|
10577
|
+
const destDir = join3(targetRoot, skill);
|
|
10578
|
+
if (!await fileExists(srcDir)) continue;
|
|
10579
|
+
if (!await fileExists(destDir)) {
|
|
10580
|
+
await copyDir(srcDir, destDir);
|
|
10581
|
+
results.push({
|
|
10582
|
+
skill,
|
|
10583
|
+
status: "installed",
|
|
10584
|
+
targetPath: destDir
|
|
10585
|
+
});
|
|
10586
|
+
continue;
|
|
10587
|
+
}
|
|
10588
|
+
if (await skillMatches(srcDir, destDir)) {
|
|
10589
|
+
results.push({
|
|
10590
|
+
skill,
|
|
10591
|
+
status: "already-current",
|
|
10592
|
+
targetPath: destDir
|
|
10593
|
+
});
|
|
10594
|
+
continue;
|
|
10595
|
+
}
|
|
10596
|
+
if (force) {
|
|
10597
|
+
await rm4(destDir, { recursive: true, force: true });
|
|
10598
|
+
await copyDir(srcDir, destDir);
|
|
10599
|
+
results.push({
|
|
10600
|
+
skill,
|
|
10601
|
+
status: "overwritten",
|
|
10602
|
+
targetPath: destDir
|
|
10603
|
+
});
|
|
10604
|
+
} else {
|
|
10605
|
+
results.push({
|
|
10606
|
+
skill,
|
|
10607
|
+
status: "differs-preserved",
|
|
10608
|
+
targetPath: destDir
|
|
10609
|
+
});
|
|
10610
|
+
}
|
|
10611
|
+
}
|
|
10612
|
+
return results;
|
|
10613
|
+
}
|
|
10614
|
+
async function uninstallSkills(options) {
|
|
10615
|
+
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
10616
|
+
if (!await fileExists(targetRoot)) return [];
|
|
10617
|
+
const removed = [];
|
|
10618
|
+
for (const skill of REQUIRED_SKILLS) {
|
|
10619
|
+
const destDir = join3(targetRoot, skill);
|
|
10620
|
+
if (!await fileExists(destDir)) continue;
|
|
10621
|
+
const skillMd = join3(destDir, "SKILL.md");
|
|
10622
|
+
if (!await fileExists(skillMd)) continue;
|
|
10623
|
+
const content = await readFile16(skillMd, "utf-8").catch(() => "");
|
|
10624
|
+
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
10625
|
+
if (!match || match[1] !== skill) continue;
|
|
10626
|
+
await rm4(destDir, { recursive: true, force: true });
|
|
10627
|
+
removed.push(destDir);
|
|
10628
|
+
}
|
|
10629
|
+
return removed;
|
|
10630
|
+
}
|
|
10631
|
+
function formatInstallReport(results, target) {
|
|
10632
|
+
const lines = [];
|
|
10633
|
+
lines.push(`Skill install (${target}):`);
|
|
10634
|
+
for (const r of results) {
|
|
10635
|
+
const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : "=";
|
|
10636
|
+
lines.push(` ${marker} ${r.skill} (${r.status})`);
|
|
10637
|
+
}
|
|
10638
|
+
const diffs = results.filter((r) => r.status === "differs-preserved");
|
|
10639
|
+
if (diffs.length > 0) {
|
|
10640
|
+
lines.push("");
|
|
10641
|
+
lines.push(
|
|
10642
|
+
` Note: ${diffs.length} skill(s) already exist with different content and were preserved.`
|
|
10643
|
+
);
|
|
10644
|
+
lines.push(
|
|
10645
|
+
" Run with --force-skills to overwrite with the vendored version."
|
|
10646
|
+
);
|
|
10647
|
+
}
|
|
10648
|
+
return lines.join("\n");
|
|
10649
|
+
}
|
|
10650
|
+
|
|
10492
10651
|
// src/commands/install-plugin.ts
|
|
10493
10652
|
async function promptForInstallPath(question, recommendedPath) {
|
|
10494
10653
|
while (true) {
|
|
@@ -10571,10 +10730,261 @@ async function installPluginCommand(options) {
|
|
|
10571
10730
|
if (currentMarketplace) {
|
|
10572
10731
|
console.log(` marketplace: ${currentMarketplace.manifestPath}`);
|
|
10573
10732
|
}
|
|
10733
|
+
if (!options.skipSkills) {
|
|
10734
|
+
try {
|
|
10735
|
+
const skillResults = await installSkills({
|
|
10736
|
+
target: "claude",
|
|
10737
|
+
force: options.forceSkills
|
|
10738
|
+
});
|
|
10739
|
+
console.log("");
|
|
10740
|
+
console.log(formatInstallReport(skillResults, "claude"));
|
|
10741
|
+
} catch (error) {
|
|
10742
|
+
console.warn(
|
|
10743
|
+
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
10744
|
+
);
|
|
10745
|
+
}
|
|
10746
|
+
}
|
|
10574
10747
|
console.log("\nThe plugin is now available in Claude Code.");
|
|
10575
|
-
console.log("
|
|
10576
|
-
console.log(" Background: syntaur-protocol (auto-invoked)");
|
|
10577
|
-
console.log(" Hook: write boundary enforcement (PreToolUse)");
|
|
10748
|
+
console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project");
|
|
10749
|
+
console.log(" Background: syntaur-protocol skill (auto-invoked)");
|
|
10750
|
+
console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End");
|
|
10751
|
+
}
|
|
10752
|
+
|
|
10753
|
+
// src/commands/install-statusline.ts
|
|
10754
|
+
init_paths();
|
|
10755
|
+
init_fs();
|
|
10756
|
+
import { readFile as readFile17, writeFile as writeFile7, copyFile as copyFile2, rm as rm5, stat as stat3, symlink as symlink2, unlink as unlink6, lstat as lstat2 } from "fs/promises";
|
|
10757
|
+
import { resolve as resolve26, dirname as dirname8 } from "path";
|
|
10758
|
+
import { homedir as homedir4 } from "os";
|
|
10759
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
10760
|
+
function getPackageStatuslineSource() {
|
|
10761
|
+
const here = dirname8(fileURLToPath5(import.meta.url));
|
|
10762
|
+
return resolve26(here, "..", "statusline", "statusline.sh");
|
|
10763
|
+
}
|
|
10764
|
+
async function readSettingsJson(settingsPath) {
|
|
10765
|
+
if (!await fileExists(settingsPath)) return {};
|
|
10766
|
+
const raw = await readFile17(settingsPath, "utf-8");
|
|
10767
|
+
if (raw.trim() === "") return {};
|
|
10768
|
+
try {
|
|
10769
|
+
const parsed = JSON.parse(raw);
|
|
10770
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
10771
|
+
} catch (error) {
|
|
10772
|
+
throw new Error(
|
|
10773
|
+
`Unable to parse ${settingsPath}: ${error.message}. Fix the JSON and re-run.`
|
|
10774
|
+
);
|
|
10775
|
+
}
|
|
10776
|
+
}
|
|
10777
|
+
async function writeSettingsJson(settingsPath, data) {
|
|
10778
|
+
await ensureDir(dirname8(settingsPath));
|
|
10779
|
+
await writeFile7(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
10780
|
+
}
|
|
10781
|
+
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
10782
|
+
if (mode !== "ask") return mode;
|
|
10783
|
+
if (!existingCommand || existingCommand === ourCommand) return "replace";
|
|
10784
|
+
if (!isInteractiveTerminal()) {
|
|
10785
|
+
return "wrap";
|
|
10786
|
+
}
|
|
10787
|
+
console.log(
|
|
10788
|
+
`A Claude Code statusLine is already configured:
|
|
10789
|
+
${existingCommand}
|
|
10790
|
+
`
|
|
10791
|
+
);
|
|
10792
|
+
const wantWrap = await confirmPrompt(
|
|
10793
|
+
"Compose (wrap) your existing statusline with syntaur segments? [Y to wrap / n to replace]",
|
|
10794
|
+
true
|
|
10795
|
+
);
|
|
10796
|
+
if (wantWrap) return "wrap";
|
|
10797
|
+
const confirmReplace = await confirmPrompt(
|
|
10798
|
+
"Replace your existing statusline with syntaur only?",
|
|
10799
|
+
false
|
|
10800
|
+
);
|
|
10801
|
+
return confirmReplace ? "replace" : "skip";
|
|
10802
|
+
}
|
|
10803
|
+
function extractExistingCommand(settings) {
|
|
10804
|
+
const sl = settings.statusLine;
|
|
10805
|
+
if (!sl || typeof sl !== "object") return void 0;
|
|
10806
|
+
const obj = sl;
|
|
10807
|
+
return {
|
|
10808
|
+
type: typeof obj.type === "string" ? obj.type : void 0,
|
|
10809
|
+
command: typeof obj.command === "string" ? obj.command : void 0
|
|
10810
|
+
};
|
|
10811
|
+
}
|
|
10812
|
+
async function backupSettings(settingsSnapshot, backupPath) {
|
|
10813
|
+
await ensureDir(dirname8(backupPath));
|
|
10814
|
+
await writeFile7(
|
|
10815
|
+
backupPath,
|
|
10816
|
+
JSON.stringify(
|
|
10817
|
+
{
|
|
10818
|
+
version: 1,
|
|
10819
|
+
takenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10820
|
+
settingsPath: settingsSnapshot.settingsPath,
|
|
10821
|
+
previousStatusLine: settingsSnapshot.existingStatusLine ?? null
|
|
10822
|
+
},
|
|
10823
|
+
null,
|
|
10824
|
+
2
|
|
10825
|
+
) + "\n",
|
|
10826
|
+
"utf-8"
|
|
10827
|
+
);
|
|
10828
|
+
}
|
|
10829
|
+
async function installScript(sourceScript, destScript, link) {
|
|
10830
|
+
await ensureDir(dirname8(destScript));
|
|
10831
|
+
try {
|
|
10832
|
+
const s = await lstat2(destScript);
|
|
10833
|
+
if (s.isSymbolicLink() || s.isFile()) {
|
|
10834
|
+
await unlink6(destScript);
|
|
10835
|
+
}
|
|
10836
|
+
} catch {
|
|
10837
|
+
}
|
|
10838
|
+
if (link) {
|
|
10839
|
+
await symlink2(sourceScript, destScript);
|
|
10840
|
+
} else {
|
|
10841
|
+
await copyFile2(sourceScript, destScript);
|
|
10842
|
+
}
|
|
10843
|
+
}
|
|
10844
|
+
async function installStatuslineCommand(options = {}) {
|
|
10845
|
+
const mode = options.mode ?? "ask";
|
|
10846
|
+
const settingsPath = options.settingsPath ?? resolve26(homedir4(), ".claude", "settings.json");
|
|
10847
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
10848
|
+
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
10849
|
+
const destScript = resolve26(installRoot, "statusline.sh");
|
|
10850
|
+
const confPath = resolve26(installRoot, "statusline.conf");
|
|
10851
|
+
const backupPath = resolve26(installRoot, "statusline.backup.json");
|
|
10852
|
+
if (!await fileExists(sourceScript)) {
|
|
10853
|
+
throw new Error(
|
|
10854
|
+
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
10855
|
+
);
|
|
10856
|
+
}
|
|
10857
|
+
await installScript(sourceScript, destScript, Boolean(options.link));
|
|
10858
|
+
const settings = await readSettingsJson(settingsPath);
|
|
10859
|
+
const existingStatusLine = extractExistingCommand(settings);
|
|
10860
|
+
const existingCommand = existingStatusLine?.command;
|
|
10861
|
+
const ourCommand = `bash ${destScript}`;
|
|
10862
|
+
const resolvedMode = await resolveMode(mode, existingCommand, ourCommand);
|
|
10863
|
+
if (resolvedMode === "skip") {
|
|
10864
|
+
console.log("Installed statusline script only:");
|
|
10865
|
+
console.log(` script: ${destScript}`);
|
|
10866
|
+
console.log(` source: ${sourceScript}`);
|
|
10867
|
+
console.log(
|
|
10868
|
+
" (settings.json left unchanged \u2014 run with --mode=replace or --mode=wrap to wire it up)"
|
|
10869
|
+
);
|
|
10870
|
+
return;
|
|
10871
|
+
}
|
|
10872
|
+
await backupSettings(
|
|
10873
|
+
{
|
|
10874
|
+
settingsPath,
|
|
10875
|
+
existingStatusLine,
|
|
10876
|
+
existingCommand
|
|
10877
|
+
},
|
|
10878
|
+
backupPath
|
|
10879
|
+
);
|
|
10880
|
+
let wrapTarget = "";
|
|
10881
|
+
if (resolvedMode === "wrap" && existingCommand && existingCommand !== ourCommand) {
|
|
10882
|
+
const parsed = parseWrapCommand(existingCommand);
|
|
10883
|
+
if (parsed) {
|
|
10884
|
+
wrapTarget = parsed;
|
|
10885
|
+
} else {
|
|
10886
|
+
const wrapperPath = resolve26(installRoot, "statusline-wrapped.sh");
|
|
10887
|
+
const wrapperBody = `#!/usr/bin/env bash
|
|
10888
|
+
# Auto-generated by syntaur install-statusline.
|
|
10889
|
+
# Executes the previously configured statusLine command.
|
|
10890
|
+
exec ${existingCommand}
|
|
10891
|
+
`;
|
|
10892
|
+
await writeFile7(wrapperPath, wrapperBody, "utf-8");
|
|
10893
|
+
await chmodExec(wrapperPath);
|
|
10894
|
+
wrapTarget = wrapperPath;
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10897
|
+
await ensureDir(dirname8(confPath));
|
|
10898
|
+
await writeFile7(
|
|
10899
|
+
confPath,
|
|
10900
|
+
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
10901
|
+
# stdout becomes the leading segment of the statusline. Remove this
|
|
10902
|
+
# line or comment it out to disable wrapping.
|
|
10903
|
+
${wrapTarget}
|
|
10904
|
+
` : `# syntaur statusline config. Add a single path on a non-comment line to
|
|
10905
|
+
# wrap another statusline script with syntaur segments appended.
|
|
10906
|
+
`,
|
|
10907
|
+
"utf-8"
|
|
10908
|
+
);
|
|
10909
|
+
settings.statusLine = {
|
|
10910
|
+
type: "command",
|
|
10911
|
+
command: ourCommand
|
|
10912
|
+
};
|
|
10913
|
+
await writeSettingsJson(settingsPath, settings);
|
|
10914
|
+
console.log("Installed syntaur statusline:");
|
|
10915
|
+
console.log(` script: ${destScript}`);
|
|
10916
|
+
console.log(` source: ${sourceScript}`);
|
|
10917
|
+
console.log(` mode: ${resolvedMode}`);
|
|
10918
|
+
console.log(` settings.json:${settingsPath}`);
|
|
10919
|
+
console.log(` backup: ${backupPath}`);
|
|
10920
|
+
if (wrapTarget) {
|
|
10921
|
+
console.log(` wrap target: ${wrapTarget}`);
|
|
10922
|
+
console.log(` (edit ${confPath} to change or disable wrapping)`);
|
|
10923
|
+
}
|
|
10924
|
+
}
|
|
10925
|
+
function parseWrapCommand(command) {
|
|
10926
|
+
const trimmed = command.trim();
|
|
10927
|
+
const bashMatch = trimmed.match(/^bash\s+(\S+)$/);
|
|
10928
|
+
if (bashMatch) return bashMatch[1];
|
|
10929
|
+
if (/^\S+\.(sh|bash)$/.test(trimmed)) return trimmed;
|
|
10930
|
+
return null;
|
|
10931
|
+
}
|
|
10932
|
+
async function chmodExec(path) {
|
|
10933
|
+
const fs = await import("fs/promises");
|
|
10934
|
+
try {
|
|
10935
|
+
const s = await stat3(path);
|
|
10936
|
+
await fs.chmod(path, s.mode | 73);
|
|
10937
|
+
} catch {
|
|
10938
|
+
}
|
|
10939
|
+
}
|
|
10940
|
+
async function uninstallStatuslineCommand(options = {}) {
|
|
10941
|
+
const settingsPath = options.settingsPath ?? resolve26(homedir4(), ".claude", "settings.json");
|
|
10942
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
10943
|
+
const destScript = resolve26(installRoot, "statusline.sh");
|
|
10944
|
+
const confPath = resolve26(installRoot, "statusline.conf");
|
|
10945
|
+
const backupPath = resolve26(installRoot, "statusline.backup.json");
|
|
10946
|
+
const wrapperPath = resolve26(installRoot, "statusline-wrapped.sh");
|
|
10947
|
+
const settings = await readSettingsJson(settingsPath);
|
|
10948
|
+
const existing = extractExistingCommand(settings);
|
|
10949
|
+
const ourCommand = `bash ${destScript}`;
|
|
10950
|
+
let restored = null;
|
|
10951
|
+
if (await fileExists(backupPath)) {
|
|
10952
|
+
try {
|
|
10953
|
+
const raw = await readFile17(backupPath, "utf-8");
|
|
10954
|
+
const parsed = JSON.parse(raw);
|
|
10955
|
+
const prev = parsed?.previousStatusLine;
|
|
10956
|
+
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
10957
|
+
restored = { command: prev.command };
|
|
10958
|
+
}
|
|
10959
|
+
} catch {
|
|
10960
|
+
}
|
|
10961
|
+
}
|
|
10962
|
+
if (existing?.command === ourCommand) {
|
|
10963
|
+
if (restored) {
|
|
10964
|
+
settings.statusLine = {
|
|
10965
|
+
type: "command",
|
|
10966
|
+
command: restored.command
|
|
10967
|
+
};
|
|
10968
|
+
} else {
|
|
10969
|
+
delete settings.statusLine;
|
|
10970
|
+
}
|
|
10971
|
+
await writeSettingsJson(settingsPath, settings);
|
|
10972
|
+
}
|
|
10973
|
+
if (!options.keepScript) {
|
|
10974
|
+
for (const path of [destScript, confPath, backupPath, wrapperPath]) {
|
|
10975
|
+
try {
|
|
10976
|
+
await rm5(path, { force: true });
|
|
10977
|
+
} catch {
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
console.log("Uninstalled syntaur statusline.");
|
|
10982
|
+
if (restored) {
|
|
10983
|
+
console.log(` Restored previous command: ${restored.command}`);
|
|
10984
|
+
} else {
|
|
10985
|
+
console.log(" Removed statusLine entry from settings.json.");
|
|
10986
|
+
}
|
|
10987
|
+
console.log(` settings.json: ${settingsPath}`);
|
|
10578
10988
|
}
|
|
10579
10989
|
|
|
10580
10990
|
// src/commands/install-codex-plugin.ts
|
|
@@ -10656,14 +11066,61 @@ async function installCodexPluginCommand(options) {
|
|
|
10656
11066
|
console.log(` source: ${result.sourceDir}`);
|
|
10657
11067
|
console.log(` mode: ${result.mode}`);
|
|
10658
11068
|
console.log(` marketplace: ${marketplace.marketplacePath}`);
|
|
11069
|
+
if (!options.skipSkills) {
|
|
11070
|
+
try {
|
|
11071
|
+
const skillResults = await installSkills({
|
|
11072
|
+
target: "codex",
|
|
11073
|
+
force: options.forceSkills
|
|
11074
|
+
});
|
|
11075
|
+
console.log("");
|
|
11076
|
+
console.log(formatInstallReport(skillResults, "codex"));
|
|
11077
|
+
} catch (error) {
|
|
11078
|
+
console.warn(
|
|
11079
|
+
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
11080
|
+
);
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
10659
11083
|
console.log("\nThe plugin is now available to Codex.");
|
|
10660
11084
|
console.log(
|
|
10661
|
-
"
|
|
11085
|
+
" Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment"
|
|
10662
11086
|
);
|
|
10663
|
-
console.log("
|
|
11087
|
+
console.log(" Codex-specific: track-session skill (rollout path aware)");
|
|
10664
11088
|
console.log(" Hooks: write boundary enforcement, session cleanup");
|
|
10665
11089
|
}
|
|
10666
11090
|
|
|
11091
|
+
// src/commands/uninstall-skills.ts
|
|
11092
|
+
async function uninstallSkillsCommand(options) {
|
|
11093
|
+
const runClaude = Boolean(options.claude || options.all);
|
|
11094
|
+
const runCodex = Boolean(options.codex || options.all);
|
|
11095
|
+
if (!runClaude && !runCodex) {
|
|
11096
|
+
throw new Error(
|
|
11097
|
+
"Specify --claude, --codex, or --all (use one or more)."
|
|
11098
|
+
);
|
|
11099
|
+
}
|
|
11100
|
+
let totalRemoved = 0;
|
|
11101
|
+
if (runClaude) {
|
|
11102
|
+
const removed = await uninstallSkills({ target: "claude" });
|
|
11103
|
+
totalRemoved += removed.length;
|
|
11104
|
+
console.log(
|
|
11105
|
+
`Removed ${removed.length} Syntaur protocol skill(s) from ~/.claude/skills:`
|
|
11106
|
+
);
|
|
11107
|
+
for (const p of removed) console.log(` - ${p}`);
|
|
11108
|
+
}
|
|
11109
|
+
if (runCodex) {
|
|
11110
|
+
const removed = await uninstallSkills({ target: "codex" });
|
|
11111
|
+
totalRemoved += removed.length;
|
|
11112
|
+
console.log(
|
|
11113
|
+
`Removed ${removed.length} Syntaur protocol skill(s) from ~/.codex/skills:`
|
|
11114
|
+
);
|
|
11115
|
+
for (const p of removed) console.log(` - ${p}`);
|
|
11116
|
+
}
|
|
11117
|
+
if (totalRemoved === 0) {
|
|
11118
|
+
console.log(
|
|
11119
|
+
"No Syntaur protocol skills found to remove. (User-authored skills with matching directory names are preserved.)"
|
|
11120
|
+
);
|
|
11121
|
+
}
|
|
11122
|
+
}
|
|
11123
|
+
|
|
10667
11124
|
// src/commands/setup.ts
|
|
10668
11125
|
import { execSync } from "child_process";
|
|
10669
11126
|
init_config2();
|
|
@@ -10768,7 +11225,7 @@ async function setupCommand(options) {
|
|
|
10768
11225
|
}
|
|
10769
11226
|
|
|
10770
11227
|
// src/commands/uninstall.ts
|
|
10771
|
-
import { resolve as
|
|
11228
|
+
import { resolve as resolve27 } from "path";
|
|
10772
11229
|
init_paths();
|
|
10773
11230
|
function expandTargets(options) {
|
|
10774
11231
|
if (options.all) {
|
|
@@ -10848,7 +11305,7 @@ async function uninstallCommand(options) {
|
|
|
10848
11305
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
10849
11306
|
await removeSyntaurData();
|
|
10850
11307
|
console.log(`Removed ${syntaurRoot()}`);
|
|
10851
|
-
if (configuredProjectDir &&
|
|
11308
|
+
if (configuredProjectDir && resolve27(configuredProjectDir) !== resolve27(syntaurRoot(), "projects")) {
|
|
10852
11309
|
console.warn(
|
|
10853
11310
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
10854
11311
|
);
|
|
@@ -10863,7 +11320,7 @@ async function uninstallCommand(options) {
|
|
|
10863
11320
|
init_paths();
|
|
10864
11321
|
init_fs();
|
|
10865
11322
|
init_config2();
|
|
10866
|
-
import { resolve as
|
|
11323
|
+
import { resolve as resolve28 } from "path";
|
|
10867
11324
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
10868
11325
|
async function setupAdapterCommand(framework, options) {
|
|
10869
11326
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -10889,19 +11346,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10889
11346
|
}
|
|
10890
11347
|
const config = await readConfig();
|
|
10891
11348
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10892
|
-
const projectDir =
|
|
10893
|
-
const assignmentDir =
|
|
11349
|
+
const projectDir = resolve28(baseDir, options.project);
|
|
11350
|
+
const assignmentDir = resolve28(
|
|
10894
11351
|
projectDir,
|
|
10895
11352
|
"assignments",
|
|
10896
11353
|
options.assignment
|
|
10897
11354
|
);
|
|
10898
|
-
const projectMdPath =
|
|
11355
|
+
const projectMdPath = resolve28(projectDir, "project.md");
|
|
10899
11356
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
10900
11357
|
throw new Error(
|
|
10901
11358
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
10902
11359
|
);
|
|
10903
11360
|
}
|
|
10904
|
-
const assignmentMdPath =
|
|
11361
|
+
const assignmentMdPath = resolve28(assignmentDir, "assignment.md");
|
|
10905
11362
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
10906
11363
|
throw new Error(
|
|
10907
11364
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -10929,15 +11386,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10929
11386
|
}
|
|
10930
11387
|
}
|
|
10931
11388
|
if (framework === "cursor") {
|
|
10932
|
-
const protocolPath =
|
|
10933
|
-
const assignmentPath =
|
|
11389
|
+
const protocolPath = resolve28(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
11390
|
+
const assignmentPath = resolve28(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
10934
11391
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
10935
11392
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
10936
11393
|
} else if (framework === "codex" || framework === "opencode") {
|
|
10937
|
-
const agentsPath =
|
|
11394
|
+
const agentsPath = resolve28(cwd, "AGENTS.md");
|
|
10938
11395
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
10939
11396
|
if (framework === "opencode") {
|
|
10940
|
-
const configPath =
|
|
11397
|
+
const configPath = resolve28(cwd, "opencode.json");
|
|
10941
11398
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
10942
11399
|
}
|
|
10943
11400
|
}
|
|
@@ -10962,7 +11419,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10962
11419
|
init_paths();
|
|
10963
11420
|
init_fs();
|
|
10964
11421
|
init_config2();
|
|
10965
|
-
import { resolve as
|
|
11422
|
+
import { resolve as resolve29 } from "path";
|
|
10966
11423
|
async function trackSessionCommand(options) {
|
|
10967
11424
|
if (!options.agent) {
|
|
10968
11425
|
throw new Error("--agent <name> is required.");
|
|
@@ -10975,7 +11432,7 @@ async function trackSessionCommand(options) {
|
|
|
10975
11432
|
if (options.project) {
|
|
10976
11433
|
const config = await readConfig();
|
|
10977
11434
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10978
|
-
const projectDir =
|
|
11435
|
+
const projectDir = resolve29(baseDir, options.project);
|
|
10979
11436
|
if (!await fileExists(projectDir)) {
|
|
10980
11437
|
throw new Error(
|
|
10981
11438
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -11030,7 +11487,7 @@ async function browseCommand(options) {
|
|
|
11030
11487
|
}
|
|
11031
11488
|
|
|
11032
11489
|
// src/commands/create-playbook.ts
|
|
11033
|
-
import { resolve as
|
|
11490
|
+
import { resolve as resolve31 } from "path";
|
|
11034
11491
|
init_timestamp();
|
|
11035
11492
|
init_paths();
|
|
11036
11493
|
init_fs();
|
|
@@ -11046,7 +11503,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
11046
11503
|
}
|
|
11047
11504
|
const dir = playbooksDir();
|
|
11048
11505
|
await ensureDir(dir);
|
|
11049
|
-
const filePath =
|
|
11506
|
+
const filePath = resolve31(dir, `${slug}.md`);
|
|
11050
11507
|
if (await fileExists(filePath)) {
|
|
11051
11508
|
throw new Error(
|
|
11052
11509
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -11067,15 +11524,15 @@ Use --slug to specify a different slug.`
|
|
|
11067
11524
|
init_paths();
|
|
11068
11525
|
init_fs();
|
|
11069
11526
|
init_parser();
|
|
11070
|
-
import { readdir as
|
|
11071
|
-
import { resolve as
|
|
11527
|
+
import { readdir as readdir12, readFile as readFile18 } from "fs/promises";
|
|
11528
|
+
import { resolve as resolve32 } from "path";
|
|
11072
11529
|
async function listPlaybooksCommand() {
|
|
11073
11530
|
const dir = playbooksDir();
|
|
11074
11531
|
if (!await fileExists(dir)) {
|
|
11075
11532
|
console.log('No playbooks directory found. Run "syntaur init" first.');
|
|
11076
11533
|
return;
|
|
11077
11534
|
}
|
|
11078
|
-
const entries = await
|
|
11535
|
+
const entries = await readdir12(dir, { withFileTypes: true });
|
|
11079
11536
|
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md");
|
|
11080
11537
|
if (mdFiles.length === 0) {
|
|
11081
11538
|
console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
|
|
@@ -11086,8 +11543,8 @@ async function listPlaybooksCommand() {
|
|
|
11086
11543
|
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
11087
11544
|
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
11088
11545
|
for (const entry of mdFiles) {
|
|
11089
|
-
const filePath =
|
|
11090
|
-
const raw = await
|
|
11546
|
+
const filePath = resolve32(dir, entry.name);
|
|
11547
|
+
const raw = await readFile18(filePath, "utf-8");
|
|
11091
11548
|
const parsed = parsePlaybook(raw);
|
|
11092
11549
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
11093
11550
|
const name = parsed.name || slug;
|
|
@@ -11101,8 +11558,8 @@ init_paths();
|
|
|
11101
11558
|
init_parser2();
|
|
11102
11559
|
init_fs();
|
|
11103
11560
|
import { Command } from "commander";
|
|
11104
|
-
import { readFile as
|
|
11105
|
-
import { resolve as
|
|
11561
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
11562
|
+
import { resolve as resolve33 } from "path";
|
|
11106
11563
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
11107
11564
|
function resolveWorkspace(options) {
|
|
11108
11565
|
if (options.global) return "_global";
|
|
@@ -11383,10 +11840,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
11383
11840
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
11384
11841
|
);
|
|
11385
11842
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
11386
|
-
await ensureDir(
|
|
11843
|
+
await ensureDir(resolve33(todosPath, "archive"));
|
|
11387
11844
|
let archContent = "";
|
|
11388
11845
|
if (await fileExists(archFile)) {
|
|
11389
|
-
archContent = await
|
|
11846
|
+
archContent = await readFile19(archFile, "utf-8");
|
|
11390
11847
|
archContent = archContent.trimEnd() + "\n\n";
|
|
11391
11848
|
} else {
|
|
11392
11849
|
archContent = `---
|
|
@@ -11574,20 +12031,20 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
11574
12031
|
import { Command as Command3 } from "commander";
|
|
11575
12032
|
|
|
11576
12033
|
// src/utils/doctor/index.ts
|
|
11577
|
-
import { fileURLToPath as
|
|
11578
|
-
import { readFile as
|
|
11579
|
-
import { dirname as
|
|
12034
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12035
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
12036
|
+
import { dirname as dirname10, join as join5 } from "path";
|
|
11580
12037
|
|
|
11581
12038
|
// src/utils/doctor/context.ts
|
|
11582
12039
|
init_config2();
|
|
11583
12040
|
init_paths();
|
|
11584
12041
|
init_fs();
|
|
11585
12042
|
import Database2 from "better-sqlite3";
|
|
11586
|
-
import { resolve as
|
|
12043
|
+
import { resolve as resolve34 } from "path";
|
|
11587
12044
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
11588
12045
|
const config = await readConfig();
|
|
11589
12046
|
const root = syntaurRoot();
|
|
11590
|
-
const dbPath =
|
|
12047
|
+
const dbPath = resolve34(root, "syntaur.db");
|
|
11591
12048
|
let db2 = null;
|
|
11592
12049
|
let dbError = null;
|
|
11593
12050
|
if (await fileExists(dbPath)) {
|
|
@@ -11621,10 +12078,10 @@ function closeCheckContext(ctx) {
|
|
|
11621
12078
|
// src/utils/doctor/checks/env.ts
|
|
11622
12079
|
init_fs();
|
|
11623
12080
|
init_paths();
|
|
11624
|
-
import { resolve as
|
|
11625
|
-
import { readFile as
|
|
11626
|
-
import { fileURLToPath as
|
|
11627
|
-
import { dirname as
|
|
12081
|
+
import { resolve as resolve35, isAbsolute as isAbsolute3 } from "path";
|
|
12082
|
+
import { readFile as readFile20, stat as stat4 } from "fs/promises";
|
|
12083
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12084
|
+
import { dirname as dirname9, join as join4 } from "path";
|
|
11628
12085
|
var CATEGORY = "env";
|
|
11629
12086
|
var syntaurRootExists = {
|
|
11630
12087
|
id: "env.syntaur-root-exists",
|
|
@@ -11632,7 +12089,7 @@ var syntaurRootExists = {
|
|
|
11632
12089
|
title: "~/.syntaur/ directory exists",
|
|
11633
12090
|
async run(ctx) {
|
|
11634
12091
|
try {
|
|
11635
|
-
const s = await
|
|
12092
|
+
const s = await stat4(ctx.syntaurRoot);
|
|
11636
12093
|
if (!s.isDirectory()) {
|
|
11637
12094
|
return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
|
|
11638
12095
|
ctx.syntaurRoot
|
|
@@ -11662,7 +12119,7 @@ var configValid = {
|
|
|
11662
12119
|
category: CATEGORY,
|
|
11663
12120
|
title: "~/.syntaur/config.md is valid",
|
|
11664
12121
|
async run(ctx) {
|
|
11665
|
-
const configPath =
|
|
12122
|
+
const configPath = resolve35(ctx.syntaurRoot, "config.md");
|
|
11666
12123
|
if (!await fileExists(configPath)) {
|
|
11667
12124
|
return {
|
|
11668
12125
|
id: this.id,
|
|
@@ -11679,7 +12136,7 @@ var configValid = {
|
|
|
11679
12136
|
autoFixable: false
|
|
11680
12137
|
};
|
|
11681
12138
|
}
|
|
11682
|
-
const content = await
|
|
12139
|
+
const content = await readFile20(configPath, "utf-8");
|
|
11683
12140
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
11684
12141
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
11685
12142
|
return {
|
|
@@ -11954,15 +12411,15 @@ async function readLocalVersion() {
|
|
|
11954
12411
|
}
|
|
11955
12412
|
async function readLocalPkg() {
|
|
11956
12413
|
try {
|
|
11957
|
-
const here =
|
|
11958
|
-
let dir =
|
|
12414
|
+
const here = fileURLToPath6(import.meta.url);
|
|
12415
|
+
let dir = dirname9(here);
|
|
11959
12416
|
for (let i = 0; i < 6; i++) {
|
|
11960
|
-
const candidate =
|
|
12417
|
+
const candidate = join4(dir, "package.json");
|
|
11961
12418
|
try {
|
|
11962
|
-
const text = await
|
|
12419
|
+
const text = await readFile20(candidate, "utf-8");
|
|
11963
12420
|
return JSON.parse(text);
|
|
11964
12421
|
} catch {
|
|
11965
|
-
dir =
|
|
12422
|
+
dir = dirname9(dir);
|
|
11966
12423
|
}
|
|
11967
12424
|
}
|
|
11968
12425
|
return null;
|
|
@@ -12011,8 +12468,8 @@ function versionGte(a, b) {
|
|
|
12011
12468
|
|
|
12012
12469
|
// src/utils/doctor/checks/structure.ts
|
|
12013
12470
|
init_fs();
|
|
12014
|
-
import { resolve as
|
|
12015
|
-
import { readdir as
|
|
12471
|
+
import { resolve as resolve36 } from "path";
|
|
12472
|
+
import { readdir as readdir13, stat as stat5 } from "fs/promises";
|
|
12016
12473
|
var CATEGORY2 = "structure";
|
|
12017
12474
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
12018
12475
|
"projects",
|
|
@@ -12031,7 +12488,7 @@ var projectsDir = {
|
|
|
12031
12488
|
category: CATEGORY2,
|
|
12032
12489
|
title: "projects/ directory exists",
|
|
12033
12490
|
async run(ctx) {
|
|
12034
|
-
const p =
|
|
12491
|
+
const p = resolve36(ctx.syntaurRoot, "projects");
|
|
12035
12492
|
if (!await fileExists(p)) {
|
|
12036
12493
|
return {
|
|
12037
12494
|
id: this.id,
|
|
@@ -12056,7 +12513,7 @@ var playbooksDir2 = {
|
|
|
12056
12513
|
category: CATEGORY2,
|
|
12057
12514
|
title: "playbooks/ directory exists",
|
|
12058
12515
|
async run(ctx) {
|
|
12059
|
-
const p =
|
|
12516
|
+
const p = resolve36(ctx.syntaurRoot, "playbooks");
|
|
12060
12517
|
if (!await fileExists(p)) {
|
|
12061
12518
|
return {
|
|
12062
12519
|
id: this.id,
|
|
@@ -12081,7 +12538,7 @@ var todosDirValid = {
|
|
|
12081
12538
|
category: CATEGORY2,
|
|
12082
12539
|
title: "todos/ directory is readable (if present)",
|
|
12083
12540
|
async run(ctx) {
|
|
12084
|
-
const p =
|
|
12541
|
+
const p = resolve36(ctx.syntaurRoot, "todos");
|
|
12085
12542
|
if (!await fileExists(p)) {
|
|
12086
12543
|
return {
|
|
12087
12544
|
id: this.id,
|
|
@@ -12092,7 +12549,7 @@ var todosDirValid = {
|
|
|
12092
12549
|
autoFixable: false
|
|
12093
12550
|
};
|
|
12094
12551
|
}
|
|
12095
|
-
const s = await
|
|
12552
|
+
const s = await stat5(p);
|
|
12096
12553
|
if (!s.isDirectory()) {
|
|
12097
12554
|
return {
|
|
12098
12555
|
id: this.id,
|
|
@@ -12112,7 +12569,7 @@ var serversDirValid = {
|
|
|
12112
12569
|
category: CATEGORY2,
|
|
12113
12570
|
title: "servers/ directory is readable (if present)",
|
|
12114
12571
|
async run(ctx) {
|
|
12115
|
-
const p =
|
|
12572
|
+
const p = resolve36(ctx.syntaurRoot, "servers");
|
|
12116
12573
|
if (!await fileExists(p)) {
|
|
12117
12574
|
return {
|
|
12118
12575
|
id: this.id,
|
|
@@ -12123,7 +12580,7 @@ var serversDirValid = {
|
|
|
12123
12580
|
autoFixable: false
|
|
12124
12581
|
};
|
|
12125
12582
|
}
|
|
12126
|
-
const s = await
|
|
12583
|
+
const s = await stat5(p);
|
|
12127
12584
|
if (!s.isDirectory()) {
|
|
12128
12585
|
return {
|
|
12129
12586
|
id: this.id,
|
|
@@ -12143,7 +12600,7 @@ var knownFilesRecognized = {
|
|
|
12143
12600
|
category: CATEGORY2,
|
|
12144
12601
|
title: "No unexpected top-level entries under ~/.syntaur/",
|
|
12145
12602
|
async run(ctx) {
|
|
12146
|
-
const entries = await
|
|
12603
|
+
const entries = await readdir13(ctx.syntaurRoot, { withFileTypes: true });
|
|
12147
12604
|
const unexpected = [];
|
|
12148
12605
|
for (const e of entries) {
|
|
12149
12606
|
if (e.name.startsWith(".")) continue;
|
|
@@ -12157,7 +12614,7 @@ var knownFilesRecognized = {
|
|
|
12157
12614
|
title: this.title,
|
|
12158
12615
|
status: "warn",
|
|
12159
12616
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
12160
|
-
affected: unexpected.map((n) =>
|
|
12617
|
+
affected: unexpected.map((n) => resolve36(ctx.syntaurRoot, n)),
|
|
12161
12618
|
remediation: {
|
|
12162
12619
|
kind: "manual",
|
|
12163
12620
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -12186,8 +12643,8 @@ function pass2(check) {
|
|
|
12186
12643
|
|
|
12187
12644
|
// src/utils/doctor/checks/project.ts
|
|
12188
12645
|
init_fs();
|
|
12189
|
-
import { resolve as
|
|
12190
|
-
import { readdir as
|
|
12646
|
+
import { resolve as resolve37 } from "path";
|
|
12647
|
+
import { readdir as readdir14, stat as stat6 } from "fs/promises";
|
|
12191
12648
|
var CATEGORY3 = "project";
|
|
12192
12649
|
var REQUIRED_PROJECT_FILES = [
|
|
12193
12650
|
"project.md",
|
|
@@ -12211,15 +12668,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
|
|
|
12211
12668
|
async function listProjects2(ctx) {
|
|
12212
12669
|
const dir = ctx.config.defaultProjectDir;
|
|
12213
12670
|
if (!await fileExists(dir)) return [];
|
|
12214
|
-
const entries = await
|
|
12671
|
+
const entries = await readdir14(dir, { withFileTypes: true });
|
|
12215
12672
|
const result = [];
|
|
12216
12673
|
for (const e of entries) {
|
|
12217
12674
|
if (!e.isDirectory()) continue;
|
|
12218
12675
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
12219
|
-
const projectDir =
|
|
12676
|
+
const projectDir = resolve37(dir, e.name);
|
|
12220
12677
|
let looksLikeProject = false;
|
|
12221
12678
|
for (const marker of PROJECT_MARKERS) {
|
|
12222
|
-
if (await fileExists(
|
|
12679
|
+
if (await fileExists(resolve37(projectDir, marker))) {
|
|
12223
12680
|
looksLikeProject = true;
|
|
12224
12681
|
break;
|
|
12225
12682
|
}
|
|
@@ -12238,7 +12695,7 @@ var requiredFiles = {
|
|
|
12238
12695
|
for (const projectDir of projects) {
|
|
12239
12696
|
const missing = [];
|
|
12240
12697
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
12241
|
-
const p =
|
|
12698
|
+
const p = resolve37(projectDir, rel);
|
|
12242
12699
|
if (!await fileExists(p)) missing.push(rel);
|
|
12243
12700
|
}
|
|
12244
12701
|
if (missing.length === 0) continue;
|
|
@@ -12248,7 +12705,7 @@ var requiredFiles = {
|
|
|
12248
12705
|
title: this.title,
|
|
12249
12706
|
status: "error",
|
|
12250
12707
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
12251
|
-
affected: missing.map((m) =>
|
|
12708
|
+
affected: missing.map((m) => resolve37(projectDir, m)),
|
|
12252
12709
|
remediation: {
|
|
12253
12710
|
kind: "manual",
|
|
12254
12711
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -12271,9 +12728,9 @@ var manifestStale = {
|
|
|
12271
12728
|
const projects = await listProjects2(ctx);
|
|
12272
12729
|
const results = [];
|
|
12273
12730
|
for (const projectDir of projects) {
|
|
12274
|
-
const manifestPath =
|
|
12731
|
+
const manifestPath = resolve37(projectDir, "manifest.md");
|
|
12275
12732
|
if (!await fileExists(manifestPath)) continue;
|
|
12276
|
-
const manifestMtime = (await
|
|
12733
|
+
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
12277
12734
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
12278
12735
|
if (newestAssignment === 0) continue;
|
|
12279
12736
|
if (newestAssignment > manifestMtime) {
|
|
@@ -12305,7 +12762,7 @@ var orphanFiles = {
|
|
|
12305
12762
|
const projects = await listProjects2(ctx);
|
|
12306
12763
|
const results = [];
|
|
12307
12764
|
for (const projectDir of projects) {
|
|
12308
|
-
const entries = await
|
|
12765
|
+
const entries = await readdir14(projectDir, { withFileTypes: true });
|
|
12309
12766
|
const orphans = [];
|
|
12310
12767
|
for (const e of entries) {
|
|
12311
12768
|
if (e.name.startsWith(".")) continue;
|
|
@@ -12320,7 +12777,7 @@ var orphanFiles = {
|
|
|
12320
12777
|
title: this.title,
|
|
12321
12778
|
status: "warn",
|
|
12322
12779
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
12323
|
-
affected: orphans.map((o) =>
|
|
12780
|
+
affected: orphans.map((o) => resolve37(projectDir, o)),
|
|
12324
12781
|
autoFixable: false
|
|
12325
12782
|
});
|
|
12326
12783
|
}
|
|
@@ -12330,20 +12787,20 @@ var orphanFiles = {
|
|
|
12330
12787
|
};
|
|
12331
12788
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
12332
12789
|
async function newestAssignmentMtime(projectDir) {
|
|
12333
|
-
const assignmentsRoot =
|
|
12790
|
+
const assignmentsRoot = resolve37(projectDir, "assignments");
|
|
12334
12791
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
12335
12792
|
let newest = 0;
|
|
12336
12793
|
let entries;
|
|
12337
12794
|
try {
|
|
12338
|
-
entries = await
|
|
12795
|
+
entries = await readdir14(assignmentsRoot, { withFileTypes: true });
|
|
12339
12796
|
} catch {
|
|
12340
12797
|
return 0;
|
|
12341
12798
|
}
|
|
12342
12799
|
for (const e of entries) {
|
|
12343
12800
|
if (!e.isDirectory()) continue;
|
|
12344
|
-
const assignmentMd =
|
|
12801
|
+
const assignmentMd = resolve37(assignmentsRoot, e.name, "assignment.md");
|
|
12345
12802
|
try {
|
|
12346
|
-
const s = await
|
|
12803
|
+
const s = await stat6(assignmentMd);
|
|
12347
12804
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
12348
12805
|
} catch {
|
|
12349
12806
|
}
|
|
@@ -12365,28 +12822,28 @@ init_fs();
|
|
|
12365
12822
|
init_parser();
|
|
12366
12823
|
init_types();
|
|
12367
12824
|
init_paths();
|
|
12368
|
-
import { resolve as
|
|
12369
|
-
import { readdir as
|
|
12825
|
+
import { resolve as resolve38 } from "path";
|
|
12826
|
+
import { readdir as readdir15, readFile as readFile21 } from "fs/promises";
|
|
12370
12827
|
var CATEGORY4 = "assignment";
|
|
12371
12828
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
12372
12829
|
async function listAssignments(ctx) {
|
|
12373
12830
|
const result = { withAssignmentMd: [], orphanFolders: [] };
|
|
12374
12831
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12375
12832
|
if (await fileExists(projectsDir2)) {
|
|
12376
|
-
const projects = await
|
|
12833
|
+
const projects = await readdir15(projectsDir2, { withFileTypes: true });
|
|
12377
12834
|
for (const m of projects) {
|
|
12378
12835
|
if (!m.isDirectory()) continue;
|
|
12379
12836
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
12380
|
-
const assignmentsDir2 =
|
|
12837
|
+
const assignmentsDir2 = resolve38(projectsDir2, m.name, "assignments");
|
|
12381
12838
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
12382
|
-
const entries = await
|
|
12839
|
+
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
12383
12840
|
for (const a of entries) {
|
|
12384
12841
|
if (!a.isDirectory()) continue;
|
|
12385
12842
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12386
|
-
const assignmentDir =
|
|
12387
|
-
const assignmentMd =
|
|
12843
|
+
const assignmentDir = resolve38(assignmentsDir2, a.name);
|
|
12844
|
+
const assignmentMd = resolve38(assignmentDir, "assignment.md");
|
|
12388
12845
|
const entry = {
|
|
12389
|
-
projectDir:
|
|
12846
|
+
projectDir: resolve38(projectsDir2, m.name),
|
|
12390
12847
|
projectSlug: m.name,
|
|
12391
12848
|
assignmentDir,
|
|
12392
12849
|
assignmentSlug: a.name,
|
|
@@ -12402,12 +12859,12 @@ async function listAssignments(ctx) {
|
|
|
12402
12859
|
}
|
|
12403
12860
|
const standaloneRoot = assignmentsDir();
|
|
12404
12861
|
if (await fileExists(standaloneRoot)) {
|
|
12405
|
-
const entries = await
|
|
12862
|
+
const entries = await readdir15(standaloneRoot, { withFileTypes: true });
|
|
12406
12863
|
for (const a of entries) {
|
|
12407
12864
|
if (!a.isDirectory()) continue;
|
|
12408
12865
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12409
|
-
const assignmentDir =
|
|
12410
|
-
const assignmentMd =
|
|
12866
|
+
const assignmentDir = resolve38(standaloneRoot, a.name);
|
|
12867
|
+
const assignmentMd = resolve38(assignmentDir, "assignment.md");
|
|
12411
12868
|
const entry = {
|
|
12412
12869
|
projectDir: standaloneRoot,
|
|
12413
12870
|
projectSlug: null,
|
|
@@ -12485,7 +12942,7 @@ var invalidStatus = {
|
|
|
12485
12942
|
const allowed = configuredStatuses(ctx);
|
|
12486
12943
|
const results = [];
|
|
12487
12944
|
for (const a of withAssignmentMd) {
|
|
12488
|
-
const path =
|
|
12945
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12489
12946
|
const parsed = await parseSafe(path);
|
|
12490
12947
|
if (!parsed) continue;
|
|
12491
12948
|
if (!allowed.has(parsed.status)) {
|
|
@@ -12518,7 +12975,7 @@ var workspaceMissing = {
|
|
|
12518
12975
|
const terminal = terminalStatuses(ctx);
|
|
12519
12976
|
const results = [];
|
|
12520
12977
|
for (const a of withAssignmentMd) {
|
|
12521
|
-
const path =
|
|
12978
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12522
12979
|
const parsed = await parseSafe(path);
|
|
12523
12980
|
if (!parsed) continue;
|
|
12524
12981
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -12565,12 +13022,12 @@ var requiredFilesByStatus = {
|
|
|
12565
13022
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12566
13023
|
const results = [];
|
|
12567
13024
|
for (const a of withAssignmentMd) {
|
|
12568
|
-
const assignmentPath =
|
|
13025
|
+
const assignmentPath = resolve38(a.assignmentDir, "assignment.md");
|
|
12569
13026
|
const parsed = await parseSafe(assignmentPath);
|
|
12570
13027
|
if (!parsed) continue;
|
|
12571
13028
|
const missing = [];
|
|
12572
13029
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
12573
|
-
const handoffPath =
|
|
13030
|
+
const handoffPath = resolve38(a.assignmentDir, "handoff.md");
|
|
12574
13031
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
12575
13032
|
}
|
|
12576
13033
|
if (missing.length === 0) continue;
|
|
@@ -12580,7 +13037,7 @@ var requiredFilesByStatus = {
|
|
|
12580
13037
|
title: this.title,
|
|
12581
13038
|
status: "warn",
|
|
12582
13039
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
12583
|
-
affected: missing.map((m) =>
|
|
13040
|
+
affected: missing.map((m) => resolve38(a.assignmentDir, m)),
|
|
12584
13041
|
remediation: {
|
|
12585
13042
|
kind: "manual",
|
|
12586
13043
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -12603,7 +13060,7 @@ var companionFilesScaffolded = {
|
|
|
12603
13060
|
for (const a of withAssignmentMd) {
|
|
12604
13061
|
const missing = [];
|
|
12605
13062
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
12606
|
-
if (!await fileExists(
|
|
13063
|
+
if (!await fileExists(resolve38(a.assignmentDir, filename))) {
|
|
12607
13064
|
missing.push(filename);
|
|
12608
13065
|
}
|
|
12609
13066
|
}
|
|
@@ -12615,7 +13072,7 @@ var companionFilesScaffolded = {
|
|
|
12615
13072
|
title: this.title,
|
|
12616
13073
|
status: "warn",
|
|
12617
13074
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
12618
|
-
affected: missing.map((m) =>
|
|
13075
|
+
affected: missing.map((m) => resolve38(a.assignmentDir, m)),
|
|
12619
13076
|
remediation: {
|
|
12620
13077
|
kind: "manual",
|
|
12621
13078
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -12648,7 +13105,7 @@ var typeDefinition = {
|
|
|
12648
13105
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12649
13106
|
const results = [];
|
|
12650
13107
|
for (const a of withAssignmentMd) {
|
|
12651
|
-
const path =
|
|
13108
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12652
13109
|
const parsed = await parseSafe(path);
|
|
12653
13110
|
if (!parsed) continue;
|
|
12654
13111
|
if (!parsed.type) continue;
|
|
@@ -12682,7 +13139,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
12682
13139
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12683
13140
|
const results = [];
|
|
12684
13141
|
for (const a of withAssignmentMd) {
|
|
12685
|
-
const path =
|
|
13142
|
+
const path = resolve38(a.assignmentDir, "assignment.md");
|
|
12686
13143
|
const parsed = await parseSafe(path);
|
|
12687
13144
|
if (!parsed) continue;
|
|
12688
13145
|
if (a.standalone) {
|
|
@@ -12737,7 +13194,7 @@ var assignmentChecks = [
|
|
|
12737
13194
|
];
|
|
12738
13195
|
async function parseSafe(path) {
|
|
12739
13196
|
try {
|
|
12740
|
-
const content = await
|
|
13197
|
+
const content = await readFile21(path, "utf-8");
|
|
12741
13198
|
return parseAssignmentFull(content);
|
|
12742
13199
|
} catch {
|
|
12743
13200
|
return null;
|
|
@@ -12756,7 +13213,7 @@ function pass4(check, detail) {
|
|
|
12756
13213
|
|
|
12757
13214
|
// src/utils/doctor/checks/dashboard.ts
|
|
12758
13215
|
init_fs();
|
|
12759
|
-
import { resolve as
|
|
13216
|
+
import { resolve as resolve39 } from "path";
|
|
12760
13217
|
var CATEGORY5 = "dashboard";
|
|
12761
13218
|
var dbReachable = {
|
|
12762
13219
|
id: "dashboard.db-reachable",
|
|
@@ -12770,7 +13227,7 @@ var dbReachable = {
|
|
|
12770
13227
|
title: this.title,
|
|
12771
13228
|
status: "error",
|
|
12772
13229
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
12773
|
-
affected: [
|
|
13230
|
+
affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
|
|
12774
13231
|
remediation: {
|
|
12775
13232
|
kind: "manual",
|
|
12776
13233
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -12788,7 +13245,7 @@ var dbReachable = {
|
|
|
12788
13245
|
title: this.title,
|
|
12789
13246
|
status: "error",
|
|
12790
13247
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
12791
|
-
affected: [
|
|
13248
|
+
affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
|
|
12792
13249
|
autoFixable: false
|
|
12793
13250
|
};
|
|
12794
13251
|
}
|
|
@@ -12800,7 +13257,7 @@ var dbReachable = {
|
|
|
12800
13257
|
title: this.title,
|
|
12801
13258
|
status: "error",
|
|
12802
13259
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
12803
|
-
affected: [
|
|
13260
|
+
affected: [resolve39(ctx.syntaurRoot, "syntaur.db")],
|
|
12804
13261
|
autoFixable: false
|
|
12805
13262
|
};
|
|
12806
13263
|
}
|
|
@@ -12826,7 +13283,7 @@ var ghostSessions = {
|
|
|
12826
13283
|
const results = [];
|
|
12827
13284
|
for (const row of rows) {
|
|
12828
13285
|
if (!row.project_slug) continue;
|
|
12829
|
-
const projectPath =
|
|
13286
|
+
const projectPath = resolve39(projectsDir2, row.project_slug, "project.md");
|
|
12830
13287
|
if (!await fileExists(projectPath)) {
|
|
12831
13288
|
results.push({
|
|
12832
13289
|
id: this.id,
|
|
@@ -12845,7 +13302,7 @@ var ghostSessions = {
|
|
|
12845
13302
|
continue;
|
|
12846
13303
|
}
|
|
12847
13304
|
if (row.assignment_slug) {
|
|
12848
|
-
const assignmentPath =
|
|
13305
|
+
const assignmentPath = resolve39(
|
|
12849
13306
|
projectsDir2,
|
|
12850
13307
|
row.project_slug,
|
|
12851
13308
|
"assignments",
|
|
@@ -12897,7 +13354,7 @@ function skipped(check, reason) {
|
|
|
12897
13354
|
|
|
12898
13355
|
// src/utils/doctor/checks/integrations.ts
|
|
12899
13356
|
init_fs();
|
|
12900
|
-
import { readdir as
|
|
13357
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
12901
13358
|
var CATEGORY6 = "integrations";
|
|
12902
13359
|
var claudePluginLinked = {
|
|
12903
13360
|
id: "integrations.claude-plugin-linked",
|
|
@@ -12959,7 +13416,7 @@ var backupConfigured = {
|
|
|
12959
13416
|
if (ctx.config.backup?.repo) return pass6(this);
|
|
12960
13417
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12961
13418
|
if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
|
|
12962
|
-
const entries = await
|
|
13419
|
+
const entries = await readdir16(projectsDir2, { withFileTypes: true });
|
|
12963
13420
|
const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
|
|
12964
13421
|
if (!hasProjects) return skipped2(this, "no projects yet");
|
|
12965
13422
|
return {
|
|
@@ -13002,8 +13459,8 @@ function skipped2(check, reason) {
|
|
|
13002
13459
|
init_fs();
|
|
13003
13460
|
init_parser();
|
|
13004
13461
|
init_types();
|
|
13005
|
-
import { resolve as
|
|
13006
|
-
import { readFile as
|
|
13462
|
+
import { resolve as resolve40 } from "path";
|
|
13463
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
13007
13464
|
var CATEGORY7 = "workspace";
|
|
13008
13465
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
13009
13466
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -13015,12 +13472,12 @@ function isStandaloneSession(ctx) {
|
|
|
13015
13472
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
13016
13473
|
}
|
|
13017
13474
|
async function loadContext(ctx) {
|
|
13018
|
-
const path =
|
|
13475
|
+
const path = resolve40(ctx.cwd, ".syntaur", "context.json");
|
|
13019
13476
|
if (!await fileExists(path)) {
|
|
13020
13477
|
return { data: null, path, exists: false, parseError: null };
|
|
13021
13478
|
}
|
|
13022
13479
|
try {
|
|
13023
|
-
const raw = await
|
|
13480
|
+
const raw = await readFile22(path, "utf-8");
|
|
13024
13481
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
13025
13482
|
} catch (err2) {
|
|
13026
13483
|
return {
|
|
@@ -13095,7 +13552,7 @@ var contextAssignmentResolves = {
|
|
|
13095
13552
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
13096
13553
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
13097
13554
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
13098
|
-
const assignmentMd =
|
|
13555
|
+
const assignmentMd = resolve40(data.assignmentDir, "assignment.md");
|
|
13099
13556
|
if (!await fileExists(assignmentMd)) {
|
|
13100
13557
|
return {
|
|
13101
13558
|
id: this.id,
|
|
@@ -13124,10 +13581,10 @@ var contextTerminal = {
|
|
|
13124
13581
|
if (!exists) return skipped3(this, "no context to check");
|
|
13125
13582
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
13126
13583
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
13127
|
-
const assignmentMd =
|
|
13584
|
+
const assignmentMd = resolve40(data.assignmentDir, "assignment.md");
|
|
13128
13585
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
13129
13586
|
try {
|
|
13130
|
-
const content = await
|
|
13587
|
+
const content = await readFile22(assignmentMd, "utf-8");
|
|
13131
13588
|
const parsed = parseAssignmentFull(content);
|
|
13132
13589
|
const terminal = terminalStatuses2(ctx);
|
|
13133
13590
|
if (terminal.has(parsed.status)) {
|
|
@@ -13270,15 +13727,15 @@ async function finalize(checks) {
|
|
|
13270
13727
|
}
|
|
13271
13728
|
async function readVersion() {
|
|
13272
13729
|
try {
|
|
13273
|
-
const here =
|
|
13274
|
-
let dir =
|
|
13730
|
+
const here = fileURLToPath7(import.meta.url);
|
|
13731
|
+
let dir = dirname10(here);
|
|
13275
13732
|
for (let i = 0; i < 6; i++) {
|
|
13276
13733
|
try {
|
|
13277
|
-
const raw = await
|
|
13734
|
+
const raw = await readFile23(join5(dir, "package.json"), "utf-8");
|
|
13278
13735
|
const parsed = JSON.parse(raw);
|
|
13279
13736
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13280
13737
|
} catch {
|
|
13281
|
-
dir =
|
|
13738
|
+
dir = dirname10(dir);
|
|
13282
13739
|
}
|
|
13283
13740
|
}
|
|
13284
13741
|
return null;
|
|
@@ -13411,8 +13868,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
13411
13868
|
init_paths();
|
|
13412
13869
|
init_fs();
|
|
13413
13870
|
init_config2();
|
|
13414
|
-
import { resolve as
|
|
13415
|
-
import { readFile as
|
|
13871
|
+
import { resolve as resolve41 } from "path";
|
|
13872
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
13416
13873
|
init_timestamp();
|
|
13417
13874
|
init_assignment_resolver();
|
|
13418
13875
|
function shortId() {
|
|
@@ -13444,7 +13901,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13444
13901
|
if (!isValidSlug(target)) {
|
|
13445
13902
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13446
13903
|
}
|
|
13447
|
-
assignmentDir =
|
|
13904
|
+
assignmentDir = resolve41(baseDir, options.project, "assignments", target);
|
|
13448
13905
|
assignmentRef = target;
|
|
13449
13906
|
} else {
|
|
13450
13907
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13454,13 +13911,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13454
13911
|
assignmentDir = resolved.assignmentDir;
|
|
13455
13912
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13456
13913
|
}
|
|
13457
|
-
const commentsPath =
|
|
13914
|
+
const commentsPath = resolve41(assignmentDir, "comments.md");
|
|
13458
13915
|
const timestamp = nowTimestamp();
|
|
13459
13916
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
13460
13917
|
let currentContent;
|
|
13461
13918
|
let currentCount = 0;
|
|
13462
13919
|
if (await fileExists(commentsPath)) {
|
|
13463
|
-
currentContent = await
|
|
13920
|
+
currentContent = await readFile24(commentsPath, "utf-8");
|
|
13464
13921
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
13465
13922
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
13466
13923
|
} else {
|
|
@@ -13497,8 +13954,8 @@ ${entry}`;
|
|
|
13497
13954
|
init_paths();
|
|
13498
13955
|
init_fs();
|
|
13499
13956
|
init_config2();
|
|
13500
|
-
import { resolve as
|
|
13501
|
-
import { readFile as
|
|
13957
|
+
import { resolve as resolve42 } from "path";
|
|
13958
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
13502
13959
|
init_timestamp();
|
|
13503
13960
|
init_assignment_resolver();
|
|
13504
13961
|
function setTopLevelField3(content, key, value) {
|
|
@@ -13523,7 +13980,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13523
13980
|
if (!isValidSlug(target)) {
|
|
13524
13981
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13525
13982
|
}
|
|
13526
|
-
assignmentDir =
|
|
13983
|
+
assignmentDir = resolve42(baseDir, options.project, "assignments", target);
|
|
13527
13984
|
targetRef = target;
|
|
13528
13985
|
} else {
|
|
13529
13986
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13533,12 +13990,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13533
13990
|
assignmentDir = resolved.assignmentDir;
|
|
13534
13991
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13535
13992
|
}
|
|
13536
|
-
const assignmentMdPath =
|
|
13993
|
+
const assignmentMdPath = resolve42(assignmentDir, "assignment.md");
|
|
13537
13994
|
if (!await fileExists(assignmentMdPath)) {
|
|
13538
13995
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
13539
13996
|
}
|
|
13540
13997
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
13541
|
-
let content = await
|
|
13998
|
+
let content = await readFile25(assignmentMdPath, "utf-8");
|
|
13542
13999
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
13543
14000
|
const todosHeading = /^## Todos\s*$/m;
|
|
13544
14001
|
if (todosHeading.test(content)) {
|
|
@@ -13566,10 +14023,10 @@ ${todoLine}
|
|
|
13566
14023
|
|
|
13567
14024
|
// src/cli-default-command.ts
|
|
13568
14025
|
init_config2();
|
|
13569
|
-
import { readdir as
|
|
14026
|
+
import { readdir as readdir17 } from "fs/promises";
|
|
13570
14027
|
async function hasAnyProjectContent(projectsDir2) {
|
|
13571
14028
|
try {
|
|
13572
|
-
const entries = await
|
|
14029
|
+
const entries = await readdir17(projectsDir2, { withFileTypes: true });
|
|
13573
14030
|
return entries.some((entry) => entry.isDirectory());
|
|
13574
14031
|
} catch {
|
|
13575
14032
|
return false;
|
|
@@ -13605,21 +14062,21 @@ async function getDefaultCommandName() {
|
|
|
13605
14062
|
// src/utils/npx-prompt.ts
|
|
13606
14063
|
init_paths();
|
|
13607
14064
|
init_fs();
|
|
13608
|
-
import { fileURLToPath as
|
|
13609
|
-
import { readFile as
|
|
13610
|
-
import { dirname as
|
|
14065
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
14066
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
14067
|
+
import { dirname as dirname12, join as join7, resolve as resolve43 } from "path";
|
|
13611
14068
|
import { spawn as spawn3 } from "child_process";
|
|
13612
14069
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
13613
14070
|
|
|
13614
14071
|
// src/utils/version.ts
|
|
13615
|
-
import { fileURLToPath as
|
|
13616
|
-
import { readFile as
|
|
13617
|
-
import { dirname as
|
|
14072
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
14073
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
14074
|
+
import { dirname as dirname11, join as join6 } from "path";
|
|
13618
14075
|
async function readPackageVersion(scriptUrl) {
|
|
13619
14076
|
try {
|
|
13620
|
-
const scriptPath =
|
|
13621
|
-
const pkgRoot =
|
|
13622
|
-
const raw = await
|
|
14077
|
+
const scriptPath = fileURLToPath8(scriptUrl);
|
|
14078
|
+
const pkgRoot = dirname11(dirname11(scriptPath));
|
|
14079
|
+
const raw = await readFile26(join6(pkgRoot, "package.json"), "utf-8");
|
|
13623
14080
|
const parsed = JSON.parse(raw);
|
|
13624
14081
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13625
14082
|
} catch {
|
|
@@ -13628,13 +14085,13 @@ async function readPackageVersion(scriptUrl) {
|
|
|
13628
14085
|
}
|
|
13629
14086
|
|
|
13630
14087
|
// src/utils/npx-prompt.ts
|
|
13631
|
-
var STATE_FILE =
|
|
14088
|
+
var STATE_FILE = resolve43(syntaurRoot(), "npx-install.json");
|
|
13632
14089
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
13633
14090
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
13634
14091
|
function isRunningViaNpx(scriptUrl) {
|
|
13635
14092
|
let scriptPath;
|
|
13636
14093
|
try {
|
|
13637
|
-
scriptPath =
|
|
14094
|
+
scriptPath = fileURLToPath9(scriptUrl);
|
|
13638
14095
|
} catch {
|
|
13639
14096
|
return false;
|
|
13640
14097
|
}
|
|
@@ -13649,7 +14106,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
13649
14106
|
async function readState() {
|
|
13650
14107
|
if (!await fileExists(STATE_FILE)) return null;
|
|
13651
14108
|
try {
|
|
13652
|
-
const raw = await
|
|
14109
|
+
const raw = await readFile27(STATE_FILE, "utf-8");
|
|
13653
14110
|
return JSON.parse(raw);
|
|
13654
14111
|
} catch {
|
|
13655
14112
|
return null;
|
|
@@ -13660,10 +14117,10 @@ async function writeState(state) {
|
|
|
13660
14117
|
`);
|
|
13661
14118
|
}
|
|
13662
14119
|
async function resolveNpmBin() {
|
|
13663
|
-
const nodeDir =
|
|
14120
|
+
const nodeDir = dirname12(process.execPath);
|
|
13664
14121
|
const isWin = process.platform === "win32";
|
|
13665
14122
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
13666
|
-
const nearNode =
|
|
14123
|
+
const nearNode = join7(nodeDir, npmName);
|
|
13667
14124
|
if (await fileExists(nearNode)) {
|
|
13668
14125
|
return { cmd: nearNode, shell: false };
|
|
13669
14126
|
}
|
|
@@ -13706,9 +14163,9 @@ async function readGlobalVersion() {
|
|
|
13706
14163
|
});
|
|
13707
14164
|
if (!rootPath) return null;
|
|
13708
14165
|
try {
|
|
13709
|
-
const manifestPath =
|
|
14166
|
+
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
13710
14167
|
if (!await fileExists(manifestPath)) return null;
|
|
13711
|
-
const raw = await
|
|
14168
|
+
const raw = await readFile27(manifestPath, "utf-8");
|
|
13712
14169
|
const parsed = JSON.parse(raw);
|
|
13713
14170
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13714
14171
|
} catch {
|
|
@@ -14003,7 +14460,7 @@ program.command("setup").description("Initialize Syntaur and optionally install
|
|
|
14003
14460
|
process.exit(1);
|
|
14004
14461
|
}
|
|
14005
14462
|
});
|
|
14006
|
-
program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").action(async (options) => {
|
|
14463
|
+
program.command("install-plugin").description("Install the Syntaur Claude Code plugin").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.claude/skills").option("--skip-skills", "Do not install protocol skills into ~/.claude/skills").action(async (options) => {
|
|
14007
14464
|
try {
|
|
14008
14465
|
await installPluginCommand({ ...options, promptForTarget: true });
|
|
14009
14466
|
} catch (error) {
|
|
@@ -14014,7 +14471,45 @@ program.command("install-plugin").description("Install the Syntaur Claude Code p
|
|
|
14014
14471
|
process.exit(1);
|
|
14015
14472
|
}
|
|
14016
14473
|
});
|
|
14017
|
-
program.command("install-
|
|
14474
|
+
program.command("install-statusline").description(
|
|
14475
|
+
"Install the syntaur statusLine for Claude Code. Augments ~/.claude/settings.json; wraps any existing script by default."
|
|
14476
|
+
).option("--mode <mode>", "replace | wrap | skip | ask (default: ask, wrap in non-TTY)", "ask").option("--link", "Symlink the installed script to the package source (dev mode)").action(async (options) => {
|
|
14477
|
+
try {
|
|
14478
|
+
const rawMode = (options.mode ?? "ask").toLowerCase();
|
|
14479
|
+
const valid = ["replace", "wrap", "skip", "ask"];
|
|
14480
|
+
if (!valid.includes(rawMode)) {
|
|
14481
|
+
throw new Error(
|
|
14482
|
+
`Invalid --mode "${rawMode}". Must be one of: ${valid.join(", ")}.`
|
|
14483
|
+
);
|
|
14484
|
+
}
|
|
14485
|
+
await installStatuslineCommand({
|
|
14486
|
+
mode: rawMode,
|
|
14487
|
+
link: options.link
|
|
14488
|
+
});
|
|
14489
|
+
} catch (error) {
|
|
14490
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14491
|
+
process.exit(1);
|
|
14492
|
+
}
|
|
14493
|
+
});
|
|
14494
|
+
program.command("uninstall-statusline").description(
|
|
14495
|
+
"Remove the syntaur statusLine. Restores the previously configured command from backup if present."
|
|
14496
|
+
).option("--keep-script", "Leave ~/.syntaur/statusline.sh on disk (only edit settings.json)").action(async (options) => {
|
|
14497
|
+
try {
|
|
14498
|
+
await uninstallStatuslineCommand({ keepScript: options.keepScript });
|
|
14499
|
+
} catch (error) {
|
|
14500
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14501
|
+
process.exit(1);
|
|
14502
|
+
}
|
|
14503
|
+
});
|
|
14504
|
+
program.command("uninstall-skills").description("Remove Syntaur protocol skills from ~/.claude/skills and/or ~/.codex/skills").option("--claude", "Remove from ~/.claude/skills").option("--codex", "Remove from ~/.codex/skills").option("--all", "Remove from both").action(async (options) => {
|
|
14505
|
+
try {
|
|
14506
|
+
await uninstallSkillsCommand(options);
|
|
14507
|
+
} catch (error) {
|
|
14508
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14509
|
+
process.exit(1);
|
|
14510
|
+
}
|
|
14511
|
+
});
|
|
14512
|
+
program.command("install-codex-plugin").description("Install the Syntaur Codex plugin and marketplace entry").option("--force", "Overwrite an existing Syntaur-managed install").option("--target-dir <path>", "Install the plugin at a specific directory").option("--marketplace-path <path>", "Write the marketplace entry to a specific file").option("--link", "Use a symlink instead of copying files (repo-local dev only)").option("--force-skills", "Overwrite user-edited skills in ~/.codex/skills").option("--skip-skills", "Do not install protocol skills into ~/.codex/skills").action(async (options) => {
|
|
14018
14513
|
try {
|
|
14019
14514
|
await installCodexPluginCommand({ ...options, promptForTarget: true });
|
|
14020
14515
|
} catch (error) {
|