thoth-agents 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/cli/index.js +524 -513
- package/dist/harness/core/sdd.d.ts +20 -0
- package/dist/harness/core/skills.d.ts +2 -2
- package/dist/harness/writers/skill-layout.d.ts +1 -0
- package/dist/index.js +23 -23
- package/package.json +2 -1
- package/src/skills/sdd-design/SKILL.md +12 -2
package/dist/cli/index.js
CHANGED
|
@@ -215,7 +215,7 @@ Push back when context, risk, or assumptions are weak. Avoid verbosity.
|
|
|
215
215
|
- Load \`thoth-mem-agents\` and \`requirements-interview\`.
|
|
216
216
|
- You MUST NOT read or write any file in the workspace except \`openspec/\` coordination artifacts for the SDD pipeline.
|
|
217
217
|
- Delegate all inspection, writing, searching, debugging, and verification.
|
|
218
|
-
- Own the thinking: analyze
|
|
218
|
+
- Own the thinking: analyze, choose approach, handle task sequencing, synthesize facts, decide, ask \`{{userQuestionTool}}\` for blocking user input, manage progress, own root-session memory, and write the final report.
|
|
219
219
|
- Use sub-agents for evidence and action, not to outsource architecture or planning.
|
|
220
220
|
- Never request raw file dumps from sub-agents; ask for findings, paths, line anchors, diffs, verification, and blockers.
|
|
221
221
|
- Use openspec/ for coordination artifacts, especially
|
|
@@ -223,6 +223,7 @@ Push back when context, risk, or assumptions are weak. Avoid verbosity.
|
|
|
223
223
|
- Visual or UX work and screenshots always go to {{role.designer}}.
|
|
224
224
|
- Verify through delegation, not inline.
|
|
225
225
|
- Verification should follow the user's project instructions and use the smallest sufficient delegated checks: typecheck, lint, focused tests, or build when appropriate.
|
|
226
|
+
- When a harness cannot enforce a rule directly, preserve the rule as instruction-only guidance and disclose the enforcement gap instead of weakening the contract.
|
|
226
227
|
</core-rules>
|
|
227
228
|
|
|
228
229
|
<session-bootstrap>
|
|
@@ -255,18 +256,11 @@ Tiebreakers:
|
|
|
255
256
|
<internal-handoff>
|
|
256
257
|
Before dispatching {{role.designer}}, {{role.quick}}, or {{role.deep}} after discovery, synthesize a compact internal handoff. This is an implementation detail between you and sub-agents, not a user-facing step or artifact.
|
|
257
258
|
|
|
258
|
-
Internal handoff fields:
|
|
259
|
-
- Goal: the specific outcome for this task.
|
|
260
|
-
- Decision: the chosen approach and why it is the right next move.
|
|
261
|
-
- Evidence: relevant files, symbols, line anchors, docs, constraints, and known invariants from {{role.explorer}}/{{role.librarian}}.
|
|
262
|
-
- Scope: exact files/areas to change and non-goals to avoid.
|
|
263
|
-
- Steps: ordered implementation instructions, including what to preserve.
|
|
264
|
-
- Verification: smallest sufficient checks or visual QA required.
|
|
265
|
-
- Uncertainty: remaining unknowns the implementer may resolve locally, plus what should be escalated instead of guessed.
|
|
259
|
+
Internal handoff fields: Goal, Decision, Evidence, Scope, Steps, Verification, and Uncertainty. Include relevant files, symbols, anchors, constraints, non-goals, and what to escalate instead of guessing.
|
|
266
260
|
|
|
267
261
|
Never mention the internal handoff to the user, ask the user to prepare it, or present handoff preparation as the recommended next step. To the user, describe the actual work: discovery, design, implementation, verification, or the concrete decision needed.
|
|
268
262
|
|
|
269
|
-
For {{role.explorer}}/{{role.librarian}}, ask narrow fact-finding questions
|
|
263
|
+
For {{role.explorer}}/{{role.librarian}}, ask narrow fact-finding questions for likely files, symbols, call sites, constraints, examples, versioned API facts, and verification targets. Require decision-ready findings, not raw context.
|
|
270
264
|
</internal-handoff>
|
|
271
265
|
|
|
272
266
|
<dispatch>
|
|
@@ -342,10 +336,11 @@ function createReadOnlySpecialistPromptSections(role) {
|
|
|
342
336
|
mode: "read-only",
|
|
343
337
|
dispatch: "task",
|
|
344
338
|
scope: "local repository discovery",
|
|
345
|
-
responsibility: "Find workspace facts fast. Return decision-ready evidence for internal handoffs: paths, lines, symbols, constraints, edit targets, and conclusions.",
|
|
339
|
+
responsibility: "Find workspace facts fast. Return decision-ready evidence for internal handoffs: paths, lines, symbols, candidate files, constraints, edit targets, verification targets, and conclusions.",
|
|
346
340
|
rules: [
|
|
347
341
|
"- Questions should be rare; exhaust local evidence first.",
|
|
348
342
|
"- Prefer paths, lines, symbols, and concise summaries over dumps.",
|
|
343
|
+
"- Do not implement, edit files, mutate the repository, or own durable session memory.",
|
|
349
344
|
"- When full content is explicitly requested, reproduce it faithfully."
|
|
350
345
|
],
|
|
351
346
|
memoryAccess: "readonly",
|
|
@@ -361,11 +356,11 @@ FINDINGS: bullets with claim, evidence type [direct|inferred|assumed], confidenc
|
|
|
361
356
|
|
|
362
357
|
ALTERNATIVES CONSIDERED: ranked candidates when more than one plausible match exists. Omit if only one candidate.
|
|
363
358
|
|
|
364
|
-
UNRESOLVED QUESTIONS:
|
|
359
|
+
UNRESOLVED QUESTIONS: ambiguity and what context would unblock it.
|
|
365
360
|
|
|
366
361
|
UNCHECKED AREAS: what you did not inspect that could change the answer. Omit if nothing notable.
|
|
367
362
|
|
|
368
|
-
SHORT EVIDENCE: at most one
|
|
363
|
+
SHORT EVIDENCE: at most one 2-line excerpt per key finding.
|
|
369
364
|
|
|
370
365
|
Lead with STATUS. Stay under 40 lines total when possible. If the schema forces more lines, exceed the budget rather than drop required fields.`
|
|
371
366
|
});
|
|
@@ -375,12 +370,13 @@ Lead with STATUS. Stay under 40 lines total when possible. If the schema forces
|
|
|
375
370
|
role,
|
|
376
371
|
mode: "read-only",
|
|
377
372
|
dispatch: "task",
|
|
378
|
-
scope: "external research plus local confirmation when needed",
|
|
379
|
-
responsibility: "Gather authoritative external evidence that helps the orchestrator make implementation decisions. Prefer official docs first, then high-signal public examples. Every substantive claim must carry a source URL.",
|
|
373
|
+
scope: "external docs and research plus local confirmation when needed",
|
|
374
|
+
responsibility: "Gather authoritative external evidence that helps the orchestrator make implementation decisions. Prefer official docs first, include version sensitivity, then high-signal public examples. Every substantive claim must carry a source URL.",
|
|
380
375
|
rules: [
|
|
381
376
|
"- Questions should be rare; exhaust available sources first.",
|
|
382
377
|
"- Prefer official documentation over commentary when both answer the same point.",
|
|
383
|
-
"- Distinguish clearly between official guidance and community examples."
|
|
378
|
+
"- Distinguish clearly between official guidance and community examples.",
|
|
379
|
+
"- Do not mutate the repository, invent undocumented APIs, or perform broad implementation work."
|
|
384
380
|
],
|
|
385
381
|
memoryAccess: "readonly",
|
|
386
382
|
output: `- Organize by finding. Include a source URL for every claim.
|
|
@@ -394,11 +390,12 @@ Lead with STATUS. Stay under 40 lines total when possible. If the schema forces
|
|
|
394
390
|
mode: "read-only",
|
|
395
391
|
dispatch: "synchronous task only",
|
|
396
392
|
scope: "advice, diagnosis, architecture, code review, and plan review",
|
|
397
|
-
responsibility: "Provide strategic technical guidance anchored to evidence. Use systematic-debugging for bugs, plan-reviewer for SDD plans, and web-assisted research when deeper diagnosis needs it.",
|
|
393
|
+
responsibility: "Provide read-only review and strategic technical guidance anchored to evidence, including findings, risks, assumptions, and decision-ready conclusions. Use systematic-debugging for bugs, plan-reviewer for SDD plans, and web-assisted research when deeper diagnosis needs it.",
|
|
398
394
|
rules: [
|
|
399
395
|
"- Cite exact files and lines for local claims.",
|
|
400
396
|
"- Separate observations, risks, and recommendations.",
|
|
401
|
-
"- Ask only when tradeoffs, risk tolerance, or approval materially change the recommendation."
|
|
397
|
+
"- Ask only when tradeoffs, risk tolerance, or approval materially change the recommendation.",
|
|
398
|
+
"- Do not produce SDD artifacts, implement edits, or mutate the workspace."
|
|
402
399
|
],
|
|
403
400
|
memoryAccess: "readonly",
|
|
404
401
|
output: `- Cite exact files and lines \u2014 do not quote large code blocks.
|
|
@@ -414,13 +411,14 @@ function createWriteCapableSpecialistPromptSections(role) {
|
|
|
414
411
|
mode: "write-capable",
|
|
415
412
|
dispatch: "synchronous task only",
|
|
416
413
|
scope: "UI/UX decisions, implementation, and visual verification",
|
|
417
|
-
responsibility: "Own the user-facing solution
|
|
414
|
+
responsibility: "Own the user-facing solution: choose the UX approach, implement it, and verify it visually across responsive states when screens change. Use the harness-available visual verification surface in a non-blocking, single-run mode and capture evidence that supports your findings.\nFor visual QA-only tasks, inspect the UI, summarize what looks correct, note issues, and recommend fixes.",
|
|
418
415
|
rules: [
|
|
419
416
|
"- Treat the orchestrator's internal handoff as the handoff; do not rediscover settled scope or constraints.",
|
|
420
417
|
"- Own UX decisions instead of bouncing them back unless a real user preference is required.",
|
|
421
|
-
"- Verify visually when feasible; do not stop at code that merely compiles.",
|
|
418
|
+
"- Verify visually and check responsive behavior when feasible; do not stop at code that merely compiles.",
|
|
422
419
|
"- Keep changes focused on the user-facing outcome.",
|
|
423
|
-
"-
|
|
420
|
+
"- Preserve unrelated working-tree changes.",
|
|
421
|
+
"- Avoid interactive, blocking, or persistent visual verification modes unless explicitly requested; keep verification single-run and evidence-driven."
|
|
424
422
|
],
|
|
425
423
|
memoryAccess: "writable",
|
|
426
424
|
output: `For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
@@ -435,10 +433,11 @@ For non-SDD work: state what was implemented, verification status, and remaining
|
|
|
435
433
|
mode: "write-capable",
|
|
436
434
|
dispatch: "synchronous task only",
|
|
437
435
|
scope: "fast bounded implementation",
|
|
438
|
-
responsibility: "Implement well-defined changes quickly. Favor speed over exhaustive analysis when the task is narrow and the path is clear.",
|
|
436
|
+
responsibility: "Implement well-defined changes quickly. Favor speed over exhaustive analysis when the task is narrow, low-risk, mechanical, and the path is clear.",
|
|
439
437
|
rules: [
|
|
440
438
|
"- Optimize for fast execution on narrow, clear tasks.",
|
|
441
439
|
"- Treat the orchestrator's internal handoff as the starting point; follow its file anchors, scope, non-goals, and verification target.",
|
|
440
|
+
"- Preserve unrelated working-tree changes.",
|
|
442
441
|
"- Read only the context you need.",
|
|
443
442
|
"- Do not redo broad discovery. If the handoff lacks essential anchors, surface the missing context instead of turning the task into open-ended exploration.",
|
|
444
443
|
"- Avoid multi-step planning; if the task stops being bounded, surface it.",
|
|
@@ -461,6 +460,7 @@ For non-SDD work: status + summary + files changed + issues. Nothing more.
|
|
|
461
460
|
"- Treat the orchestrator's internal handoff as the architecture handoff; validate it against nearby code, but do not restart upstream discovery unless evidence contradicts it.",
|
|
462
461
|
"- Do not skip verification \u2014 thoroughness is your value proposition.",
|
|
463
462
|
"- Investigate related files, types, and call sites before changing shared behavior, prioritizing the anchors and constraints in the handoff.",
|
|
463
|
+
"- Preserve unrelated working-tree changes.",
|
|
464
464
|
"- Ask when a real architecture or implementation tradeoff blocks correct execution."
|
|
465
465
|
],
|
|
466
466
|
memoryAccess: "writable",
|
|
@@ -1549,7 +1549,7 @@ var BUNDLED_SKILL_REGISTRY = [
|
|
|
1549
1549
|
},
|
|
1550
1550
|
{
|
|
1551
1551
|
name: "sdd-design",
|
|
1552
|
-
description: "Create technical design artifacts for changes",
|
|
1552
|
+
description: "Create technical solution design artifacts for changes",
|
|
1553
1553
|
allowedRoles: ORCHESTRATOR_ONLY,
|
|
1554
1554
|
sourcePath: "src/skills/sdd-design",
|
|
1555
1555
|
kind: "skill",
|
|
@@ -2462,7 +2462,8 @@ function renderCodexSkillLayout(input) {
|
|
|
2462
2462
|
for (const skill of [...input.skills].sort(
|
|
2463
2463
|
(left, right) => left.name.localeCompare(right.name)
|
|
2464
2464
|
)) {
|
|
2465
|
-
const
|
|
2465
|
+
const sourceBaseRoot = input.packageRoot ?? input.projectRoot;
|
|
2466
|
+
const sourceRoot = path2.join(sourceBaseRoot, skill.sourcePath);
|
|
2466
2467
|
const files = collectFiles(sourceRoot);
|
|
2467
2468
|
if (files.length === 0) {
|
|
2468
2469
|
diagnostics.push({
|
|
@@ -2478,7 +2479,7 @@ function renderCodexSkillLayout(input) {
|
|
|
2478
2479
|
for (const file of files) {
|
|
2479
2480
|
const relative4 = normalizePath2(path2.relative(sourceRoot, file));
|
|
2480
2481
|
const content = fs2.readFileSync(file, "utf8");
|
|
2481
|
-
const sourcePath = normalizePath2(path2.relative(
|
|
2482
|
+
const sourcePath = normalizePath2(path2.relative(sourceBaseRoot, file));
|
|
2482
2483
|
for (const mode of outputModes) {
|
|
2483
2484
|
const config = OUTPUT_MODE_CONFIG[mode];
|
|
2484
2485
|
const outputPath = `${config.basePath}/${skill.name}/${relative4}`;
|
|
@@ -2670,7 +2671,7 @@ function codexRoleInstructions(role) {
|
|
|
2670
2671
|
`- Responsibility: ${role.responsibility}`,
|
|
2671
2672
|
"- Use request_user_input for local blocking decisions.",
|
|
2672
2673
|
"- Permissions, memory governance, runtime hooks, and provider-per-agent controls are instruction-level unless the active Codex runtime documents stronger enforcement.",
|
|
2673
|
-
`- ${role.name} runs as a Codex custom
|
|
2674
|
+
`- ${role.name} runs as a Codex custom-agent TOML entry; the orchestrator remains the ambient Codex root session, not a generated role TOML.`,
|
|
2674
2675
|
...role.toolGovernance.map((rule) => `- ${rule}`),
|
|
2675
2676
|
...role.verification.map((rule) => `- ${rule}`),
|
|
2676
2677
|
"</role-operational-contract>"
|
|
@@ -2703,7 +2704,7 @@ function renderCodexRootInstructions(config) {
|
|
|
2703
2704
|
"- After receiving a delegated subagent response, close that subagent session unless you will retry or intentionally keep using that exact same session; explorer and librarian sessions must always be closed immediately after their response, and retry sessions must be closed after the retry result unless explicit same-session reuse is still required.",
|
|
2704
2705
|
"- Use packaged thoth-agents plugin capabilities through Codex plugin, skill, MCP, and hook review surfaces after enabling them with /plugins and /hooks.",
|
|
2705
2706
|
"- For blocking user decisions in Codex Default mode, use request_user_input after features.default_mode_request_user_input is enabled; do not ask those questions in plain prose.",
|
|
2706
|
-
"-
|
|
2707
|
+
"- Memory governance, role permissions, provider-per-agent controls, and hooks are instruction-level unless the active Codex runtime documents stronger enforcement.",
|
|
2707
2708
|
"</codex-runtime>",
|
|
2708
2709
|
CODEX_ROOT_END,
|
|
2709
2710
|
""
|
|
@@ -2854,6 +2855,7 @@ var codexAdapter = {
|
|
|
2854
2855
|
const skillOutputModes = resolveSkillOutputModes(context);
|
|
2855
2856
|
const skillLayout = renderCodexSkillLayout({
|
|
2856
2857
|
projectRoot: context.projectRoot,
|
|
2858
|
+
...hasCodexPackageRoot(context) ? { packageRoot: context.packageRoot } : {},
|
|
2857
2859
|
skills: getSkillRegistry(),
|
|
2858
2860
|
surfaceId: "plugin-skills-directory",
|
|
2859
2861
|
outputModes: skillOutputModes
|
|
@@ -2910,14 +2912,15 @@ import { cwd } from "process";
|
|
|
2910
2912
|
|
|
2911
2913
|
// src/cli/codex-install.ts
|
|
2912
2914
|
import {
|
|
2913
|
-
copyFileSync as
|
|
2914
|
-
existsSync as
|
|
2915
|
-
mkdirSync as
|
|
2916
|
-
readFileSync as
|
|
2917
|
-
rmSync,
|
|
2918
|
-
writeFileSync as
|
|
2915
|
+
copyFileSync as copyFileSync4,
|
|
2916
|
+
existsSync as existsSync9,
|
|
2917
|
+
mkdirSync as mkdirSync5,
|
|
2918
|
+
readFileSync as readFileSync7,
|
|
2919
|
+
rmSync as rmSync2,
|
|
2920
|
+
writeFileSync as writeFileSync4
|
|
2919
2921
|
} from "fs";
|
|
2920
|
-
import { basename, dirname as
|
|
2922
|
+
import { basename, dirname as dirname5, isAbsolute, join as join8, relative as relative3 } from "path";
|
|
2923
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2921
2924
|
|
|
2922
2925
|
// src/harness/codex-plugin-paths.ts
|
|
2923
2926
|
import { join as join4 } from "path";
|
|
@@ -3118,116 +3121,496 @@ function resolveCodexTargets(options) {
|
|
|
3118
3121
|
};
|
|
3119
3122
|
}
|
|
3120
3123
|
|
|
3121
|
-
// src/cli/
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3124
|
+
// src/cli/custom-skills.ts
|
|
3125
|
+
import {
|
|
3126
|
+
copyFileSync as copyFileSync3,
|
|
3127
|
+
existsSync as existsSync8,
|
|
3128
|
+
mkdirSync as mkdirSync4,
|
|
3129
|
+
readdirSync as readdirSync3,
|
|
3130
|
+
rmSync,
|
|
3131
|
+
statSync as statSync3
|
|
3132
|
+
} from "fs";
|
|
3133
|
+
import { dirname as dirname4, join as join7, parse } from "path";
|
|
3134
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3135
|
+
|
|
3136
|
+
// src/cli/skill-manifest.ts
|
|
3137
|
+
import { createHash as createHash3 } from "crypto";
|
|
3138
|
+
import {
|
|
3139
|
+
existsSync as existsSync7,
|
|
3140
|
+
mkdirSync as mkdirSync3,
|
|
3141
|
+
readdirSync as readdirSync2,
|
|
3142
|
+
readFileSync as readFileSync6,
|
|
3143
|
+
statSync as statSync2,
|
|
3144
|
+
writeFileSync as writeFileSync3
|
|
3145
|
+
} from "fs";
|
|
3146
|
+
import { join as join6, relative as relative2 } from "path";
|
|
3147
|
+
var SHARED_SKILL_DIRECTORY = "_shared";
|
|
3148
|
+
var SKILLS_SOURCE_ROOT = join6("src", "skills");
|
|
3149
|
+
var MANIFEST_FILE_NAME = ".skill-manifest.json";
|
|
3150
|
+
function getManifestPath() {
|
|
3151
|
+
return join6(getCustomSkillsDir(), MANIFEST_FILE_NAME);
|
|
3152
|
+
}
|
|
3153
|
+
function listFilesRecursive(dirPath) {
|
|
3154
|
+
if (!existsSync7(dirPath)) {
|
|
3155
|
+
return [];
|
|
3130
3156
|
}
|
|
3131
|
-
|
|
3132
|
-
|
|
3157
|
+
const files = [];
|
|
3158
|
+
const entries = readdirSync2(dirPath).sort(
|
|
3159
|
+
(left, right) => left.localeCompare(right)
|
|
3160
|
+
);
|
|
3161
|
+
for (const entry of entries) {
|
|
3162
|
+
const entryPath = join6(dirPath, entry);
|
|
3163
|
+
const stat = statSync2(entryPath);
|
|
3164
|
+
if (stat.isDirectory()) {
|
|
3165
|
+
files.push(...listFilesRecursive(entryPath));
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
files.push(entryPath);
|
|
3169
|
+
}
|
|
3170
|
+
return files;
|
|
3133
3171
|
}
|
|
3134
|
-
function
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
if (
|
|
3138
|
-
|
|
3139
|
-
|
|
3172
|
+
function readPackageVersion(packageRoot) {
|
|
3173
|
+
const packageJsonPath = join6(packageRoot, "package.json");
|
|
3174
|
+
const packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
3175
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
3176
|
+
throw new Error(`Invalid package version in ${packageJsonPath}`);
|
|
3177
|
+
}
|
|
3178
|
+
return packageJson.version;
|
|
3140
3179
|
}
|
|
3141
|
-
function
|
|
3142
|
-
|
|
3180
|
+
function readManifest() {
|
|
3181
|
+
const manifestPath = getManifestPath();
|
|
3182
|
+
if (!existsSync7(manifestPath)) {
|
|
3183
|
+
return null;
|
|
3184
|
+
}
|
|
3185
|
+
try {
|
|
3186
|
+
return JSON.parse(readFileSync6(manifestPath, "utf-8"));
|
|
3187
|
+
} catch (error) {
|
|
3188
|
+
console.warn(`Failed to read skill manifest: ${manifestPath}`, error);
|
|
3189
|
+
return null;
|
|
3190
|
+
}
|
|
3143
3191
|
}
|
|
3144
|
-
function
|
|
3145
|
-
const
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3192
|
+
function writeManifest(manifest) {
|
|
3193
|
+
const manifestPath = getManifestPath();
|
|
3194
|
+
mkdirSync3(getCustomSkillsDir(), { recursive: true });
|
|
3195
|
+
writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
3196
|
+
`);
|
|
3149
3197
|
}
|
|
3150
|
-
function
|
|
3151
|
-
|
|
3152
|
-
|
|
3198
|
+
function computeSkillHash(skillDirPath) {
|
|
3199
|
+
const hash = createHash3("sha256");
|
|
3200
|
+
for (const filePath of listFilesRecursive(skillDirPath)) {
|
|
3201
|
+
hash.update(`${relative2(skillDirPath, filePath)}
|
|
3202
|
+
`);
|
|
3203
|
+
hash.update(readFileSync6(filePath));
|
|
3204
|
+
hash.update("\n");
|
|
3205
|
+
}
|
|
3206
|
+
return hash.digest("hex");
|
|
3207
|
+
}
|
|
3208
|
+
function findRemovedSkills(manifest) {
|
|
3209
|
+
const currentSkillNames = new Set(CUSTOM_SKILLS.map((skill) => skill.name));
|
|
3210
|
+
return Object.keys(manifest.skills).filter(
|
|
3211
|
+
(skillName) => !currentSkillNames.has(skillName)
|
|
3153
3212
|
);
|
|
3154
3213
|
}
|
|
3155
|
-
function
|
|
3214
|
+
function checkSkillsNeedUpdate(packageRoot) {
|
|
3215
|
+
const manifest = readManifest();
|
|
3216
|
+
const pluginVersion = readPackageVersion(packageRoot);
|
|
3217
|
+
const sharedHash = computeSkillHash(
|
|
3218
|
+
join6(packageRoot, SKILLS_SOURCE_ROOT, SHARED_SKILL_DIRECTORY)
|
|
3219
|
+
);
|
|
3220
|
+
const versionChanged = manifest !== null && manifest.pluginVersion !== pluginVersion;
|
|
3221
|
+
const sharedChanged = manifest !== null && manifest.sharedHash !== sharedHash;
|
|
3222
|
+
const removedSkills = manifest ? findRemovedSkills(manifest) : [];
|
|
3223
|
+
const skillsNeedingUpdate = CUSTOM_SKILLS.flatMap((skill) => {
|
|
3224
|
+
const sourcePath = join6(packageRoot, skill.sourcePath);
|
|
3225
|
+
const targetPath = join6(getCustomSkillsDir(), skill.name);
|
|
3226
|
+
const sourceHash = computeSkillHash(sourcePath);
|
|
3227
|
+
const manifestEntry = manifest?.skills[skill.name];
|
|
3228
|
+
const reasons = [];
|
|
3229
|
+
if (!manifest) {
|
|
3230
|
+
reasons.push("manifest-missing");
|
|
3231
|
+
}
|
|
3232
|
+
if (versionChanged) {
|
|
3233
|
+
reasons.push("version-change");
|
|
3234
|
+
}
|
|
3235
|
+
if (sharedChanged) {
|
|
3236
|
+
reasons.push("shared-hash-mismatch");
|
|
3237
|
+
}
|
|
3238
|
+
if (!manifestEntry) {
|
|
3239
|
+
reasons.push("new-skill");
|
|
3240
|
+
} else if (manifestEntry.hash !== sourceHash) {
|
|
3241
|
+
reasons.push("hash-mismatch");
|
|
3242
|
+
}
|
|
3243
|
+
if (!existsSync7(targetPath)) {
|
|
3244
|
+
reasons.push("missing-install");
|
|
3245
|
+
}
|
|
3246
|
+
if (reasons.length === 0) {
|
|
3247
|
+
return [];
|
|
3248
|
+
}
|
|
3249
|
+
return [
|
|
3250
|
+
{
|
|
3251
|
+
skill,
|
|
3252
|
+
sourceHash,
|
|
3253
|
+
targetPath,
|
|
3254
|
+
reasons
|
|
3255
|
+
}
|
|
3256
|
+
];
|
|
3257
|
+
});
|
|
3156
3258
|
return {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
},
|
|
3166
|
-
category: "Productivity"
|
|
3259
|
+
pluginVersion,
|
|
3260
|
+
sharedHash,
|
|
3261
|
+
manifest,
|
|
3262
|
+
versionChanged,
|
|
3263
|
+
sharedChanged,
|
|
3264
|
+
needsUpdate: skillsNeedingUpdate.length > 0 || removedSkills.length > 0,
|
|
3265
|
+
skillsNeedingUpdate,
|
|
3266
|
+
removedSkills
|
|
3167
3267
|
};
|
|
3168
3268
|
}
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3269
|
+
|
|
3270
|
+
// src/cli/custom-skills.ts
|
|
3271
|
+
var SHARED_SKILL_DIRECTORY2 = "_shared";
|
|
3272
|
+
var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
|
|
3273
|
+
var CUSTOM_SKILLS = BUNDLED_SKILL_REGISTRY.map(
|
|
3274
|
+
(skill) => ({
|
|
3275
|
+
name: skill.name,
|
|
3276
|
+
description: skill.description,
|
|
3277
|
+
allowedAgents: [...skill.allowedRoles],
|
|
3278
|
+
sourcePath: skill.sourcePath
|
|
3279
|
+
})
|
|
3280
|
+
);
|
|
3281
|
+
function getCustomSkillsDir() {
|
|
3282
|
+
return join7(getConfigDir(), "skills");
|
|
3178
3283
|
}
|
|
3179
|
-
function
|
|
3180
|
-
if (!
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3284
|
+
function copyDirRecursive(src, dest) {
|
|
3285
|
+
if (!existsSync8(dest)) {
|
|
3286
|
+
mkdirSync4(dest, { recursive: true });
|
|
3287
|
+
}
|
|
3288
|
+
const entries = readdirSync3(src);
|
|
3289
|
+
for (const entry of entries) {
|
|
3290
|
+
const srcPath = join7(src, entry);
|
|
3291
|
+
const destPath = join7(dest, entry);
|
|
3292
|
+
const stat = statSync3(srcPath);
|
|
3293
|
+
if (stat.isDirectory()) {
|
|
3294
|
+
copyDirRecursive(srcPath, destPath);
|
|
3295
|
+
} else {
|
|
3296
|
+
const destDir = dirname4(destPath);
|
|
3297
|
+
if (!existsSync8(destDir)) {
|
|
3298
|
+
mkdirSync4(destDir, { recursive: true });
|
|
3299
|
+
}
|
|
3300
|
+
copyFileSync3(srcPath, destPath);
|
|
3185
3301
|
}
|
|
3186
|
-
return {
|
|
3187
|
-
version: MANAGED_MODEL_STATE_VERSION,
|
|
3188
|
-
models: Object.fromEntries(
|
|
3189
|
-
Object.entries(parsed.models).filter(
|
|
3190
|
-
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
3191
|
-
)
|
|
3192
|
-
)
|
|
3193
|
-
};
|
|
3194
|
-
} catch {
|
|
3195
|
-
return emptyManagedModelState();
|
|
3196
3302
|
}
|
|
3197
3303
|
}
|
|
3198
|
-
function
|
|
3199
|
-
const
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3304
|
+
function installSharedSkillAssets(packageRoot) {
|
|
3305
|
+
const sharedSourcePath = join7(packageRoot, SHARED_SKILL_SOURCE_PATH);
|
|
3306
|
+
const sharedTargetPath = join7(getCustomSkillsDir(), SHARED_SKILL_DIRECTORY2);
|
|
3307
|
+
if (!existsSync8(sharedSourcePath)) {
|
|
3308
|
+
console.error(`Custom skill shared assets not found: ${sharedSourcePath}`);
|
|
3309
|
+
return false;
|
|
3310
|
+
}
|
|
3311
|
+
rmSync(sharedTargetPath, { recursive: true, force: true });
|
|
3312
|
+
copyDirRecursive(sharedSourcePath, sharedTargetPath);
|
|
3313
|
+
return true;
|
|
3205
3314
|
}
|
|
3206
|
-
function
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3315
|
+
function findPackageRoot(startDir) {
|
|
3316
|
+
let currentDir = startDir;
|
|
3317
|
+
const filesystemRoot = parse(startDir).root;
|
|
3318
|
+
while (true) {
|
|
3319
|
+
if (existsSync8(join7(currentDir, "package.json")) && existsSync8(join7(currentDir, "src", "skills"))) {
|
|
3320
|
+
return currentDir;
|
|
3321
|
+
}
|
|
3322
|
+
if (currentDir === filesystemRoot) {
|
|
3323
|
+
return null;
|
|
3324
|
+
}
|
|
3325
|
+
const parentDir = dirname4(currentDir);
|
|
3326
|
+
if (parentDir === currentDir) {
|
|
3327
|
+
return null;
|
|
3328
|
+
}
|
|
3329
|
+
currentDir = parentDir;
|
|
3210
3330
|
}
|
|
3211
|
-
return `${rendered}
|
|
3212
|
-
${content}`;
|
|
3213
3331
|
}
|
|
3214
|
-
function
|
|
3215
|
-
|
|
3332
|
+
function resolvePackageRoot(packageRoot) {
|
|
3333
|
+
if (packageRoot) {
|
|
3334
|
+
return packageRoot;
|
|
3335
|
+
}
|
|
3336
|
+
const moduleDir = fileURLToPath2(new URL(".", import.meta.url));
|
|
3337
|
+
return findPackageRoot(moduleDir) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
3216
3338
|
}
|
|
3217
|
-
function
|
|
3218
|
-
const
|
|
3219
|
-
const
|
|
3220
|
-
if (!
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
return options.renderedContent;
|
|
3339
|
+
function installCustomSkillFiles(skill, packageRoot) {
|
|
3340
|
+
const sourcePath = join7(packageRoot, skill.sourcePath);
|
|
3341
|
+
const targetPath = join7(getCustomSkillsDir(), skill.name);
|
|
3342
|
+
if (!existsSync8(sourcePath)) {
|
|
3343
|
+
console.error(`Custom skill source not found: ${sourcePath}`);
|
|
3344
|
+
return false;
|
|
3224
3345
|
}
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3346
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
3347
|
+
copyDirRecursive(sourcePath, targetPath);
|
|
3348
|
+
return true;
|
|
3349
|
+
}
|
|
3350
|
+
function removeObsoleteSkills(removedSkillNames) {
|
|
3351
|
+
const removedSkills = [];
|
|
3352
|
+
for (const skillName of removedSkillNames) {
|
|
3353
|
+
const targetPath = join7(getCustomSkillsDir(), skillName);
|
|
3354
|
+
try {
|
|
3355
|
+
console.log(`Removing obsolete bundled skill: ${skillName}`);
|
|
3356
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
3357
|
+
removedSkills.push(skillName);
|
|
3358
|
+
} catch (error) {
|
|
3359
|
+
console.warn(`Failed to remove obsolete bundled skill: ${skillName}`);
|
|
3360
|
+
console.warn(error);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
return removedSkills;
|
|
3364
|
+
}
|
|
3365
|
+
function buildManifest(packageRoot, updatedSkills, previousManifest, pluginVersion, sharedHash) {
|
|
3366
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3367
|
+
const updatedSkillNames = new Set(
|
|
3368
|
+
updatedSkills.map(({ skill }) => skill.name)
|
|
3369
|
+
);
|
|
3370
|
+
const skills = Object.fromEntries(
|
|
3371
|
+
CUSTOM_SKILLS.map((skill) => {
|
|
3372
|
+
const previousEntry = previousManifest?.skills[skill.name];
|
|
3373
|
+
const nextInstalledAt = updatedSkillNames.has(skill.name) ? installedAt : previousEntry?.installedAt ?? installedAt;
|
|
3374
|
+
return [
|
|
3375
|
+
skill.name,
|
|
3376
|
+
{
|
|
3377
|
+
hash: computeSkillHash(join7(packageRoot, skill.sourcePath)),
|
|
3378
|
+
installedAt: nextInstalledAt
|
|
3379
|
+
}
|
|
3380
|
+
];
|
|
3381
|
+
})
|
|
3382
|
+
);
|
|
3383
|
+
return {
|
|
3384
|
+
pluginVersion,
|
|
3385
|
+
sharedHash,
|
|
3386
|
+
skills
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
function pruneRemovedSkillsFromManifest(manifest, removedSkills) {
|
|
3390
|
+
const removedSkillNames = new Set(removedSkills);
|
|
3391
|
+
return {
|
|
3392
|
+
...manifest,
|
|
3393
|
+
skills: Object.fromEntries(
|
|
3394
|
+
Object.entries(manifest.skills).filter(
|
|
3395
|
+
([skillName]) => !removedSkillNames.has(skillName)
|
|
3396
|
+
)
|
|
3397
|
+
)
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
function installCustomSkills(packageRoot = resolvePackageRoot()) {
|
|
3401
|
+
const updateCheck = checkSkillsNeedUpdate(packageRoot);
|
|
3402
|
+
if (!updateCheck.needsUpdate) {
|
|
3403
|
+
return {
|
|
3404
|
+
success: true,
|
|
3405
|
+
updatedSkills: [],
|
|
3406
|
+
skippedSkills: [...CUSTOM_SKILLS],
|
|
3407
|
+
failedSkills: [],
|
|
3408
|
+
removedSkills: []
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
const removedSkills = removeObsoleteSkills(updateCheck.removedSkills);
|
|
3412
|
+
if (removedSkills.length > 0 && updateCheck.manifest) {
|
|
3413
|
+
writeManifest(
|
|
3414
|
+
pruneRemovedSkillsFromManifest(updateCheck.manifest, removedSkills)
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
if (updateCheck.skillsNeedingUpdate.length === 0) {
|
|
3418
|
+
writeManifest(
|
|
3419
|
+
buildManifest(
|
|
3420
|
+
packageRoot,
|
|
3421
|
+
[],
|
|
3422
|
+
updateCheck.manifest,
|
|
3423
|
+
updateCheck.pluginVersion,
|
|
3424
|
+
updateCheck.sharedHash
|
|
3425
|
+
)
|
|
3426
|
+
);
|
|
3427
|
+
return {
|
|
3428
|
+
success: true,
|
|
3429
|
+
updatedSkills: [],
|
|
3430
|
+
skippedSkills: [...CUSTOM_SKILLS],
|
|
3431
|
+
failedSkills: [],
|
|
3432
|
+
removedSkills
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
if (!installSharedSkillAssets(packageRoot)) {
|
|
3436
|
+
return {
|
|
3437
|
+
success: false,
|
|
3438
|
+
updatedSkills: [],
|
|
3439
|
+
skippedSkills: [],
|
|
3440
|
+
failedSkills: updateCheck.skillsNeedingUpdate.map(
|
|
3441
|
+
({ skill, reasons }) => ({
|
|
3442
|
+
skill,
|
|
3443
|
+
reasons
|
|
3444
|
+
})
|
|
3445
|
+
),
|
|
3446
|
+
removedSkills
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
const updatesBySkillName = new Map(
|
|
3450
|
+
updateCheck.skillsNeedingUpdate.map((entry) => [entry.skill.name, entry])
|
|
3451
|
+
);
|
|
3452
|
+
const updatedSkills = [];
|
|
3453
|
+
const skippedSkills = [];
|
|
3454
|
+
const failedSkills = [];
|
|
3455
|
+
for (const skill of CUSTOM_SKILLS) {
|
|
3456
|
+
const pendingUpdate = updatesBySkillName.get(skill.name);
|
|
3457
|
+
if (!pendingUpdate) {
|
|
3458
|
+
skippedSkills.push(skill);
|
|
3459
|
+
continue;
|
|
3460
|
+
}
|
|
3461
|
+
if (installCustomSkillFiles(skill, packageRoot)) {
|
|
3462
|
+
updatedSkills.push({
|
|
3463
|
+
skill,
|
|
3464
|
+
reasons: pendingUpdate.reasons
|
|
3465
|
+
});
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3468
|
+
failedSkills.push({
|
|
3469
|
+
skill,
|
|
3470
|
+
reasons: pendingUpdate.reasons
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
if (failedSkills.length > 0) {
|
|
3474
|
+
return {
|
|
3475
|
+
success: false,
|
|
3476
|
+
updatedSkills,
|
|
3477
|
+
skippedSkills,
|
|
3478
|
+
failedSkills,
|
|
3479
|
+
removedSkills
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
writeManifest(
|
|
3483
|
+
buildManifest(
|
|
3484
|
+
packageRoot,
|
|
3485
|
+
updatedSkills,
|
|
3486
|
+
updateCheck.manifest,
|
|
3487
|
+
updateCheck.pluginVersion,
|
|
3488
|
+
updateCheck.sharedHash
|
|
3489
|
+
)
|
|
3490
|
+
);
|
|
3491
|
+
return {
|
|
3492
|
+
success: true,
|
|
3493
|
+
updatedSkills,
|
|
3494
|
+
skippedSkills,
|
|
3495
|
+
failedSkills,
|
|
3496
|
+
removedSkills
|
|
3497
|
+
};
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// src/cli/codex-install.ts
|
|
3501
|
+
var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
|
|
3502
|
+
var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
|
|
3503
|
+
var MANAGED_MODEL_STATE_VERSION = 1;
|
|
3504
|
+
function mergeManagedBlock(existing, managedBlock) {
|
|
3505
|
+
const start = existing.indexOf(ROOT_START);
|
|
3506
|
+
const end = existing.indexOf(ROOT_END);
|
|
3507
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
3508
|
+
return `${existing.slice(0, start)}${managedBlock}${existing.slice(end + ROOT_END.length).replace(/^\s*\n/, "")}`;
|
|
3509
|
+
}
|
|
3510
|
+
return `${existing}${existing.endsWith("\n") || existing.length === 0 ? "" : "\n"}
|
|
3511
|
+
${managedBlock}`;
|
|
3512
|
+
}
|
|
3513
|
+
function writeTextWithBackup(path3, content) {
|
|
3514
|
+
mkdirSync5(dirname5(path3), { recursive: true });
|
|
3515
|
+
if (existsSync9(path3) && readFileSync7(path3, "utf8") === content) return false;
|
|
3516
|
+
if (existsSync9(path3)) copyFileSync4(path3, `${path3}.bak`);
|
|
3517
|
+
writeFileSync4(path3, content);
|
|
3518
|
+
return true;
|
|
3519
|
+
}
|
|
3520
|
+
function packageArtifactTarget(packageRoot, artifact) {
|
|
3521
|
+
return join8(packageRoot, codexPluginRootArtifactPath(artifact.path));
|
|
3522
|
+
}
|
|
3523
|
+
function resolvePackageRoot2(packageRoot) {
|
|
3524
|
+
if (packageRoot) return packageRoot;
|
|
3525
|
+
return findPackageRoot(fileURLToPath3(new URL(".", import.meta.url))) ?? void 0;
|
|
3526
|
+
}
|
|
3527
|
+
function normalizeRelativeMarketplacePath(path3) {
|
|
3528
|
+
const normalized = path3.replaceAll("\\", "/");
|
|
3529
|
+
if (isAbsolute(path3) || /^[A-Za-z]:\//.test(normalized)) return normalized;
|
|
3530
|
+
if (normalized.startsWith("./")) return normalized;
|
|
3531
|
+
return `./${normalized}`;
|
|
3532
|
+
}
|
|
3533
|
+
function marketplaceSourcePath(homeDir, personalPluginRoot) {
|
|
3534
|
+
return normalizeRelativeMarketplacePath(
|
|
3535
|
+
relative3(homeDir, personalPluginRoot)
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
function managedMarketplaceEntry(homeDir, personalPluginRoot) {
|
|
3539
|
+
return {
|
|
3540
|
+
name: "thoth-agents",
|
|
3541
|
+
source: {
|
|
3542
|
+
source: "local",
|
|
3543
|
+
path: marketplaceSourcePath(homeDir, personalPluginRoot)
|
|
3544
|
+
},
|
|
3545
|
+
policy: {
|
|
3546
|
+
installation: "AVAILABLE",
|
|
3547
|
+
authentication: "ON_INSTALL"
|
|
3548
|
+
},
|
|
3549
|
+
category: "Productivity"
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
function stableJson3(value) {
|
|
3553
|
+
return `${JSON.stringify(value, null, 2)}
|
|
3554
|
+
`;
|
|
3555
|
+
}
|
|
3556
|
+
function emptyManagedModelState() {
|
|
3557
|
+
return {
|
|
3558
|
+
version: MANAGED_MODEL_STATE_VERSION,
|
|
3559
|
+
models: {}
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
function readManagedModelState(path3) {
|
|
3563
|
+
if (!existsSync9(path3)) return emptyManagedModelState();
|
|
3564
|
+
try {
|
|
3565
|
+
const parsed = JSON.parse(readFileSync7(path3, "utf8"));
|
|
3566
|
+
if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
|
|
3567
|
+
return emptyManagedModelState();
|
|
3568
|
+
}
|
|
3569
|
+
return {
|
|
3570
|
+
version: MANAGED_MODEL_STATE_VERSION,
|
|
3571
|
+
models: Object.fromEntries(
|
|
3572
|
+
Object.entries(parsed.models).filter(
|
|
3573
|
+
(entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
|
|
3574
|
+
)
|
|
3575
|
+
)
|
|
3576
|
+
};
|
|
3577
|
+
} catch {
|
|
3578
|
+
return emptyManagedModelState();
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
function parseRoleTomlModel(content) {
|
|
3582
|
+
const match = /^model\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m.exec(content);
|
|
3583
|
+
if (!match) return void 0;
|
|
3584
|
+
return match[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
3585
|
+
}
|
|
3586
|
+
function escapeTomlString2(value) {
|
|
3587
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\f/g, "\\f").replace(/\r/g, "\\r");
|
|
3588
|
+
}
|
|
3589
|
+
function replaceRoleTomlModel(content, model) {
|
|
3590
|
+
const rendered = `model = "${escapeTomlString2(model)}"`;
|
|
3591
|
+
if (/^model\s*=\s*"(?:\\.|[^"\\])*"\s*$/m.test(content)) {
|
|
3592
|
+
return content.replace(/^model\s*=\s*"(?:\\.|[^"\\])*"\s*$/m, rendered);
|
|
3593
|
+
}
|
|
3594
|
+
return `${rendered}
|
|
3595
|
+
${content}`;
|
|
3596
|
+
}
|
|
3597
|
+
function roleManagedModelStateKey(path3) {
|
|
3598
|
+
return basename(path3);
|
|
3599
|
+
}
|
|
3600
|
+
function resolveRoleTomlContent(options) {
|
|
3601
|
+
const renderedModel = parseRoleTomlModel(options.renderedContent);
|
|
3602
|
+
const key = roleManagedModelStateKey(options.targetPath);
|
|
3603
|
+
if (!renderedModel) return options.renderedContent;
|
|
3604
|
+
if (options.reset || !existsSync9(options.targetPath)) {
|
|
3605
|
+
options.nextState.models[key] = renderedModel;
|
|
3606
|
+
return options.renderedContent;
|
|
3607
|
+
}
|
|
3608
|
+
const currentModel = parseRoleTomlModel(
|
|
3609
|
+
readFileSync7(options.targetPath, "utf8")
|
|
3610
|
+
);
|
|
3611
|
+
const trackedModel = options.state.models[key];
|
|
3612
|
+
const isUserOwned = currentModel !== void 0 && (trackedModel === void 0 ? currentModel !== renderedModel : currentModel !== trackedModel);
|
|
3613
|
+
if (isUserOwned) {
|
|
3231
3614
|
if (trackedModel !== void 0)
|
|
3232
3615
|
options.nextState.models[key] = trackedModel;
|
|
3233
3616
|
return replaceRoleTomlModel(options.renderedContent, currentModel);
|
|
@@ -3263,7 +3646,11 @@ function buildCodexSetupPlan(config) {
|
|
|
3263
3646
|
homeDir: config.homeDir,
|
|
3264
3647
|
codexHome: config.codexHome
|
|
3265
3648
|
});
|
|
3266
|
-
const
|
|
3649
|
+
const packageRoot = resolvePackageRoot2(config.packageRoot);
|
|
3650
|
+
const render = codexAdapter.render({
|
|
3651
|
+
projectRoot: config.projectRoot,
|
|
3652
|
+
...packageRoot ? { packageRoot } : {}
|
|
3653
|
+
});
|
|
3267
3654
|
const packageArtifacts = render.artifacts.filter(
|
|
3268
3655
|
(artifact) => artifact.path.startsWith(".codex-plugin/")
|
|
3269
3656
|
);
|
|
@@ -3285,7 +3672,7 @@ function buildCodexSetupPlan(config) {
|
|
|
3285
3672
|
action: "write-role-toml",
|
|
3286
3673
|
targetPath: target.path,
|
|
3287
3674
|
description: `Materialize Codex role subagent ${target.role}.`,
|
|
3288
|
-
requiresBackup:
|
|
3675
|
+
requiresBackup: existsSync9(target.path),
|
|
3289
3676
|
role: target.role,
|
|
3290
3677
|
content: resolveRoleTomlContent({
|
|
3291
3678
|
renderedContent: roleArtifactContent(target.role, render.artifacts),
|
|
@@ -3301,7 +3688,7 @@ function buildCodexSetupPlan(config) {
|
|
|
3301
3688
|
action: "write-managed-model-state",
|
|
3302
3689
|
targetPath: targets.managedModelsPath,
|
|
3303
3690
|
description: "Record thoth-agents-managed Codex role model ownership state.",
|
|
3304
|
-
requiresBackup:
|
|
3691
|
+
requiresBackup: existsSync9(targets.managedModelsPath),
|
|
3305
3692
|
content: stableJson3(nextManagedModelState)
|
|
3306
3693
|
},
|
|
3307
3694
|
...packageArtifacts.map(
|
|
@@ -3319,7 +3706,7 @@ function buildCodexSetupPlan(config) {
|
|
|
3319
3706
|
action: "merge-marketplace",
|
|
3320
3707
|
targetPath: targets.personalMarketplacePath,
|
|
3321
3708
|
description: "Register Personal Codex marketplace entry for the local thoth-agents plugin source.",
|
|
3322
|
-
requiresBackup:
|
|
3709
|
+
requiresBackup: existsSync9(targets.personalMarketplacePath),
|
|
3323
3710
|
content: targets.personalPluginRoot
|
|
3324
3711
|
},
|
|
3325
3712
|
{
|
|
@@ -3383,10 +3770,10 @@ function formatRefreshPackageGroup(kind, groups) {
|
|
|
3383
3770
|
}
|
|
3384
3771
|
function commonTargetDirectory(items) {
|
|
3385
3772
|
if (items.length === 0) return "";
|
|
3386
|
-
let common =
|
|
3773
|
+
let common = dirname5(items[0]?.targetPath ?? "");
|
|
3387
3774
|
for (const item of items.slice(1)) {
|
|
3388
3775
|
while (!isSameOrChildPath(item.targetPath, common)) {
|
|
3389
|
-
const parent =
|
|
3776
|
+
const parent = dirname5(common);
|
|
3390
3777
|
if (parent === common) return common;
|
|
3391
3778
|
common = parent;
|
|
3392
3779
|
}
|
|
@@ -3408,7 +3795,7 @@ function applyCodexSetup(plan) {
|
|
|
3408
3795
|
if (plan.dryRun) return { success: true, changed, diagnostics };
|
|
3409
3796
|
try {
|
|
3410
3797
|
for (const targetPath of managedRefreshRoots(plan)) {
|
|
3411
|
-
|
|
3798
|
+
rmSync2(targetPath, { recursive: true, force: true });
|
|
3412
3799
|
}
|
|
3413
3800
|
for (const item of plan.items) {
|
|
3414
3801
|
if (item.action === "diagnose-only") continue;
|
|
@@ -3426,8 +3813,8 @@ function applyCodexSetup(plan) {
|
|
|
3426
3813
|
if (item.action === "merge-marketplace") {
|
|
3427
3814
|
if (item.content === void 0) continue;
|
|
3428
3815
|
const content2 = mergePersonalMarketplace(
|
|
3429
|
-
|
|
3430
|
-
|
|
3816
|
+
existsSync9(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : "",
|
|
3817
|
+
dirname5(dirname5(dirname5(item.targetPath))),
|
|
3431
3818
|
item.content
|
|
3432
3819
|
);
|
|
3433
3820
|
if (writeTextWithBackup(item.targetPath, content2))
|
|
@@ -3436,7 +3823,7 @@ function applyCodexSetup(plan) {
|
|
|
3436
3823
|
}
|
|
3437
3824
|
if (item.content === void 0) continue;
|
|
3438
3825
|
const content = item.action === "merge-managed-block" ? mergeManagedBlock(
|
|
3439
|
-
|
|
3826
|
+
existsSync9(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : "",
|
|
3440
3827
|
item.content
|
|
3441
3828
|
) : item.content;
|
|
3442
3829
|
if (writeTextWithBackup(item.targetPath, content))
|
|
@@ -3464,7 +3851,7 @@ function managedRefreshRoots(plan) {
|
|
|
3464
3851
|
}
|
|
3465
3852
|
|
|
3466
3853
|
// src/cli/system.ts
|
|
3467
|
-
import { statSync as
|
|
3854
|
+
import { statSync as statSync4 } from "fs";
|
|
3468
3855
|
|
|
3469
3856
|
// src/utils/subprocess.ts
|
|
3470
3857
|
import {
|
|
@@ -3579,7 +3966,7 @@ function resolveOpenCodePath() {
|
|
|
3579
3966
|
for (const opencodePath of paths) {
|
|
3580
3967
|
if (opencodePath === "opencode") continue;
|
|
3581
3968
|
try {
|
|
3582
|
-
const stat =
|
|
3969
|
+
const stat = statSync4(opencodePath);
|
|
3583
3970
|
if (stat.isFile()) {
|
|
3584
3971
|
cachedOpenCodePath = opencodePath;
|
|
3585
3972
|
return opencodePath;
|
|
@@ -3628,382 +4015,6 @@ function getOpenCodePath() {
|
|
|
3628
4015
|
return path3 === "opencode" ? null : path3;
|
|
3629
4016
|
}
|
|
3630
4017
|
|
|
3631
|
-
// src/cli/custom-skills.ts
|
|
3632
|
-
import {
|
|
3633
|
-
copyFileSync as copyFileSync4,
|
|
3634
|
-
existsSync as existsSync9,
|
|
3635
|
-
mkdirSync as mkdirSync5,
|
|
3636
|
-
readdirSync as readdirSync3,
|
|
3637
|
-
rmSync as rmSync2,
|
|
3638
|
-
statSync as statSync4
|
|
3639
|
-
} from "fs";
|
|
3640
|
-
import { dirname as dirname5, join as join8, parse } from "path";
|
|
3641
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3642
|
-
|
|
3643
|
-
// src/cli/skill-manifest.ts
|
|
3644
|
-
import { createHash as createHash3 } from "crypto";
|
|
3645
|
-
import {
|
|
3646
|
-
existsSync as existsSync8,
|
|
3647
|
-
mkdirSync as mkdirSync4,
|
|
3648
|
-
readdirSync as readdirSync2,
|
|
3649
|
-
readFileSync as readFileSync7,
|
|
3650
|
-
statSync as statSync3,
|
|
3651
|
-
writeFileSync as writeFileSync4
|
|
3652
|
-
} from "fs";
|
|
3653
|
-
import { join as join7, relative as relative3 } from "path";
|
|
3654
|
-
var SHARED_SKILL_DIRECTORY = "_shared";
|
|
3655
|
-
var SKILLS_SOURCE_ROOT = join7("src", "skills");
|
|
3656
|
-
var MANIFEST_FILE_NAME = ".skill-manifest.json";
|
|
3657
|
-
function getManifestPath() {
|
|
3658
|
-
return join7(getCustomSkillsDir(), MANIFEST_FILE_NAME);
|
|
3659
|
-
}
|
|
3660
|
-
function listFilesRecursive(dirPath) {
|
|
3661
|
-
if (!existsSync8(dirPath)) {
|
|
3662
|
-
return [];
|
|
3663
|
-
}
|
|
3664
|
-
const files = [];
|
|
3665
|
-
const entries = readdirSync2(dirPath).sort(
|
|
3666
|
-
(left, right) => left.localeCompare(right)
|
|
3667
|
-
);
|
|
3668
|
-
for (const entry of entries) {
|
|
3669
|
-
const entryPath = join7(dirPath, entry);
|
|
3670
|
-
const stat = statSync3(entryPath);
|
|
3671
|
-
if (stat.isDirectory()) {
|
|
3672
|
-
files.push(...listFilesRecursive(entryPath));
|
|
3673
|
-
continue;
|
|
3674
|
-
}
|
|
3675
|
-
files.push(entryPath);
|
|
3676
|
-
}
|
|
3677
|
-
return files;
|
|
3678
|
-
}
|
|
3679
|
-
function readPackageVersion(packageRoot) {
|
|
3680
|
-
const packageJsonPath = join7(packageRoot, "package.json");
|
|
3681
|
-
const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
|
|
3682
|
-
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
3683
|
-
throw new Error(`Invalid package version in ${packageJsonPath}`);
|
|
3684
|
-
}
|
|
3685
|
-
return packageJson.version;
|
|
3686
|
-
}
|
|
3687
|
-
function readManifest() {
|
|
3688
|
-
const manifestPath = getManifestPath();
|
|
3689
|
-
if (!existsSync8(manifestPath)) {
|
|
3690
|
-
return null;
|
|
3691
|
-
}
|
|
3692
|
-
try {
|
|
3693
|
-
return JSON.parse(readFileSync7(manifestPath, "utf-8"));
|
|
3694
|
-
} catch (error) {
|
|
3695
|
-
console.warn(`Failed to read skill manifest: ${manifestPath}`, error);
|
|
3696
|
-
return null;
|
|
3697
|
-
}
|
|
3698
|
-
}
|
|
3699
|
-
function writeManifest(manifest) {
|
|
3700
|
-
const manifestPath = getManifestPath();
|
|
3701
|
-
mkdirSync4(getCustomSkillsDir(), { recursive: true });
|
|
3702
|
-
writeFileSync4(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
3703
|
-
`);
|
|
3704
|
-
}
|
|
3705
|
-
function computeSkillHash(skillDirPath) {
|
|
3706
|
-
const hash = createHash3("sha256");
|
|
3707
|
-
for (const filePath of listFilesRecursive(skillDirPath)) {
|
|
3708
|
-
hash.update(`${relative3(skillDirPath, filePath)}
|
|
3709
|
-
`);
|
|
3710
|
-
hash.update(readFileSync7(filePath));
|
|
3711
|
-
hash.update("\n");
|
|
3712
|
-
}
|
|
3713
|
-
return hash.digest("hex");
|
|
3714
|
-
}
|
|
3715
|
-
function findRemovedSkills(manifest) {
|
|
3716
|
-
const currentSkillNames = new Set(CUSTOM_SKILLS.map((skill) => skill.name));
|
|
3717
|
-
return Object.keys(manifest.skills).filter(
|
|
3718
|
-
(skillName) => !currentSkillNames.has(skillName)
|
|
3719
|
-
);
|
|
3720
|
-
}
|
|
3721
|
-
function checkSkillsNeedUpdate(packageRoot) {
|
|
3722
|
-
const manifest = readManifest();
|
|
3723
|
-
const pluginVersion = readPackageVersion(packageRoot);
|
|
3724
|
-
const sharedHash = computeSkillHash(
|
|
3725
|
-
join7(packageRoot, SKILLS_SOURCE_ROOT, SHARED_SKILL_DIRECTORY)
|
|
3726
|
-
);
|
|
3727
|
-
const versionChanged = manifest !== null && manifest.pluginVersion !== pluginVersion;
|
|
3728
|
-
const sharedChanged = manifest !== null && manifest.sharedHash !== sharedHash;
|
|
3729
|
-
const removedSkills = manifest ? findRemovedSkills(manifest) : [];
|
|
3730
|
-
const skillsNeedingUpdate = CUSTOM_SKILLS.flatMap((skill) => {
|
|
3731
|
-
const sourcePath = join7(packageRoot, skill.sourcePath);
|
|
3732
|
-
const targetPath = join7(getCustomSkillsDir(), skill.name);
|
|
3733
|
-
const sourceHash = computeSkillHash(sourcePath);
|
|
3734
|
-
const manifestEntry = manifest?.skills[skill.name];
|
|
3735
|
-
const reasons = [];
|
|
3736
|
-
if (!manifest) {
|
|
3737
|
-
reasons.push("manifest-missing");
|
|
3738
|
-
}
|
|
3739
|
-
if (versionChanged) {
|
|
3740
|
-
reasons.push("version-change");
|
|
3741
|
-
}
|
|
3742
|
-
if (sharedChanged) {
|
|
3743
|
-
reasons.push("shared-hash-mismatch");
|
|
3744
|
-
}
|
|
3745
|
-
if (!manifestEntry) {
|
|
3746
|
-
reasons.push("new-skill");
|
|
3747
|
-
} else if (manifestEntry.hash !== sourceHash) {
|
|
3748
|
-
reasons.push("hash-mismatch");
|
|
3749
|
-
}
|
|
3750
|
-
if (!existsSync8(targetPath)) {
|
|
3751
|
-
reasons.push("missing-install");
|
|
3752
|
-
}
|
|
3753
|
-
if (reasons.length === 0) {
|
|
3754
|
-
return [];
|
|
3755
|
-
}
|
|
3756
|
-
return [
|
|
3757
|
-
{
|
|
3758
|
-
skill,
|
|
3759
|
-
sourceHash,
|
|
3760
|
-
targetPath,
|
|
3761
|
-
reasons
|
|
3762
|
-
}
|
|
3763
|
-
];
|
|
3764
|
-
});
|
|
3765
|
-
return {
|
|
3766
|
-
pluginVersion,
|
|
3767
|
-
sharedHash,
|
|
3768
|
-
manifest,
|
|
3769
|
-
versionChanged,
|
|
3770
|
-
sharedChanged,
|
|
3771
|
-
needsUpdate: skillsNeedingUpdate.length > 0 || removedSkills.length > 0,
|
|
3772
|
-
skillsNeedingUpdate,
|
|
3773
|
-
removedSkills
|
|
3774
|
-
};
|
|
3775
|
-
}
|
|
3776
|
-
|
|
3777
|
-
// src/cli/custom-skills.ts
|
|
3778
|
-
var SHARED_SKILL_DIRECTORY2 = "_shared";
|
|
3779
|
-
var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
|
|
3780
|
-
var CUSTOM_SKILLS = BUNDLED_SKILL_REGISTRY.map(
|
|
3781
|
-
(skill) => ({
|
|
3782
|
-
name: skill.name,
|
|
3783
|
-
description: skill.description,
|
|
3784
|
-
allowedAgents: [...skill.allowedRoles],
|
|
3785
|
-
sourcePath: skill.sourcePath
|
|
3786
|
-
})
|
|
3787
|
-
);
|
|
3788
|
-
function getCustomSkillsDir() {
|
|
3789
|
-
return join8(getConfigDir(), "skills");
|
|
3790
|
-
}
|
|
3791
|
-
function copyDirRecursive(src, dest) {
|
|
3792
|
-
if (!existsSync9(dest)) {
|
|
3793
|
-
mkdirSync5(dest, { recursive: true });
|
|
3794
|
-
}
|
|
3795
|
-
const entries = readdirSync3(src);
|
|
3796
|
-
for (const entry of entries) {
|
|
3797
|
-
const srcPath = join8(src, entry);
|
|
3798
|
-
const destPath = join8(dest, entry);
|
|
3799
|
-
const stat = statSync4(srcPath);
|
|
3800
|
-
if (stat.isDirectory()) {
|
|
3801
|
-
copyDirRecursive(srcPath, destPath);
|
|
3802
|
-
} else {
|
|
3803
|
-
const destDir = dirname5(destPath);
|
|
3804
|
-
if (!existsSync9(destDir)) {
|
|
3805
|
-
mkdirSync5(destDir, { recursive: true });
|
|
3806
|
-
}
|
|
3807
|
-
copyFileSync4(srcPath, destPath);
|
|
3808
|
-
}
|
|
3809
|
-
}
|
|
3810
|
-
}
|
|
3811
|
-
function installSharedSkillAssets(packageRoot) {
|
|
3812
|
-
const sharedSourcePath = join8(packageRoot, SHARED_SKILL_SOURCE_PATH);
|
|
3813
|
-
const sharedTargetPath = join8(getCustomSkillsDir(), SHARED_SKILL_DIRECTORY2);
|
|
3814
|
-
if (!existsSync9(sharedSourcePath)) {
|
|
3815
|
-
console.error(`Custom skill shared assets not found: ${sharedSourcePath}`);
|
|
3816
|
-
return false;
|
|
3817
|
-
}
|
|
3818
|
-
rmSync2(sharedTargetPath, { recursive: true, force: true });
|
|
3819
|
-
copyDirRecursive(sharedSourcePath, sharedTargetPath);
|
|
3820
|
-
return true;
|
|
3821
|
-
}
|
|
3822
|
-
function findPackageRoot(startDir) {
|
|
3823
|
-
let currentDir = startDir;
|
|
3824
|
-
const filesystemRoot = parse(startDir).root;
|
|
3825
|
-
while (true) {
|
|
3826
|
-
if (existsSync9(join8(currentDir, "package.json")) && existsSync9(join8(currentDir, "src", "skills"))) {
|
|
3827
|
-
return currentDir;
|
|
3828
|
-
}
|
|
3829
|
-
if (currentDir === filesystemRoot) {
|
|
3830
|
-
return null;
|
|
3831
|
-
}
|
|
3832
|
-
const parentDir = dirname5(currentDir);
|
|
3833
|
-
if (parentDir === currentDir) {
|
|
3834
|
-
return null;
|
|
3835
|
-
}
|
|
3836
|
-
currentDir = parentDir;
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
|
-
function resolvePackageRoot(packageRoot) {
|
|
3840
|
-
if (packageRoot) {
|
|
3841
|
-
return packageRoot;
|
|
3842
|
-
}
|
|
3843
|
-
const moduleDir = fileURLToPath2(new URL(".", import.meta.url));
|
|
3844
|
-
return findPackageRoot(moduleDir) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
3845
|
-
}
|
|
3846
|
-
function installCustomSkillFiles(skill, packageRoot) {
|
|
3847
|
-
const sourcePath = join8(packageRoot, skill.sourcePath);
|
|
3848
|
-
const targetPath = join8(getCustomSkillsDir(), skill.name);
|
|
3849
|
-
if (!existsSync9(sourcePath)) {
|
|
3850
|
-
console.error(`Custom skill source not found: ${sourcePath}`);
|
|
3851
|
-
return false;
|
|
3852
|
-
}
|
|
3853
|
-
rmSync2(targetPath, { recursive: true, force: true });
|
|
3854
|
-
copyDirRecursive(sourcePath, targetPath);
|
|
3855
|
-
return true;
|
|
3856
|
-
}
|
|
3857
|
-
function removeObsoleteSkills(removedSkillNames) {
|
|
3858
|
-
const removedSkills = [];
|
|
3859
|
-
for (const skillName of removedSkillNames) {
|
|
3860
|
-
const targetPath = join8(getCustomSkillsDir(), skillName);
|
|
3861
|
-
try {
|
|
3862
|
-
console.log(`Removing obsolete bundled skill: ${skillName}`);
|
|
3863
|
-
rmSync2(targetPath, { recursive: true, force: true });
|
|
3864
|
-
removedSkills.push(skillName);
|
|
3865
|
-
} catch (error) {
|
|
3866
|
-
console.warn(`Failed to remove obsolete bundled skill: ${skillName}`);
|
|
3867
|
-
console.warn(error);
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
return removedSkills;
|
|
3871
|
-
}
|
|
3872
|
-
function buildManifest(packageRoot, updatedSkills, previousManifest, pluginVersion, sharedHash) {
|
|
3873
|
-
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3874
|
-
const updatedSkillNames = new Set(
|
|
3875
|
-
updatedSkills.map(({ skill }) => skill.name)
|
|
3876
|
-
);
|
|
3877
|
-
const skills = Object.fromEntries(
|
|
3878
|
-
CUSTOM_SKILLS.map((skill) => {
|
|
3879
|
-
const previousEntry = previousManifest?.skills[skill.name];
|
|
3880
|
-
const nextInstalledAt = updatedSkillNames.has(skill.name) ? installedAt : previousEntry?.installedAt ?? installedAt;
|
|
3881
|
-
return [
|
|
3882
|
-
skill.name,
|
|
3883
|
-
{
|
|
3884
|
-
hash: computeSkillHash(join8(packageRoot, skill.sourcePath)),
|
|
3885
|
-
installedAt: nextInstalledAt
|
|
3886
|
-
}
|
|
3887
|
-
];
|
|
3888
|
-
})
|
|
3889
|
-
);
|
|
3890
|
-
return {
|
|
3891
|
-
pluginVersion,
|
|
3892
|
-
sharedHash,
|
|
3893
|
-
skills
|
|
3894
|
-
};
|
|
3895
|
-
}
|
|
3896
|
-
function pruneRemovedSkillsFromManifest(manifest, removedSkills) {
|
|
3897
|
-
const removedSkillNames = new Set(removedSkills);
|
|
3898
|
-
return {
|
|
3899
|
-
...manifest,
|
|
3900
|
-
skills: Object.fromEntries(
|
|
3901
|
-
Object.entries(manifest.skills).filter(
|
|
3902
|
-
([skillName]) => !removedSkillNames.has(skillName)
|
|
3903
|
-
)
|
|
3904
|
-
)
|
|
3905
|
-
};
|
|
3906
|
-
}
|
|
3907
|
-
function installCustomSkills(packageRoot = resolvePackageRoot()) {
|
|
3908
|
-
const updateCheck = checkSkillsNeedUpdate(packageRoot);
|
|
3909
|
-
if (!updateCheck.needsUpdate) {
|
|
3910
|
-
return {
|
|
3911
|
-
success: true,
|
|
3912
|
-
updatedSkills: [],
|
|
3913
|
-
skippedSkills: [...CUSTOM_SKILLS],
|
|
3914
|
-
failedSkills: [],
|
|
3915
|
-
removedSkills: []
|
|
3916
|
-
};
|
|
3917
|
-
}
|
|
3918
|
-
const removedSkills = removeObsoleteSkills(updateCheck.removedSkills);
|
|
3919
|
-
if (removedSkills.length > 0 && updateCheck.manifest) {
|
|
3920
|
-
writeManifest(
|
|
3921
|
-
pruneRemovedSkillsFromManifest(updateCheck.manifest, removedSkills)
|
|
3922
|
-
);
|
|
3923
|
-
}
|
|
3924
|
-
if (updateCheck.skillsNeedingUpdate.length === 0) {
|
|
3925
|
-
writeManifest(
|
|
3926
|
-
buildManifest(
|
|
3927
|
-
packageRoot,
|
|
3928
|
-
[],
|
|
3929
|
-
updateCheck.manifest,
|
|
3930
|
-
updateCheck.pluginVersion,
|
|
3931
|
-
updateCheck.sharedHash
|
|
3932
|
-
)
|
|
3933
|
-
);
|
|
3934
|
-
return {
|
|
3935
|
-
success: true,
|
|
3936
|
-
updatedSkills: [],
|
|
3937
|
-
skippedSkills: [...CUSTOM_SKILLS],
|
|
3938
|
-
failedSkills: [],
|
|
3939
|
-
removedSkills
|
|
3940
|
-
};
|
|
3941
|
-
}
|
|
3942
|
-
if (!installSharedSkillAssets(packageRoot)) {
|
|
3943
|
-
return {
|
|
3944
|
-
success: false,
|
|
3945
|
-
updatedSkills: [],
|
|
3946
|
-
skippedSkills: [],
|
|
3947
|
-
failedSkills: updateCheck.skillsNeedingUpdate.map(
|
|
3948
|
-
({ skill, reasons }) => ({
|
|
3949
|
-
skill,
|
|
3950
|
-
reasons
|
|
3951
|
-
})
|
|
3952
|
-
),
|
|
3953
|
-
removedSkills
|
|
3954
|
-
};
|
|
3955
|
-
}
|
|
3956
|
-
const updatesBySkillName = new Map(
|
|
3957
|
-
updateCheck.skillsNeedingUpdate.map((entry) => [entry.skill.name, entry])
|
|
3958
|
-
);
|
|
3959
|
-
const updatedSkills = [];
|
|
3960
|
-
const skippedSkills = [];
|
|
3961
|
-
const failedSkills = [];
|
|
3962
|
-
for (const skill of CUSTOM_SKILLS) {
|
|
3963
|
-
const pendingUpdate = updatesBySkillName.get(skill.name);
|
|
3964
|
-
if (!pendingUpdate) {
|
|
3965
|
-
skippedSkills.push(skill);
|
|
3966
|
-
continue;
|
|
3967
|
-
}
|
|
3968
|
-
if (installCustomSkillFiles(skill, packageRoot)) {
|
|
3969
|
-
updatedSkills.push({
|
|
3970
|
-
skill,
|
|
3971
|
-
reasons: pendingUpdate.reasons
|
|
3972
|
-
});
|
|
3973
|
-
continue;
|
|
3974
|
-
}
|
|
3975
|
-
failedSkills.push({
|
|
3976
|
-
skill,
|
|
3977
|
-
reasons: pendingUpdate.reasons
|
|
3978
|
-
});
|
|
3979
|
-
}
|
|
3980
|
-
if (failedSkills.length > 0) {
|
|
3981
|
-
return {
|
|
3982
|
-
success: false,
|
|
3983
|
-
updatedSkills,
|
|
3984
|
-
skippedSkills,
|
|
3985
|
-
failedSkills,
|
|
3986
|
-
removedSkills
|
|
3987
|
-
};
|
|
3988
|
-
}
|
|
3989
|
-
writeManifest(
|
|
3990
|
-
buildManifest(
|
|
3991
|
-
packageRoot,
|
|
3992
|
-
updatedSkills,
|
|
3993
|
-
updateCheck.manifest,
|
|
3994
|
-
updateCheck.pluginVersion,
|
|
3995
|
-
updateCheck.sharedHash
|
|
3996
|
-
)
|
|
3997
|
-
);
|
|
3998
|
-
return {
|
|
3999
|
-
success: true,
|
|
4000
|
-
updatedSkills,
|
|
4001
|
-
skippedSkills,
|
|
4002
|
-
failedSkills,
|
|
4003
|
-
removedSkills
|
|
4004
|
-
};
|
|
4005
|
-
}
|
|
4006
|
-
|
|
4007
4018
|
// src/cli/skills.ts
|
|
4008
4019
|
import { spawnSync } from "child_process";
|
|
4009
4020
|
var RECOMMENDED_SKILLS = [
|