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 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 sourceRoot = path2.join(input.projectRoot, skill.sourcePath);
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(input.projectRoot, file));
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 copyFileSync3,
2914
- existsSync as existsSync7,
2915
- mkdirSync as mkdirSync3,
2916
- readFileSync as readFileSync6,
2917
- rmSync,
2918
- writeFileSync as writeFileSync3
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 dirname4, isAbsolute, join as join6, relative as relative2 } from "path";
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/codex-install.ts
3122
- var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
3123
- var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
3124
- var MANAGED_MODEL_STATE_VERSION = 1;
3125
- function mergeManagedBlock(existing, managedBlock) {
3126
- const start = existing.indexOf(ROOT_START);
3127
- const end = existing.indexOf(ROOT_END);
3128
- if (start !== -1 && end !== -1 && end > start) {
3129
- return `${existing.slice(0, start)}${managedBlock}${existing.slice(end + ROOT_END.length).replace(/^\s*\n/, "")}`;
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
- return `${existing}${existing.endsWith("\n") || existing.length === 0 ? "" : "\n"}
3132
- ${managedBlock}`;
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 writeTextWithBackup(path3, content) {
3135
- mkdirSync3(dirname4(path3), { recursive: true });
3136
- if (existsSync7(path3) && readFileSync6(path3, "utf8") === content) return false;
3137
- if (existsSync7(path3)) copyFileSync3(path3, `${path3}.bak`);
3138
- writeFileSync3(path3, content);
3139
- return true;
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 packageArtifactTarget(packageRoot, artifact) {
3142
- return join6(packageRoot, codexPluginRootArtifactPath(artifact.path));
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 normalizeRelativeMarketplacePath(path3) {
3145
- const normalized = path3.replaceAll("\\", "/");
3146
- if (isAbsolute(path3) || /^[A-Za-z]:\//.test(normalized)) return normalized;
3147
- if (normalized.startsWith("./")) return normalized;
3148
- return `./${normalized}`;
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 marketplaceSourcePath(homeDir, personalPluginRoot) {
3151
- return normalizeRelativeMarketplacePath(
3152
- relative2(homeDir, personalPluginRoot)
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 managedMarketplaceEntry(homeDir, personalPluginRoot) {
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
- name: "thoth-agents",
3158
- source: {
3159
- source: "local",
3160
- path: marketplaceSourcePath(homeDir, personalPluginRoot)
3161
- },
3162
- policy: {
3163
- installation: "AVAILABLE",
3164
- authentication: "ON_INSTALL"
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
- function stableJson3(value) {
3170
- return `${JSON.stringify(value, null, 2)}
3171
- `;
3172
- }
3173
- function emptyManagedModelState() {
3174
- return {
3175
- version: MANAGED_MODEL_STATE_VERSION,
3176
- models: {}
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 readManagedModelState(path3) {
3180
- if (!existsSync7(path3)) return emptyManagedModelState();
3181
- try {
3182
- const parsed = JSON.parse(readFileSync6(path3, "utf8"));
3183
- if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
3184
- return emptyManagedModelState();
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 parseRoleTomlModel(content) {
3199
- const match = /^model\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m.exec(content);
3200
- if (!match) return void 0;
3201
- return match[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
3202
- }
3203
- function escapeTomlString2(value) {
3204
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\t/g, "\\t").replace(/\n/g, "\\n").replace(/\f/g, "\\f").replace(/\r/g, "\\r");
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 replaceRoleTomlModel(content, model) {
3207
- const rendered = `model = "${escapeTomlString2(model)}"`;
3208
- if (/^model\s*=\s*"(?:\\.|[^"\\])*"\s*$/m.test(content)) {
3209
- return content.replace(/^model\s*=\s*"(?:\\.|[^"\\])*"\s*$/m, rendered);
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 roleManagedModelStateKey(path3) {
3215
- return basename(path3);
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 resolveRoleTomlContent(options) {
3218
- const renderedModel = parseRoleTomlModel(options.renderedContent);
3219
- const key = roleManagedModelStateKey(options.targetPath);
3220
- if (!renderedModel) return options.renderedContent;
3221
- if (options.reset || !existsSync7(options.targetPath)) {
3222
- options.nextState.models[key] = renderedModel;
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
- const currentModel = parseRoleTomlModel(
3226
- readFileSync6(options.targetPath, "utf8")
3227
- );
3228
- const trackedModel = options.state.models[key];
3229
- const isUserOwned = currentModel !== void 0 && (trackedModel === void 0 ? currentModel !== renderedModel : currentModel !== trackedModel);
3230
- if (isUserOwned) {
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 render = codexAdapter.render({ projectRoot: config.projectRoot });
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: existsSync7(target.path),
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: existsSync7(targets.managedModelsPath),
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: existsSync7(targets.personalMarketplacePath),
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 = dirname4(items[0]?.targetPath ?? "");
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 = dirname4(common);
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
- rmSync(targetPath, { recursive: true, force: true });
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
- existsSync7(item.targetPath) ? readFileSync6(item.targetPath, "utf8") : "",
3430
- dirname4(dirname4(dirname4(item.targetPath))),
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
- existsSync7(item.targetPath) ? readFileSync6(item.targetPath, "utf8") : "",
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 statSync2 } from "fs";
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 = statSync2(opencodePath);
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.4",
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 .",