syntaur 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +23 -0
- package/README.md +64 -24
- package/dist/index.js +632 -254
- package/dist/index.js.map +1 -1
- package/package.json +6 -8
- package/platforms/claude-code/.claude-plugin/plugin.json +15 -2
- package/{vendor/syntaur-skills → platforms/claude-code}/skills/complete-assignment/SKILL.md +0 -2
- package/{vendor/syntaur-skills → platforms/claude-code}/skills/grab-assignment/SKILL.md +2 -7
- package/{vendor/syntaur-skills → platforms/claude-code}/skills/plan-assignment/SKILL.md +1 -3
- package/{vendor/syntaur-skills → platforms/claude-code}/skills/syntaur-protocol/SKILL.md +4 -23
- package/{vendor/syntaur-skills → platforms/claude-code}/skills/syntaur-protocol/references/file-ownership.md +1 -2
- package/{vendor/syntaur-skills → platforms/claude-code}/skills/syntaur-protocol/references/protocol-summary.md +1 -6
- package/platforms/claude-code/skills/track-server/SKILL.md +49 -0
- package/platforms/codex/.codex-plugin/plugin.json +2 -2
- package/platforms/codex/skills/clear-assignment/SKILL.md +111 -0
- package/platforms/codex/skills/complete-assignment/SKILL.md +146 -0
- package/platforms/codex/skills/create-assignment/SKILL.md +73 -0
- package/platforms/codex/skills/create-project/SKILL.md +56 -0
- package/platforms/codex/skills/grab-assignment/SKILL.md +158 -0
- package/platforms/codex/skills/manage-statuses/SKILL.md +72 -0
- package/platforms/codex/skills/plan-assignment/SKILL.md +137 -0
- package/platforms/codex/skills/save-session-summary/SKILL.md +113 -0
- package/platforms/codex/skills/syntaur-protocol/SKILL.md +119 -0
- package/platforms/codex/skills/syntaur-protocol/references/file-ownership.md +67 -0
- package/platforms/codex/skills/syntaur-protocol/references/protocol-summary.md +82 -0
- package/platforms/codex/skills/track-server/SKILL.md +49 -0
- package/platforms/codex/skills/track-session/SKILL.md +63 -26
- package/skills/clear-assignment/SKILL.md +111 -0
- package/skills/complete-assignment/SKILL.md +146 -0
- package/skills/create-assignment/SKILL.md +73 -0
- package/skills/create-project/SKILL.md +56 -0
- package/skills/grab-assignment/SKILL.md +158 -0
- package/skills/manage-statuses/SKILL.md +72 -0
- package/skills/plan-assignment/SKILL.md +137 -0
- package/skills/save-session-summary/SKILL.md +113 -0
- package/skills/syntaur-protocol/SKILL.md +119 -0
- package/skills/syntaur-protocol/references/file-ownership.md +67 -0
- package/skills/syntaur-protocol/references/protocol-summary.md +82 -0
- package/skills/track-server/SKILL.md +49 -0
- package/skills/track-session/SKILL.md +86 -0
- package/scripts/postinstall-submodules.mjs +0 -40
- package/vendor/syntaur-skills/LICENSE +0 -21
- package/vendor/syntaur-skills/README.md +0 -57
- /package/{vendor/syntaur-skills → platforms/claude-code}/skills/clear-assignment/SKILL.md +0 -0
- /package/{vendor/syntaur-skills → platforms/claude-code}/skills/create-assignment/SKILL.md +0 -0
- /package/{vendor/syntaur-skills → platforms/claude-code}/skills/create-project/SKILL.md +0 -0
- /package/{vendor/syntaur-skills → platforms/claude-code}/skills/manage-statuses/SKILL.md +0 -0
- /package/{vendor/syntaur-skills → platforms/claude-code}/skills/save-session-summary/SKILL.md +0 -0
package/dist/index.js
CHANGED
|
@@ -6706,7 +6706,7 @@ __export(launch_exports, {
|
|
|
6706
6706
|
});
|
|
6707
6707
|
import { spawn as spawn2 } from "child_process";
|
|
6708
6708
|
import { mkdir as mkdir6, writeFile as writeFile9 } from "fs/promises";
|
|
6709
|
-
import { isAbsolute as isAbsolute3, resolve as
|
|
6709
|
+
import { isAbsolute as isAbsolute3, resolve as resolve33 } from "path";
|
|
6710
6710
|
function formatFallbackCwdWarning(opts) {
|
|
6711
6711
|
const missing = [];
|
|
6712
6712
|
if (!opts.worktreePath) missing.push("worktreePath");
|
|
@@ -6750,8 +6750,8 @@ async function launchAgent(options) {
|
|
|
6750
6750
|
console.error(`Assignment not found: ${projectSlug}/${assignmentSlug}`);
|
|
6751
6751
|
process.exit(1);
|
|
6752
6752
|
}
|
|
6753
|
-
const projectDir =
|
|
6754
|
-
const assignmentDir =
|
|
6753
|
+
const projectDir = resolve33(projectsDir2, projectSlug);
|
|
6754
|
+
const assignmentDir = resolve33(projectDir, "assignments", assignmentSlug);
|
|
6755
6755
|
const resolvedFromWorkspace = cwdOverride ?? detail.workspace.worktreePath ?? (detail.workspace.repository?.startsWith("/") ? detail.workspace.repository : null);
|
|
6756
6756
|
const workspaceDir = resolvedFromWorkspace ?? process.cwd();
|
|
6757
6757
|
if (!cwdOverride) {
|
|
@@ -6763,7 +6763,7 @@ async function launchAgent(options) {
|
|
|
6763
6763
|
});
|
|
6764
6764
|
if (warning) console.warn(warning);
|
|
6765
6765
|
}
|
|
6766
|
-
const contextDir =
|
|
6766
|
+
const contextDir = resolve33(workspaceDir, ".syntaur");
|
|
6767
6767
|
await mkdir6(contextDir, { recursive: true });
|
|
6768
6768
|
const context = {
|
|
6769
6769
|
projectSlug,
|
|
@@ -6776,7 +6776,7 @@ async function launchAgent(options) {
|
|
|
6776
6776
|
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6777
6777
|
};
|
|
6778
6778
|
await writeFile9(
|
|
6779
|
-
|
|
6779
|
+
resolve33(contextDir, "context.json"),
|
|
6780
6780
|
JSON.stringify(context, null, 2) + "\n"
|
|
6781
6781
|
);
|
|
6782
6782
|
const { argv, shellFallbackWarning } = buildAgentArgv(
|
|
@@ -6835,7 +6835,7 @@ __export(git_worktree_exports, {
|
|
|
6835
6835
|
removeWorktree: () => removeWorktree
|
|
6836
6836
|
});
|
|
6837
6837
|
import { spawn as spawn3 } from "child_process";
|
|
6838
|
-
import { readFile as
|
|
6838
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
6839
6839
|
function run(command, args, cwd) {
|
|
6840
6840
|
return new Promise((resolvePromise) => {
|
|
6841
6841
|
const child = spawn3(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -6879,7 +6879,7 @@ async function createWorktreeAndRecord(opts) {
|
|
|
6879
6879
|
const { assignmentPath, repository, branch, worktreePath, parentBranch } = opts;
|
|
6880
6880
|
await createWorktree({ repository, branch, worktreePath, parentBranch });
|
|
6881
6881
|
try {
|
|
6882
|
-
const content = await
|
|
6882
|
+
const content = await readFile21(assignmentPath, "utf-8");
|
|
6883
6883
|
const updated = updateAssignmentWorkspace(content, {
|
|
6884
6884
|
repository,
|
|
6885
6885
|
worktreePath,
|
|
@@ -7263,8 +7263,8 @@ async function migrateFromMarkdown(projectsDir2) {
|
|
|
7263
7263
|
return allSessions.length;
|
|
7264
7264
|
}
|
|
7265
7265
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
7266
|
-
const { readFile:
|
|
7267
|
-
const raw = await
|
|
7266
|
+
const { readFile: readFile35 } = await import("fs/promises");
|
|
7267
|
+
const raw = await readFile35(filePath, "utf-8");
|
|
7268
7268
|
const sessions = [];
|
|
7269
7269
|
const lines = raw.split("\n");
|
|
7270
7270
|
let inTable = false;
|
|
@@ -9736,8 +9736,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
9736
9736
|
router.post("/:workspace/archive", async (req, res) => {
|
|
9737
9737
|
try {
|
|
9738
9738
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
9739
|
-
const { resolve:
|
|
9740
|
-
const { readFile:
|
|
9739
|
+
const { resolve: resolve50 } = await import("path");
|
|
9740
|
+
const { readFile: readFile35 } = await import("fs/promises");
|
|
9741
9741
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
9742
9742
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
9743
9743
|
const checklist = await readChecklist(todosDir2, workspace);
|
|
@@ -9753,10 +9753,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir2) {
|
|
|
9753
9753
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
9754
9754
|
);
|
|
9755
9755
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
9756
|
-
await ensureDir(
|
|
9756
|
+
await ensureDir(resolve50(todosDir2, "archive"));
|
|
9757
9757
|
let archContent = "";
|
|
9758
9758
|
if (await fileExists(archFile)) {
|
|
9759
|
-
archContent = await
|
|
9759
|
+
archContent = await readFile35(archFile, "utf-8");
|
|
9760
9760
|
archContent = archContent.trimEnd() + "\n\n";
|
|
9761
9761
|
} else {
|
|
9762
9762
|
archContent = `---
|
|
@@ -10015,7 +10015,7 @@ workspace: ${workspace}
|
|
|
10015
10015
|
const { readConfig: readConfig3 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
10016
10016
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
10017
10017
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
10018
|
-
const { readFile:
|
|
10018
|
+
const { readFile: readFile35 } = await import("fs/promises");
|
|
10019
10019
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
10020
10020
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
10021
10021
|
let assignmentRef;
|
|
@@ -10053,7 +10053,7 @@ workspace: ${workspace}
|
|
|
10053
10053
|
if (!await fileExists2(assignmentMdPath2)) return { error: `Target assignment not found: ${assignmentMdPath2}` };
|
|
10054
10054
|
}
|
|
10055
10055
|
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
10056
|
-
let content = await
|
|
10056
|
+
let content = await readFile35(assignmentMdPath, "utf-8");
|
|
10057
10057
|
content = appendTodosToAssignmentBody2(
|
|
10058
10058
|
content,
|
|
10059
10059
|
items.map((it) => ({
|
|
@@ -12447,6 +12447,7 @@ import {
|
|
|
12447
12447
|
lstat,
|
|
12448
12448
|
readFile as readFile16,
|
|
12449
12449
|
readlink,
|
|
12450
|
+
rename as rename6,
|
|
12450
12451
|
rm as rm3,
|
|
12451
12452
|
unlink as unlink5,
|
|
12452
12453
|
writeFile as writeFile6
|
|
@@ -12653,8 +12654,29 @@ async function readClaudeMarketplaceFile(manifestPath) {
|
|
|
12653
12654
|
}
|
|
12654
12655
|
async function writeClaudeMarketplaceFile(manifestPath, marketplace) {
|
|
12655
12656
|
await ensureDir(dirname8(manifestPath));
|
|
12656
|
-
|
|
12657
|
+
if (Array.isArray(marketplace.plugins)) {
|
|
12658
|
+
marketplace.plugins = [...marketplace.plugins].sort((a, b) => {
|
|
12659
|
+
const an = a?.name ?? "";
|
|
12660
|
+
const bn = b?.name ?? "";
|
|
12661
|
+
return an.localeCompare(bn);
|
|
12662
|
+
});
|
|
12663
|
+
}
|
|
12664
|
+
if (await fileExists(manifestPath)) {
|
|
12665
|
+
try {
|
|
12666
|
+
const prev = await readFile16(manifestPath, "utf-8");
|
|
12667
|
+
JSON.parse(prev);
|
|
12668
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12669
|
+
await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
|
|
12670
|
+
} catch {
|
|
12671
|
+
throw new Error(
|
|
12672
|
+
`Refusing to overwrite ${manifestPath}: existing file is not valid JSON. Inspect and remove or repair it manually before re-running.`
|
|
12673
|
+
);
|
|
12674
|
+
}
|
|
12675
|
+
}
|
|
12676
|
+
const tmpPath = `${manifestPath}.tmp`;
|
|
12677
|
+
await writeFile6(tmpPath, `${JSON.stringify(marketplace, null, 2)}
|
|
12657
12678
|
`, "utf-8");
|
|
12679
|
+
await rename6(tmpPath, manifestPath);
|
|
12658
12680
|
}
|
|
12659
12681
|
function buildClaudeMarketplaceSourcePath(pluginTargetDir, marketplaceRootDir) {
|
|
12660
12682
|
const relPath = relative2(marketplaceRootDir, pluginTargetDir).replaceAll("\\", "/");
|
|
@@ -12787,11 +12809,25 @@ async function getPreferredClaudeMarketplace() {
|
|
|
12787
12809
|
}
|
|
12788
12810
|
async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
12789
12811
|
const manifestPath = getClaudeKnownMarketplacesPath();
|
|
12790
|
-
|
|
12791
|
-
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12812
|
+
let existing = {};
|
|
12813
|
+
if (await fileExists(manifestPath)) {
|
|
12814
|
+
const raw = await readFile16(manifestPath, "utf-8");
|
|
12815
|
+
try {
|
|
12816
|
+
const parsed = JSON.parse(raw);
|
|
12817
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
12818
|
+
existing = parsed;
|
|
12819
|
+
} else {
|
|
12820
|
+
throw new Error("not a JSON object");
|
|
12821
|
+
}
|
|
12822
|
+
} catch (err2) {
|
|
12823
|
+
throw new Error(
|
|
12824
|
+
`Refusing to update ${manifestPath}: existing file is not a valid JSON object (${err2 instanceof Error ? err2.message : String(err2)}). Inspect and repair (or delete) it before re-running install-plugin.`
|
|
12825
|
+
);
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
const had = Object.prototype.hasOwnProperty.call(existing, name);
|
|
12829
|
+
if (had && existing[name]?.installLocation === rootDir) {
|
|
12830
|
+
return { added: false, updated: false };
|
|
12795
12831
|
}
|
|
12796
12832
|
existing[name] = {
|
|
12797
12833
|
...existing[name] ?? {},
|
|
@@ -12801,8 +12837,52 @@ async function registerKnownClaudeMarketplace(name, rootDir) {
|
|
|
12801
12837
|
existing[name].lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
12802
12838
|
existing[name].autoUpdate = true;
|
|
12803
12839
|
await ensureDir(dirname8(manifestPath));
|
|
12804
|
-
await
|
|
12840
|
+
if (await fileExists(manifestPath)) {
|
|
12841
|
+
const prev = await readFile16(manifestPath, "utf-8");
|
|
12842
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12843
|
+
await writeFile6(`${manifestPath}.bak-${stamp}`, prev, "utf-8");
|
|
12844
|
+
}
|
|
12845
|
+
const tmpPath = `${manifestPath}.tmp`;
|
|
12846
|
+
await writeFile6(tmpPath, `${JSON.stringify(existing, null, 2)}
|
|
12847
|
+
`, "utf-8");
|
|
12848
|
+
await rename6(tmpPath, manifestPath);
|
|
12849
|
+
return { added: !had, updated: had };
|
|
12850
|
+
}
|
|
12851
|
+
async function ensureKnownClaudeMarketplaceForRoot(options) {
|
|
12852
|
+
return registerKnownClaudeMarketplace(options.name, options.rootDir);
|
|
12853
|
+
}
|
|
12854
|
+
async function setSyntaurPluginEnabled(options) {
|
|
12855
|
+
const settingsPath = resolve25(homedir2(), ".claude", "settings.json");
|
|
12856
|
+
const key = `syntaur@${options.marketplaceName}`;
|
|
12857
|
+
let parsed = {};
|
|
12858
|
+
if (await fileExists(settingsPath)) {
|
|
12859
|
+
const raw = await readFile16(settingsPath, "utf-8");
|
|
12860
|
+
try {
|
|
12861
|
+
parsed = JSON.parse(raw);
|
|
12862
|
+
} catch {
|
|
12863
|
+
throw new Error(
|
|
12864
|
+
`Cannot toggle plugin: ${settingsPath} is not valid JSON. Inspect and repair it before retrying.`
|
|
12865
|
+
);
|
|
12866
|
+
}
|
|
12867
|
+
}
|
|
12868
|
+
const enabledPlugins = parsed.enabledPlugins ?? {};
|
|
12869
|
+
const previous = typeof enabledPlugins[key] === "boolean" ? enabledPlugins[key] : void 0;
|
|
12870
|
+
if (previous === options.enabled) {
|
|
12871
|
+
return { key, previous, current: options.enabled, changed: false };
|
|
12872
|
+
}
|
|
12873
|
+
enabledPlugins[key] = options.enabled;
|
|
12874
|
+
parsed.enabledPlugins = enabledPlugins;
|
|
12875
|
+
await ensureDir(dirname8(settingsPath));
|
|
12876
|
+
if (await fileExists(settingsPath)) {
|
|
12877
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12878
|
+
const prev = await readFile16(settingsPath, "utf-8");
|
|
12879
|
+
await writeFile6(`${settingsPath}.bak-${stamp}`, prev, "utf-8");
|
|
12880
|
+
}
|
|
12881
|
+
const tmpPath = `${settingsPath}.tmp`;
|
|
12882
|
+
await writeFile6(tmpPath, `${JSON.stringify(parsed, null, 2)}
|
|
12805
12883
|
`, "utf-8");
|
|
12884
|
+
await rename6(tmpPath, settingsPath);
|
|
12885
|
+
return { key, previous, current: options.enabled, changed: true };
|
|
12806
12886
|
}
|
|
12807
12887
|
async function ensureClaudeUserMarketplace() {
|
|
12808
12888
|
const existing = await getPreferredClaudeMarketplace();
|
|
@@ -13269,31 +13349,63 @@ async function textPrompt(question, defaultValue) {
|
|
|
13269
13349
|
|
|
13270
13350
|
// src/utils/install-skills.ts
|
|
13271
13351
|
init_fs();
|
|
13272
|
-
import { readFile as
|
|
13273
|
-
import {
|
|
13274
|
-
import {
|
|
13352
|
+
import { readFile as readFile18, readdir as readdir11, mkdir as mkdir4, copyFile, rm as rm4, lstat as lstat2 } from "fs/promises";
|
|
13353
|
+
import { resolve as resolve27, relative as relative3, join as join3 } from "path";
|
|
13354
|
+
import { homedir as homedir4 } from "os";
|
|
13355
|
+
|
|
13356
|
+
// src/utils/plugin-state.ts
|
|
13357
|
+
init_fs();
|
|
13358
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
13359
|
+
import { resolve as resolve26 } from "path";
|
|
13275
13360
|
import { homedir as homedir3 } from "os";
|
|
13276
|
-
|
|
13361
|
+
function settingsPathFor(agent) {
|
|
13362
|
+
if (agent === "claude") return resolve26(homedir3(), ".claude", "settings.json");
|
|
13363
|
+
return null;
|
|
13364
|
+
}
|
|
13365
|
+
async function readJsonOrNull(path) {
|
|
13366
|
+
if (!path) return null;
|
|
13367
|
+
if (!await fileExists(path)) return null;
|
|
13368
|
+
try {
|
|
13369
|
+
const raw = await readFile17(path, "utf-8");
|
|
13370
|
+
return JSON.parse(raw);
|
|
13371
|
+
} catch {
|
|
13372
|
+
return null;
|
|
13373
|
+
}
|
|
13374
|
+
}
|
|
13375
|
+
async function isSyntaurPluginEnabledFor(agent) {
|
|
13376
|
+
const settings = await readJsonOrNull(settingsPathFor(agent));
|
|
13377
|
+
const enabled = settings?.enabledPlugins ?? {};
|
|
13378
|
+
for (const [key, value] of Object.entries(enabled)) {
|
|
13379
|
+
if (value !== true) continue;
|
|
13380
|
+
const atIndex = key.lastIndexOf("@");
|
|
13381
|
+
const pluginName = atIndex > 0 ? key.slice(0, atIndex) : key;
|
|
13382
|
+
if (pluginName === "syntaur") return true;
|
|
13383
|
+
}
|
|
13384
|
+
return false;
|
|
13385
|
+
}
|
|
13386
|
+
|
|
13387
|
+
// src/utils/install-skills.ts
|
|
13388
|
+
var KNOWN_SKILL_NAMES = [
|
|
13277
13389
|
"syntaur-protocol",
|
|
13278
13390
|
"grab-assignment",
|
|
13279
13391
|
"plan-assignment",
|
|
13280
13392
|
"complete-assignment",
|
|
13281
13393
|
"create-assignment",
|
|
13282
13394
|
"create-project",
|
|
13283
|
-
"
|
|
13395
|
+
"manage-statuses",
|
|
13396
|
+
"clear-assignment",
|
|
13397
|
+
"save-session-summary",
|
|
13398
|
+
"track-session",
|
|
13399
|
+
"track-server"
|
|
13284
13400
|
];
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
function getPlatformSkillsDir(target) {
|
|
13290
|
-
const here = dirname9(fileURLToPath4(import.meta.url));
|
|
13291
|
-
const kind = target === "claude" ? "claude-code" : "codex";
|
|
13292
|
-
return resolve26(here, "..", "platforms", kind, "skills");
|
|
13401
|
+
var KNOWN_SKILLS = KNOWN_SKILL_NAMES;
|
|
13402
|
+
async function getSkillsDir() {
|
|
13403
|
+
const packageRoot = await findPackageRoot("skills");
|
|
13404
|
+
return resolve27(packageRoot, "skills");
|
|
13293
13405
|
}
|
|
13294
13406
|
function defaultSkillTargetDir(target) {
|
|
13295
|
-
if (target === "claude") return
|
|
13296
|
-
return
|
|
13407
|
+
if (target === "claude") return resolve27(homedir4(), ".claude", "skills");
|
|
13408
|
+
return resolve27(homedir4(), ".codex", "skills");
|
|
13297
13409
|
}
|
|
13298
13410
|
async function walkFiles(root) {
|
|
13299
13411
|
const out = [];
|
|
@@ -13313,7 +13425,7 @@ async function walkFiles(root) {
|
|
|
13313
13425
|
}
|
|
13314
13426
|
async function filesEqual(a, b) {
|
|
13315
13427
|
try {
|
|
13316
|
-
const [ba, bb] = await Promise.all([
|
|
13428
|
+
const [ba, bb] = await Promise.all([readFile18(a), readFile18(b)]);
|
|
13317
13429
|
if (ba.length !== bb.length) return false;
|
|
13318
13430
|
return ba.equals(bb);
|
|
13319
13431
|
} catch {
|
|
@@ -13345,70 +13457,97 @@ async function skillMatches(srcDir, destDir) {
|
|
|
13345
13457
|
if (destFiles.length !== srcFiles.length) return false;
|
|
13346
13458
|
return true;
|
|
13347
13459
|
}
|
|
13348
|
-
async function
|
|
13460
|
+
async function isSymlink(path) {
|
|
13461
|
+
try {
|
|
13462
|
+
const stat6 = await lstat2(path);
|
|
13463
|
+
return stat6.isSymbolicLink();
|
|
13464
|
+
} catch {
|
|
13465
|
+
return false;
|
|
13466
|
+
}
|
|
13467
|
+
}
|
|
13468
|
+
async function installSkillDir(srcDir, destDir, skillName, force) {
|
|
13469
|
+
if (await isSymlink(destDir)) {
|
|
13470
|
+
return {
|
|
13471
|
+
skill: skillName,
|
|
13472
|
+
status: "skipped-symlink",
|
|
13473
|
+
targetPath: destDir
|
|
13474
|
+
};
|
|
13475
|
+
}
|
|
13349
13476
|
if (!await fileExists(destDir)) {
|
|
13350
13477
|
await copyDir(srcDir, destDir);
|
|
13351
|
-
return { skill: skillName, status: "installed", targetPath: destDir
|
|
13478
|
+
return { skill: skillName, status: "installed", targetPath: destDir };
|
|
13352
13479
|
}
|
|
13353
13480
|
if (await skillMatches(srcDir, destDir)) {
|
|
13354
|
-
return { skill: skillName, status: "already-current", targetPath: destDir
|
|
13481
|
+
return { skill: skillName, status: "already-current", targetPath: destDir };
|
|
13355
13482
|
}
|
|
13356
13483
|
if (force) {
|
|
13357
13484
|
await rm4(destDir, { recursive: true, force: true });
|
|
13358
13485
|
await copyDir(srcDir, destDir);
|
|
13359
|
-
return { skill: skillName, status: "overwritten", targetPath: destDir
|
|
13486
|
+
return { skill: skillName, status: "overwritten", targetPath: destDir };
|
|
13360
13487
|
}
|
|
13361
|
-
return { skill: skillName, status: "differs-preserved", targetPath: destDir
|
|
13488
|
+
return { skill: skillName, status: "differs-preserved", targetPath: destDir };
|
|
13362
13489
|
}
|
|
13363
|
-
async function
|
|
13364
|
-
const
|
|
13365
|
-
const
|
|
13490
|
+
async function discoverSkillNames(sourceDir) {
|
|
13491
|
+
const entries = await readdir11(sourceDir, { withFileTypes: true });
|
|
13492
|
+
const names = [];
|
|
13493
|
+
for (const entry of entries) {
|
|
13494
|
+
if (!entry.isDirectory()) continue;
|
|
13495
|
+
if (entry.name.startsWith(".")) continue;
|
|
13496
|
+
if (await fileExists(join3(sourceDir, entry.name, "SKILL.md"))) {
|
|
13497
|
+
names.push(entry.name);
|
|
13498
|
+
}
|
|
13499
|
+
}
|
|
13500
|
+
const pinnedSet = new Set(KNOWN_SKILL_NAMES);
|
|
13501
|
+
const pinned = KNOWN_SKILL_NAMES.filter((name) => names.includes(name));
|
|
13502
|
+
const extras = names.filter((name) => !pinnedSet.has(name)).sort();
|
|
13503
|
+
return [...pinned, ...extras];
|
|
13504
|
+
}
|
|
13505
|
+
async function installSkillsWithReport(options) {
|
|
13506
|
+
if (!options.ignorePluginActive && options.targetDir === void 0) {
|
|
13507
|
+
if (await isSyntaurPluginEnabledFor(options.target)) {
|
|
13508
|
+
return { results: [], skippedReason: "plugin-active" };
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
const source = options.sourceDir ?? await getSkillsDir();
|
|
13366
13512
|
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
13367
13513
|
const force = options.force ?? false;
|
|
13368
13514
|
if (!await fileExists(source)) {
|
|
13369
13515
|
throw new Error(
|
|
13370
|
-
`
|
|
13516
|
+
`Syntaur skills not found at ${source}. Reinstall syntaur: npm install -g syntaur@latest`
|
|
13371
13517
|
);
|
|
13372
13518
|
}
|
|
13519
|
+
const skillNames = await discoverSkillNames(source);
|
|
13373
13520
|
const results = [];
|
|
13374
13521
|
await mkdir4(targetRoot, { recursive: true });
|
|
13375
|
-
for (const skill of
|
|
13522
|
+
for (const skill of skillNames) {
|
|
13376
13523
|
const srcDir = join3(source, skill);
|
|
13377
|
-
if (!await fileExists(srcDir)) continue;
|
|
13378
13524
|
const destDir = join3(targetRoot, skill);
|
|
13379
|
-
results.push(await installSkillDir(srcDir, destDir, skill,
|
|
13525
|
+
results.push(await installSkillDir(srcDir, destDir, skill, force));
|
|
13380
13526
|
}
|
|
13381
|
-
|
|
13382
|
-
const entries = await readdir11(platformSource, { withFileTypes: true });
|
|
13383
|
-
for (const entry of entries) {
|
|
13384
|
-
if (!entry.isDirectory()) continue;
|
|
13385
|
-
const skill = entry.name;
|
|
13386
|
-
if (REQUIRED_SKILLS.includes(skill)) continue;
|
|
13387
|
-
const srcDir = join3(platformSource, skill);
|
|
13388
|
-
const destDir = join3(targetRoot, skill);
|
|
13389
|
-
results.push(await installSkillDir(srcDir, destDir, skill, "platform", force));
|
|
13390
|
-
}
|
|
13391
|
-
}
|
|
13392
|
-
return results;
|
|
13527
|
+
return { results };
|
|
13393
13528
|
}
|
|
13394
13529
|
async function uninstallSkills(options) {
|
|
13395
13530
|
const targetRoot = options.targetDir ?? defaultSkillTargetDir(options.target);
|
|
13396
13531
|
if (!await fileExists(targetRoot)) return [];
|
|
13397
|
-
const
|
|
13398
|
-
const
|
|
13399
|
-
if (
|
|
13400
|
-
const
|
|
13401
|
-
|
|
13402
|
-
|
|
13532
|
+
const sourceDir = options.sourceDir ?? await getSkillsDir();
|
|
13533
|
+
const known = /* @__PURE__ */ new Set();
|
|
13534
|
+
if (await fileExists(sourceDir)) {
|
|
13535
|
+
for (const name of await discoverSkillNames(sourceDir)) {
|
|
13536
|
+
known.add(name);
|
|
13537
|
+
}
|
|
13538
|
+
} else {
|
|
13539
|
+
for (const name of KNOWN_SKILL_NAMES) {
|
|
13540
|
+
known.add(name);
|
|
13403
13541
|
}
|
|
13404
13542
|
}
|
|
13405
13543
|
const removed = [];
|
|
13406
13544
|
for (const skill of known) {
|
|
13407
13545
|
const destDir = join3(targetRoot, skill);
|
|
13408
13546
|
if (!await fileExists(destDir)) continue;
|
|
13547
|
+
if (await isSymlink(destDir)) continue;
|
|
13409
13548
|
const skillMd = join3(destDir, "SKILL.md");
|
|
13410
13549
|
if (!await fileExists(skillMd)) continue;
|
|
13411
|
-
const content = await
|
|
13550
|
+
const content = await readFile18(skillMd, "utf-8").catch(() => "");
|
|
13412
13551
|
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
13413
13552
|
if (!match || match[1] !== skill) continue;
|
|
13414
13553
|
await rm4(destDir, { recursive: true, force: true });
|
|
@@ -13416,11 +13555,22 @@ async function uninstallSkills(options) {
|
|
|
13416
13555
|
}
|
|
13417
13556
|
return removed;
|
|
13418
13557
|
}
|
|
13419
|
-
function formatInstallReport(
|
|
13558
|
+
function formatInstallReport(resultsOrReport, target) {
|
|
13559
|
+
const report = Array.isArray(resultsOrReport) ? { results: resultsOrReport } : resultsOrReport;
|
|
13560
|
+
const { results, skippedReason } = report;
|
|
13420
13561
|
const lines = [];
|
|
13421
13562
|
lines.push(`Skill install (${target}):`);
|
|
13563
|
+
if (skippedReason === "plugin-active") {
|
|
13564
|
+
lines.push(
|
|
13565
|
+
" Skipped \u2014 syntaur plugin is enabled for this agent and already loads skills from its manifest."
|
|
13566
|
+
);
|
|
13567
|
+
lines.push(
|
|
13568
|
+
" Run with --force-skills to install globally anyway (creates duplicates with the plugin)."
|
|
13569
|
+
);
|
|
13570
|
+
return lines.join("\n");
|
|
13571
|
+
}
|
|
13422
13572
|
for (const r of results) {
|
|
13423
|
-
const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : "=";
|
|
13573
|
+
const marker = r.status === "installed" ? "+" : r.status === "overwritten" ? "!" : r.status === "differs-preserved" ? "?" : r.status === "skipped-symlink" ? "~" : "=";
|
|
13424
13574
|
lines.push(` ${marker} ${r.skill} (${r.status})`);
|
|
13425
13575
|
}
|
|
13426
13576
|
const diffs = results.filter((r) => r.status === "differs-preserved");
|
|
@@ -13430,7 +13580,14 @@ function formatInstallReport(results, target) {
|
|
|
13430
13580
|
` Note: ${diffs.length} skill(s) already exist with different content and were preserved.`
|
|
13431
13581
|
);
|
|
13432
13582
|
lines.push(
|
|
13433
|
-
" Run with --force-skills to overwrite with the
|
|
13583
|
+
" Run with --force-skills to overwrite with the syntaur version."
|
|
13584
|
+
);
|
|
13585
|
+
}
|
|
13586
|
+
const symlinked = results.filter((r) => r.status === "skipped-symlink");
|
|
13587
|
+
if (symlinked.length > 0) {
|
|
13588
|
+
lines.push("");
|
|
13589
|
+
lines.push(
|
|
13590
|
+
` Note: ${symlinked.length} skill(s) were skipped because the target is a symlink (likely managed by skills.sh).`
|
|
13434
13591
|
);
|
|
13435
13592
|
}
|
|
13436
13593
|
return lines.join("\n");
|
|
@@ -13448,11 +13605,12 @@ async function promptForInstallPath(question, recommendedPath) {
|
|
|
13448
13605
|
}
|
|
13449
13606
|
}
|
|
13450
13607
|
async function installPluginCommand(options) {
|
|
13608
|
+
const envOverride = process.env.SYNTAUR_PLUGIN_TARGET?.trim();
|
|
13451
13609
|
const shouldPromptForTarget = Boolean(
|
|
13452
|
-
options.promptForTarget !== false && isInteractiveTerminal() && !options.targetDir
|
|
13610
|
+
options.promptForTarget !== false && isInteractiveTerminal() && !options.targetDir && !envOverride
|
|
13453
13611
|
);
|
|
13454
13612
|
const recommendedTargetDir = await recommendPluginTargetDir("claude");
|
|
13455
|
-
const targetDir = options.targetDir ? normalizeAbsoluteInstallPath(options.targetDir, "Claude plugin target") : shouldPromptForTarget ? await promptForInstallPath("Claude plugin directory", recommendedTargetDir) : recommendedTargetDir;
|
|
13613
|
+
const targetDir = options.targetDir ? normalizeAbsoluteInstallPath(options.targetDir, "Claude plugin target") : envOverride ? normalizeAbsoluteInstallPath(envOverride, "SYNTAUR_PLUGIN_TARGET") : shouldPromptForTarget ? await promptForInstallPath("Claude plugin directory", recommendedTargetDir) : recommendedTargetDir;
|
|
13456
13614
|
const previousTargetDir = await getConfiguredOrLegacyManagedPluginDir("claude");
|
|
13457
13615
|
const migrating = Boolean(previousTargetDir && previousTargetDir !== targetDir);
|
|
13458
13616
|
let previousInstall = previousTargetDir ? await inspectInstallPath("claude", previousTargetDir) : null;
|
|
@@ -13485,6 +13643,7 @@ async function installPluginCommand(options) {
|
|
|
13485
13643
|
targetDir
|
|
13486
13644
|
});
|
|
13487
13645
|
const currentMarketplace = await detectClaudeMarketplaceForTarget(result.targetDir);
|
|
13646
|
+
let knownMarketplaceState = null;
|
|
13488
13647
|
if (currentMarketplace) {
|
|
13489
13648
|
await ensureClaudeMarketplaceEntry({
|
|
13490
13649
|
marketplaceRootDir: currentMarketplace.rootDir,
|
|
@@ -13492,6 +13651,10 @@ async function installPluginCommand(options) {
|
|
|
13492
13651
|
pluginTargetDir: result.targetDir,
|
|
13493
13652
|
expectedExistingPluginTargetDir: previousMarketplace && previousMarketplace.manifestPath === currentMarketplace.manifestPath ? previousTargetDir : null
|
|
13494
13653
|
});
|
|
13654
|
+
knownMarketplaceState = await ensureKnownClaudeMarketplaceForRoot({
|
|
13655
|
+
name: currentMarketplace.name,
|
|
13656
|
+
rootDir: currentMarketplace.rootDir
|
|
13657
|
+
});
|
|
13495
13658
|
} else {
|
|
13496
13659
|
console.warn(
|
|
13497
13660
|
`Warning: ${result.targetDir} is not inside a Claude Code marketplace (expected parent path of the form ~/.claude/plugins/marketplaces/<name>/plugins/syntaur). The plugin files were copied, but Claude Code will not discover them until you place them inside a marketplace.`
|
|
@@ -13515,21 +13678,49 @@ async function installPluginCommand(options) {
|
|
|
13515
13678
|
}
|
|
13516
13679
|
previousInstall = null;
|
|
13517
13680
|
}
|
|
13681
|
+
let enableResult = null;
|
|
13682
|
+
if (options.enable && currentMarketplace) {
|
|
13683
|
+
try {
|
|
13684
|
+
enableResult = await setSyntaurPluginEnabled({
|
|
13685
|
+
marketplaceName: currentMarketplace.name,
|
|
13686
|
+
enabled: true
|
|
13687
|
+
});
|
|
13688
|
+
} catch (error) {
|
|
13689
|
+
console.warn(
|
|
13690
|
+
`Warning: could not enable plugin \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
13691
|
+
);
|
|
13692
|
+
}
|
|
13693
|
+
}
|
|
13518
13694
|
console.log("Installed Syntaur plugin:");
|
|
13519
13695
|
console.log(` target: ${result.targetDir}`);
|
|
13520
13696
|
console.log(` source: ${result.sourceDir}`);
|
|
13521
13697
|
console.log(` mode: ${result.mode}`);
|
|
13522
13698
|
if (currentMarketplace) {
|
|
13523
13699
|
console.log(` marketplace: ${currentMarketplace.manifestPath}`);
|
|
13700
|
+
if (knownMarketplaceState) {
|
|
13701
|
+
const tag = knownMarketplaceState.added ? "registered (added)" : knownMarketplaceState.updated ? "registered (updated)" : "already registered";
|
|
13702
|
+
console.log(` known_marketplaces.json: ${tag}`);
|
|
13703
|
+
}
|
|
13704
|
+
const enabledKey = `syntaur@${currentMarketplace.name}`;
|
|
13705
|
+
if (enableResult) {
|
|
13706
|
+
console.log(
|
|
13707
|
+
` enabledPlugins: ${enabledKey} = ${enableResult.current}` + (enableResult.changed ? "" : " (already)")
|
|
13708
|
+
);
|
|
13709
|
+
} else {
|
|
13710
|
+
console.log(
|
|
13711
|
+
` enabledPlugins: ${enabledKey} not modified \u2014 run /plugin to enable, or pass --enable next time`
|
|
13712
|
+
);
|
|
13713
|
+
}
|
|
13524
13714
|
}
|
|
13525
13715
|
if (!options.skipSkills) {
|
|
13526
13716
|
try {
|
|
13527
|
-
const
|
|
13717
|
+
const skillReport = await installSkillsWithReport({
|
|
13528
13718
|
target: "claude",
|
|
13529
|
-
force: options.forceSkills
|
|
13719
|
+
force: options.forceSkills,
|
|
13720
|
+
ignorePluginActive: options.forceSkills
|
|
13530
13721
|
});
|
|
13531
13722
|
console.log("");
|
|
13532
|
-
console.log(formatInstallReport(
|
|
13723
|
+
console.log(formatInstallReport(skillReport, "claude"));
|
|
13533
13724
|
} catch (error) {
|
|
13534
13725
|
console.warn(
|
|
13535
13726
|
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -13537,25 +13728,24 @@ async function installPluginCommand(options) {
|
|
|
13537
13728
|
}
|
|
13538
13729
|
}
|
|
13539
13730
|
console.log("\nThe plugin is now available in Claude Code.");
|
|
13540
|
-
console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project, /track-session, /
|
|
13731
|
+
console.log(" Slash commands: /grab-assignment, /plan-assignment, /complete-assignment, /create-assignment, /create-project, /track-session, /clear-assignment, /manage-statuses");
|
|
13541
13732
|
console.log(" Background: syntaur-protocol skill (auto-invoked)");
|
|
13542
|
-
console.log("
|
|
13543
|
-
console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End + PreCompact (prompts /save-session-summary)");
|
|
13733
|
+
console.log(" Hook: write boundary enforcement (PreToolUse) + SessionStart/End");
|
|
13544
13734
|
}
|
|
13545
13735
|
|
|
13546
13736
|
// src/commands/install-statusline.ts
|
|
13547
13737
|
init_paths();
|
|
13548
13738
|
init_fs();
|
|
13549
|
-
import { readFile as
|
|
13550
|
-
import { resolve as
|
|
13551
|
-
import { homedir as
|
|
13552
|
-
import { fileURLToPath as
|
|
13739
|
+
import { readFile as readFile20, writeFile as writeFile8, copyFile as copyFile2, rm as rm5, stat as stat2, symlink as symlink2, unlink as unlink6, lstat as lstat3 } from "fs/promises";
|
|
13740
|
+
import { resolve as resolve29, dirname as dirname10 } from "path";
|
|
13741
|
+
import { homedir as homedir5 } from "os";
|
|
13742
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13553
13743
|
|
|
13554
13744
|
// src/commands/configure-statusline.ts
|
|
13555
13745
|
init_paths();
|
|
13556
13746
|
init_fs();
|
|
13557
|
-
import { readFile as
|
|
13558
|
-
import { resolve as
|
|
13747
|
+
import { readFile as readFile19, writeFile as writeFile7 } from "fs/promises";
|
|
13748
|
+
import { resolve as resolve28, dirname as dirname9 } from "path";
|
|
13559
13749
|
import { spawnSync } from "child_process";
|
|
13560
13750
|
import { checkbox, input as input2, confirm } from "@inquirer/prompts";
|
|
13561
13751
|
var AVAILABLE_SEGMENTS = [
|
|
@@ -13576,12 +13766,12 @@ var PRESETS = {
|
|
|
13576
13766
|
tracker: { segments: ["git", "assignment", "external", "session"], separator: " \xB7 " }
|
|
13577
13767
|
};
|
|
13578
13768
|
function getConfigPath(installRoot) {
|
|
13579
|
-
return
|
|
13769
|
+
return resolve28(installRoot, "statusline.config.json");
|
|
13580
13770
|
}
|
|
13581
13771
|
async function readConfig2(path) {
|
|
13582
13772
|
if (!await fileExists(path)) return null;
|
|
13583
13773
|
try {
|
|
13584
|
-
const raw = await
|
|
13774
|
+
const raw = await readFile19(path, "utf-8");
|
|
13585
13775
|
const parsed = JSON.parse(raw);
|
|
13586
13776
|
if (!parsed || typeof parsed !== "object") return null;
|
|
13587
13777
|
const segments = Array.isArray(parsed.segments) ? parsed.segments.filter(isSegmentName) : [];
|
|
@@ -13676,7 +13866,7 @@ function renderPreview(config, statuslineScript, cwd) {
|
|
|
13676
13866
|
env: {
|
|
13677
13867
|
...process.env,
|
|
13678
13868
|
// Force the child to pick up the freshly-written config from install root.
|
|
13679
|
-
HOME:
|
|
13869
|
+
HOME: dirname9(dirname9(statuslineScript))
|
|
13680
13870
|
}
|
|
13681
13871
|
});
|
|
13682
13872
|
if (res.status !== 0) return null;
|
|
@@ -13727,14 +13917,14 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
13727
13917
|
console.log("(preview mode \u2014 config NOT written)");
|
|
13728
13918
|
return;
|
|
13729
13919
|
}
|
|
13730
|
-
await ensureDir(
|
|
13920
|
+
await ensureDir(dirname9(configPath));
|
|
13731
13921
|
await writeFile7(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
13732
13922
|
console.log("Wrote statusline config:");
|
|
13733
13923
|
console.log(` path: ${configPath}`);
|
|
13734
13924
|
console.log(` segments: ${config.segments.join(", ")}`);
|
|
13735
13925
|
console.log(` separator: ${JSON.stringify(config.separator)}`);
|
|
13736
13926
|
if (config.wrap) console.log(` wrap: ${config.wrap}`);
|
|
13737
|
-
const script = options.statuslineScript ??
|
|
13927
|
+
const script = options.statuslineScript ?? resolve28(installRoot, "statusline.sh");
|
|
13738
13928
|
if (await fileExists(script)) {
|
|
13739
13929
|
console.log("");
|
|
13740
13930
|
console.log("Live preview:");
|
|
@@ -13754,7 +13944,7 @@ async function configureStatuslineCommand(options = {}) {
|
|
|
13754
13944
|
async function writeDefaultConfigIfMissing(installRoot) {
|
|
13755
13945
|
const path = getConfigPath(installRoot);
|
|
13756
13946
|
if (await fileExists(path)) return;
|
|
13757
|
-
await ensureDir(
|
|
13947
|
+
await ensureDir(dirname9(path));
|
|
13758
13948
|
const defaultConfig = {
|
|
13759
13949
|
segments: ["git", "assignment", "session"],
|
|
13760
13950
|
separator: " \xB7 "
|
|
@@ -13764,12 +13954,12 @@ async function writeDefaultConfigIfMissing(installRoot) {
|
|
|
13764
13954
|
|
|
13765
13955
|
// src/commands/install-statusline.ts
|
|
13766
13956
|
function getPackageStatuslineSource() {
|
|
13767
|
-
const here =
|
|
13768
|
-
return
|
|
13957
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
13958
|
+
return resolve29(here, "..", "statusline", "statusline.sh");
|
|
13769
13959
|
}
|
|
13770
13960
|
async function readSettingsJson(settingsPath) {
|
|
13771
13961
|
if (!await fileExists(settingsPath)) return {};
|
|
13772
|
-
const raw = await
|
|
13962
|
+
const raw = await readFile20(settingsPath, "utf-8");
|
|
13773
13963
|
if (raw.trim() === "") return {};
|
|
13774
13964
|
try {
|
|
13775
13965
|
const parsed = JSON.parse(raw);
|
|
@@ -13781,7 +13971,7 @@ async function readSettingsJson(settingsPath) {
|
|
|
13781
13971
|
}
|
|
13782
13972
|
}
|
|
13783
13973
|
async function writeSettingsJson(settingsPath, data) {
|
|
13784
|
-
await ensureDir(
|
|
13974
|
+
await ensureDir(dirname10(settingsPath));
|
|
13785
13975
|
await writeFile8(settingsPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
13786
13976
|
}
|
|
13787
13977
|
async function resolveMode(mode, existingCommand, ourCommand) {
|
|
@@ -13816,7 +14006,7 @@ function extractExistingCommand(settings) {
|
|
|
13816
14006
|
};
|
|
13817
14007
|
}
|
|
13818
14008
|
async function backupSettings(settingsSnapshot, backupPath) {
|
|
13819
|
-
await ensureDir(
|
|
14009
|
+
await ensureDir(dirname10(backupPath));
|
|
13820
14010
|
await writeFile8(
|
|
13821
14011
|
backupPath,
|
|
13822
14012
|
JSON.stringify(
|
|
@@ -13833,9 +14023,9 @@ async function backupSettings(settingsSnapshot, backupPath) {
|
|
|
13833
14023
|
);
|
|
13834
14024
|
}
|
|
13835
14025
|
async function installScript(sourceScript, destScript, link) {
|
|
13836
|
-
await ensureDir(
|
|
14026
|
+
await ensureDir(dirname10(destScript));
|
|
13837
14027
|
try {
|
|
13838
|
-
const s = await
|
|
14028
|
+
const s = await lstat3(destScript);
|
|
13839
14029
|
if (s.isSymbolicLink() || s.isFile()) {
|
|
13840
14030
|
await unlink6(destScript);
|
|
13841
14031
|
}
|
|
@@ -13849,12 +14039,12 @@ async function installScript(sourceScript, destScript, link) {
|
|
|
13849
14039
|
}
|
|
13850
14040
|
async function installStatuslineCommand(options = {}) {
|
|
13851
14041
|
const mode = options.mode ?? "ask";
|
|
13852
|
-
const settingsPath = options.settingsPath ??
|
|
14042
|
+
const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
|
|
13853
14043
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
13854
14044
|
const sourceScript = options.sourceScript ?? getPackageStatuslineSource();
|
|
13855
|
-
const destScript =
|
|
13856
|
-
const confPath =
|
|
13857
|
-
const backupPath =
|
|
14045
|
+
const destScript = resolve29(installRoot, "statusline.sh");
|
|
14046
|
+
const confPath = resolve29(installRoot, "statusline.conf");
|
|
14047
|
+
const backupPath = resolve29(installRoot, "statusline.backup.json");
|
|
13858
14048
|
if (!await fileExists(sourceScript)) {
|
|
13859
14049
|
throw new Error(
|
|
13860
14050
|
`Statusline source script not found at ${sourceScript}. Try re-installing syntaur (npm install -g syntaur) or pass --source-script explicitly.`
|
|
@@ -13890,7 +14080,7 @@ async function installStatuslineCommand(options = {}) {
|
|
|
13890
14080
|
if (parsed) {
|
|
13891
14081
|
wrapTarget = parsed;
|
|
13892
14082
|
} else {
|
|
13893
|
-
const wrapperPath =
|
|
14083
|
+
const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
|
|
13894
14084
|
const wrapperBody = `#!/usr/bin/env bash
|
|
13895
14085
|
# Auto-generated by syntaur install-statusline.
|
|
13896
14086
|
# Executes the previously configured statusLine command.
|
|
@@ -13901,7 +14091,7 @@ exec ${existingCommand}
|
|
|
13901
14091
|
wrapTarget = wrapperPath;
|
|
13902
14092
|
}
|
|
13903
14093
|
}
|
|
13904
|
-
await ensureDir(
|
|
14094
|
+
await ensureDir(dirname10(confPath));
|
|
13905
14095
|
await writeFile8(
|
|
13906
14096
|
confPath,
|
|
13907
14097
|
wrapTarget ? `# Wrap target \u2014 the command below is invoked with the same stdin; its
|
|
@@ -13939,25 +14129,25 @@ function parseWrapCommand(command) {
|
|
|
13939
14129
|
async function chmodExec(path) {
|
|
13940
14130
|
const fs = await import("fs/promises");
|
|
13941
14131
|
try {
|
|
13942
|
-
const s = await
|
|
14132
|
+
const s = await stat2(path);
|
|
13943
14133
|
await fs.chmod(path, s.mode | 73);
|
|
13944
14134
|
} catch {
|
|
13945
14135
|
}
|
|
13946
14136
|
}
|
|
13947
14137
|
async function uninstallStatuslineCommand(options = {}) {
|
|
13948
|
-
const settingsPath = options.settingsPath ??
|
|
14138
|
+
const settingsPath = options.settingsPath ?? resolve29(homedir5(), ".claude", "settings.json");
|
|
13949
14139
|
const installRoot = options.installRoot ?? syntaurRoot();
|
|
13950
|
-
const destScript =
|
|
13951
|
-
const confPath =
|
|
13952
|
-
const backupPath =
|
|
13953
|
-
const wrapperPath =
|
|
14140
|
+
const destScript = resolve29(installRoot, "statusline.sh");
|
|
14141
|
+
const confPath = resolve29(installRoot, "statusline.conf");
|
|
14142
|
+
const backupPath = resolve29(installRoot, "statusline.backup.json");
|
|
14143
|
+
const wrapperPath = resolve29(installRoot, "statusline-wrapped.sh");
|
|
13954
14144
|
const settings = await readSettingsJson(settingsPath);
|
|
13955
14145
|
const existing = extractExistingCommand(settings);
|
|
13956
14146
|
const ourCommand = `bash ${destScript}`;
|
|
13957
14147
|
let restored = null;
|
|
13958
14148
|
if (await fileExists(backupPath)) {
|
|
13959
14149
|
try {
|
|
13960
|
-
const raw = await
|
|
14150
|
+
const raw = await readFile20(backupPath, "utf-8");
|
|
13961
14151
|
const parsed = JSON.parse(raw);
|
|
13962
14152
|
const prev = parsed?.previousStatusLine;
|
|
13963
14153
|
if (prev && typeof prev === "object" && typeof prev.command === "string") {
|
|
@@ -13978,7 +14168,7 @@ async function uninstallStatuslineCommand(options = {}) {
|
|
|
13978
14168
|
await writeSettingsJson(settingsPath, settings);
|
|
13979
14169
|
}
|
|
13980
14170
|
if (!options.keepScript) {
|
|
13981
|
-
const configPath =
|
|
14171
|
+
const configPath = resolve29(installRoot, "statusline.config.json");
|
|
13982
14172
|
for (const path of [destScript, confPath, backupPath, wrapperPath, configPath]) {
|
|
13983
14173
|
try {
|
|
13984
14174
|
await rm5(path, { force: true });
|
|
@@ -14076,12 +14266,13 @@ async function installCodexPluginCommand(options) {
|
|
|
14076
14266
|
console.log(` marketplace: ${marketplace.marketplacePath}`);
|
|
14077
14267
|
if (!options.skipSkills) {
|
|
14078
14268
|
try {
|
|
14079
|
-
const
|
|
14269
|
+
const skillReport = await installSkillsWithReport({
|
|
14080
14270
|
target: "codex",
|
|
14081
|
-
force: options.forceSkills
|
|
14271
|
+
force: options.forceSkills,
|
|
14272
|
+
ignorePluginActive: options.forceSkills
|
|
14082
14273
|
});
|
|
14083
14274
|
console.log("");
|
|
14084
|
-
console.log(formatInstallReport(
|
|
14275
|
+
console.log(formatInstallReport(skillReport, "codex"));
|
|
14085
14276
|
} catch (error) {
|
|
14086
14277
|
console.warn(
|
|
14087
14278
|
`Warning: skill install failed \u2014 ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -14234,7 +14425,7 @@ async function setupCommand(options) {
|
|
|
14234
14425
|
}
|
|
14235
14426
|
|
|
14236
14427
|
// src/commands/uninstall.ts
|
|
14237
|
-
import { resolve as
|
|
14428
|
+
import { resolve as resolve30 } from "path";
|
|
14238
14429
|
init_paths();
|
|
14239
14430
|
function expandTargets(options) {
|
|
14240
14431
|
if (options.all) {
|
|
@@ -14314,7 +14505,7 @@ async function uninstallCommand(options) {
|
|
|
14314
14505
|
const configuredProjectDir = await getConfiguredProjectDir();
|
|
14315
14506
|
await removeSyntaurData();
|
|
14316
14507
|
console.log(`Removed ${syntaurRoot()}`);
|
|
14317
|
-
if (configuredProjectDir &&
|
|
14508
|
+
if (configuredProjectDir && resolve30(configuredProjectDir) !== resolve30(syntaurRoot(), "projects")) {
|
|
14318
14509
|
console.warn(
|
|
14319
14510
|
`Warning: config.md pointed to an external project directory (${configuredProjectDir}). That directory was not removed automatically.`
|
|
14320
14511
|
);
|
|
@@ -14333,7 +14524,7 @@ init_slug();
|
|
|
14333
14524
|
init_cursor_rules();
|
|
14334
14525
|
init_codex_agents();
|
|
14335
14526
|
init_opencode_config();
|
|
14336
|
-
import { resolve as
|
|
14527
|
+
import { resolve as resolve31 } from "path";
|
|
14337
14528
|
var SUPPORTED_FRAMEWORKS = ["cursor", "codex", "opencode"];
|
|
14338
14529
|
async function setupAdapterCommand(framework, options) {
|
|
14339
14530
|
if (!SUPPORTED_FRAMEWORKS.includes(framework)) {
|
|
@@ -14359,19 +14550,19 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14359
14550
|
}
|
|
14360
14551
|
const config = await readConfig();
|
|
14361
14552
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
14362
|
-
const projectDir =
|
|
14363
|
-
const assignmentDir =
|
|
14553
|
+
const projectDir = resolve31(baseDir, options.project);
|
|
14554
|
+
const assignmentDir = resolve31(
|
|
14364
14555
|
projectDir,
|
|
14365
14556
|
"assignments",
|
|
14366
14557
|
options.assignment
|
|
14367
14558
|
);
|
|
14368
|
-
const projectMdPath =
|
|
14559
|
+
const projectMdPath = resolve31(projectDir, "project.md");
|
|
14369
14560
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
14370
14561
|
throw new Error(
|
|
14371
14562
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
14372
14563
|
);
|
|
14373
14564
|
}
|
|
14374
|
-
const assignmentMdPath =
|
|
14565
|
+
const assignmentMdPath = resolve31(assignmentDir, "assignment.md");
|
|
14375
14566
|
if (!await fileExists(assignmentDir) || !await fileExists(assignmentMdPath)) {
|
|
14376
14567
|
throw new Error(
|
|
14377
14568
|
`Assignment "${options.assignment}" not found at ${assignmentDir}.`
|
|
@@ -14399,15 +14590,15 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14399
14590
|
}
|
|
14400
14591
|
}
|
|
14401
14592
|
if (framework === "cursor") {
|
|
14402
|
-
const protocolPath =
|
|
14403
|
-
const assignmentPath =
|
|
14593
|
+
const protocolPath = resolve31(cwd, ".cursor", "rules", "syntaur-protocol.mdc");
|
|
14594
|
+
const assignmentPath = resolve31(cwd, ".cursor", "rules", "syntaur-assignment.mdc");
|
|
14404
14595
|
await writeAdapterFile(protocolPath, renderCursorProtocol());
|
|
14405
14596
|
await writeAdapterFile(assignmentPath, renderCursorAssignment(rendererParams));
|
|
14406
14597
|
} else if (framework === "codex" || framework === "opencode") {
|
|
14407
|
-
const agentsPath =
|
|
14598
|
+
const agentsPath = resolve31(cwd, "AGENTS.md");
|
|
14408
14599
|
await writeAdapterFile(agentsPath, renderCodexAgents(rendererParams));
|
|
14409
14600
|
if (framework === "opencode") {
|
|
14410
|
-
const configPath =
|
|
14601
|
+
const configPath = resolve31(cwd, "opencode.json");
|
|
14411
14602
|
await writeAdapterFile(configPath, renderOpenCodeConfig({ projectDir }));
|
|
14412
14603
|
}
|
|
14413
14604
|
}
|
|
@@ -14432,7 +14623,7 @@ async function setupAdapterCommand(framework, options) {
|
|
|
14432
14623
|
init_paths();
|
|
14433
14624
|
init_fs();
|
|
14434
14625
|
init_config2();
|
|
14435
|
-
import { resolve as
|
|
14626
|
+
import { resolve as resolve32 } from "path";
|
|
14436
14627
|
async function trackSessionCommand(options) {
|
|
14437
14628
|
if (!options.agent) {
|
|
14438
14629
|
throw new Error("--agent <name> is required.");
|
|
@@ -14445,7 +14636,7 @@ async function trackSessionCommand(options) {
|
|
|
14445
14636
|
if (options.project) {
|
|
14446
14637
|
const config = await readConfig();
|
|
14447
14638
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
14448
|
-
const projectDir =
|
|
14639
|
+
const projectDir = resolve32(baseDir, options.project);
|
|
14449
14640
|
if (!await fileExists(projectDir)) {
|
|
14450
14641
|
throw new Error(
|
|
14451
14642
|
`Project "${options.project}" not found at ${projectDir}.`
|
|
@@ -14479,8 +14670,8 @@ init_config2();
|
|
|
14479
14670
|
init_paths();
|
|
14480
14671
|
init_fs();
|
|
14481
14672
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
14482
|
-
import { resolve as
|
|
14483
|
-
import { readFile as
|
|
14673
|
+
import { resolve as resolve34, isAbsolute as isAbsolute4 } from "path";
|
|
14674
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
14484
14675
|
import { select, confirm as confirm2, input as input3 } from "@inquirer/prompts";
|
|
14485
14676
|
async function browseCommand(options) {
|
|
14486
14677
|
const config = await readConfig();
|
|
@@ -14549,7 +14740,7 @@ async function pickAgent(agents) {
|
|
|
14549
14740
|
return picked;
|
|
14550
14741
|
}
|
|
14551
14742
|
async function ensureWorktree(opts) {
|
|
14552
|
-
const assignmentPath =
|
|
14743
|
+
const assignmentPath = resolve34(
|
|
14553
14744
|
opts.projectsDir,
|
|
14554
14745
|
opts.projectSlug,
|
|
14555
14746
|
"assignments",
|
|
@@ -14559,7 +14750,7 @@ async function ensureWorktree(opts) {
|
|
|
14559
14750
|
if (!await fileExists(assignmentPath)) {
|
|
14560
14751
|
return void 0;
|
|
14561
14752
|
}
|
|
14562
|
-
const content = await
|
|
14753
|
+
const content = await readFile22(assignmentPath, "utf-8");
|
|
14563
14754
|
const { parseAssignmentFrontmatter: parseAssignmentFrontmatter2 } = await Promise.resolve().then(() => (init_frontmatter(), frontmatter_exports));
|
|
14564
14755
|
const fm = parseAssignmentFrontmatter2(content);
|
|
14565
14756
|
const { workspace } = fm;
|
|
@@ -14630,7 +14821,7 @@ function computeWorktreeDefaults(opts) {
|
|
|
14630
14821
|
const repository = opts.existing.repository ?? detectCurrentGitRoot();
|
|
14631
14822
|
const branch = opts.projectSlug ? `syntaur/${opts.projectSlug}/${opts.assignmentSlug}` : `syntaur/${opts.assignmentSlug}`;
|
|
14632
14823
|
const parentBranch = opts.existing.parentBranch ?? detectCurrentBranch() ?? "main";
|
|
14633
|
-
const worktreeBase =
|
|
14824
|
+
const worktreeBase = resolve34(
|
|
14634
14825
|
syntaurRoot(),
|
|
14635
14826
|
"worktrees",
|
|
14636
14827
|
opts.projectSlug || "standalone",
|
|
@@ -14663,7 +14854,7 @@ function detectCurrentBranch() {
|
|
|
14663
14854
|
async function runCreate(opts) {
|
|
14664
14855
|
const { createWorktreeAndRecord: createWorktreeAndRecord2, GitWorktreeError: GitWorktreeError2 } = await Promise.resolve().then(() => (init_git_worktree(), git_worktree_exports));
|
|
14665
14856
|
const expandedWorktree = expandHome(opts.worktreePath);
|
|
14666
|
-
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree :
|
|
14857
|
+
const absWorktree = isAbsolute4(expandedWorktree) ? expandedWorktree : resolve34(expandedWorktree);
|
|
14667
14858
|
try {
|
|
14668
14859
|
await createWorktreeAndRecord2({
|
|
14669
14860
|
assignmentPath: opts.assignmentPath,
|
|
@@ -14692,7 +14883,7 @@ init_paths();
|
|
|
14692
14883
|
init_fs();
|
|
14693
14884
|
init_playbook();
|
|
14694
14885
|
init_playbooks();
|
|
14695
|
-
import { resolve as
|
|
14886
|
+
import { resolve as resolve35 } from "path";
|
|
14696
14887
|
async function createPlaybookCommand(name, options) {
|
|
14697
14888
|
if (!name.trim()) {
|
|
14698
14889
|
throw new Error("Playbook name cannot be empty.");
|
|
@@ -14705,7 +14896,7 @@ async function createPlaybookCommand(name, options) {
|
|
|
14705
14896
|
}
|
|
14706
14897
|
const dir = playbooksDir();
|
|
14707
14898
|
await ensureDir(dir);
|
|
14708
|
-
const filePath =
|
|
14899
|
+
const filePath = resolve35(dir, `${slug}.md`);
|
|
14709
14900
|
if (await fileExists(filePath)) {
|
|
14710
14901
|
throw new Error(
|
|
14711
14902
|
`Playbook "${slug}" already exists at ${filePath}
|
|
@@ -14727,8 +14918,8 @@ init_paths();
|
|
|
14727
14918
|
init_fs();
|
|
14728
14919
|
init_parser();
|
|
14729
14920
|
init_config2();
|
|
14730
|
-
import { readdir as readdir12, readFile as
|
|
14731
|
-
import { resolve as
|
|
14921
|
+
import { readdir as readdir12, readFile as readFile23 } from "fs/promises";
|
|
14922
|
+
import { resolve as resolve36 } from "path";
|
|
14732
14923
|
async function listPlaybooksCommand(options = {}) {
|
|
14733
14924
|
const dir = playbooksDir();
|
|
14734
14925
|
if (!await fileExists(dir)) {
|
|
@@ -14743,8 +14934,8 @@ async function listPlaybooksCommand(options = {}) {
|
|
|
14743
14934
|
);
|
|
14744
14935
|
const rows = [];
|
|
14745
14936
|
for (const entry of mdFiles) {
|
|
14746
|
-
const filePath =
|
|
14747
|
-
const raw = await
|
|
14937
|
+
const filePath = resolve36(dir, entry.name);
|
|
14938
|
+
const raw = await readFile23(filePath, "utf-8");
|
|
14748
14939
|
const parsed = parsePlaybook(raw);
|
|
14749
14940
|
const slug = parsed.slug || entry.name.replace(/\.md$/, "");
|
|
14750
14941
|
const disabled = disabledSet.has(slug);
|
|
@@ -14842,8 +15033,8 @@ init_fs();
|
|
|
14842
15033
|
init_config2();
|
|
14843
15034
|
init_slug();
|
|
14844
15035
|
import { Command } from "commander";
|
|
14845
|
-
import { readFile as
|
|
14846
|
-
import { resolve as
|
|
15036
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
15037
|
+
import { resolve as resolve37 } from "path";
|
|
14847
15038
|
var WORKSPACE_REGEX3 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
14848
15039
|
async function resolveScope(options) {
|
|
14849
15040
|
const flagCount = [Boolean(options.project), Boolean(options.workspace), Boolean(options.global)].filter(Boolean).length;
|
|
@@ -14855,7 +15046,7 @@ async function resolveScope(options) {
|
|
|
14855
15046
|
throw new Error(`Invalid project slug: "${options.project}".`);
|
|
14856
15047
|
}
|
|
14857
15048
|
const config = await readConfig();
|
|
14858
|
-
const projectMd =
|
|
15049
|
+
const projectMd = resolve37(config.defaultProjectDir, options.project, "project.md");
|
|
14859
15050
|
if (!await fileExists(projectMd)) {
|
|
14860
15051
|
throw new Error(`Project "${options.project}" not found.`);
|
|
14861
15052
|
}
|
|
@@ -15174,10 +15365,10 @@ todoCommand.command("archive").description("Archive completed todos and their lo
|
|
|
15174
15365
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
15175
15366
|
);
|
|
15176
15367
|
const archFile = archivePath(todosPath, workspace, checklist.archiveInterval);
|
|
15177
|
-
await ensureDir(
|
|
15368
|
+
await ensureDir(resolve37(todosPath, "archive"));
|
|
15178
15369
|
let archContent = "";
|
|
15179
15370
|
if (await fileExists(archFile)) {
|
|
15180
|
-
archContent = await
|
|
15371
|
+
archContent = await readFile24(archFile, "utf-8");
|
|
15181
15372
|
archContent = archContent.trimEnd() + "\n\n";
|
|
15182
15373
|
} else {
|
|
15183
15374
|
archContent = `---
|
|
@@ -15350,12 +15541,12 @@ function describeScope(scope) {
|
|
|
15350
15541
|
}
|
|
15351
15542
|
async function injectPromotedTodos(assignmentDir, todos, scopeLabel) {
|
|
15352
15543
|
const { resolve: resolvePath2 } = await import("path");
|
|
15353
|
-
const { readFile:
|
|
15544
|
+
const { readFile: readFile35 } = await import("fs/promises");
|
|
15354
15545
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
15355
15546
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
15356
15547
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
15357
15548
|
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
15358
|
-
let content = await
|
|
15549
|
+
let content = await readFile35(assignmentMdPath, "utf-8");
|
|
15359
15550
|
content = appendTodosToAssignmentBody2(
|
|
15360
15551
|
content,
|
|
15361
15552
|
todos.map((t) => ({
|
|
@@ -15400,13 +15591,13 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
15400
15591
|
}
|
|
15401
15592
|
const planDir = todoPlanDir(todosPath, workspace, id);
|
|
15402
15593
|
await ensureDir(planDir);
|
|
15403
|
-
const { readdir:
|
|
15404
|
-
const existingFiles = (await
|
|
15594
|
+
const { readdir: readdir19 } = await import("fs/promises");
|
|
15595
|
+
const existingFiles = (await readdir19(planDir).catch(() => [])).filter(
|
|
15405
15596
|
(f) => /^plan(?:-v\d+)?\.md$/.test(f)
|
|
15406
15597
|
);
|
|
15407
15598
|
let target;
|
|
15408
15599
|
if (existingFiles.length === 0) {
|
|
15409
|
-
target =
|
|
15600
|
+
target = resolve37(planDir, "plan.md");
|
|
15410
15601
|
} else {
|
|
15411
15602
|
const versions = /* @__PURE__ */ new Set();
|
|
15412
15603
|
for (const f of existingFiles) {
|
|
@@ -15416,7 +15607,7 @@ todoCommand.command("plan").description("Create or open a plan directory for a t
|
|
|
15416
15607
|
}
|
|
15417
15608
|
let n = 2;
|
|
15418
15609
|
while (versions.has(n)) n++;
|
|
15419
|
-
target =
|
|
15610
|
+
target = resolve37(planDir, `plan-v${n}.md`);
|
|
15420
15611
|
}
|
|
15421
15612
|
if (!await fileExists(target)) {
|
|
15422
15613
|
const stub = `---
|
|
@@ -15486,10 +15677,10 @@ async function moveTodo(id, options) {
|
|
|
15486
15677
|
if (await fileExists(newPlanDir)) {
|
|
15487
15678
|
throw new Error(`Plan directory already exists at target: ${newPlanDir}; refusing to move.`);
|
|
15488
15679
|
}
|
|
15489
|
-
const { rename:
|
|
15680
|
+
const { rename: rename7, mkdir: mkdir7 } = await import("fs/promises");
|
|
15490
15681
|
const { dirname: dirname16 } = await import("path");
|
|
15491
15682
|
await mkdir7(dirname16(newPlanDir), { recursive: true });
|
|
15492
|
-
await
|
|
15683
|
+
await rename7(item.planDir, newPlanDir);
|
|
15493
15684
|
item.planDir = newPlanDir;
|
|
15494
15685
|
}
|
|
15495
15686
|
sourceChecklist.items.splice(idx, 1);
|
|
@@ -15607,20 +15798,20 @@ backupCommand.command("config").description("Show or update backup configuration
|
|
|
15607
15798
|
import { Command as Command3 } from "commander";
|
|
15608
15799
|
|
|
15609
15800
|
// src/utils/doctor/index.ts
|
|
15610
|
-
import { fileURLToPath as
|
|
15611
|
-
import { readFile as
|
|
15612
|
-
import { dirname as dirname13, join as
|
|
15801
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
15802
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
15803
|
+
import { dirname as dirname13, join as join6 } from "path";
|
|
15613
15804
|
|
|
15614
15805
|
// src/utils/doctor/context.ts
|
|
15615
15806
|
init_config2();
|
|
15616
15807
|
init_paths();
|
|
15617
15808
|
init_fs();
|
|
15618
15809
|
import Database2 from "better-sqlite3";
|
|
15619
|
-
import { resolve as
|
|
15810
|
+
import { resolve as resolve38 } from "path";
|
|
15620
15811
|
async function buildCheckContext(cwd = process.cwd()) {
|
|
15621
15812
|
const config = await readConfig();
|
|
15622
15813
|
const root = syntaurRoot();
|
|
15623
|
-
const dbPath =
|
|
15814
|
+
const dbPath = resolve38(root, "syntaur.db");
|
|
15624
15815
|
let db2 = null;
|
|
15625
15816
|
let dbError = null;
|
|
15626
15817
|
if (await fileExists(dbPath)) {
|
|
@@ -15654,10 +15845,10 @@ function closeCheckContext(ctx) {
|
|
|
15654
15845
|
// src/utils/doctor/checks/env.ts
|
|
15655
15846
|
init_fs();
|
|
15656
15847
|
init_paths();
|
|
15657
|
-
import { resolve as
|
|
15658
|
-
import { readFile as
|
|
15659
|
-
import { fileURLToPath as
|
|
15660
|
-
import { dirname as
|
|
15848
|
+
import { resolve as resolve39, isAbsolute as isAbsolute5 } from "path";
|
|
15849
|
+
import { readFile as readFile25, stat as stat3 } from "fs/promises";
|
|
15850
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
15851
|
+
import { dirname as dirname11, join as join4 } from "path";
|
|
15661
15852
|
var CATEGORY = "env";
|
|
15662
15853
|
var syntaurRootExists = {
|
|
15663
15854
|
id: "env.syntaur-root-exists",
|
|
@@ -15665,7 +15856,7 @@ var syntaurRootExists = {
|
|
|
15665
15856
|
title: "~/.syntaur/ directory exists",
|
|
15666
15857
|
async run(ctx) {
|
|
15667
15858
|
try {
|
|
15668
|
-
const s = await
|
|
15859
|
+
const s = await stat3(ctx.syntaurRoot);
|
|
15669
15860
|
if (!s.isDirectory()) {
|
|
15670
15861
|
return err(this, `${ctx.syntaurRoot} exists but is not a directory`, [
|
|
15671
15862
|
ctx.syntaurRoot
|
|
@@ -15695,7 +15886,7 @@ var configValid = {
|
|
|
15695
15886
|
category: CATEGORY,
|
|
15696
15887
|
title: "~/.syntaur/config.md is valid",
|
|
15697
15888
|
async run(ctx) {
|
|
15698
|
-
const configPath =
|
|
15889
|
+
const configPath = resolve39(ctx.syntaurRoot, "config.md");
|
|
15699
15890
|
if (!await fileExists(configPath)) {
|
|
15700
15891
|
return {
|
|
15701
15892
|
id: this.id,
|
|
@@ -15712,7 +15903,7 @@ var configValid = {
|
|
|
15712
15903
|
autoFixable: false
|
|
15713
15904
|
};
|
|
15714
15905
|
}
|
|
15715
|
-
const content = await
|
|
15906
|
+
const content = await readFile25(configPath, "utf-8");
|
|
15716
15907
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
15717
15908
|
if (!fmMatch || fmMatch[1].trim() === "") {
|
|
15718
15909
|
return {
|
|
@@ -15987,15 +16178,15 @@ async function readLocalVersion() {
|
|
|
15987
16178
|
}
|
|
15988
16179
|
async function readLocalPkg() {
|
|
15989
16180
|
try {
|
|
15990
|
-
const here =
|
|
15991
|
-
let dir =
|
|
16181
|
+
const here = fileURLToPath5(import.meta.url);
|
|
16182
|
+
let dir = dirname11(here);
|
|
15992
16183
|
for (let i = 0; i < 6; i++) {
|
|
15993
16184
|
const candidate = join4(dir, "package.json");
|
|
15994
16185
|
try {
|
|
15995
|
-
const text = await
|
|
16186
|
+
const text = await readFile25(candidate, "utf-8");
|
|
15996
16187
|
return JSON.parse(text);
|
|
15997
16188
|
} catch {
|
|
15998
|
-
dir =
|
|
16189
|
+
dir = dirname11(dir);
|
|
15999
16190
|
}
|
|
16000
16191
|
}
|
|
16001
16192
|
return null;
|
|
@@ -16044,8 +16235,8 @@ function versionGte(a, b) {
|
|
|
16044
16235
|
|
|
16045
16236
|
// src/utils/doctor/checks/structure.ts
|
|
16046
16237
|
init_fs();
|
|
16047
|
-
import { resolve as
|
|
16048
|
-
import { readdir as readdir13, stat as
|
|
16238
|
+
import { resolve as resolve40 } from "path";
|
|
16239
|
+
import { readdir as readdir13, stat as stat4 } from "fs/promises";
|
|
16049
16240
|
var CATEGORY2 = "structure";
|
|
16050
16241
|
var KNOWN_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
16051
16242
|
"projects",
|
|
@@ -16064,7 +16255,7 @@ var projectsDir = {
|
|
|
16064
16255
|
category: CATEGORY2,
|
|
16065
16256
|
title: "projects/ directory exists",
|
|
16066
16257
|
async run(ctx) {
|
|
16067
|
-
const p =
|
|
16258
|
+
const p = resolve40(ctx.syntaurRoot, "projects");
|
|
16068
16259
|
if (!await fileExists(p)) {
|
|
16069
16260
|
return {
|
|
16070
16261
|
id: this.id,
|
|
@@ -16089,7 +16280,7 @@ var playbooksDir2 = {
|
|
|
16089
16280
|
category: CATEGORY2,
|
|
16090
16281
|
title: "playbooks/ directory exists",
|
|
16091
16282
|
async run(ctx) {
|
|
16092
|
-
const p =
|
|
16283
|
+
const p = resolve40(ctx.syntaurRoot, "playbooks");
|
|
16093
16284
|
if (!await fileExists(p)) {
|
|
16094
16285
|
return {
|
|
16095
16286
|
id: this.id,
|
|
@@ -16114,7 +16305,7 @@ var todosDirValid = {
|
|
|
16114
16305
|
category: CATEGORY2,
|
|
16115
16306
|
title: "todos/ directory is readable (if present)",
|
|
16116
16307
|
async run(ctx) {
|
|
16117
|
-
const p =
|
|
16308
|
+
const p = resolve40(ctx.syntaurRoot, "todos");
|
|
16118
16309
|
if (!await fileExists(p)) {
|
|
16119
16310
|
return {
|
|
16120
16311
|
id: this.id,
|
|
@@ -16125,7 +16316,7 @@ var todosDirValid = {
|
|
|
16125
16316
|
autoFixable: false
|
|
16126
16317
|
};
|
|
16127
16318
|
}
|
|
16128
|
-
const s = await
|
|
16319
|
+
const s = await stat4(p);
|
|
16129
16320
|
if (!s.isDirectory()) {
|
|
16130
16321
|
return {
|
|
16131
16322
|
id: this.id,
|
|
@@ -16145,7 +16336,7 @@ var serversDirValid = {
|
|
|
16145
16336
|
category: CATEGORY2,
|
|
16146
16337
|
title: "servers/ directory is readable (if present)",
|
|
16147
16338
|
async run(ctx) {
|
|
16148
|
-
const p =
|
|
16339
|
+
const p = resolve40(ctx.syntaurRoot, "servers");
|
|
16149
16340
|
if (!await fileExists(p)) {
|
|
16150
16341
|
return {
|
|
16151
16342
|
id: this.id,
|
|
@@ -16156,7 +16347,7 @@ var serversDirValid = {
|
|
|
16156
16347
|
autoFixable: false
|
|
16157
16348
|
};
|
|
16158
16349
|
}
|
|
16159
|
-
const s = await
|
|
16350
|
+
const s = await stat4(p);
|
|
16160
16351
|
if (!s.isDirectory()) {
|
|
16161
16352
|
return {
|
|
16162
16353
|
id: this.id,
|
|
@@ -16190,7 +16381,7 @@ var knownFilesRecognized = {
|
|
|
16190
16381
|
title: this.title,
|
|
16191
16382
|
status: "warn",
|
|
16192
16383
|
detail: `unexpected top-level entries: ${unexpected.join(", ")}`,
|
|
16193
|
-
affected: unexpected.map((n) =>
|
|
16384
|
+
affected: unexpected.map((n) => resolve40(ctx.syntaurRoot, n)),
|
|
16194
16385
|
remediation: {
|
|
16195
16386
|
kind: "manual",
|
|
16196
16387
|
suggestion: "Review these entries \u2014 they may be leftover state from older versions",
|
|
@@ -16219,8 +16410,8 @@ function pass2(check) {
|
|
|
16219
16410
|
|
|
16220
16411
|
// src/utils/doctor/checks/project.ts
|
|
16221
16412
|
init_fs();
|
|
16222
|
-
import { resolve as
|
|
16223
|
-
import { readdir as readdir14, stat as
|
|
16413
|
+
import { resolve as resolve41 } from "path";
|
|
16414
|
+
import { readdir as readdir14, stat as stat5 } from "fs/promises";
|
|
16224
16415
|
var CATEGORY3 = "project";
|
|
16225
16416
|
var REQUIRED_PROJECT_FILES = [
|
|
16226
16417
|
"project.md",
|
|
@@ -16249,10 +16440,10 @@ async function listProjects2(ctx) {
|
|
|
16249
16440
|
for (const e of entries) {
|
|
16250
16441
|
if (!e.isDirectory()) continue;
|
|
16251
16442
|
if (e.name.startsWith(".") || e.name.startsWith("_")) continue;
|
|
16252
|
-
const projectDir =
|
|
16443
|
+
const projectDir = resolve41(dir, e.name);
|
|
16253
16444
|
let looksLikeProject = false;
|
|
16254
16445
|
for (const marker of PROJECT_MARKERS) {
|
|
16255
|
-
if (await fileExists(
|
|
16446
|
+
if (await fileExists(resolve41(projectDir, marker))) {
|
|
16256
16447
|
looksLikeProject = true;
|
|
16257
16448
|
break;
|
|
16258
16449
|
}
|
|
@@ -16271,7 +16462,7 @@ var requiredFiles = {
|
|
|
16271
16462
|
for (const projectDir of projects) {
|
|
16272
16463
|
const missing = [];
|
|
16273
16464
|
for (const rel of REQUIRED_PROJECT_FILES) {
|
|
16274
|
-
const p =
|
|
16465
|
+
const p = resolve41(projectDir, rel);
|
|
16275
16466
|
if (!await fileExists(p)) missing.push(rel);
|
|
16276
16467
|
}
|
|
16277
16468
|
if (missing.length === 0) continue;
|
|
@@ -16281,7 +16472,7 @@ var requiredFiles = {
|
|
|
16281
16472
|
title: this.title,
|
|
16282
16473
|
status: "error",
|
|
16283
16474
|
detail: `project at ${projectDir} is missing: ${missing.join(", ")}`,
|
|
16284
|
-
affected: missing.map((m) =>
|
|
16475
|
+
affected: missing.map((m) => resolve41(projectDir, m)),
|
|
16285
16476
|
remediation: {
|
|
16286
16477
|
kind: "manual",
|
|
16287
16478
|
suggestion: "Recreate the missing scaffold files from templates",
|
|
@@ -16304,9 +16495,9 @@ var manifestStale = {
|
|
|
16304
16495
|
const projects = await listProjects2(ctx);
|
|
16305
16496
|
const results = [];
|
|
16306
16497
|
for (const projectDir of projects) {
|
|
16307
|
-
const manifestPath =
|
|
16498
|
+
const manifestPath = resolve41(projectDir, "manifest.md");
|
|
16308
16499
|
if (!await fileExists(manifestPath)) continue;
|
|
16309
|
-
const manifestMtime = (await
|
|
16500
|
+
const manifestMtime = (await stat5(manifestPath)).mtimeMs;
|
|
16310
16501
|
const newestAssignment = await newestAssignmentMtime(projectDir);
|
|
16311
16502
|
if (newestAssignment === 0) continue;
|
|
16312
16503
|
if (newestAssignment > manifestMtime) {
|
|
@@ -16353,7 +16544,7 @@ var orphanFiles = {
|
|
|
16353
16544
|
title: this.title,
|
|
16354
16545
|
status: "warn",
|
|
16355
16546
|
detail: `project at ${projectDir} has unexpected entries: ${orphans.join(", ")}`,
|
|
16356
|
-
affected: orphans.map((o) =>
|
|
16547
|
+
affected: orphans.map((o) => resolve41(projectDir, o)),
|
|
16357
16548
|
autoFixable: false
|
|
16358
16549
|
});
|
|
16359
16550
|
}
|
|
@@ -16363,7 +16554,7 @@ var orphanFiles = {
|
|
|
16363
16554
|
};
|
|
16364
16555
|
var projectChecks = [requiredFiles, manifestStale, orphanFiles];
|
|
16365
16556
|
async function newestAssignmentMtime(projectDir) {
|
|
16366
|
-
const assignmentsRoot =
|
|
16557
|
+
const assignmentsRoot = resolve41(projectDir, "assignments");
|
|
16367
16558
|
if (!await fileExists(assignmentsRoot)) return 0;
|
|
16368
16559
|
let newest = 0;
|
|
16369
16560
|
let entries;
|
|
@@ -16374,9 +16565,9 @@ async function newestAssignmentMtime(projectDir) {
|
|
|
16374
16565
|
}
|
|
16375
16566
|
for (const e of entries) {
|
|
16376
16567
|
if (!e.isDirectory()) continue;
|
|
16377
|
-
const assignmentMd =
|
|
16568
|
+
const assignmentMd = resolve41(assignmentsRoot, e.name, "assignment.md");
|
|
16378
16569
|
try {
|
|
16379
|
-
const s = await
|
|
16570
|
+
const s = await stat5(assignmentMd);
|
|
16380
16571
|
if (s.mtimeMs > newest) newest = s.mtimeMs;
|
|
16381
16572
|
} catch {
|
|
16382
16573
|
}
|
|
@@ -16398,8 +16589,8 @@ init_fs();
|
|
|
16398
16589
|
init_parser();
|
|
16399
16590
|
init_types();
|
|
16400
16591
|
init_paths();
|
|
16401
|
-
import { resolve as
|
|
16402
|
-
import { readdir as readdir15, readFile as
|
|
16592
|
+
import { resolve as resolve42 } from "path";
|
|
16593
|
+
import { readdir as readdir15, readFile as readFile26 } from "fs/promises";
|
|
16403
16594
|
var CATEGORY4 = "assignment";
|
|
16404
16595
|
var STATUSES_REQUIRING_HANDOFF = /* @__PURE__ */ new Set(["review", "completed"]);
|
|
16405
16596
|
async function listAssignments(ctx) {
|
|
@@ -16410,16 +16601,16 @@ async function listAssignments(ctx) {
|
|
|
16410
16601
|
for (const m of projects) {
|
|
16411
16602
|
if (!m.isDirectory()) continue;
|
|
16412
16603
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
16413
|
-
const assignmentsDir2 =
|
|
16604
|
+
const assignmentsDir2 = resolve42(projectsDir2, m.name, "assignments");
|
|
16414
16605
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
16415
16606
|
const entries = await readdir15(assignmentsDir2, { withFileTypes: true });
|
|
16416
16607
|
for (const a of entries) {
|
|
16417
16608
|
if (!a.isDirectory()) continue;
|
|
16418
16609
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
16419
|
-
const assignmentDir =
|
|
16420
|
-
const assignmentMd =
|
|
16610
|
+
const assignmentDir = resolve42(assignmentsDir2, a.name);
|
|
16611
|
+
const assignmentMd = resolve42(assignmentDir, "assignment.md");
|
|
16421
16612
|
const entry = {
|
|
16422
|
-
projectDir:
|
|
16613
|
+
projectDir: resolve42(projectsDir2, m.name),
|
|
16423
16614
|
projectSlug: m.name,
|
|
16424
16615
|
assignmentDir,
|
|
16425
16616
|
assignmentSlug: a.name,
|
|
@@ -16439,8 +16630,8 @@ async function listAssignments(ctx) {
|
|
|
16439
16630
|
for (const a of entries) {
|
|
16440
16631
|
if (!a.isDirectory()) continue;
|
|
16441
16632
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
16442
|
-
const assignmentDir =
|
|
16443
|
-
const assignmentMd =
|
|
16633
|
+
const assignmentDir = resolve42(standaloneRoot, a.name);
|
|
16634
|
+
const assignmentMd = resolve42(assignmentDir, "assignment.md");
|
|
16444
16635
|
const entry = {
|
|
16445
16636
|
projectDir: standaloneRoot,
|
|
16446
16637
|
projectSlug: null,
|
|
@@ -16518,7 +16709,7 @@ var invalidStatus = {
|
|
|
16518
16709
|
const allowed = configuredStatuses(ctx);
|
|
16519
16710
|
const results = [];
|
|
16520
16711
|
for (const a of withAssignmentMd) {
|
|
16521
|
-
const path =
|
|
16712
|
+
const path = resolve42(a.assignmentDir, "assignment.md");
|
|
16522
16713
|
const parsed = await parseSafe(path);
|
|
16523
16714
|
if (!parsed) continue;
|
|
16524
16715
|
if (!allowed.has(parsed.status)) {
|
|
@@ -16551,7 +16742,7 @@ var workspaceMissing = {
|
|
|
16551
16742
|
const terminal = terminalStatuses(ctx);
|
|
16552
16743
|
const results = [];
|
|
16553
16744
|
for (const a of withAssignmentMd) {
|
|
16554
|
-
const path =
|
|
16745
|
+
const path = resolve42(a.assignmentDir, "assignment.md");
|
|
16555
16746
|
const parsed = await parseSafe(path);
|
|
16556
16747
|
if (!parsed) continue;
|
|
16557
16748
|
if (terminal.has(parsed.status)) continue;
|
|
@@ -16598,12 +16789,12 @@ var requiredFilesByStatus = {
|
|
|
16598
16789
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
16599
16790
|
const results = [];
|
|
16600
16791
|
for (const a of withAssignmentMd) {
|
|
16601
|
-
const assignmentPath =
|
|
16792
|
+
const assignmentPath = resolve42(a.assignmentDir, "assignment.md");
|
|
16602
16793
|
const parsed = await parseSafe(assignmentPath);
|
|
16603
16794
|
if (!parsed) continue;
|
|
16604
16795
|
const missing = [];
|
|
16605
16796
|
if (STATUSES_REQUIRING_HANDOFF.has(parsed.status)) {
|
|
16606
|
-
const handoffPath =
|
|
16797
|
+
const handoffPath = resolve42(a.assignmentDir, "handoff.md");
|
|
16607
16798
|
if (!await fileExists(handoffPath)) missing.push("handoff.md");
|
|
16608
16799
|
}
|
|
16609
16800
|
if (missing.length === 0) continue;
|
|
@@ -16613,7 +16804,7 @@ var requiredFilesByStatus = {
|
|
|
16613
16804
|
title: this.title,
|
|
16614
16805
|
status: "warn",
|
|
16615
16806
|
detail: `${a.projectSlug}/${a.assignmentSlug} (status: ${parsed.status}) is missing ${missing.join(", ")}`,
|
|
16616
|
-
affected: missing.map((m) =>
|
|
16807
|
+
affected: missing.map((m) => resolve42(a.assignmentDir, m)),
|
|
16617
16808
|
remediation: {
|
|
16618
16809
|
kind: "manual",
|
|
16619
16810
|
suggestion: `Create the missing ${missing.join(" and ")} files for this assignment`,
|
|
@@ -16636,7 +16827,7 @@ var companionFilesScaffolded = {
|
|
|
16636
16827
|
for (const a of withAssignmentMd) {
|
|
16637
16828
|
const missing = [];
|
|
16638
16829
|
for (const filename of ["progress.md", "comments.md"]) {
|
|
16639
|
-
if (!await fileExists(
|
|
16830
|
+
if (!await fileExists(resolve42(a.assignmentDir, filename))) {
|
|
16640
16831
|
missing.push(filename);
|
|
16641
16832
|
}
|
|
16642
16833
|
}
|
|
@@ -16648,7 +16839,7 @@ var companionFilesScaffolded = {
|
|
|
16648
16839
|
title: this.title,
|
|
16649
16840
|
status: "warn",
|
|
16650
16841
|
detail: `${label} is missing ${missing.join(" and ")} (pre-v2.0 assignment \u2014 not required, but scaffolding them keeps the dashboard and CLIs consistent)`,
|
|
16651
|
-
affected: missing.map((m) =>
|
|
16842
|
+
affected: missing.map((m) => resolve42(a.assignmentDir, m)),
|
|
16652
16843
|
remediation: {
|
|
16653
16844
|
kind: "manual",
|
|
16654
16845
|
suggestion: `Create ${missing.join(" and ")} with the renderProgress/renderComments templates, or re-scaffold via the CLI`,
|
|
@@ -16681,7 +16872,7 @@ var typeDefinition = {
|
|
|
16681
16872
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
16682
16873
|
const results = [];
|
|
16683
16874
|
for (const a of withAssignmentMd) {
|
|
16684
|
-
const path =
|
|
16875
|
+
const path = resolve42(a.assignmentDir, "assignment.md");
|
|
16685
16876
|
const parsed = await parseSafe(path);
|
|
16686
16877
|
if (!parsed) continue;
|
|
16687
16878
|
if (!parsed.type) continue;
|
|
@@ -16715,7 +16906,7 @@ var projectFrontmatterMatchesContainer = {
|
|
|
16715
16906
|
const { withAssignmentMd } = await listAssignments(ctx);
|
|
16716
16907
|
const results = [];
|
|
16717
16908
|
for (const a of withAssignmentMd) {
|
|
16718
|
-
const path =
|
|
16909
|
+
const path = resolve42(a.assignmentDir, "assignment.md");
|
|
16719
16910
|
const parsed = await parseSafe(path);
|
|
16720
16911
|
if (!parsed) continue;
|
|
16721
16912
|
if (a.standalone) {
|
|
@@ -16770,7 +16961,7 @@ var assignmentChecks = [
|
|
|
16770
16961
|
];
|
|
16771
16962
|
async function parseSafe(path) {
|
|
16772
16963
|
try {
|
|
16773
|
-
const content = await
|
|
16964
|
+
const content = await readFile26(path, "utf-8");
|
|
16774
16965
|
return parseAssignmentFull(content);
|
|
16775
16966
|
} catch {
|
|
16776
16967
|
return null;
|
|
@@ -16789,7 +16980,7 @@ function pass4(check, detail) {
|
|
|
16789
16980
|
|
|
16790
16981
|
// src/utils/doctor/checks/dashboard.ts
|
|
16791
16982
|
init_fs();
|
|
16792
|
-
import { resolve as
|
|
16983
|
+
import { resolve as resolve43 } from "path";
|
|
16793
16984
|
var CATEGORY5 = "dashboard";
|
|
16794
16985
|
var dbReachable = {
|
|
16795
16986
|
id: "dashboard.db-reachable",
|
|
@@ -16803,7 +16994,7 @@ var dbReachable = {
|
|
|
16803
16994
|
title: this.title,
|
|
16804
16995
|
status: "error",
|
|
16805
16996
|
detail: `could not open syntaur.db: ${ctx.dbError ?? "unknown error"}`,
|
|
16806
|
-
affected: [
|
|
16997
|
+
affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
|
|
16807
16998
|
remediation: {
|
|
16808
16999
|
kind: "manual",
|
|
16809
17000
|
suggestion: "Start the dashboard once (`syntaur dashboard`) to initialize the DB, or restore it from backup",
|
|
@@ -16821,7 +17012,7 @@ var dbReachable = {
|
|
|
16821
17012
|
title: this.title,
|
|
16822
17013
|
status: "error",
|
|
16823
17014
|
detail: 'syntaur.db is missing the expected "sessions" table',
|
|
16824
|
-
affected: [
|
|
17015
|
+
affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
|
|
16825
17016
|
autoFixable: false
|
|
16826
17017
|
};
|
|
16827
17018
|
}
|
|
@@ -16833,7 +17024,7 @@ var dbReachable = {
|
|
|
16833
17024
|
title: this.title,
|
|
16834
17025
|
status: "error",
|
|
16835
17026
|
detail: `syntaur.db query failed: ${err2 instanceof Error ? err2.message : String(err2)}`,
|
|
16836
|
-
affected: [
|
|
17027
|
+
affected: [resolve43(ctx.syntaurRoot, "syntaur.db")],
|
|
16837
17028
|
autoFixable: false
|
|
16838
17029
|
};
|
|
16839
17030
|
}
|
|
@@ -16859,7 +17050,7 @@ var ghostSessions = {
|
|
|
16859
17050
|
const results = [];
|
|
16860
17051
|
for (const row of rows) {
|
|
16861
17052
|
if (!row.project_slug) continue;
|
|
16862
|
-
const projectPath =
|
|
17053
|
+
const projectPath = resolve43(projectsDir2, row.project_slug, "project.md");
|
|
16863
17054
|
if (!await fileExists(projectPath)) {
|
|
16864
17055
|
results.push({
|
|
16865
17056
|
id: this.id,
|
|
@@ -16878,7 +17069,7 @@ var ghostSessions = {
|
|
|
16878
17069
|
continue;
|
|
16879
17070
|
}
|
|
16880
17071
|
if (row.assignment_slug) {
|
|
16881
|
-
const assignmentPath =
|
|
17072
|
+
const assignmentPath = resolve43(
|
|
16882
17073
|
projectsDir2,
|
|
16883
17074
|
row.project_slug,
|
|
16884
17075
|
"assignments",
|
|
@@ -16930,7 +17121,9 @@ function skipped(check, reason) {
|
|
|
16930
17121
|
|
|
16931
17122
|
// src/utils/doctor/checks/integrations.ts
|
|
16932
17123
|
init_fs();
|
|
16933
|
-
import {
|
|
17124
|
+
import { resolve as resolve44, dirname as dirname12, basename as basename2 } from "path";
|
|
17125
|
+
import { readdir as readdir16, readFile as readFile27 } from "fs/promises";
|
|
17126
|
+
import { homedir as homedir6 } from "os";
|
|
16934
17127
|
var CATEGORY6 = "integrations";
|
|
16935
17128
|
var claudePluginLinked = {
|
|
16936
17129
|
id: "integrations.claude-plugin-linked",
|
|
@@ -17010,7 +17203,111 @@ var backupConfigured = {
|
|
|
17010
17203
|
};
|
|
17011
17204
|
}
|
|
17012
17205
|
};
|
|
17013
|
-
|
|
17206
|
+
async function readKnownMarketplaces() {
|
|
17207
|
+
const path = resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json");
|
|
17208
|
+
if (!await fileExists(path)) return {};
|
|
17209
|
+
try {
|
|
17210
|
+
const raw = await readFile27(path, "utf-8");
|
|
17211
|
+
return JSON.parse(raw);
|
|
17212
|
+
} catch {
|
|
17213
|
+
return {};
|
|
17214
|
+
}
|
|
17215
|
+
}
|
|
17216
|
+
var claudeMarketplaceRegistered = {
|
|
17217
|
+
id: "integrations.claude-marketplace-registered",
|
|
17218
|
+
category: CATEGORY6,
|
|
17219
|
+
title: "Claude marketplace containing syntaur is registered in known_marketplaces.json",
|
|
17220
|
+
async run(ctx) {
|
|
17221
|
+
const dir = ctx.config.integrations.claudePluginDir;
|
|
17222
|
+
if (!dir) return skipped2(this, "claudePluginDir not configured");
|
|
17223
|
+
if (!await fileExists(dir)) {
|
|
17224
|
+
return skipped2(this, "claudePluginDir does not exist (run install-plugin)");
|
|
17225
|
+
}
|
|
17226
|
+
const pluginsParent = dirname12(dir);
|
|
17227
|
+
if (basename2(pluginsParent) !== "plugins") {
|
|
17228
|
+
return {
|
|
17229
|
+
id: this.id,
|
|
17230
|
+
category: this.category,
|
|
17231
|
+
title: this.title,
|
|
17232
|
+
status: "error",
|
|
17233
|
+
detail: `claudePluginDir is ${dir}, which is not under a Claude marketplace (expected path of the form <marketplace-root>/plugins/syntaur). Claude Code will not show this plugin until it lives inside a marketplace.`,
|
|
17234
|
+
affected: [dir],
|
|
17235
|
+
remediation: {
|
|
17236
|
+
kind: "manual",
|
|
17237
|
+
suggestion: "Re-run install-plugin to relocate into a marketplace, or pass --target-dir <marketplace>/plugins/syntaur.",
|
|
17238
|
+
command: "syntaur install-plugin"
|
|
17239
|
+
},
|
|
17240
|
+
autoFixable: false
|
|
17241
|
+
};
|
|
17242
|
+
}
|
|
17243
|
+
const marketplaceRoot = dirname12(pluginsParent);
|
|
17244
|
+
const marketplaceManifest = resolve44(marketplaceRoot, ".claude-plugin", "marketplace.json");
|
|
17245
|
+
if (!await fileExists(marketplaceManifest)) {
|
|
17246
|
+
return {
|
|
17247
|
+
id: this.id,
|
|
17248
|
+
category: this.category,
|
|
17249
|
+
title: this.title,
|
|
17250
|
+
status: "error",
|
|
17251
|
+
detail: `${marketplaceManifest} does not exist \u2014 Claude won't see this plugin.`,
|
|
17252
|
+
affected: [marketplaceManifest],
|
|
17253
|
+
remediation: {
|
|
17254
|
+
kind: "manual",
|
|
17255
|
+
suggestion: "Re-run install-plugin to repair the marketplace files.",
|
|
17256
|
+
command: "syntaur install-plugin"
|
|
17257
|
+
},
|
|
17258
|
+
autoFixable: false
|
|
17259
|
+
};
|
|
17260
|
+
}
|
|
17261
|
+
let parsed = {};
|
|
17262
|
+
try {
|
|
17263
|
+
parsed = JSON.parse(await readFile27(marketplaceManifest, "utf-8"));
|
|
17264
|
+
} catch {
|
|
17265
|
+
return {
|
|
17266
|
+
id: this.id,
|
|
17267
|
+
category: this.category,
|
|
17268
|
+
title: this.title,
|
|
17269
|
+
status: "error",
|
|
17270
|
+
detail: `${marketplaceManifest} is not valid JSON.`,
|
|
17271
|
+
affected: [marketplaceManifest],
|
|
17272
|
+
autoFixable: false
|
|
17273
|
+
};
|
|
17274
|
+
}
|
|
17275
|
+
const marketplaceName = parsed.name ?? basename2(marketplaceRoot);
|
|
17276
|
+
const hasSyntaurEntry = (parsed.plugins ?? []).some((p) => p?.name === "syntaur");
|
|
17277
|
+
const known = await readKnownMarketplaces();
|
|
17278
|
+
const registered = known[marketplaceName]?.installLocation === marketplaceRoot || Object.values(known).some((v) => v.installLocation === marketplaceRoot);
|
|
17279
|
+
const issues = [];
|
|
17280
|
+
if (!hasSyntaurEntry) {
|
|
17281
|
+
issues.push(`marketplace.json at ${marketplaceManifest} does not list a "syntaur" plugin`);
|
|
17282
|
+
}
|
|
17283
|
+
if (!registered) {
|
|
17284
|
+
issues.push(
|
|
17285
|
+
`known_marketplaces.json does not register ${marketplaceName} \u2192 ${marketplaceRoot} (Claude will not show this plugin)`
|
|
17286
|
+
);
|
|
17287
|
+
}
|
|
17288
|
+
if (issues.length === 0) return pass6(this);
|
|
17289
|
+
return {
|
|
17290
|
+
id: this.id,
|
|
17291
|
+
category: this.category,
|
|
17292
|
+
title: this.title,
|
|
17293
|
+
status: "error",
|
|
17294
|
+
detail: issues.join("; "),
|
|
17295
|
+
affected: [marketplaceManifest, resolve44(homedir6(), ".claude", "plugins", "known_marketplaces.json")],
|
|
17296
|
+
remediation: {
|
|
17297
|
+
kind: "manual",
|
|
17298
|
+
suggestion: "Re-run install-plugin to ensure both files are in sync.",
|
|
17299
|
+
command: "syntaur install-plugin"
|
|
17300
|
+
},
|
|
17301
|
+
autoFixable: false
|
|
17302
|
+
};
|
|
17303
|
+
}
|
|
17304
|
+
};
|
|
17305
|
+
var integrationChecks = [
|
|
17306
|
+
claudePluginLinked,
|
|
17307
|
+
claudeMarketplaceRegistered,
|
|
17308
|
+
codexPluginLinked,
|
|
17309
|
+
backupConfigured
|
|
17310
|
+
];
|
|
17014
17311
|
function pass6(check) {
|
|
17015
17312
|
return {
|
|
17016
17313
|
id: check.id,
|
|
@@ -17035,8 +17332,8 @@ function skipped2(check, reason) {
|
|
|
17035
17332
|
init_fs();
|
|
17036
17333
|
init_parser();
|
|
17037
17334
|
init_types();
|
|
17038
|
-
import { resolve as
|
|
17039
|
-
import { readFile as
|
|
17335
|
+
import { resolve as resolve45 } from "path";
|
|
17336
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
17040
17337
|
var CATEGORY7 = "workspace";
|
|
17041
17338
|
var ASSIGNMENT_FIELDS = ["projectSlug", "assignmentSlug", "projectDir", "assignmentDir"];
|
|
17042
17339
|
function hasAnyAssignmentField(ctx) {
|
|
@@ -17048,12 +17345,12 @@ function isStandaloneSession(ctx) {
|
|
|
17048
17345
|
return !hasAnyAssignmentField(ctx) && typeof ctx.sessionId === "string" && ctx.sessionId.length > 0;
|
|
17049
17346
|
}
|
|
17050
17347
|
async function loadContext(ctx) {
|
|
17051
|
-
const path =
|
|
17348
|
+
const path = resolve45(ctx.cwd, ".syntaur", "context.json");
|
|
17052
17349
|
if (!await fileExists(path)) {
|
|
17053
17350
|
return { data: null, path, exists: false, parseError: null };
|
|
17054
17351
|
}
|
|
17055
17352
|
try {
|
|
17056
|
-
const raw = await
|
|
17353
|
+
const raw = await readFile28(path, "utf-8");
|
|
17057
17354
|
return { data: JSON.parse(raw), path, exists: true, parseError: null };
|
|
17058
17355
|
} catch (err2) {
|
|
17059
17356
|
return {
|
|
@@ -17128,7 +17425,7 @@ var contextAssignmentResolves = {
|
|
|
17128
17425
|
if (!exists) return skipped3(this, "no context to resolve");
|
|
17129
17426
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to resolve");
|
|
17130
17427
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
17131
|
-
const assignmentMd =
|
|
17428
|
+
const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
|
|
17132
17429
|
if (!await fileExists(assignmentMd)) {
|
|
17133
17430
|
return {
|
|
17134
17431
|
id: this.id,
|
|
@@ -17157,10 +17454,10 @@ var contextTerminal = {
|
|
|
17157
17454
|
if (!exists) return skipped3(this, "no context to check");
|
|
17158
17455
|
if (isStandaloneSession(data)) return skipped3(this, "standalone session context \u2014 no assignment to check");
|
|
17159
17456
|
if (!data?.assignmentDir) return skipped3(this, "context has no assignmentDir");
|
|
17160
|
-
const assignmentMd =
|
|
17457
|
+
const assignmentMd = resolve45(data.assignmentDir, "assignment.md");
|
|
17161
17458
|
if (!await fileExists(assignmentMd)) return skipped3(this, "assignment file missing");
|
|
17162
17459
|
try {
|
|
17163
|
-
const content = await
|
|
17460
|
+
const content = await readFile28(assignmentMd, "utf-8");
|
|
17164
17461
|
const parsed = parseAssignmentFull(content);
|
|
17165
17462
|
const terminal = terminalStatuses2(ctx);
|
|
17166
17463
|
if (terminal.has(parsed.status)) {
|
|
@@ -17303,6 +17600,86 @@ async function checkAgent(agent) {
|
|
|
17303
17600
|
}
|
|
17304
17601
|
var agentChecks = [agentsResolvable];
|
|
17305
17602
|
|
|
17603
|
+
// src/utils/doctor/checks/skills.ts
|
|
17604
|
+
init_fs();
|
|
17605
|
+
import { resolve as resolve46, join as join5 } from "path";
|
|
17606
|
+
import { readdir as readdir17, readFile as readFile29, lstat as lstat4 } from "fs/promises";
|
|
17607
|
+
import { homedir as homedir7 } from "os";
|
|
17608
|
+
var CATEGORY9 = "skills";
|
|
17609
|
+
var skillTargets = [
|
|
17610
|
+
{ agent: "claude", dir: resolve46(homedir7(), ".claude", "skills"), label: "~/.claude/skills" },
|
|
17611
|
+
{ agent: "codex", dir: resolve46(homedir7(), ".codex", "skills"), label: "~/.codex/skills" }
|
|
17612
|
+
];
|
|
17613
|
+
var skillsDedupCheck = {
|
|
17614
|
+
id: "skills.dedup",
|
|
17615
|
+
category: CATEGORY9,
|
|
17616
|
+
title: "Syntaur skills are not duplicated across install paths",
|
|
17617
|
+
async run() {
|
|
17618
|
+
const findings = [];
|
|
17619
|
+
const affected = [];
|
|
17620
|
+
for (const { agent, dir, label } of skillTargets) {
|
|
17621
|
+
if (!await fileExists(dir)) continue;
|
|
17622
|
+
const pluginEnabled = await isSyntaurPluginEnabledFor(agent);
|
|
17623
|
+
const present = [];
|
|
17624
|
+
let entries;
|
|
17625
|
+
try {
|
|
17626
|
+
entries = await readdir17(dir, { withFileTypes: true });
|
|
17627
|
+
} catch {
|
|
17628
|
+
continue;
|
|
17629
|
+
}
|
|
17630
|
+
for (const entry of entries) {
|
|
17631
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
17632
|
+
if (!KNOWN_SKILLS.includes(entry.name)) continue;
|
|
17633
|
+
const skillMd = join5(dir, entry.name, "SKILL.md");
|
|
17634
|
+
if (!await fileExists(skillMd)) continue;
|
|
17635
|
+
const content = await readFile29(skillMd, "utf-8").catch(() => "");
|
|
17636
|
+
const match = content.match(/^name:\s*(\S+)\s*$/m);
|
|
17637
|
+
if (!match || match[1] !== entry.name) continue;
|
|
17638
|
+
let isSymlink2 = false;
|
|
17639
|
+
try {
|
|
17640
|
+
isSymlink2 = (await lstat4(join5(dir, entry.name))).isSymbolicLink();
|
|
17641
|
+
} catch {
|
|
17642
|
+
}
|
|
17643
|
+
present.push({ name: entry.name, isSymlink: isSymlink2 });
|
|
17644
|
+
}
|
|
17645
|
+
if (present.length === 0) continue;
|
|
17646
|
+
if (pluginEnabled) {
|
|
17647
|
+
const nonSymlink = present.filter((p) => !p.isSymlink);
|
|
17648
|
+
if (nonSymlink.length > 0) {
|
|
17649
|
+
findings.push(
|
|
17650
|
+
`${label}: ${nonSymlink.length} syntaur skill(s) installed globally while the syntaur plugin is enabled (${agent}) \u2014 duplicate registrations`
|
|
17651
|
+
);
|
|
17652
|
+
for (const p of nonSymlink) affected.push(join5(dir, p.name));
|
|
17653
|
+
}
|
|
17654
|
+
}
|
|
17655
|
+
}
|
|
17656
|
+
if (findings.length === 0) {
|
|
17657
|
+
return {
|
|
17658
|
+
id: this.id,
|
|
17659
|
+
category: this.category,
|
|
17660
|
+
title: this.title,
|
|
17661
|
+
status: "pass",
|
|
17662
|
+
autoFixable: false
|
|
17663
|
+
};
|
|
17664
|
+
}
|
|
17665
|
+
return {
|
|
17666
|
+
id: this.id,
|
|
17667
|
+
category: this.category,
|
|
17668
|
+
title: this.title,
|
|
17669
|
+
status: "warn",
|
|
17670
|
+
detail: findings.join("; "),
|
|
17671
|
+
affected,
|
|
17672
|
+
remediation: {
|
|
17673
|
+
kind: "manual",
|
|
17674
|
+
suggestion: "Either disable the plugin or remove the global skill copies. Recommended: keep the plugin path and remove the global copies via `syntaur uninstall --skills` (preserves user-authored skills with non-syntaur frontmatter).",
|
|
17675
|
+
command: null
|
|
17676
|
+
},
|
|
17677
|
+
autoFixable: false
|
|
17678
|
+
};
|
|
17679
|
+
}
|
|
17680
|
+
};
|
|
17681
|
+
var skillsChecks = [skillsDedupCheck];
|
|
17682
|
+
|
|
17306
17683
|
// src/utils/doctor/registry.ts
|
|
17307
17684
|
function allChecks() {
|
|
17308
17685
|
return [
|
|
@@ -17313,7 +17690,8 @@ function allChecks() {
|
|
|
17313
17690
|
...dashboardChecks,
|
|
17314
17691
|
...integrationChecks,
|
|
17315
17692
|
...workspaceChecks,
|
|
17316
|
-
...agentChecks
|
|
17693
|
+
...agentChecks,
|
|
17694
|
+
...skillsChecks
|
|
17317
17695
|
];
|
|
17318
17696
|
}
|
|
17319
17697
|
|
|
@@ -17395,11 +17773,11 @@ async function finalize(checks) {
|
|
|
17395
17773
|
}
|
|
17396
17774
|
async function readVersion() {
|
|
17397
17775
|
try {
|
|
17398
|
-
const here =
|
|
17776
|
+
const here = fileURLToPath6(import.meta.url);
|
|
17399
17777
|
let dir = dirname13(here);
|
|
17400
17778
|
for (let i = 0; i < 6; i++) {
|
|
17401
17779
|
try {
|
|
17402
|
-
const raw = await
|
|
17780
|
+
const raw = await readFile30(join6(dir, "package.json"), "utf-8");
|
|
17403
17781
|
const parsed = JSON.parse(raw);
|
|
17404
17782
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
17405
17783
|
} catch {
|
|
@@ -17747,8 +18125,8 @@ init_uuid();
|
|
|
17747
18125
|
init_timestamp();
|
|
17748
18126
|
init_assignment_resolver();
|
|
17749
18127
|
init_templates();
|
|
17750
|
-
import { resolve as
|
|
17751
|
-
import { readFile as
|
|
18128
|
+
import { resolve as resolve47 } from "path";
|
|
18129
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
17752
18130
|
function shortId() {
|
|
17753
18131
|
return generateId().split("-")[0];
|
|
17754
18132
|
}
|
|
@@ -17778,7 +18156,7 @@ async function commentCommand(target, text, options = {}) {
|
|
|
17778
18156
|
if (!isValidSlug(target)) {
|
|
17779
18157
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
17780
18158
|
}
|
|
17781
|
-
assignmentDir =
|
|
18159
|
+
assignmentDir = resolve47(baseDir, options.project, "assignments", target);
|
|
17782
18160
|
assignmentRef = target;
|
|
17783
18161
|
} else {
|
|
17784
18162
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -17788,13 +18166,13 @@ async function commentCommand(target, text, options = {}) {
|
|
|
17788
18166
|
assignmentDir = resolved.assignmentDir;
|
|
17789
18167
|
assignmentRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
17790
18168
|
}
|
|
17791
|
-
const commentsPath =
|
|
18169
|
+
const commentsPath = resolve47(assignmentDir, "comments.md");
|
|
17792
18170
|
const timestamp = nowTimestamp();
|
|
17793
18171
|
const author = options.author ?? process.env.USER ?? "unknown";
|
|
17794
18172
|
let currentContent;
|
|
17795
18173
|
let currentCount = 0;
|
|
17796
18174
|
if (await fileExists(commentsPath)) {
|
|
17797
|
-
currentContent = await
|
|
18175
|
+
currentContent = await readFile31(commentsPath, "utf-8");
|
|
17798
18176
|
const countMatch = currentContent.match(/^entryCount:\s*(\d+)/m);
|
|
17799
18177
|
if (countMatch) currentCount = parseInt(countMatch[1], 10);
|
|
17800
18178
|
} else {
|
|
@@ -17835,8 +18213,8 @@ init_slug();
|
|
|
17835
18213
|
init_timestamp();
|
|
17836
18214
|
init_assignment_resolver();
|
|
17837
18215
|
init_assignment_todos();
|
|
17838
|
-
import { resolve as
|
|
17839
|
-
import { readFile as
|
|
18216
|
+
import { resolve as resolve48 } from "path";
|
|
18217
|
+
import { readFile as readFile32 } from "fs/promises";
|
|
17840
18218
|
async function requestCommand(target, text, options = {}) {
|
|
17841
18219
|
if (!text || !text.trim()) {
|
|
17842
18220
|
throw new Error("Request text cannot be empty.");
|
|
@@ -17852,7 +18230,7 @@ async function requestCommand(target, text, options = {}) {
|
|
|
17852
18230
|
if (!isValidSlug(target)) {
|
|
17853
18231
|
throw new Error(`Invalid assignment slug "${target}".`);
|
|
17854
18232
|
}
|
|
17855
|
-
assignmentDir =
|
|
18233
|
+
assignmentDir = resolve48(baseDir, options.project, "assignments", target);
|
|
17856
18234
|
targetRef = target;
|
|
17857
18235
|
} else {
|
|
17858
18236
|
const resolved = await resolveAssignmentById(baseDir, assignmentsDir(), target);
|
|
@@ -17862,12 +18240,12 @@ async function requestCommand(target, text, options = {}) {
|
|
|
17862
18240
|
assignmentDir = resolved.assignmentDir;
|
|
17863
18241
|
targetRef = resolved.standalone ? resolved.id : resolved.assignmentSlug;
|
|
17864
18242
|
}
|
|
17865
|
-
const assignmentMdPath =
|
|
18243
|
+
const assignmentMdPath = resolve48(assignmentDir, "assignment.md");
|
|
17866
18244
|
if (!await fileExists(assignmentMdPath)) {
|
|
17867
18245
|
throw new Error(`assignment.md not found at ${assignmentMdPath}`);
|
|
17868
18246
|
}
|
|
17869
18247
|
const source = options.from ?? process.env.SYNTAUR_ASSIGNMENT ?? "unknown";
|
|
17870
|
-
let content = await
|
|
18248
|
+
let content = await readFile32(assignmentMdPath, "utf-8");
|
|
17871
18249
|
content = appendTodosToAssignmentBody(content, [
|
|
17872
18250
|
{ description: `${text.trim()} (from: ${source})` }
|
|
17873
18251
|
]);
|
|
@@ -17878,10 +18256,10 @@ async function requestCommand(target, text, options = {}) {
|
|
|
17878
18256
|
|
|
17879
18257
|
// src/cli-default-command.ts
|
|
17880
18258
|
init_config2();
|
|
17881
|
-
import { readdir as
|
|
18259
|
+
import { readdir as readdir18 } from "fs/promises";
|
|
17882
18260
|
async function hasAnyProjectContent(projectsDir2) {
|
|
17883
18261
|
try {
|
|
17884
|
-
const entries = await
|
|
18262
|
+
const entries = await readdir18(projectsDir2, { withFileTypes: true });
|
|
17885
18263
|
return entries.some((entry) => entry.isDirectory());
|
|
17886
18264
|
} catch {
|
|
17887
18265
|
return false;
|
|
@@ -17917,21 +18295,21 @@ async function getDefaultCommandName() {
|
|
|
17917
18295
|
// src/utils/npx-prompt.ts
|
|
17918
18296
|
init_paths();
|
|
17919
18297
|
init_fs();
|
|
17920
|
-
import { fileURLToPath as
|
|
17921
|
-
import { readFile as
|
|
17922
|
-
import { dirname as dirname15, join as
|
|
18298
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
18299
|
+
import { readFile as readFile34 } from "fs/promises";
|
|
18300
|
+
import { dirname as dirname15, join as join8, resolve as resolve49 } from "path";
|
|
17923
18301
|
import { spawn as spawn4 } from "child_process";
|
|
17924
18302
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
17925
18303
|
|
|
17926
18304
|
// src/utils/version.ts
|
|
17927
|
-
import { fileURLToPath as
|
|
17928
|
-
import { readFile as
|
|
17929
|
-
import { dirname as dirname14, join as
|
|
18305
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
18306
|
+
import { readFile as readFile33 } from "fs/promises";
|
|
18307
|
+
import { dirname as dirname14, join as join7 } from "path";
|
|
17930
18308
|
async function readPackageVersion(scriptUrl) {
|
|
17931
18309
|
try {
|
|
17932
|
-
const scriptPath =
|
|
18310
|
+
const scriptPath = fileURLToPath7(scriptUrl);
|
|
17933
18311
|
const pkgRoot = dirname14(dirname14(scriptPath));
|
|
17934
|
-
const raw = await
|
|
18312
|
+
const raw = await readFile33(join7(pkgRoot, "package.json"), "utf-8");
|
|
17935
18313
|
const parsed = JSON.parse(raw);
|
|
17936
18314
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
17937
18315
|
} catch {
|
|
@@ -17940,13 +18318,13 @@ async function readPackageVersion(scriptUrl) {
|
|
|
17940
18318
|
}
|
|
17941
18319
|
|
|
17942
18320
|
// src/utils/npx-prompt.ts
|
|
17943
|
-
var STATE_FILE =
|
|
18321
|
+
var STATE_FILE = resolve49(syntaurRoot(), "npx-install.json");
|
|
17944
18322
|
var META_ARGS = /* @__PURE__ */ new Set(["-h", "--help", "-V", "--version", "help"]);
|
|
17945
18323
|
var GLOBAL_VERSION_TIMEOUT_MS = 2e3;
|
|
17946
18324
|
function isRunningViaNpx(scriptUrl) {
|
|
17947
18325
|
let scriptPath;
|
|
17948
18326
|
try {
|
|
17949
|
-
scriptPath =
|
|
18327
|
+
scriptPath = fileURLToPath8(scriptUrl);
|
|
17950
18328
|
} catch {
|
|
17951
18329
|
return false;
|
|
17952
18330
|
}
|
|
@@ -17961,7 +18339,7 @@ function isRunningViaNpx(scriptUrl) {
|
|
|
17961
18339
|
async function readState() {
|
|
17962
18340
|
if (!await fileExists(STATE_FILE)) return null;
|
|
17963
18341
|
try {
|
|
17964
|
-
const raw = await
|
|
18342
|
+
const raw = await readFile34(STATE_FILE, "utf-8");
|
|
17965
18343
|
return JSON.parse(raw);
|
|
17966
18344
|
} catch {
|
|
17967
18345
|
return null;
|
|
@@ -17975,7 +18353,7 @@ async function resolveNpmBin() {
|
|
|
17975
18353
|
const nodeDir = dirname15(process.execPath);
|
|
17976
18354
|
const isWin = process.platform === "win32";
|
|
17977
18355
|
const npmName = isWin ? "npm.cmd" : "npm";
|
|
17978
|
-
const nearNode =
|
|
18356
|
+
const nearNode = join8(nodeDir, npmName);
|
|
17979
18357
|
if (await fileExists(nearNode)) {
|
|
17980
18358
|
return { cmd: nearNode, shell: false };
|
|
17981
18359
|
}
|
|
@@ -18018,9 +18396,9 @@ async function readGlobalVersion() {
|
|
|
18018
18396
|
});
|
|
18019
18397
|
if (!rootPath) return null;
|
|
18020
18398
|
try {
|
|
18021
|
-
const manifestPath =
|
|
18399
|
+
const manifestPath = join8(rootPath, "syntaur", "package.json");
|
|
18022
18400
|
if (!await fileExists(manifestPath)) return null;
|
|
18023
|
-
const raw = await
|
|
18401
|
+
const raw = await readFile34(manifestPath, "utf-8");
|
|
18024
18402
|
const parsed = JSON.parse(raw);
|
|
18025
18403
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
18026
18404
|
} catch {
|
|
@@ -18315,7 +18693,7 @@ program.command("setup").description("Initialize Syntaur and optionally install
|
|
|
18315
18693
|
process.exit(1);
|
|
18316
18694
|
}
|
|
18317
18695
|
});
|
|
18318
|
-
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) => {
|
|
18696
|
+
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").option("--enable", "Enable the plugin in ~/.claude/settings.json after install").action(async (options) => {
|
|
18319
18697
|
try {
|
|
18320
18698
|
await installPluginCommand({ ...options, promptForTarget: true });
|
|
18321
18699
|
} catch (error) {
|