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.
Files changed (33) hide show
  1. package/README.md +26 -3
  2. package/dist/index.js +650 -155
  3. package/dist/index.js.map +1 -1
  4. package/package.json +8 -2
  5. package/platforms/claude-code/.claude-plugin/plugin.json +1 -5
  6. package/platforms/claude-code/agents/syntaur-expert.md +30 -18
  7. package/platforms/claude-code/commands/complete-assignment/complete-assignment.md +20 -0
  8. package/platforms/claude-code/commands/create-assignment/create-assignment.md +20 -0
  9. package/platforms/claude-code/commands/create-project/create-project.md +20 -0
  10. package/platforms/claude-code/commands/grab-assignment/grab-assignment.md +20 -0
  11. package/platforms/claude-code/commands/plan-assignment/plan-assignment.md +20 -0
  12. package/statusline/statusline.sh +133 -0
  13. package/vendor/syntaur-skills/LICENSE +21 -0
  14. package/vendor/syntaur-skills/README.md +43 -0
  15. package/vendor/syntaur-skills/skills/complete-assignment/SKILL.md +146 -0
  16. package/vendor/syntaur-skills/skills/create-assignment/SKILL.md +72 -0
  17. package/vendor/syntaur-skills/skills/create-project/SKILL.md +56 -0
  18. package/vendor/syntaur-skills/skills/grab-assignment/SKILL.md +158 -0
  19. package/vendor/syntaur-skills/skills/plan-assignment/SKILL.md +137 -0
  20. package/vendor/syntaur-skills/skills/syntaur-protocol/SKILL.md +119 -0
  21. package/vendor/syntaur-skills/skills/syntaur-protocol/references/file-ownership.md +67 -0
  22. package/vendor/syntaur-skills/skills/syntaur-protocol/references/protocol-summary.md +82 -0
  23. package/platforms/claude-code/hooks/statusline.sh +0 -110
  24. package/platforms/claude-code/skills/complete-assignment/SKILL.md +0 -155
  25. package/platforms/claude-code/skills/create-assignment/SKILL.md +0 -67
  26. package/platforms/claude-code/skills/grab-assignment/SKILL.md +0 -202
  27. package/platforms/claude-code/skills/plan-assignment/SKILL.md +0 -156
  28. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +0 -86
  29. package/platforms/codex/skills/complete-assignment/SKILL.md +0 -64
  30. package/platforms/codex/skills/create-assignment/SKILL.md +0 -49
  31. package/platforms/codex/skills/grab-assignment/SKILL.md +0 -73
  32. package/platforms/codex/skills/plan-assignment/SKILL.md +0 -61
  33. 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 mkdir2, writeFile as writeFile7 } from "fs/promises";
4550
- import { resolve as resolve28 } from "path";
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 = resolve28(projectsDir2, projectSlug);
4561
- const assignmentDir = resolve28(projectDir, "assignments", assignmentSlug);
4562
- const contextDir = resolve28(workspaceDir, ".syntaur");
4563
- await mkdir2(contextDir, { recursive: true });
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 writeFile7(
4575
- resolve28(contextDir, "context.json"),
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: readFile26 } = await import("fs/promises");
5913
- const raw = await readFile26(filePath, "utf-8");
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: resolve42 } = await import("path");
8222
- const { readFile: readFile26 } = await import("fs/promises");
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(resolve42(todosDir2, "archive"));
8238
+ await ensureDir(resolve44(todosDir2, "archive"));
8239
8239
  let archContent = "";
8240
8240
  if (await fileExists(archFile)) {
8241
- archContent = await readFile26(archFile, "utf-8");
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(" Skills: /grab-assignment, /plan-assignment, /complete-assignment");
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
- " Skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment, track-session"
11085
+ " Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment"
10662
11086
  );
10663
- console.log(" Command: /track-session");
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 resolve25 } from "path";
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 && resolve25(configuredProjectDir) !== resolve25(syntaurRoot(), "projects")) {
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 resolve26 } from "path";
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 = resolve26(baseDir, options.project);
10893
- const assignmentDir = resolve26(
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 = resolve26(projectDir, "project.md");
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 = resolve26(assignmentDir, "assignment.md");
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 = resolve26(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
10933
- const assignmentPath = resolve26(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
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 = resolve26(cwd, "AGENTS.md");
11394
+ const agentsPath = resolve28(cwd, "AGENTS.md");
10938
11395
  await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
10939
11396
  if (framework === "opencode") {
10940
- const configPath = resolve26(cwd, "opencode.json");
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 resolve27 } from "path";
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 = resolve27(baseDir, options.project);
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 resolve29 } from "path";
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 = resolve29(dir, `${slug}.md`);
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 readdir11, readFile as readFile16 } from "fs/promises";
11071
- import { resolve as resolve30 } from "path";
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 readdir11(dir, { withFileTypes: true });
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 = resolve30(dir, entry.name);
11090
- const raw = await readFile16(filePath, "utf-8");
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 readFile17 } from "fs/promises";
11105
- import { resolve as resolve31 } from "path";
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(resolve31(todosPath, "archive"));
11843
+ await ensureDir(resolve33(todosPath, "archive"));
11387
11844
  let archContent = "";
11388
11845
  if (await fileExists(archFile)) {
11389
- archContent = await readFile17(archFile, "utf-8");
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 fileURLToPath5 } from "url";
11578
- import { readFile as readFile21 } from "fs/promises";
11579
- import { dirname as dirname8, join as join4 } from "path";
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 resolve32 } from "path";
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 = resolve32(root, "syntaur.db");
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 resolve33, isAbsolute as isAbsolute3 } from "path";
11625
- import { readFile as readFile18, stat as stat2 } from "fs/promises";
11626
- import { fileURLToPath as fileURLToPath4 } from "url";
11627
- import { dirname as dirname7, join as join3 } from "path";
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 stat2(ctx.syntaurRoot);
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 = resolve33(ctx.syntaurRoot, "config.md");
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 readFile18(configPath, "utf-8");
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 = fileURLToPath4(import.meta.url);
11958
- let dir = dirname7(here);
12414
+ const here = fileURLToPath6(import.meta.url);
12415
+ let dir = dirname9(here);
11959
12416
  for (let i = 0; i < 6; i++) {
11960
- const candidate = join3(dir, "package.json");
12417
+ const candidate = join4(dir, "package.json");
11961
12418
  try {
11962
- const text = await readFile18(candidate, "utf-8");
12419
+ const text = await readFile20(candidate, "utf-8");
11963
12420
  return JSON.parse(text);
11964
12421
  } catch {
11965
- dir = dirname7(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 resolve34 } from "path";
12015
- import { readdir as readdir12, stat as stat3 } from "fs/promises";
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 = resolve34(ctx.syntaurRoot, "projects");
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 = resolve34(ctx.syntaurRoot, "playbooks");
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 = resolve34(ctx.syntaurRoot, "todos");
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 stat3(p);
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 = resolve34(ctx.syntaurRoot, "servers");
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 stat3(p);
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 readdir12(ctx.syntaurRoot, { withFileTypes: true });
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) => resolve34(ctx.syntaurRoot, 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 resolve35 } from "path";
12190
- import { readdir as readdir13, stat as stat4 } from "fs/promises";
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 readdir13(dir, { withFileTypes: true });
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 = resolve35(dir, e.name);
12676
+ const projectDir = resolve37(dir, e.name);
12220
12677
  let looksLikeProject = false;
12221
12678
  for (const marker of PROJECT_MARKERS) {
12222
- if (await fileExists(resolve35(projectDir, marker))) {
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 = resolve35(projectDir, rel);
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) => resolve35(projectDir, 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 = resolve35(projectDir, "manifest.md");
12731
+ const manifestPath = resolve37(projectDir, "manifest.md");
12275
12732
  if (!await fileExists(manifestPath)) continue;
12276
- const manifestMtime = (await stat4(manifestPath)).mtimeMs;
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 readdir13(projectDir, { withFileTypes: true });
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) => resolve35(projectDir, 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 = resolve35(projectDir, "assignments");
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 readdir13(assignmentsRoot, { withFileTypes: true });
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 = resolve35(assignmentsRoot, e.name, "assignment.md");
12801
+ const assignmentMd = resolve37(assignmentsRoot, e.name, "assignment.md");
12345
12802
  try {
12346
- const s = await stat4(assignmentMd);
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 resolve36 } from "path";
12369
- import { readdir as readdir14, readFile as readFile19 } from "fs/promises";
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 readdir14(projectsDir2, { withFileTypes: true });
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 = resolve36(projectsDir2, m.name, "assignments");
12837
+ const assignmentsDir2 = resolve38(projectsDir2, m.name, "assignments");
12381
12838
  if (!await fileExists(assignmentsDir2)) continue;
12382
- const entries = await readdir14(assignmentsDir2, { withFileTypes: true });
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 = resolve36(assignmentsDir2, a.name);
12387
- const assignmentMd = resolve36(assignmentDir, "assignment.md");
12843
+ const assignmentDir = resolve38(assignmentsDir2, a.name);
12844
+ const assignmentMd = resolve38(assignmentDir, "assignment.md");
12388
12845
  const entry = {
12389
- projectDir: resolve36(projectsDir2, m.name),
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 readdir14(standaloneRoot, { withFileTypes: true });
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 = resolve36(standaloneRoot, a.name);
12410
- const assignmentMd = resolve36(assignmentDir, "assignment.md");
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 = resolve36(a.assignmentDir, "assignment.md");
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 = resolve36(a.assignmentDir, "assignment.md");
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 = resolve36(a.assignmentDir, "assignment.md");
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 = resolve36(a.assignmentDir, "handoff.md");
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) => resolve36(a.assignmentDir, 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(resolve36(a.assignmentDir, filename))) {
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) => resolve36(a.assignmentDir, 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 = resolve36(a.assignmentDir, "assignment.md");
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 = resolve36(a.assignmentDir, "assignment.md");
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 readFile19(path, "utf-8");
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 resolve37 } from "path";
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: [resolve37(ctx.syntaurRoot, "syntaur.db")],
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: [resolve37(ctx.syntaurRoot, "syntaur.db")],
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: [resolve37(ctx.syntaurRoot, "syntaur.db")],
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 = resolve37(projectsDir2, row.project_slug, "project.md");
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 = resolve37(
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 readdir15 } from "fs/promises";
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 readdir15(projectsDir2, { withFileTypes: true });
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 resolve38 } from "path";
13006
- import { readFile as readFile20 } from "fs/promises";
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 = resolve38(ctx.cwd, ".syntaur", "context.json");
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 readFile20(path, "utf-8");
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 = resolve38(data.assignmentDir, "assignment.md");
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 = resolve38(data.assignmentDir, "assignment.md");
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 readFile20(assignmentMd, "utf-8");
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 = fileURLToPath5(import.meta.url);
13274
- let dir = dirname8(here);
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 readFile21(join4(dir, "package.json"), "utf-8");
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 = dirname8(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 resolve39 } from "path";
13415
- import { readFile as readFile22 } from "fs/promises";
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 = resolve39(baseDir, options.project, "assignments", target);
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 = resolve39(assignmentDir, "comments.md");
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 readFile22(commentsPath, "utf-8");
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 resolve40 } from "path";
13501
- import { readFile as readFile23 } from "fs/promises";
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 = resolve40(baseDir, options.project, "assignments", target);
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 = resolve40(assignmentDir, "assignment.md");
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 readFile23(assignmentMdPath, "utf-8");
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 readdir16 } from "fs/promises";
14026
+ import { readdir as readdir17 } from "fs/promises";
13570
14027
  async function hasAnyProjectContent(projectsDir2) {
13571
14028
  try {
13572
- const entries = await readdir16(projectsDir2, { withFileTypes: true });
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 fileURLToPath7 } from "url";
13609
- import { readFile as readFile25 } from "fs/promises";
13610
- import { dirname as dirname10, join as join6, resolve as resolve41 } from "path";
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 fileURLToPath6 } from "url";
13616
- import { readFile as readFile24 } from "fs/promises";
13617
- import { dirname as dirname9, join as join5 } from "path";
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 = fileURLToPath6(scriptUrl);
13621
- const pkgRoot = dirname9(dirname9(scriptPath));
13622
- const raw = await readFile24(join5(pkgRoot, "package.json"), "utf-8");
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 = resolve41(syntaurRoot(), "npx-install.json");
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 = fileURLToPath7(scriptUrl);
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 readFile25(STATE_FILE, "utf-8");
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 = dirname10(process.execPath);
14120
+ const nodeDir = dirname12(process.execPath);
13664
14121
  const isWin = process.platform === "win32";
13665
14122
  const npmName = isWin ? "npm.cmd" : "npm";
13666
- const nearNode = join6(nodeDir, npmName);
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 = join6(rootPath, "syntaur", "package.json");
14166
+ const manifestPath = join7(rootPath, "syntaur", "package.json");
13710
14167
  if (!await fileExists(manifestPath)) return null;
13711
- const raw = await readFile25(manifestPath, "utf-8");
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-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)").action(async (options) => {
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) {