syntaur 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -49
- package/dist/index.js +886 -162
- package/dist/index.js.map +1 -1
- package/package.json +9 -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 +224 -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
|
@@ -4429,7 +4429,7 @@ function App({ projectsDir: projectsDir2, onLaunch }) {
|
|
|
4429
4429
|
collapseNode,
|
|
4430
4430
|
currentNode
|
|
4431
4431
|
} = useTreeState(nodes, filteredIds);
|
|
4432
|
-
useInput((
|
|
4432
|
+
useInput((input3, key) => {
|
|
4433
4433
|
if (searchActive) {
|
|
4434
4434
|
if (key.escape) {
|
|
4435
4435
|
setSearchActive(false);
|
|
@@ -4442,19 +4442,19 @@ function App({ projectsDir: projectsDir2, onLaunch }) {
|
|
|
4442
4442
|
}
|
|
4443
4443
|
return;
|
|
4444
4444
|
}
|
|
4445
|
-
if (
|
|
4445
|
+
if (input3 === "q" || key.escape) {
|
|
4446
4446
|
exit();
|
|
4447
4447
|
return;
|
|
4448
4448
|
}
|
|
4449
|
-
if (
|
|
4449
|
+
if (input3 === "/") {
|
|
4450
4450
|
setSearchActive(true);
|
|
4451
4451
|
return;
|
|
4452
4452
|
}
|
|
4453
|
-
if (key.upArrow ||
|
|
4453
|
+
if (key.upArrow || input3 === "k") {
|
|
4454
4454
|
moveUp();
|
|
4455
4455
|
return;
|
|
4456
4456
|
}
|
|
4457
|
-
if (key.downArrow ||
|
|
4457
|
+
if (key.downArrow || input3 === "j") {
|
|
4458
4458
|
moveDown();
|
|
4459
4459
|
return;
|
|
4460
4460
|
}
|
|
@@ -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 writeFile9 } from "fs/promises";
|
|
4550
|
+
import { resolve as resolve31 } 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 = resolve31(projectsDir2, projectSlug);
|
|
4561
|
+
const assignmentDir = resolve31(projectDir, "assignments", assignmentSlug);
|
|
4562
|
+
const contextDir = resolve31(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 writeFile9(
|
|
4575
|
+
resolve31(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: readFile29 } = await import("fs/promises");
|
|
5913
|
+
const raw = await readFile29(filePath, "utf-8");
|
|
5914
5914
|
const sessions = [];
|
|
5915
5915
|
const lines = raw.split("\n");
|
|
5916
5916
|
let inTable = false;
|
|
@@ -7637,8 +7637,8 @@ ${entry}`;
|
|
|
7637
7637
|
});
|
|
7638
7638
|
return router;
|
|
7639
7639
|
}
|
|
7640
|
-
function slugifyLocal(
|
|
7641
|
-
return
|
|
7640
|
+
function slugifyLocal(input3) {
|
|
7641
|
+
return input3.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "untitled";
|
|
7642
7642
|
}
|
|
7643
7643
|
async function appendCommentTo(assignmentDir, assignmentRef, req, res, reloadDetail) {
|
|
7644
7644
|
const commentsPath = resolve15(assignmentDir, "comments.md");
|
|
@@ -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: resolve45 } = await import("path");
|
|
8222
|
+
const { readFile: readFile29 } = 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(resolve45(todosDir2, "archive"));
|
|
8239
8239
|
let archContent = "";
|
|
8240
8240
|
if (await fileExists(archFile)) {
|
|
8241
|
-
archContent = await
|
|
8241
|
+
archContent = await readFile29(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,474 @@ 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 readFile18, writeFile as writeFile8, 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 resolve27, dirname as dirname9 } from "path";
|
|
10758
|
+
import { homedir as homedir4 } from "os";
|
|
10759
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
10760
|
+
|
|
10761
|
+
// src/commands/configure-statusline.ts
|
|
10762
|
+
init_paths();
|
|
10763
|
+
init_fs();
|
|
10764
|
+
import { readFile as readFile17, writeFile as writeFile7 } from "fs/promises";
|
|
10765
|
+
import { resolve as resolve26, dirname as dirname8 } from "path";
|
|
10766
|
+
import { spawnSync } from "child_process";
|
|
10767
|
+
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
10768
|
+
var AVAILABLE_SEGMENTS = [
|
|
10769
|
+
{ name: "git", preview: "syntaur:main* +2", description: "repo:branch (with dirty marker and ahead/behind)" },
|
|
10770
|
+
{ name: "assignment", preview: "my-proj/demo-assn \u2014 Demo Assignment", description: "active syntaur assignment (project/slug or standalone/uuid)" },
|
|
10771
|
+
{ name: "session", preview: "\u2026ccddeeff", description: "Claude Code session id \u2014 last 8 chars prefixed by \u2026" },
|
|
10772
|
+
{ name: "model", preview: "Opus 4.7", description: "Claude model display name" },
|
|
10773
|
+
{ name: "ctx", preview: "ctx:[####------] 42%", description: "context window fill bar" },
|
|
10774
|
+
{ name: "cwd", preview: "syntaur", description: "basename of current working directory" },
|
|
10775
|
+
{ name: "wrap", preview: "<output of an external script>", description: "compose another statusline script as a leading segment" }
|
|
10776
|
+
];
|
|
10777
|
+
var PRESETS = {
|
|
10778
|
+
minimal: { segments: ["git", "session"], separator: " \xB7 " },
|
|
10779
|
+
syntaur: { segments: ["git", "assignment", "session"], separator: " \xB7 " },
|
|
10780
|
+
full: { segments: ["wrap", "git", "assignment", "model", "ctx", "session"], separator: " \xB7 " },
|
|
10781
|
+
dev: { segments: ["git", "assignment", "ctx", "session"], separator: " \xB7 " }
|
|
10782
|
+
};
|
|
10783
|
+
function getConfigPath(installRoot) {
|
|
10784
|
+
return resolve26(installRoot, "statusline.config.json");
|
|
10785
|
+
}
|
|
10786
|
+
async function readConfig2(path) {
|
|
10787
|
+
if (!await fileExists(path)) return null;
|
|
10788
|
+
try {
|
|
10789
|
+
const raw = await readFile17(path, "utf-8");
|
|
10790
|
+
const parsed = JSON.parse(raw);
|
|
10791
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
10792
|
+
const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
|
|
10793
|
+
const separator = typeof parsed.separator === "string" ? parsed.separator : " \xB7 ";
|
|
10794
|
+
const wrap = typeof parsed.wrap === "string" ? parsed.wrap : void 0;
|
|
10795
|
+
return { segments, separator, wrap };
|
|
10796
|
+
} catch {
|
|
10797
|
+
return null;
|
|
10798
|
+
}
|
|
10799
|
+
}
|
|
10800
|
+
function isSegmentName(value) {
|
|
10801
|
+
return typeof value === "string" && AVAILABLE_SEGMENTS.some((s) => s.name === value);
|
|
10802
|
+
}
|
|
10803
|
+
function parseSegmentsFlag(flag) {
|
|
10804
|
+
const parts = flag.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10805
|
+
const invalid = parts.filter((p) => !AVAILABLE_SEGMENTS.some((s) => s.name === p));
|
|
10806
|
+
if (invalid.length > 0) {
|
|
10807
|
+
throw new Error(
|
|
10808
|
+
`Unknown segment${invalid.length > 1 ? "s" : ""}: ${invalid.join(", ")}. Valid segments: ${AVAILABLE_SEGMENTS.map((s) => s.name).join(", ")}.`
|
|
10809
|
+
);
|
|
10810
|
+
}
|
|
10811
|
+
return parts;
|
|
10812
|
+
}
|
|
10813
|
+
async function promptSegmentsInteractive(current) {
|
|
10814
|
+
const canonicalOrder = ["wrap", "git", "assignment", "session", "model", "ctx", "cwd"];
|
|
10815
|
+
const selectedSet = new Set(current?.segments ?? ["git", "assignment", "session"]);
|
|
10816
|
+
const choices = canonicalOrder.map((name) => {
|
|
10817
|
+
const def = AVAILABLE_SEGMENTS.find((s) => s.name === name);
|
|
10818
|
+
return {
|
|
10819
|
+
name: `${def.name.padEnd(11)} ${def.description}`,
|
|
10820
|
+
value: name,
|
|
10821
|
+
checked: selectedSet.has(name),
|
|
10822
|
+
description: `preview: ${def.preview}`
|
|
10823
|
+
};
|
|
10824
|
+
});
|
|
10825
|
+
const selected = await checkbox({
|
|
10826
|
+
message: "Pick segments (space to toggle, enter to confirm):",
|
|
10827
|
+
choices,
|
|
10828
|
+
loop: false,
|
|
10829
|
+
pageSize: choices.length,
|
|
10830
|
+
required: true
|
|
10831
|
+
});
|
|
10832
|
+
const defaultReorderHint = selected.join(", ");
|
|
10833
|
+
let orderedSegments = [...selected];
|
|
10834
|
+
const wantReorder = await confirm({
|
|
10835
|
+
message: `Order will be: ${defaultReorderHint}. Customize order?`,
|
|
10836
|
+
default: false
|
|
10837
|
+
});
|
|
10838
|
+
if (wantReorder) {
|
|
10839
|
+
const raw = await input2({
|
|
10840
|
+
message: `Enter the segments in the order you want, comma-separated:`,
|
|
10841
|
+
default: defaultReorderHint,
|
|
10842
|
+
validate: (value) => {
|
|
10843
|
+
const parts = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10844
|
+
const invalid = parts.filter((p) => !canonicalOrder.includes(p));
|
|
10845
|
+
if (invalid.length > 0) {
|
|
10846
|
+
return `Unknown: ${invalid.join(", ")}. Valid: ${canonicalOrder.join(", ")}.`;
|
|
10847
|
+
}
|
|
10848
|
+
const missing = selected.filter((s) => !parts.includes(s));
|
|
10849
|
+
if (missing.length > 0) {
|
|
10850
|
+
return `Missing previously-selected segment(s): ${missing.join(", ")}. Include all of them or go back.`;
|
|
10851
|
+
}
|
|
10852
|
+
return true;
|
|
10853
|
+
}
|
|
10854
|
+
});
|
|
10855
|
+
orderedSegments = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10856
|
+
}
|
|
10857
|
+
const separator = await input2({
|
|
10858
|
+
message: "Separator between segments:",
|
|
10859
|
+
default: current?.separator ?? " \xB7 "
|
|
10860
|
+
});
|
|
10861
|
+
let wrap = current?.wrap;
|
|
10862
|
+
if (orderedSegments.includes("wrap")) {
|
|
10863
|
+
wrap = await input2({
|
|
10864
|
+
message: "Path to external script to wrap (leave blank to skip):",
|
|
10865
|
+
default: current?.wrap ?? ""
|
|
10866
|
+
});
|
|
10867
|
+
wrap = wrap.trim() ? wrap.trim() : void 0;
|
|
10868
|
+
}
|
|
10869
|
+
return { segments: orderedSegments, separator, wrap };
|
|
10870
|
+
}
|
|
10871
|
+
function renderPreview(config, statuslineScript, cwd) {
|
|
10872
|
+
const payload = {
|
|
10873
|
+
session_id: "preview-demo-0000000000abcdef12",
|
|
10874
|
+
cwd,
|
|
10875
|
+
model: { display_name: "Opus 4.7" },
|
|
10876
|
+
context_window: { used_percentage: 42 }
|
|
10877
|
+
};
|
|
10878
|
+
const res = spawnSync("bash", [statuslineScript], {
|
|
10879
|
+
input: JSON.stringify(payload),
|
|
10880
|
+
encoding: "utf-8",
|
|
10881
|
+
env: {
|
|
10882
|
+
...process.env,
|
|
10883
|
+
// Force the child to pick up the freshly-written config from install root.
|
|
10884
|
+
HOME: dirname8(dirname8(statuslineScript))
|
|
10885
|
+
}
|
|
10886
|
+
});
|
|
10887
|
+
if (res.status !== 0) return null;
|
|
10888
|
+
return res.stdout;
|
|
10889
|
+
}
|
|
10890
|
+
async function configureStatuslineCommand(options = {}) {
|
|
10891
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
10892
|
+
const configPath = getConfigPath(installRoot);
|
|
10893
|
+
const current = await readConfig2(configPath);
|
|
10894
|
+
let segments;
|
|
10895
|
+
let separator;
|
|
10896
|
+
let wrap;
|
|
10897
|
+
if (options.preset) {
|
|
10898
|
+
const preset = PRESETS[options.preset.toLowerCase()];
|
|
10899
|
+
if (!preset) {
|
|
10900
|
+
throw new Error(
|
|
10901
|
+
`Unknown preset "${options.preset}". Presets: ${Object.keys(PRESETS).join(", ")}.`
|
|
10902
|
+
);
|
|
10903
|
+
}
|
|
10904
|
+
segments = [...preset.segments];
|
|
10905
|
+
separator = options.separator ?? preset.separator;
|
|
10906
|
+
wrap = options.wrap ?? current?.wrap;
|
|
10907
|
+
} else if (options.segments) {
|
|
10908
|
+
segments = parseSegmentsFlag(options.segments);
|
|
10909
|
+
separator = options.separator ?? current?.separator ?? " \xB7 ";
|
|
10910
|
+
wrap = options.wrap ?? current?.wrap;
|
|
10911
|
+
} else if (isInteractiveTerminal()) {
|
|
10912
|
+
const answers = await promptSegmentsInteractive(current);
|
|
10913
|
+
segments = answers.segments;
|
|
10914
|
+
separator = answers.separator;
|
|
10915
|
+
wrap = answers.wrap;
|
|
10916
|
+
} else {
|
|
10917
|
+
throw new Error(
|
|
10918
|
+
"Non-interactive invocation requires --preset, --segments, or run in a TTY."
|
|
10919
|
+
);
|
|
10920
|
+
}
|
|
10921
|
+
if (segments.includes("wrap") && !wrap) {
|
|
10922
|
+
console.warn(
|
|
10923
|
+
`Note: the "wrap" segment is selected but no wrap path is configured. Set one with --wrap <path> or edit ${configPath} afterwards.`
|
|
10924
|
+
);
|
|
10925
|
+
}
|
|
10926
|
+
const config = { segments, separator, ...wrap ? { wrap } : {} };
|
|
10927
|
+
if (options.preview) {
|
|
10928
|
+
console.log("Segments: " + config.segments.join(", "));
|
|
10929
|
+
console.log("Separator: " + JSON.stringify(config.separator));
|
|
10930
|
+
if (config.wrap) console.log("Wrap: " + config.wrap);
|
|
10931
|
+
console.log("");
|
|
10932
|
+
console.log("(preview mode \u2014 config NOT written)");
|
|
10933
|
+
return;
|
|
10934
|
+
}
|
|
10935
|
+
await ensureDir(dirname8(configPath));
|
|
10936
|
+
await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
10937
|
+
console.log("Wrote statusline config:");
|
|
10938
|
+
console.log(` path: ${configPath}`);
|
|
10939
|
+
console.log(` segments: ${config.segments.join(", ")}`);
|
|
10940
|
+
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
10941
|
+
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
10942
|
+
const script = options.statuslineScript ?? resolve26(installRoot, "statusline.sh");
|
|
10943
|
+
if (await fileExists(script)) {
|
|
10944
|
+
console.log("");
|
|
10945
|
+
console.log("Live preview:");
|
|
10946
|
+
const out = renderPreview(config, script, process.cwd());
|
|
10947
|
+
if (out) {
|
|
10948
|
+
console.log(" " + out);
|
|
10949
|
+
} else {
|
|
10950
|
+
console.log(" (preview failed \u2014 run `syntaur install-statusline` if the script is missing)");
|
|
10951
|
+
}
|
|
10952
|
+
} else {
|
|
10953
|
+
console.log("");
|
|
10954
|
+
console.log(
|
|
10955
|
+
"(statusline script not yet installed \u2014 run `syntaur install-statusline` to wire it up)"
|
|
10956
|
+
);
|
|
10957
|
+
}
|
|
10958
|
+
}
|
|
10959
|
+
async function writeDefaultConfigIfMissing(installRoot) {
|
|
10960
|
+
const path = getConfigPath(installRoot);
|
|
10961
|
+
if (await fileExists(path)) return;
|
|
10962
|
+
await ensureDir(dirname8(path));
|
|
10963
|
+
const defaultConfig = {
|
|
10964
|
+
segments: ["git", "assignment", "session"],
|
|
10965
|
+
separator: " \xB7 "
|
|
10966
|
+
};
|
|
10967
|
+
await writeFile7(path, JSON.stringify(defaultConfig, null, 2) + "\n", "utf-8");
|
|
10968
|
+
}
|
|
10969
|
+
|
|
10970
|
+
// src/commands/install-statusline.ts
|
|
10971
|
+
function getPackageStatuslineSource() {
|
|
10972
|
+
const here = dirname9(fileURLToPath5(import.meta.url));
|
|
10973
|
+
return resolve27(here, "..", "statusline", "statusline.sh");
|
|
10974
|
+
}
|
|
10975
|
+
async function readSettingsJson(settingsPath) {
|
|
10976
|
+
if (!await fileExists(settingsPath)) return {};
|
|
10977
|
+
const raw = await readFile18(settingsPath, "utf-8");
|
|
10978
|
+
if (raw.trim() === "") return {};
|
|
10979
|
+
try {
|
|
10980
|
+
const parsed = JSON.parse(raw);
|
|
10981
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
10982
|
+
} catch (error) {
|
|
10983
|
+
throw new Error(
|
|
10984
|
+
`Unable to parse ${settingsPath}: ${error.message}. Fix the JSON and re-run.`
|
|
10985
|
+
);
|
|
10986
|
+
}
|
|
10987
|
+
}
|
|
10988
|
+
async function writeSettingsJson(settingsPath, data) {
|
|
10989
|
+
await ensureDir(dirname9(settingsPath));
|
|
10990
|
+
await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
10991
|
+
}
|
|
10992
|
+
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
10993
|
+
if (mode !== "ask") return mode;
|
|
10994
|
+
if (!existingCommand || existingCommand === ourCommand) return "replace";
|
|
10995
|
+
if (!isInteractiveTerminal()) {
|
|
10996
|
+
return "wrap";
|
|
10997
|
+
}
|
|
10998
|
+
console.log(
|
|
10999
|
+
`A Claude Code statusLine is already configured:
|
|
11000
|
+
${existingCommand}
|
|
11001
|
+
`
|
|
11002
|
+
);
|
|
11003
|
+
const wantWrap = await confirmPrompt(
|
|
11004
|
+
"Compose (wrap) your existing statusline with syntaur segments? [Y to wrap / n to replace]",
|
|
11005
|
+
true
|
|
11006
|
+
);
|
|
11007
|
+
if (wantWrap) return "wrap";
|
|
11008
|
+
const confirmReplace = await confirmPrompt(
|
|
11009
|
+
"Replace your existing statusline with syntaur only?",
|
|
11010
|
+
false
|
|
11011
|
+
);
|
|
11012
|
+
return confirmReplace ? "replace" : "skip";
|
|
11013
|
+
}
|
|
11014
|
+
function extractExistingCommand(settings) {
|
|
11015
|
+
const sl = settings.statusLine;
|
|
11016
|
+
if (!sl || typeof sl !== "object") return void 0;
|
|
11017
|
+
const obj = sl;
|
|
11018
|
+
return {
|
|
11019
|
+
type: typeof obj.type === "string" ? obj.type : void 0,
|
|
11020
|
+
command: typeof obj.command === "string" ? obj.command : void 0
|
|
11021
|
+
};
|
|
11022
|
+
}
|
|
11023
|
+
async function backupSettings(settingsSnapshot, backupPath) {
|
|
11024
|
+
await ensureDir(dirname9(backupPath));
|
|
11025
|
+
await writeFile8(
|
|
11026
|
+
backupPath,
|
|
11027
|
+
JSON.stringify(
|
|
11028
|
+
{
|
|
11029
|
+
version: 1,
|
|
11030
|
+
takenAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11031
|
+
settingsPath: settingsSnapshot.settingsPath,
|
|
11032
|
+
previousStatusLine: settingsSnapshot.existingStatusLine ?? null
|
|
11033
|
+
},
|
|
11034
|
+
null,
|
|
11035
|
+
2
|
|
11036
|
+
) + "\n",
|
|
11037
|
+
"utf-8"
|
|
11038
|
+
);
|
|
11039
|
+
}
|
|
11040
|
+
async function installScript(sourceScript, destScript, link) {
|
|
11041
|
+
await ensureDir(dirname9(destScript));
|
|
11042
|
+
try {
|
|
11043
|
+
const s = await lstat2(destScript);
|
|
11044
|
+
if (s.isSymbolicLink() || s.isFile()) {
|
|
11045
|
+
await unlink6(destScript);
|
|
11046
|
+
}
|
|
11047
|
+
} catch {
|
|
11048
|
+
}
|
|
11049
|
+
if (link) {
|
|
11050
|
+
await symlink2(sourceScript, destScript);
|
|
11051
|
+
} else {
|
|
11052
|
+
await copyFile2(sourceScript, destScript);
|
|
11053
|
+
}
|
|
11054
|
+
}
|
|
11055
|
+
async function installStatuslineCommand(options = {}) {
|
|
11056
|
+
const mode = options.mode ?? "ask";
|
|
11057
|
+
const settingsPath = options.settingsPath ?? resolve27(homedir4(), ".claude", "settings.json");
|
|
11058
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
11059
|
+
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
11060
|
+
const destScript = resolve27(installRoot, "statusline.sh");
|
|
11061
|
+
const confPath = resolve27(installRoot, "statusline.conf");
|
|
11062
|
+
const backupPath = resolve27(installRoot, "statusline.backup.json");
|
|
11063
|
+
if (!await fileExists(sourceScript)) {
|
|
11064
|
+
throw new Error(
|
|
11065
|
+
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
11066
|
+
);
|
|
11067
|
+
}
|
|
11068
|
+
await installScript(sourceScript, destScript, Boolean(options.link));
|
|
11069
|
+
await writeDefaultConfigIfMissing(installRoot);
|
|
11070
|
+
const settings = await readSettingsJson(settingsPath);
|
|
11071
|
+
const existingStatusLine = extractExistingCommand(settings);
|
|
11072
|
+
const existingCommand = existingStatusLine?.command;
|
|
11073
|
+
const ourCommand = `bash ${destScript}`;
|
|
11074
|
+
const resolvedMode = await resolveMode(mode, existingCommand, ourCommand);
|
|
11075
|
+
if (resolvedMode === "skip") {
|
|
11076
|
+
console.log("Installed statusline script only:");
|
|
11077
|
+
console.log(` script: ${destScript}`);
|
|
11078
|
+
console.log(` source: ${sourceScript}`);
|
|
11079
|
+
console.log(
|
|
11080
|
+
" (settings.json left unchanged \u2014 run with --mode=replace or --mode=wrap to wire it up)"
|
|
11081
|
+
);
|
|
11082
|
+
return;
|
|
11083
|
+
}
|
|
11084
|
+
await backupSettings(
|
|
11085
|
+
{
|
|
11086
|
+
settingsPath,
|
|
11087
|
+
existingStatusLine,
|
|
11088
|
+
existingCommand
|
|
11089
|
+
},
|
|
11090
|
+
backupPath
|
|
11091
|
+
);
|
|
11092
|
+
let wrapTarget = "";
|
|
11093
|
+
if (resolvedMode === "wrap" && existingCommand && existingCommand !== ourCommand) {
|
|
11094
|
+
const parsed = parseWrapCommand(existingCommand);
|
|
11095
|
+
if (parsed) {
|
|
11096
|
+
wrapTarget = parsed;
|
|
11097
|
+
} else {
|
|
11098
|
+
const wrapperPath = resolve27(installRoot, "statusline-wrapped.sh");
|
|
11099
|
+
const wrapperBody = `#!/usr/bin/env bash
|
|
11100
|
+
# Auto-generated by syntaur install-statusline.
|
|
11101
|
+
# Executes the previously configured statusLine command.
|
|
11102
|
+
exec ${existingCommand}
|
|
11103
|
+
`;
|
|
11104
|
+
await writeFile8(wrapperPath, wrapperBody, "utf-8");
|
|
11105
|
+
await chmodExec(wrapperPath);
|
|
11106
|
+
wrapTarget = wrapperPath;
|
|
11107
|
+
}
|
|
11108
|
+
}
|
|
11109
|
+
await ensureDir(dirname9(confPath));
|
|
11110
|
+
await writeFile8(
|
|
11111
|
+
confPath,
|
|
11112
|
+
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
11113
|
+
# stdout becomes the leading segment of the statusline. Remove this
|
|
11114
|
+
# line or comment it out to disable wrapping.
|
|
11115
|
+
${wrapTarget}
|
|
11116
|
+
` : `# syntaur statusline config. Add a single path on a non-comment line to
|
|
11117
|
+
# wrap another statusline script with syntaur segments appended.
|
|
11118
|
+
`,
|
|
11119
|
+
"utf-8"
|
|
11120
|
+
);
|
|
11121
|
+
settings.statusLine = {
|
|
11122
|
+
type: "command",
|
|
11123
|
+
command: ourCommand
|
|
11124
|
+
};
|
|
11125
|
+
await writeSettingsJson(settingsPath, settings);
|
|
11126
|
+
console.log("Installed syntaur statusline:");
|
|
11127
|
+
console.log(` script: ${destScript}`);
|
|
11128
|
+
console.log(` source: ${sourceScript}`);
|
|
11129
|
+
console.log(` mode: ${resolvedMode}`);
|
|
11130
|
+
console.log(` settings.json:${settingsPath}`);
|
|
11131
|
+
console.log(` backup: ${backupPath}`);
|
|
11132
|
+
if (wrapTarget) {
|
|
11133
|
+
console.log(` wrap target: ${wrapTarget}`);
|
|
11134
|
+
console.log(` (edit ${confPath} to change or disable wrapping)`);
|
|
11135
|
+
}
|
|
11136
|
+
}
|
|
11137
|
+
function parseWrapCommand(command) {
|
|
11138
|
+
const trimmed = command.trim();
|
|
11139
|
+
const bashMatch = trimmed.match(/^bash\s+(\S+)$/);
|
|
11140
|
+
if (bashMatch) return bashMatch[1];
|
|
11141
|
+
if (/^\S+\.(sh|bash)$/.test(trimmed)) return trimmed;
|
|
11142
|
+
return null;
|
|
11143
|
+
}
|
|
11144
|
+
async function chmodExec(path) {
|
|
11145
|
+
const fs = await import("fs/promises");
|
|
11146
|
+
try {
|
|
11147
|
+
const s = await stat3(path);
|
|
11148
|
+
await fs.chmod(path, s.mode | 73);
|
|
11149
|
+
} catch {
|
|
11150
|
+
}
|
|
11151
|
+
}
|
|
11152
|
+
async function uninstallStatuslineCommand(options = {}) {
|
|
11153
|
+
const settingsPath = options.settingsPath ?? resolve27(homedir4(), ".claude", "settings.json");
|
|
11154
|
+
const installRoot = options.installRoot ?? syntaurRoot();
|
|
11155
|
+
const destScript = resolve27(installRoot, "statusline.sh");
|
|
11156
|
+
const confPath = resolve27(installRoot, "statusline.conf");
|
|
11157
|
+
const backupPath = resolve27(installRoot, "statusline.backup.json");
|
|
11158
|
+
const wrapperPath = resolve27(installRoot, "statusline-wrapped.sh");
|
|
11159
|
+
const settings = await readSettingsJson(settingsPath);
|
|
11160
|
+
const existing = extractExistingCommand(settings);
|
|
11161
|
+
const ourCommand = `bash ${destScript}`;
|
|
11162
|
+
let restored = null;
|
|
11163
|
+
if (await fileExists(backupPath)) {
|
|
11164
|
+
try {
|
|
11165
|
+
const raw = await readFile18(backupPath, "utf-8");
|
|
11166
|
+
const parsed = JSON.parse(raw);
|
|
11167
|
+
const prev = parsed?.previousStatusLine;
|
|
11168
|
+
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
11169
|
+
restored = { command: prev.command };
|
|
11170
|
+
}
|
|
11171
|
+
} catch {
|
|
11172
|
+
}
|
|
11173
|
+
}
|
|
11174
|
+
if (existing?.command === ourCommand) {
|
|
11175
|
+
if (restored) {
|
|
11176
|
+
settings.statusLine = {
|
|
11177
|
+
type: "command",
|
|
11178
|
+
command: restored.command
|
|
11179
|
+
};
|
|
11180
|
+
} else {
|
|
11181
|
+
delete settings.statusLine;
|
|
11182
|
+
}
|
|
11183
|
+
await writeSettingsJson(settingsPath, settings);
|
|
11184
|
+
}
|
|
11185
|
+
if (!options.keepScript) {
|
|
11186
|
+
const configPath = resolve27(installRoot, "statusline.config.json");
|
|
11187
|
+
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
11188
|
+
try {
|
|
11189
|
+
await rm5(path, { force: true });
|
|
11190
|
+
} catch {
|
|
11191
|
+
}
|
|
11192
|
+
}
|
|
11193
|
+
}
|
|
11194
|
+
console.log("Uninstalled syntaur statusline.");
|
|
11195
|
+
if (restored) {
|
|
11196
|
+
console.log(` Restored previous command: ${restored.command}`);
|
|
11197
|
+
} else {
|
|
11198
|
+
console.log(" Removed statusLine entry from settings.json.");
|
|
11199
|
+
}
|
|
11200
|
+
console.log(` settings.json: ${settingsPath}`);
|
|
10578
11201
|
}
|
|
10579
11202
|
|
|
10580
11203
|
// src/commands/install-codex-plugin.ts
|
|
@@ -10656,14 +11279,61 @@ async function installCodexPluginCommand(options) {
|
|
|
10656
11279
|
console.log(` source: ${result.sourceDir}`);
|
|
10657
11280
|
console.log(` mode: ${result.mode}`);
|
|
10658
11281
|
console.log(` marketplace: ${marketplace.marketplacePath}`);
|
|
11282
|
+
if (!options.skipSkills) {
|
|
11283
|
+
try {
|
|
11284
|
+
const skillResults = await installSkills({
|
|
11285
|
+
target: "codex",
|
|
11286
|
+
force: options.forceSkills
|
|
11287
|
+
});
|
|
11288
|
+
console.log("");
|
|
11289
|
+
console.log(formatInstallReport(skillResults, "codex"));
|
|
11290
|
+
} catch (error) {
|
|
11291
|
+
console.warn(
|
|
11292
|
+
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
11293
|
+
);
|
|
11294
|
+
}
|
|
11295
|
+
}
|
|
10659
11296
|
console.log("\nThe plugin is now available to Codex.");
|
|
10660
11297
|
console.log(
|
|
10661
|
-
"
|
|
11298
|
+
" Protocol skills: syntaur-protocol, create-project, create-assignment, grab-assignment, plan-assignment, complete-assignment"
|
|
10662
11299
|
);
|
|
10663
|
-
console.log("
|
|
11300
|
+
console.log(" Codex-specific: track-session skill (rollout path aware)");
|
|
10664
11301
|
console.log(" Hooks: write boundary enforcement, session cleanup");
|
|
10665
11302
|
}
|
|
10666
11303
|
|
|
11304
|
+
// src/commands/uninstall-skills.ts
|
|
11305
|
+
async function uninstallSkillsCommand(options) {
|
|
11306
|
+
const runClaude = Boolean(options.claude || options.all);
|
|
11307
|
+
const runCodex = Boolean(options.codex || options.all);
|
|
11308
|
+
if (!runClaude && !runCodex) {
|
|
11309
|
+
throw new Error(
|
|
11310
|
+
"Specify --claude, --codex, or --all (use one or more)."
|
|
11311
|
+
);
|
|
11312
|
+
}
|
|
11313
|
+
let totalRemoved = 0;
|
|
11314
|
+
if (runClaude) {
|
|
11315
|
+
const removed = await uninstallSkills({ target: "claude" });
|
|
11316
|
+
totalRemoved += removed.length;
|
|
11317
|
+
console.log(
|
|
11318
|
+
`Removed ${removed.length} Syntaur protocol skill(s) from ~/.claude/skills:`
|
|
11319
|
+
);
|
|
11320
|
+
for (const p of removed) console.log(` - ${p}`);
|
|
11321
|
+
}
|
|
11322
|
+
if (runCodex) {
|
|
11323
|
+
const removed = await uninstallSkills({ target: "codex" });
|
|
11324
|
+
totalRemoved += removed.length;
|
|
11325
|
+
console.log(
|
|
11326
|
+
`Removed ${removed.length} Syntaur protocol skill(s) from ~/.codex/skills:`
|
|
11327
|
+
);
|
|
11328
|
+
for (const p of removed) console.log(` - ${p}`);
|
|
11329
|
+
}
|
|
11330
|
+
if (totalRemoved === 0) {
|
|
11331
|
+
console.log(
|
|
11332
|
+
"No Syntaur protocol skills found to remove. (User-authored skills with matching directory names are preserved.)"
|
|
11333
|
+
);
|
|
11334
|
+
}
|
|
11335
|
+
}
|
|
11336
|
+
|
|
10667
11337
|
// src/commands/setup.ts
|
|
10668
11338
|
import { execSync } from "child_process";
|
|
10669
11339
|
init_config2();
|
|
@@ -10768,7 +11438,7 @@ async function setupCommand(options) {
|
|
|
10768
11438
|
}
|
|
10769
11439
|
|
|
10770
11440
|
// src/commands/uninstall.ts
|
|
10771
|
-
import { resolve as
|
|
11441
|
+
import { resolve as resolve28 } from "path";
|
|
10772
11442
|
init_paths();
|
|
10773
11443
|
function expandTargets(options) {
|
|
10774
11444
|
if (options.all) {
|
|
@@ -10848,7 +11518,7 @@ async function uninstallCommand(options) {
|
|
|
10848
11518
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
10849
11519
|
await removeSyntaurData();
|
|
10850
11520
|
console.log(`Removed ${syntaurRoot()}`);
|
|
10851
|
-
if (configuredProjectDir &&
|
|
11521
|
+
if (configuredProjectDir && resolve28(configuredProjectDir) !== resolve28(syntaurRoot(), "projects")) {
|
|
10852
11522
|
console.warn(
|
|
10853
11523
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
10854
11524
|
);
|
|
@@ -10863,7 +11533,7 @@ async function uninstallCommand(options) {
|
|
|
10863
11533
|
init_paths();
|
|
10864
11534
|
init_fs();
|
|
10865
11535
|
init_config2();
|
|
10866
|
-
import { resolve as
|
|
11536
|
+
import { resolve as resolve29 } from "path";
|
|
10867
11537
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
10868
11538
|
async function setupAdapterCommand(framework, options) {
|
|
10869
11539
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -10889,19 +11559,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10889
11559
|
}
|
|
10890
11560
|
const config = await readConfig();
|
|
10891
11561
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10892
|
-
const projectDir =
|
|
10893
|
-
const assignmentDir =
|
|
11562
|
+
const projectDir = resolve29(baseDir, options.project);
|
|
11563
|
+
const assignmentDir = resolve29(
|
|
10894
11564
|
projectDir,
|
|
10895
11565
|
"assignments",
|
|
10896
11566
|
options.assignment
|
|
10897
11567
|
);
|
|
10898
|
-
const projectMdPath =
|
|
11568
|
+
const projectMdPath = resolve29(projectDir, "project.md");
|
|
10899
11569
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
10900
11570
|
throw new Error(
|
|
10901
11571
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
10902
11572
|
);
|
|
10903
11573
|
}
|
|
10904
|
-
const assignmentMdPath =
|
|
11574
|
+
const assignmentMdPath = resolve29(assignmentDir, "assignment.md");
|
|
10905
11575
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
10906
11576
|
throw new Error(
|
|
10907
11577
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -10929,15 +11599,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10929
11599
|
}
|
|
10930
11600
|
}
|
|
10931
11601
|
if (framework === "cursor") {
|
|
10932
|
-
const protocolPath =
|
|
10933
|
-
const assignmentPath =
|
|
11602
|
+
const protocolPath = resolve29(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
11603
|
+
const assignmentPath = resolve29(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
10934
11604
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
10935
11605
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
10936
11606
|
} else if (framework === "codex" || framework === "opencode") {
|
|
10937
|
-
const agentsPath =
|
|
11607
|
+
const agentsPath = resolve29(cwd, "AGENTS.md");
|
|
10938
11608
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
10939
11609
|
if (framework === "opencode") {
|
|
10940
|
-
const configPath =
|
|
11610
|
+
const configPath = resolve29(cwd, "opencode.json");
|
|
10941
11611
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
10942
11612
|
}
|
|
10943
11613
|
}
|
|
@@ -10962,7 +11632,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
10962
11632
|
init_paths();
|
|
10963
11633
|
init_fs();
|
|
10964
11634
|
init_config2();
|
|
10965
|
-
import { resolve as
|
|
11635
|
+
import { resolve as resolve30 } from "path";
|
|
10966
11636
|
async function trackSessionCommand(options) {
|
|
10967
11637
|
if (!options.agent) {
|
|
10968
11638
|
throw new Error("--agent <name> is required.");
|
|
@@ -10975,7 +11645,7 @@ async function trackSessionCommand(options) {
|
|
|
10975
11645
|
if (options.project) {
|
|
10976
11646
|
const config = await readConfig();
|
|
10977
11647
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
10978
|
-
const projectDir =
|
|
11648
|
+
const projectDir = resolve30(baseDir, options.project);
|
|
10979
11649
|
if (!await fileExists(projectDir)) {
|
|
10980
11650
|
throw new Error(
|
|
10981
11651
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -11030,7 +11700,7 @@ async function browseCommand(options) {
|
|
|
11030
11700
|
}
|
|
11031
11701
|
|
|
11032
11702
|
// src/commands/create-playbook.ts
|
|
11033
|
-
import { resolve as
|
|
11703
|
+
import { resolve as resolve32 } from "path";
|
|
11034
11704
|
init_timestamp();
|
|
11035
11705
|
init_paths();
|
|
11036
11706
|
init_fs();
|
|
@@ -11046,7 +11716,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
11046
11716
|
}
|
|
11047
11717
|
const dir = playbooksDir();
|
|
11048
11718
|
await ensureDir(dir);
|
|
11049
|
-
const filePath =
|
|
11719
|
+
const filePath = resolve32(dir, `${slug}.md`);
|
|
11050
11720
|
if (await fileExists(filePath)) {
|
|
11051
11721
|
throw new Error(
|
|
11052
11722
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -11067,15 +11737,15 @@ Use --slug to specify a different slug.`
|
|
|
11067
11737
|
init_paths();
|
|
11068
11738
|
init_fs();
|
|
11069
11739
|
init_parser();
|
|
11070
|
-
import { readdir as
|
|
11071
|
-
import { resolve as
|
|
11740
|
+
import { readdir as readdir12, readFile as readFile19 } from "fs/promises";
|
|
11741
|
+
import { resolve as resolve33 } from "path";
|
|
11072
11742
|
async function listPlaybooksCommand() {
|
|
11073
11743
|
const dir = playbooksDir();
|
|
11074
11744
|
if (!await fileExists(dir)) {
|
|
11075
11745
|
console.log('No playbooks directory found. Run "syntaur init" first.');
|
|
11076
11746
|
return;
|
|
11077
11747
|
}
|
|
11078
|
-
const entries = await
|
|
11748
|
+
const entries = await readdir12(dir, { withFileTypes: true });
|
|
11079
11749
|
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".md") && !e.name.startsWith("_") && e.name !== "manifest.md");
|
|
11080
11750
|
if (mdFiles.length === 0) {
|
|
11081
11751
|
console.log('No playbooks found. Create one with "syntaur create-playbook <name>".');
|
|
@@ -11086,8 +11756,8 @@ async function listPlaybooksCommand() {
|
|
|
11086
11756
|
console.log(`${"Slug".padEnd(30)} ${"Name".padEnd(30)} Description`);
|
|
11087
11757
|
console.log(`${"\u2500".repeat(30)} ${"\u2500".repeat(30)} ${"\u2500".repeat(40)}`);
|
|
11088
11758
|
for (const entry of mdFiles) {
|
|
11089
|
-
const filePath =
|
|
11090
|
-
const raw = await
|
|
11759
|
+
const filePath = resolve33(dir, entry.name);
|
|
11760
|
+
const raw = await readFile19(filePath, "utf-8");
|
|
11091
11761
|
const parsed = parsePlaybook(raw);
|
|
11092
11762
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
11093
11763
|
const name = parsed.name || slug;
|
|
@@ -11101,8 +11771,8 @@ init_paths();
|
|
|
11101
11771
|
init_parser2();
|
|
11102
11772
|
init_fs();
|
|
11103
11773
|
import { Command } from "commander";
|
|
11104
|
-
import { readFile as
|
|
11105
|
-
import { resolve as
|
|
11774
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
11775
|
+
import { resolve as resolve34 } from "path";
|
|
11106
11776
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
11107
11777
|
function resolveWorkspace(options) {
|
|
11108
11778
|
if (options.global) return "_global";
|
|
@@ -11383,10 +12053,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
11383
12053
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
11384
12054
|
);
|
|
11385
12055
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
11386
|
-
await ensureDir(
|
|
12056
|
+
await ensureDir(resolve34(todosPath, "archive"));
|
|
11387
12057
|
let archContent = "";
|
|
11388
12058
|
if (await fileExists(archFile)) {
|
|
11389
|
-
archContent = await
|
|
12059
|
+
archContent = await readFile20(archFile, "utf-8");
|
|
11390
12060
|
archContent = archContent.trimEnd() + "\n\n";
|
|
11391
12061
|
} else {
|
|
11392
12062
|
archContent = `---
|
|
@@ -11574,20 +12244,20 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
11574
12244
|
import { Command as Command3 } from "commander";
|
|
11575
12245
|
|
|
11576
12246
|
// src/utils/doctor/index.ts
|
|
11577
|
-
import { fileURLToPath as
|
|
11578
|
-
import { readFile as
|
|
11579
|
-
import { dirname as
|
|
12247
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
12248
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
12249
|
+
import { dirname as dirname11, join as join5 } from "path";
|
|
11580
12250
|
|
|
11581
12251
|
// src/utils/doctor/context.ts
|
|
11582
12252
|
init_config2();
|
|
11583
12253
|
init_paths();
|
|
11584
12254
|
init_fs();
|
|
11585
12255
|
import Database2 from "better-sqlite3";
|
|
11586
|
-
import { resolve as
|
|
12256
|
+
import { resolve as resolve35 } from "path";
|
|
11587
12257
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
11588
12258
|
const config = await readConfig();
|
|
11589
12259
|
const root = syntaurRoot();
|
|
11590
|
-
const dbPath =
|
|
12260
|
+
const dbPath = resolve35(root, "syntaur.db");
|
|
11591
12261
|
let db2 = null;
|
|
11592
12262
|
let dbError = null;
|
|
11593
12263
|
if (await fileExists(dbPath)) {
|
|
@@ -11621,10 +12291,10 @@ function closeCheckContext(ctx) {
|
|
|
11621
12291
|
// src/utils/doctor/checks/env.ts
|
|
11622
12292
|
init_fs();
|
|
11623
12293
|
init_paths();
|
|
11624
|
-
import { resolve as
|
|
11625
|
-
import { readFile as
|
|
11626
|
-
import { fileURLToPath as
|
|
11627
|
-
import { dirname as
|
|
12294
|
+
import { resolve as resolve36, isAbsolute as isAbsolute3 } from "path";
|
|
12295
|
+
import { readFile as readFile21, stat as stat4 } from "fs/promises";
|
|
12296
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12297
|
+
import { dirname as dirname10, join as join4 } from "path";
|
|
11628
12298
|
var CATEGORY = "env";
|
|
11629
12299
|
var syntaurRootExists = {
|
|
11630
12300
|
id: "env.syntaur-root-exists",
|
|
@@ -11632,7 +12302,7 @@ var syntaurRootExists = {
|
|
|
11632
12302
|
title: "~/.syntaur/ directory exists",
|
|
11633
12303
|
async run(ctx) {
|
|
11634
12304
|
try {
|
|
11635
|
-
const s = await
|
|
12305
|
+
const s = await stat4(ctx.syntaurRoot);
|
|
11636
12306
|
if (!s.isDirectory()) {
|
|
11637
12307
|
return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
|
|
11638
12308
|
ctx.syntaurRoot
|
|
@@ -11662,7 +12332,7 @@ var configValid = {
|
|
|
11662
12332
|
category: CATEGORY,
|
|
11663
12333
|
title: "~/.syntaur/config.md is valid",
|
|
11664
12334
|
async run(ctx) {
|
|
11665
|
-
const configPath =
|
|
12335
|
+
const configPath = resolve36(ctx.syntaurRoot, "config.md");
|
|
11666
12336
|
if (!await fileExists(configPath)) {
|
|
11667
12337
|
return {
|
|
11668
12338
|
id: this.id,
|
|
@@ -11679,7 +12349,7 @@ var configValid = {
|
|
|
11679
12349
|
autoFixable: false
|
|
11680
12350
|
};
|
|
11681
12351
|
}
|
|
11682
|
-
const content = await
|
|
12352
|
+
const content = await readFile21(configPath, "utf-8");
|
|
11683
12353
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
11684
12354
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
11685
12355
|
return {
|
|
@@ -11954,15 +12624,15 @@ async function readLocalVersion() {
|
|
|
11954
12624
|
}
|
|
11955
12625
|
async function readLocalPkg() {
|
|
11956
12626
|
try {
|
|
11957
|
-
const here =
|
|
11958
|
-
let dir =
|
|
12627
|
+
const here = fileURLToPath6(import.meta.url);
|
|
12628
|
+
let dir = dirname10(here);
|
|
11959
12629
|
for (let i = 0; i < 6; i++) {
|
|
11960
|
-
const candidate =
|
|
12630
|
+
const candidate = join4(dir, "package.json");
|
|
11961
12631
|
try {
|
|
11962
|
-
const text = await
|
|
12632
|
+
const text = await readFile21(candidate, "utf-8");
|
|
11963
12633
|
return JSON.parse(text);
|
|
11964
12634
|
} catch {
|
|
11965
|
-
dir =
|
|
12635
|
+
dir = dirname10(dir);
|
|
11966
12636
|
}
|
|
11967
12637
|
}
|
|
11968
12638
|
return null;
|
|
@@ -12011,8 +12681,8 @@ function versionGte(a, b) {
|
|
|
12011
12681
|
|
|
12012
12682
|
// src/utils/doctor/checks/structure.ts
|
|
12013
12683
|
init_fs();
|
|
12014
|
-
import { resolve as
|
|
12015
|
-
import { readdir as
|
|
12684
|
+
import { resolve as resolve37 } from "path";
|
|
12685
|
+
import { readdir as readdir13, stat as stat5 } from "fs/promises";
|
|
12016
12686
|
var CATEGORY2 = "structure";
|
|
12017
12687
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
12018
12688
|
"projects",
|
|
@@ -12031,7 +12701,7 @@ var projectsDir = {
|
|
|
12031
12701
|
category: CATEGORY2,
|
|
12032
12702
|
title: "projects/ directory exists",
|
|
12033
12703
|
async run(ctx) {
|
|
12034
|
-
const p =
|
|
12704
|
+
const p = resolve37(ctx.syntaurRoot, "projects");
|
|
12035
12705
|
if (!await fileExists(p)) {
|
|
12036
12706
|
return {
|
|
12037
12707
|
id: this.id,
|
|
@@ -12056,7 +12726,7 @@ var playbooksDir2 = {
|
|
|
12056
12726
|
category: CATEGORY2,
|
|
12057
12727
|
title: "playbooks/ directory exists",
|
|
12058
12728
|
async run(ctx) {
|
|
12059
|
-
const p =
|
|
12729
|
+
const p = resolve37(ctx.syntaurRoot, "playbooks");
|
|
12060
12730
|
if (!await fileExists(p)) {
|
|
12061
12731
|
return {
|
|
12062
12732
|
id: this.id,
|
|
@@ -12081,7 +12751,7 @@ var todosDirValid = {
|
|
|
12081
12751
|
category: CATEGORY2,
|
|
12082
12752
|
title: "todos/ directory is readable (if present)",
|
|
12083
12753
|
async run(ctx) {
|
|
12084
|
-
const p =
|
|
12754
|
+
const p = resolve37(ctx.syntaurRoot, "todos");
|
|
12085
12755
|
if (!await fileExists(p)) {
|
|
12086
12756
|
return {
|
|
12087
12757
|
id: this.id,
|
|
@@ -12092,7 +12762,7 @@ var todosDirValid = {
|
|
|
12092
12762
|
autoFixable: false
|
|
12093
12763
|
};
|
|
12094
12764
|
}
|
|
12095
|
-
const s = await
|
|
12765
|
+
const s = await stat5(p);
|
|
12096
12766
|
if (!s.isDirectory()) {
|
|
12097
12767
|
return {
|
|
12098
12768
|
id: this.id,
|
|
@@ -12112,7 +12782,7 @@ var serversDirValid = {
|
|
|
12112
12782
|
category: CATEGORY2,
|
|
12113
12783
|
title: "servers/ directory is readable (if present)",
|
|
12114
12784
|
async run(ctx) {
|
|
12115
|
-
const p =
|
|
12785
|
+
const p = resolve37(ctx.syntaurRoot, "servers");
|
|
12116
12786
|
if (!await fileExists(p)) {
|
|
12117
12787
|
return {
|
|
12118
12788
|
id: this.id,
|
|
@@ -12123,7 +12793,7 @@ var serversDirValid = {
|
|
|
12123
12793
|
autoFixable: false
|
|
12124
12794
|
};
|
|
12125
12795
|
}
|
|
12126
|
-
const s = await
|
|
12796
|
+
const s = await stat5(p);
|
|
12127
12797
|
if (!s.isDirectory()) {
|
|
12128
12798
|
return {
|
|
12129
12799
|
id: this.id,
|
|
@@ -12143,7 +12813,7 @@ var knownFilesRecognized = {
|
|
|
12143
12813
|
category: CATEGORY2,
|
|
12144
12814
|
title: "No unexpected top-level entries under ~/.syntaur/",
|
|
12145
12815
|
async run(ctx) {
|
|
12146
|
-
const entries = await
|
|
12816
|
+
const entries = await readdir13(ctx.syntaurRoot, { withFileTypes: true });
|
|
12147
12817
|
const unexpected = [];
|
|
12148
12818
|
for (const e of entries) {
|
|
12149
12819
|
if (e.name.startsWith(".")) continue;
|
|
@@ -12157,7 +12827,7 @@ var knownFilesRecognized = {
|
|
|
12157
12827
|
title: this.title,
|
|
12158
12828
|
status: "warn",
|
|
12159
12829
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
12160
|
-
affected: unexpected.map((n) =>
|
|
12830
|
+
affected: unexpected.map((n) => resolve37(ctx.syntaurRoot, n)),
|
|
12161
12831
|
remediation: {
|
|
12162
12832
|
kind: "manual",
|
|
12163
12833
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -12186,8 +12856,8 @@ function pass2(check) {
|
|
|
12186
12856
|
|
|
12187
12857
|
// src/utils/doctor/checks/project.ts
|
|
12188
12858
|
init_fs();
|
|
12189
|
-
import { resolve as
|
|
12190
|
-
import { readdir as
|
|
12859
|
+
import { resolve as resolve38 } from "path";
|
|
12860
|
+
import { readdir as readdir14, stat as stat6 } from "fs/promises";
|
|
12191
12861
|
var CATEGORY3 = "project";
|
|
12192
12862
|
var REQUIRED_PROJECT_FILES = [
|
|
12193
12863
|
"project.md",
|
|
@@ -12211,15 +12881,15 @@ var PROJECT_MARKERS = ["project.md", "manifest.md", "assignments"];
|
|
|
12211
12881
|
async function listProjects2(ctx) {
|
|
12212
12882
|
const dir = ctx.config.defaultProjectDir;
|
|
12213
12883
|
if (!await fileExists(dir)) return [];
|
|
12214
|
-
const entries = await
|
|
12884
|
+
const entries = await readdir14(dir, { withFileTypes: true });
|
|
12215
12885
|
const result = [];
|
|
12216
12886
|
for (const e of entries) {
|
|
12217
12887
|
if (!e.isDirectory()) continue;
|
|
12218
12888
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
12219
|
-
const projectDir =
|
|
12889
|
+
const projectDir = resolve38(dir, e.name);
|
|
12220
12890
|
let looksLikeProject = false;
|
|
12221
12891
|
for (const marker of PROJECT_MARKERS) {
|
|
12222
|
-
if (await fileExists(
|
|
12892
|
+
if (await fileExists(resolve38(projectDir, marker))) {
|
|
12223
12893
|
looksLikeProject = true;
|
|
12224
12894
|
break;
|
|
12225
12895
|
}
|
|
@@ -12238,7 +12908,7 @@ var requiredFiles = {
|
|
|
12238
12908
|
for (const projectDir of projects) {
|
|
12239
12909
|
const missing = [];
|
|
12240
12910
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
12241
|
-
const p =
|
|
12911
|
+
const p = resolve38(projectDir, rel);
|
|
12242
12912
|
if (!await fileExists(p)) missing.push(rel);
|
|
12243
12913
|
}
|
|
12244
12914
|
if (missing.length === 0) continue;
|
|
@@ -12248,7 +12918,7 @@ var requiredFiles = {
|
|
|
12248
12918
|
title: this.title,
|
|
12249
12919
|
status: "error",
|
|
12250
12920
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
12251
|
-
affected: missing.map((m) =>
|
|
12921
|
+
affected: missing.map((m) => resolve38(projectDir, m)),
|
|
12252
12922
|
remediation: {
|
|
12253
12923
|
kind: "manual",
|
|
12254
12924
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -12271,9 +12941,9 @@ var manifestStale = {
|
|
|
12271
12941
|
const projects = await listProjects2(ctx);
|
|
12272
12942
|
const results = [];
|
|
12273
12943
|
for (const projectDir of projects) {
|
|
12274
|
-
const manifestPath =
|
|
12944
|
+
const manifestPath = resolve38(projectDir, "manifest.md");
|
|
12275
12945
|
if (!await fileExists(manifestPath)) continue;
|
|
12276
|
-
const manifestMtime = (await
|
|
12946
|
+
const manifestMtime = (await stat6(manifestPath)).mtimeMs;
|
|
12277
12947
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
12278
12948
|
if (newestAssignment === 0) continue;
|
|
12279
12949
|
if (newestAssignment > manifestMtime) {
|
|
@@ -12305,7 +12975,7 @@ var orphanFiles = {
|
|
|
12305
12975
|
const projects = await listProjects2(ctx);
|
|
12306
12976
|
const results = [];
|
|
12307
12977
|
for (const projectDir of projects) {
|
|
12308
|
-
const entries = await
|
|
12978
|
+
const entries = await readdir14(projectDir, { withFileTypes: true });
|
|
12309
12979
|
const orphans = [];
|
|
12310
12980
|
for (const e of entries) {
|
|
12311
12981
|
if (e.name.startsWith(".")) continue;
|
|
@@ -12320,7 +12990,7 @@ var orphanFiles = {
|
|
|
12320
12990
|
title: this.title,
|
|
12321
12991
|
status: "warn",
|
|
12322
12992
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
12323
|
-
affected: orphans.map((o) =>
|
|
12993
|
+
affected: orphans.map((o) => resolve38(projectDir, o)),
|
|
12324
12994
|
autoFixable: false
|
|
12325
12995
|
});
|
|
12326
12996
|
}
|
|
@@ -12330,20 +13000,20 @@ var orphanFiles = {
|
|
|
12330
13000
|
};
|
|
12331
13001
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
12332
13002
|
async function newestAssignmentMtime(projectDir) {
|
|
12333
|
-
const assignmentsRoot =
|
|
13003
|
+
const assignmentsRoot = resolve38(projectDir, "assignments");
|
|
12334
13004
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
12335
13005
|
let newest = 0;
|
|
12336
13006
|
let entries;
|
|
12337
13007
|
try {
|
|
12338
|
-
entries = await
|
|
13008
|
+
entries = await readdir14(assignmentsRoot, { withFileTypes: true });
|
|
12339
13009
|
} catch {
|
|
12340
13010
|
return 0;
|
|
12341
13011
|
}
|
|
12342
13012
|
for (const e of entries) {
|
|
12343
13013
|
if (!e.isDirectory()) continue;
|
|
12344
|
-
const assignmentMd =
|
|
13014
|
+
const assignmentMd = resolve38(assignmentsRoot, e.name, "assignment.md");
|
|
12345
13015
|
try {
|
|
12346
|
-
const s = await
|
|
13016
|
+
const s = await stat6(assignmentMd);
|
|
12347
13017
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
12348
13018
|
} catch {
|
|
12349
13019
|
}
|
|
@@ -12365,28 +13035,28 @@ init_fs();
|
|
|
12365
13035
|
init_parser();
|
|
12366
13036
|
init_types();
|
|
12367
13037
|
init_paths();
|
|
12368
|
-
import { resolve as
|
|
12369
|
-
import { readdir as
|
|
13038
|
+
import { resolve as resolve39 } from "path";
|
|
13039
|
+
import { readdir as readdir15, readFile as readFile22 } from "fs/promises";
|
|
12370
13040
|
var CATEGORY4 = "assignment";
|
|
12371
13041
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
12372
13042
|
async function listAssignments(ctx) {
|
|
12373
13043
|
const result = { withAssignmentMd: [], orphanFolders: [] };
|
|
12374
13044
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12375
13045
|
if (await fileExists(projectsDir2)) {
|
|
12376
|
-
const projects = await
|
|
13046
|
+
const projects = await readdir15(projectsDir2, { withFileTypes: true });
|
|
12377
13047
|
for (const m of projects) {
|
|
12378
13048
|
if (!m.isDirectory()) continue;
|
|
12379
13049
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
12380
|
-
const assignmentsDir2 =
|
|
13050
|
+
const assignmentsDir2 = resolve39(projectsDir2, m.name, "assignments");
|
|
12381
13051
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
12382
|
-
const entries = await
|
|
13052
|
+
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
12383
13053
|
for (const a of entries) {
|
|
12384
13054
|
if (!a.isDirectory()) continue;
|
|
12385
13055
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12386
|
-
const assignmentDir =
|
|
12387
|
-
const assignmentMd =
|
|
13056
|
+
const assignmentDir = resolve39(assignmentsDir2, a.name);
|
|
13057
|
+
const assignmentMd = resolve39(assignmentDir, "assignment.md");
|
|
12388
13058
|
const entry = {
|
|
12389
|
-
projectDir:
|
|
13059
|
+
projectDir: resolve39(projectsDir2, m.name),
|
|
12390
13060
|
projectSlug: m.name,
|
|
12391
13061
|
assignmentDir,
|
|
12392
13062
|
assignmentSlug: a.name,
|
|
@@ -12402,12 +13072,12 @@ async function listAssignments(ctx) {
|
|
|
12402
13072
|
}
|
|
12403
13073
|
const standaloneRoot = assignmentsDir();
|
|
12404
13074
|
if (await fileExists(standaloneRoot)) {
|
|
12405
|
-
const entries = await
|
|
13075
|
+
const entries = await readdir15(standaloneRoot, { withFileTypes: true });
|
|
12406
13076
|
for (const a of entries) {
|
|
12407
13077
|
if (!a.isDirectory()) continue;
|
|
12408
13078
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
12409
|
-
const assignmentDir =
|
|
12410
|
-
const assignmentMd =
|
|
13079
|
+
const assignmentDir = resolve39(standaloneRoot, a.name);
|
|
13080
|
+
const assignmentMd = resolve39(assignmentDir, "assignment.md");
|
|
12411
13081
|
const entry = {
|
|
12412
13082
|
projectDir: standaloneRoot,
|
|
12413
13083
|
projectSlug: null,
|
|
@@ -12485,7 +13155,7 @@ var invalidStatus = {
|
|
|
12485
13155
|
const allowed = configuredStatuses(ctx);
|
|
12486
13156
|
const results = [];
|
|
12487
13157
|
for (const a of withAssignmentMd) {
|
|
12488
|
-
const path =
|
|
13158
|
+
const path = resolve39(a.assignmentDir, "assignment.md");
|
|
12489
13159
|
const parsed = await parseSafe(path);
|
|
12490
13160
|
if (!parsed) continue;
|
|
12491
13161
|
if (!allowed.has(parsed.status)) {
|
|
@@ -12518,7 +13188,7 @@ var workspaceMissing = {
|
|
|
12518
13188
|
const terminal = terminalStatuses(ctx);
|
|
12519
13189
|
const results = [];
|
|
12520
13190
|
for (const a of withAssignmentMd) {
|
|
12521
|
-
const path =
|
|
13191
|
+
const path = resolve39(a.assignmentDir, "assignment.md");
|
|
12522
13192
|
const parsed = await parseSafe(path);
|
|
12523
13193
|
if (!parsed) continue;
|
|
12524
13194
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -12565,12 +13235,12 @@ var requiredFilesByStatus = {
|
|
|
12565
13235
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12566
13236
|
const results = [];
|
|
12567
13237
|
for (const a of withAssignmentMd) {
|
|
12568
|
-
const assignmentPath =
|
|
13238
|
+
const assignmentPath = resolve39(a.assignmentDir, "assignment.md");
|
|
12569
13239
|
const parsed = await parseSafe(assignmentPath);
|
|
12570
13240
|
if (!parsed) continue;
|
|
12571
13241
|
const missing = [];
|
|
12572
13242
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
12573
|
-
const handoffPath =
|
|
13243
|
+
const handoffPath = resolve39(a.assignmentDir, "handoff.md");
|
|
12574
13244
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
12575
13245
|
}
|
|
12576
13246
|
if (missing.length === 0) continue;
|
|
@@ -12580,7 +13250,7 @@ var requiredFilesByStatus = {
|
|
|
12580
13250
|
title: this.title,
|
|
12581
13251
|
status: "warn",
|
|
12582
13252
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
12583
|
-
affected: missing.map((m) =>
|
|
13253
|
+
affected: missing.map((m) => resolve39(a.assignmentDir, m)),
|
|
12584
13254
|
remediation: {
|
|
12585
13255
|
kind: "manual",
|
|
12586
13256
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -12603,7 +13273,7 @@ var companionFilesScaffolded = {
|
|
|
12603
13273
|
for (const a of withAssignmentMd) {
|
|
12604
13274
|
const missing = [];
|
|
12605
13275
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
12606
|
-
if (!await fileExists(
|
|
13276
|
+
if (!await fileExists(resolve39(a.assignmentDir, filename))) {
|
|
12607
13277
|
missing.push(filename);
|
|
12608
13278
|
}
|
|
12609
13279
|
}
|
|
@@ -12615,7 +13285,7 @@ var companionFilesScaffolded = {
|
|
|
12615
13285
|
title: this.title,
|
|
12616
13286
|
status: "warn",
|
|
12617
13287
|
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) =>
|
|
13288
|
+
affected: missing.map((m) => resolve39(a.assignmentDir, m)),
|
|
12619
13289
|
remediation: {
|
|
12620
13290
|
kind: "manual",
|
|
12621
13291
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -12648,7 +13318,7 @@ var typeDefinition = {
|
|
|
12648
13318
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12649
13319
|
const results = [];
|
|
12650
13320
|
for (const a of withAssignmentMd) {
|
|
12651
|
-
const path =
|
|
13321
|
+
const path = resolve39(a.assignmentDir, "assignment.md");
|
|
12652
13322
|
const parsed = await parseSafe(path);
|
|
12653
13323
|
if (!parsed) continue;
|
|
12654
13324
|
if (!parsed.type) continue;
|
|
@@ -12682,7 +13352,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
12682
13352
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
12683
13353
|
const results = [];
|
|
12684
13354
|
for (const a of withAssignmentMd) {
|
|
12685
|
-
const path =
|
|
13355
|
+
const path = resolve39(a.assignmentDir, "assignment.md");
|
|
12686
13356
|
const parsed = await parseSafe(path);
|
|
12687
13357
|
if (!parsed) continue;
|
|
12688
13358
|
if (a.standalone) {
|
|
@@ -12737,7 +13407,7 @@ var assignmentChecks = [
|
|
|
12737
13407
|
];
|
|
12738
13408
|
async function parseSafe(path) {
|
|
12739
13409
|
try {
|
|
12740
|
-
const content = await
|
|
13410
|
+
const content = await readFile22(path, "utf-8");
|
|
12741
13411
|
return parseAssignmentFull(content);
|
|
12742
13412
|
} catch {
|
|
12743
13413
|
return null;
|
|
@@ -12756,7 +13426,7 @@ function pass4(check, detail) {
|
|
|
12756
13426
|
|
|
12757
13427
|
// src/utils/doctor/checks/dashboard.ts
|
|
12758
13428
|
init_fs();
|
|
12759
|
-
import { resolve as
|
|
13429
|
+
import { resolve as resolve40 } from "path";
|
|
12760
13430
|
var CATEGORY5 = "dashboard";
|
|
12761
13431
|
var dbReachable = {
|
|
12762
13432
|
id: "dashboard.db-reachable",
|
|
@@ -12770,7 +13440,7 @@ var dbReachable = {
|
|
|
12770
13440
|
title: this.title,
|
|
12771
13441
|
status: "error",
|
|
12772
13442
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
12773
|
-
affected: [
|
|
13443
|
+
affected: [resolve40(ctx.syntaurRoot, "syntaur.db")],
|
|
12774
13444
|
remediation: {
|
|
12775
13445
|
kind: "manual",
|
|
12776
13446
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -12788,7 +13458,7 @@ var dbReachable = {
|
|
|
12788
13458
|
title: this.title,
|
|
12789
13459
|
status: "error",
|
|
12790
13460
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
12791
|
-
affected: [
|
|
13461
|
+
affected: [resolve40(ctx.syntaurRoot, "syntaur.db")],
|
|
12792
13462
|
autoFixable: false
|
|
12793
13463
|
};
|
|
12794
13464
|
}
|
|
@@ -12800,7 +13470,7 @@ var dbReachable = {
|
|
|
12800
13470
|
title: this.title,
|
|
12801
13471
|
status: "error",
|
|
12802
13472
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
12803
|
-
affected: [
|
|
13473
|
+
affected: [resolve40(ctx.syntaurRoot, "syntaur.db")],
|
|
12804
13474
|
autoFixable: false
|
|
12805
13475
|
};
|
|
12806
13476
|
}
|
|
@@ -12826,7 +13496,7 @@ var ghostSessions = {
|
|
|
12826
13496
|
const results = [];
|
|
12827
13497
|
for (const row of rows) {
|
|
12828
13498
|
if (!row.project_slug) continue;
|
|
12829
|
-
const projectPath =
|
|
13499
|
+
const projectPath = resolve40(projectsDir2, row.project_slug, "project.md");
|
|
12830
13500
|
if (!await fileExists(projectPath)) {
|
|
12831
13501
|
results.push({
|
|
12832
13502
|
id: this.id,
|
|
@@ -12845,7 +13515,7 @@ var ghostSessions = {
|
|
|
12845
13515
|
continue;
|
|
12846
13516
|
}
|
|
12847
13517
|
if (row.assignment_slug) {
|
|
12848
|
-
const assignmentPath =
|
|
13518
|
+
const assignmentPath = resolve40(
|
|
12849
13519
|
projectsDir2,
|
|
12850
13520
|
row.project_slug,
|
|
12851
13521
|
"assignments",
|
|
@@ -12897,7 +13567,7 @@ function skipped(check, reason) {
|
|
|
12897
13567
|
|
|
12898
13568
|
// src/utils/doctor/checks/integrations.ts
|
|
12899
13569
|
init_fs();
|
|
12900
|
-
import { readdir as
|
|
13570
|
+
import { readdir as readdir16 } from "fs/promises";
|
|
12901
13571
|
var CATEGORY6 = "integrations";
|
|
12902
13572
|
var claudePluginLinked = {
|
|
12903
13573
|
id: "integrations.claude-plugin-linked",
|
|
@@ -12959,7 +13629,7 @@ var backupConfigured = {
|
|
|
12959
13629
|
if (ctx.config.backup?.repo) return pass6(this);
|
|
12960
13630
|
const projectsDir2 = ctx.config.defaultProjectDir;
|
|
12961
13631
|
if (!await fileExists(projectsDir2)) return skipped2(this, "no projects dir");
|
|
12962
|
-
const entries = await
|
|
13632
|
+
const entries = await readdir16(projectsDir2, { withFileTypes: true });
|
|
12963
13633
|
const hasProjects = entries.some((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_"));
|
|
12964
13634
|
if (!hasProjects) return skipped2(this, "no projects yet");
|
|
12965
13635
|
return {
|
|
@@ -13002,8 +13672,8 @@ function skipped2(check, reason) {
|
|
|
13002
13672
|
init_fs();
|
|
13003
13673
|
init_parser();
|
|
13004
13674
|
init_types();
|
|
13005
|
-
import { resolve as
|
|
13006
|
-
import { readFile as
|
|
13675
|
+
import { resolve as resolve41 } from "path";
|
|
13676
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
13007
13677
|
var CATEGORY7 = "workspace";
|
|
13008
13678
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
13009
13679
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -13015,12 +13685,12 @@ function isStandaloneSession(ctx) {
|
|
|
13015
13685
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
13016
13686
|
}
|
|
13017
13687
|
async function loadContext(ctx) {
|
|
13018
|
-
const path =
|
|
13688
|
+
const path = resolve41(ctx.cwd, ".syntaur", "context.json");
|
|
13019
13689
|
if (!await fileExists(path)) {
|
|
13020
13690
|
return { data: null, path, exists: false, parseError: null };
|
|
13021
13691
|
}
|
|
13022
13692
|
try {
|
|
13023
|
-
const raw = await
|
|
13693
|
+
const raw = await readFile23(path, "utf-8");
|
|
13024
13694
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
13025
13695
|
} catch (err2) {
|
|
13026
13696
|
return {
|
|
@@ -13095,7 +13765,7 @@ var contextAssignmentResolves = {
|
|
|
13095
13765
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
13096
13766
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
13097
13767
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
13098
|
-
const assignmentMd =
|
|
13768
|
+
const assignmentMd = resolve41(data.assignmentDir, "assignment.md");
|
|
13099
13769
|
if (!await fileExists(assignmentMd)) {
|
|
13100
13770
|
return {
|
|
13101
13771
|
id: this.id,
|
|
@@ -13124,10 +13794,10 @@ var contextTerminal = {
|
|
|
13124
13794
|
if (!exists) return skipped3(this, "no context to check");
|
|
13125
13795
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
13126
13796
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
13127
|
-
const assignmentMd =
|
|
13797
|
+
const assignmentMd = resolve41(data.assignmentDir, "assignment.md");
|
|
13128
13798
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
13129
13799
|
try {
|
|
13130
|
-
const content = await
|
|
13800
|
+
const content = await readFile23(assignmentMd, "utf-8");
|
|
13131
13801
|
const parsed = parseAssignmentFull(content);
|
|
13132
13802
|
const terminal = terminalStatuses2(ctx);
|
|
13133
13803
|
if (terminal.has(parsed.status)) {
|
|
@@ -13270,15 +13940,15 @@ async function finalize(checks) {
|
|
|
13270
13940
|
}
|
|
13271
13941
|
async function readVersion() {
|
|
13272
13942
|
try {
|
|
13273
|
-
const here =
|
|
13274
|
-
let dir =
|
|
13943
|
+
const here = fileURLToPath7(import.meta.url);
|
|
13944
|
+
let dir = dirname11(here);
|
|
13275
13945
|
for (let i = 0; i < 6; i++) {
|
|
13276
13946
|
try {
|
|
13277
|
-
const raw = await
|
|
13947
|
+
const raw = await readFile24(join5(dir, "package.json"), "utf-8");
|
|
13278
13948
|
const parsed = JSON.parse(raw);
|
|
13279
13949
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13280
13950
|
} catch {
|
|
13281
|
-
dir =
|
|
13951
|
+
dir = dirname11(dir);
|
|
13282
13952
|
}
|
|
13283
13953
|
}
|
|
13284
13954
|
return null;
|
|
@@ -13411,8 +14081,8 @@ var doctorCommand = new Command3("doctor").description("Diagnose Syntaur state a
|
|
|
13411
14081
|
init_paths();
|
|
13412
14082
|
init_fs();
|
|
13413
14083
|
init_config2();
|
|
13414
|
-
import { resolve as
|
|
13415
|
-
import { readFile as
|
|
14084
|
+
import { resolve as resolve42 } from "path";
|
|
14085
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
13416
14086
|
init_timestamp();
|
|
13417
14087
|
init_assignment_resolver();
|
|
13418
14088
|
function shortId() {
|
|
@@ -13444,7 +14114,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13444
14114
|
if (!isValidSlug(target)) {
|
|
13445
14115
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13446
14116
|
}
|
|
13447
|
-
assignmentDir =
|
|
14117
|
+
assignmentDir = resolve42(baseDir, options.project, "assignments", target);
|
|
13448
14118
|
assignmentRef = target;
|
|
13449
14119
|
} else {
|
|
13450
14120
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13454,13 +14124,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
13454
14124
|
assignmentDir = resolved.assignmentDir;
|
|
13455
14125
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13456
14126
|
}
|
|
13457
|
-
const commentsPath =
|
|
14127
|
+
const commentsPath = resolve42(assignmentDir, "comments.md");
|
|
13458
14128
|
const timestamp = nowTimestamp();
|
|
13459
14129
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
13460
14130
|
let currentContent;
|
|
13461
14131
|
let currentCount = 0;
|
|
13462
14132
|
if (await fileExists(commentsPath)) {
|
|
13463
|
-
currentContent = await
|
|
14133
|
+
currentContent = await readFile25(commentsPath, "utf-8");
|
|
13464
14134
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
13465
14135
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
13466
14136
|
} else {
|
|
@@ -13497,8 +14167,8 @@ ${entry}`;
|
|
|
13497
14167
|
init_paths();
|
|
13498
14168
|
init_fs();
|
|
13499
14169
|
init_config2();
|
|
13500
|
-
import { resolve as
|
|
13501
|
-
import { readFile as
|
|
14170
|
+
import { resolve as resolve43 } from "path";
|
|
14171
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
13502
14172
|
init_timestamp();
|
|
13503
14173
|
init_assignment_resolver();
|
|
13504
14174
|
function setTopLevelField3(content, key, value) {
|
|
@@ -13523,7 +14193,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13523
14193
|
if (!isValidSlug(target)) {
|
|
13524
14194
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
13525
14195
|
}
|
|
13526
|
-
assignmentDir =
|
|
14196
|
+
assignmentDir = resolve43(baseDir, options.project, "assignments", target);
|
|
13527
14197
|
targetRef = target;
|
|
13528
14198
|
} else {
|
|
13529
14199
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -13533,12 +14203,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
13533
14203
|
assignmentDir = resolved.assignmentDir;
|
|
13534
14204
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
13535
14205
|
}
|
|
13536
|
-
const assignmentMdPath =
|
|
14206
|
+
const assignmentMdPath = resolve43(assignmentDir, "assignment.md");
|
|
13537
14207
|
if (!await fileExists(assignmentMdPath)) {
|
|
13538
14208
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
13539
14209
|
}
|
|
13540
14210
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
13541
|
-
let content = await
|
|
14211
|
+
let content = await readFile26(assignmentMdPath, "utf-8");
|
|
13542
14212
|
const todoLine = `- [ ] ${text.trim()} (from: ${source})`;
|
|
13543
14213
|
const todosHeading = /^## Todos\s*$/m;
|
|
13544
14214
|
if (todosHeading.test(content)) {
|
|
@@ -13566,10 +14236,10 @@ ${todoLine}
|
|
|
13566
14236
|
|
|
13567
14237
|
// src/cli-default-command.ts
|
|
13568
14238
|
init_config2();
|
|
13569
|
-
import { readdir as
|
|
14239
|
+
import { readdir as readdir17 } from "fs/promises";
|
|
13570
14240
|
async function hasAnyProjectContent(projectsDir2) {
|
|
13571
14241
|
try {
|
|
13572
|
-
const entries = await
|
|
14242
|
+
const entries = await readdir17(projectsDir2, { withFileTypes: true });
|
|
13573
14243
|
return entries.some((entry) => entry.isDirectory());
|
|
13574
14244
|
} catch {
|
|
13575
14245
|
return false;
|
|
@@ -13605,21 +14275,21 @@ async function getDefaultCommandName() {
|
|
|
13605
14275
|
// src/utils/npx-prompt.ts
|
|
13606
14276
|
init_paths();
|
|
13607
14277
|
init_fs();
|
|
13608
|
-
import { fileURLToPath as
|
|
13609
|
-
import { readFile as
|
|
13610
|
-
import { dirname as
|
|
14278
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
14279
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
14280
|
+
import { dirname as dirname13, join as join7, resolve as resolve44 } from "path";
|
|
13611
14281
|
import { spawn as spawn3 } from "child_process";
|
|
13612
14282
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
13613
14283
|
|
|
13614
14284
|
// src/utils/version.ts
|
|
13615
|
-
import { fileURLToPath as
|
|
13616
|
-
import { readFile as
|
|
13617
|
-
import { dirname as
|
|
14285
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
14286
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
14287
|
+
import { dirname as dirname12, join as join6 } from "path";
|
|
13618
14288
|
async function readPackageVersion(scriptUrl) {
|
|
13619
14289
|
try {
|
|
13620
|
-
const scriptPath =
|
|
13621
|
-
const pkgRoot =
|
|
13622
|
-
const raw = await
|
|
14290
|
+
const scriptPath = fileURLToPath8(scriptUrl);
|
|
14291
|
+
const pkgRoot = dirname12(dirname12(scriptPath));
|
|
14292
|
+
const raw = await readFile27(join6(pkgRoot, "package.json"), "utf-8");
|
|
13623
14293
|
const parsed = JSON.parse(raw);
|
|
13624
14294
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13625
14295
|
} catch {
|
|
@@ -13628,13 +14298,13 @@ async function readPackageVersion(scriptUrl) {
|
|
|
13628
14298
|
}
|
|
13629
14299
|
|
|
13630
14300
|
// src/utils/npx-prompt.ts
|
|
13631
|
-
var STATE_FILE =
|
|
14301
|
+
var STATE_FILE = resolve44(syntaurRoot(), "npx-install.json");
|
|
13632
14302
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
13633
14303
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
13634
14304
|
function isRunningViaNpx(scriptUrl) {
|
|
13635
14305
|
let scriptPath;
|
|
13636
14306
|
try {
|
|
13637
|
-
scriptPath =
|
|
14307
|
+
scriptPath = fileURLToPath9(scriptUrl);
|
|
13638
14308
|
} catch {
|
|
13639
14309
|
return false;
|
|
13640
14310
|
}
|
|
@@ -13649,7 +14319,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
13649
14319
|
async function readState() {
|
|
13650
14320
|
if (!await fileExists(STATE_FILE)) return null;
|
|
13651
14321
|
try {
|
|
13652
|
-
const raw = await
|
|
14322
|
+
const raw = await readFile28(STATE_FILE, "utf-8");
|
|
13653
14323
|
return JSON.parse(raw);
|
|
13654
14324
|
} catch {
|
|
13655
14325
|
return null;
|
|
@@ -13660,10 +14330,10 @@ async function writeState(state) {
|
|
|
13660
14330
|
`);
|
|
13661
14331
|
}
|
|
13662
14332
|
async function resolveNpmBin() {
|
|
13663
|
-
const nodeDir =
|
|
14333
|
+
const nodeDir = dirname13(process.execPath);
|
|
13664
14334
|
const isWin = process.platform === "win32";
|
|
13665
14335
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
13666
|
-
const nearNode =
|
|
14336
|
+
const nearNode = join7(nodeDir, npmName);
|
|
13667
14337
|
if (await fileExists(nearNode)) {
|
|
13668
14338
|
return { cmd: nearNode, shell: false };
|
|
13669
14339
|
}
|
|
@@ -13706,9 +14376,9 @@ async function readGlobalVersion() {
|
|
|
13706
14376
|
});
|
|
13707
14377
|
if (!rootPath) return null;
|
|
13708
14378
|
try {
|
|
13709
|
-
const manifestPath =
|
|
14379
|
+
const manifestPath = join7(rootPath, "syntaur", "package.json");
|
|
13710
14380
|
if (!await fileExists(manifestPath)) return null;
|
|
13711
|
-
const raw = await
|
|
14381
|
+
const raw = await readFile28(manifestPath, "utf-8");
|
|
13712
14382
|
const parsed = JSON.parse(raw);
|
|
13713
14383
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
13714
14384
|
} catch {
|
|
@@ -14003,7 +14673,7 @@ program.command("setup").description("Initialize Syntaur and optionally install
|
|
|
14003
14673
|
process.exit(1);
|
|
14004
14674
|
}
|
|
14005
14675
|
});
|
|
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) => {
|
|
14676
|
+
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
14677
|
try {
|
|
14008
14678
|
await installPluginCommand({ ...options, promptForTarget: true });
|
|
14009
14679
|
} catch (error) {
|
|
@@ -14014,7 +14684,61 @@ program.command("install-plugin").description("Install the Syntaur Claude Code p
|
|
|
14014
14684
|
process.exit(1);
|
|
14015
14685
|
}
|
|
14016
14686
|
});
|
|
14017
|
-
program.command("install-
|
|
14687
|
+
program.command("install-statusline").description(
|
|
14688
|
+
"Install the syntaur statusLine for Claude Code. Augments ~/.claude/settings.json; wraps any existing script by default."
|
|
14689
|
+
).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) => {
|
|
14690
|
+
try {
|
|
14691
|
+
const rawMode = (options.mode ?? "ask").toLowerCase();
|
|
14692
|
+
const valid = ["replace", "wrap", "skip", "ask"];
|
|
14693
|
+
if (!valid.includes(rawMode)) {
|
|
14694
|
+
throw new Error(
|
|
14695
|
+
`Invalid --mode "${rawMode}". Must be one of: ${valid.join(", ")}.`
|
|
14696
|
+
);
|
|
14697
|
+
}
|
|
14698
|
+
await installStatuslineCommand({
|
|
14699
|
+
mode: rawMode,
|
|
14700
|
+
link: options.link
|
|
14701
|
+
});
|
|
14702
|
+
} catch (error) {
|
|
14703
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14704
|
+
process.exit(1);
|
|
14705
|
+
}
|
|
14706
|
+
});
|
|
14707
|
+
program.command("uninstall-statusline").description(
|
|
14708
|
+
"Remove the syntaur statusLine. Restores the previously configured command from backup if present."
|
|
14709
|
+
).option("--keep-script", "Leave ~/.syntaur/statusline.sh on disk (only edit settings.json)").action(async (options) => {
|
|
14710
|
+
try {
|
|
14711
|
+
await uninstallStatuslineCommand({ keepScript: options.keepScript });
|
|
14712
|
+
} catch (error) {
|
|
14713
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14714
|
+
process.exit(1);
|
|
14715
|
+
}
|
|
14716
|
+
});
|
|
14717
|
+
program.command("configure-statusline").description(
|
|
14718
|
+
"Configure which segments (git, assignment, session, model, ctx, cwd, wrap) appear in the syntaur statusLine and in what order."
|
|
14719
|
+
).option(
|
|
14720
|
+
"--preset <name>",
|
|
14721
|
+
`Preset shortcut. Choices: ${Object.keys(PRESETS).join(", ")}.`
|
|
14722
|
+
).option(
|
|
14723
|
+
"--segments <list>",
|
|
14724
|
+
'Comma-separated segment list, e.g. "git,assignment,session,model,ctx".'
|
|
14725
|
+
).option("--separator <string>", 'Segment separator (default " \xB7 ")').option("--wrap <path>", 'Path to an external statusline script to compose as a "wrap" segment').option("--preview", "Print the resolved config and a preview line without writing").action(async (options) => {
|
|
14726
|
+
try {
|
|
14727
|
+
await configureStatuslineCommand(options);
|
|
14728
|
+
} catch (error) {
|
|
14729
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14730
|
+
process.exit(1);
|
|
14731
|
+
}
|
|
14732
|
+
});
|
|
14733
|
+
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) => {
|
|
14734
|
+
try {
|
|
14735
|
+
await uninstallSkillsCommand(options);
|
|
14736
|
+
} catch (error) {
|
|
14737
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
14738
|
+
process.exit(1);
|
|
14739
|
+
}
|
|
14740
|
+
});
|
|
14741
|
+
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
14742
|
try {
|
|
14019
14743
|
await installCodexPluginCommand({ ...options, promptForTarget: true });
|
|
14020
14744
|
} catch (error) {
|