thoth-agents 0.1.4 → 0.1.5
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/dist/cli/index.js +499 -488
- package/dist/harness/writers/skill-layout.d.ts +1 -0
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -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}`;
|
|
@@ -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 = [
|
|
@@ -2,6 +2,7 @@ import type { SkillRegistryEntry } from '../core/skills';
|
|
|
2
2
|
import type { HarnessArtifact, HarnessDiagnostic } from '../types';
|
|
3
3
|
export interface CodexSkillLayoutInput {
|
|
4
4
|
projectRoot: string;
|
|
5
|
+
packageRoot?: string;
|
|
5
6
|
skills: SkillRegistryEntry[];
|
|
6
7
|
surfaceId: string;
|
|
7
8
|
outputMode?: CodexSkillOutputMode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thoth-agents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Delegate-first OpenCode plugin with seven agents, thoth-mem persistence, and bundled SDD skills.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup && tsc --emitDeclarationOnly && pnpm run generate-schema",
|
|
45
45
|
"generate-schema": "tsx scripts/generate-schema.ts",
|
|
46
|
+
"release:notes": "tsx scripts/generate-release-notes.ts",
|
|
46
47
|
"typecheck": "tsc --noEmit",
|
|
47
48
|
"test": "vitest run",
|
|
48
49
|
"lint": "biome lint .",
|