thoth-agents 0.1.2 → 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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli/index.js CHANGED
@@ -6,6 +6,7 @@ import { pathToFileURL } from "url";
6
6
  // src/harness/adapters/codex.ts
7
7
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
8
8
  import { dirname as dirname2, resolve } from "path";
9
+ import { fileURLToPath } from "url";
9
10
 
10
11
  // src/agents/prompt-dialects.ts
11
12
  var OPENCODE_CAPABILITIES = {
@@ -2461,7 +2462,8 @@ function renderCodexSkillLayout(input) {
2461
2462
  for (const skill of [...input.skills].sort(
2462
2463
  (left, right) => left.name.localeCompare(right.name)
2463
2464
  )) {
2464
- const sourceRoot = path2.join(input.projectRoot, skill.sourcePath);
2465
+ const sourceBaseRoot = input.packageRoot ?? input.projectRoot;
2466
+ const sourceRoot = path2.join(sourceBaseRoot, skill.sourcePath);
2465
2467
  const files = collectFiles(sourceRoot);
2466
2468
  if (files.length === 0) {
2467
2469
  diagnostics.push({
@@ -2477,7 +2479,7 @@ function renderCodexSkillLayout(input) {
2477
2479
  for (const file of files) {
2478
2480
  const relative4 = normalizePath2(path2.relative(sourceRoot, file));
2479
2481
  const content = fs2.readFileSync(file, "utf8");
2480
- const sourcePath = normalizePath2(path2.relative(input.projectRoot, file));
2482
+ const sourcePath = normalizePath2(path2.relative(sourceBaseRoot, file));
2481
2483
  for (const mode of outputModes) {
2482
2484
  const config = OUTPUT_MODE_CONFIG[mode];
2483
2485
  const outputPath = `${config.basePath}/${skill.name}/${relative4}`;
@@ -2512,14 +2514,19 @@ function renderCodexSkillLayout(input) {
2512
2514
  }
2513
2515
 
2514
2516
  // src/harness/adapters/codex.ts
2515
- function readRootPackageVersion(startDir) {
2516
- const packageJsonPath = findRootPackageJsonPath([startDir, process.cwd()]);
2517
+ function readRootPackageVersion(context) {
2518
+ const packageJsonPath = findRootPackageJsonPath([
2519
+ ...hasCodexPackageRoot(context) ? [context.packageRoot] : [],
2520
+ context.projectRoot,
2521
+ process.cwd(),
2522
+ fileURLToPath(new URL(".", import.meta.url))
2523
+ ]);
2517
2524
  return readPackageJsonVersion(packageJsonPath);
2518
2525
  }
2519
- function createCodexPluginPackageManifest(projectRoot) {
2526
+ function createCodexPluginPackageManifest(context) {
2520
2527
  return {
2521
2528
  name: "thoth-agents",
2522
- version: readRootPackageVersion(projectRoot),
2529
+ version: readRootPackageVersion(context),
2523
2530
  description: "Delegate-first OpenCode plugin with seven agents, thoth-mem persistence, and bundled SDD skills."
2524
2531
  };
2525
2532
  }
@@ -2726,6 +2733,9 @@ function codexSurfaceHasField(surfaceId, field) {
2726
2733
  function hasCodexConfig(context) {
2727
2734
  return "config" in context;
2728
2735
  }
2736
+ function hasCodexPackageRoot(context) {
2737
+ return "packageRoot" in context && typeof context.packageRoot === "string" && context.packageRoot.length > 0;
2738
+ }
2729
2739
  function renderAgentArtifacts({ config }) {
2730
2740
  const artifacts = [];
2731
2741
  const diagnostics = [];
@@ -2845,12 +2855,13 @@ var codexAdapter = {
2845
2855
  const skillOutputModes = resolveSkillOutputModes(context);
2846
2856
  const skillLayout = renderCodexSkillLayout({
2847
2857
  projectRoot: context.projectRoot,
2858
+ ...hasCodexPackageRoot(context) ? { packageRoot: context.packageRoot } : {},
2848
2859
  skills: getSkillRegistry(),
2849
2860
  surfaceId: "plugin-skills-directory",
2850
2861
  outputModes: skillOutputModes
2851
2862
  });
2852
2863
  const pluginPackage = renderCodexPluginPackage({
2853
- manifest: createCodexPluginPackageManifest(context.projectRoot),
2864
+ manifest: createCodexPluginPackageManifest(context),
2854
2865
  assets: [
2855
2866
  {
2856
2867
  surfaceId: "plugin-skills-directory",
@@ -2901,14 +2912,15 @@ import { cwd } from "process";
2901
2912
 
2902
2913
  // src/cli/codex-install.ts
2903
2914
  import {
2904
- copyFileSync as copyFileSync3,
2905
- existsSync as existsSync7,
2906
- mkdirSync as mkdirSync3,
2907
- readFileSync as readFileSync6,
2908
- rmSync,
2909
- 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
2910
2921
  } from "fs";
2911
- 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";
2912
2924
 
2913
2925
  // src/harness/codex-plugin-paths.ts
2914
2926
  import { join as join4 } from "path";
@@ -3109,116 +3121,496 @@ function resolveCodexTargets(options) {
3109
3121
  };
3110
3122
  }
3111
3123
 
3112
- // src/cli/codex-install.ts
3113
- var ROOT_START = "<!-- thoth-agents:codex-root:start -->";
3114
- var ROOT_END = "<!-- thoth-agents:codex-root:end -->";
3115
- var MANAGED_MODEL_STATE_VERSION = 1;
3116
- function mergeManagedBlock(existing, managedBlock) {
3117
- const start = existing.indexOf(ROOT_START);
3118
- const end = existing.indexOf(ROOT_END);
3119
- if (start !== -1 && end !== -1 && end > start) {
3120
- 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 [];
3121
3156
  }
3122
- return `${existing}${existing.endsWith("\n") || existing.length === 0 ? "" : "\n"}
3123
- ${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;
3124
3171
  }
3125
- function writeTextWithBackup(path3, content) {
3126
- mkdirSync3(dirname4(path3), { recursive: true });
3127
- if (existsSync7(path3) && readFileSync6(path3, "utf8") === content) return false;
3128
- if (existsSync7(path3)) copyFileSync3(path3, `${path3}.bak`);
3129
- writeFileSync3(path3, content);
3130
- 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;
3131
3179
  }
3132
- function packageArtifactTarget(packageRoot, artifact) {
3133
- 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
+ }
3134
3191
  }
3135
- function normalizeRelativeMarketplacePath(path3) {
3136
- const normalized = path3.replaceAll("\\", "/");
3137
- if (isAbsolute(path3) || /^[A-Za-z]:\//.test(normalized)) return normalized;
3138
- if (normalized.startsWith("./")) return normalized;
3139
- return `./${normalized}`;
3192
+ function writeManifest(manifest) {
3193
+ const manifestPath = getManifestPath();
3194
+ mkdirSync3(getCustomSkillsDir(), { recursive: true });
3195
+ writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
3196
+ `);
3140
3197
  }
3141
- function marketplaceSourcePath(homeDir, personalPluginRoot) {
3142
- return normalizeRelativeMarketplacePath(
3143
- 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)
3144
3212
  );
3145
3213
  }
3146
- 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
+ });
3147
3258
  return {
3148
- name: "thoth-agents",
3149
- source: {
3150
- source: "local",
3151
- path: marketplaceSourcePath(homeDir, personalPluginRoot)
3152
- },
3153
- policy: {
3154
- installation: "AVAILABLE",
3155
- authentication: "ON_INSTALL"
3156
- },
3157
- category: "Productivity"
3259
+ pluginVersion,
3260
+ sharedHash,
3261
+ manifest,
3262
+ versionChanged,
3263
+ sharedChanged,
3264
+ needsUpdate: skillsNeedingUpdate.length > 0 || removedSkills.length > 0,
3265
+ skillsNeedingUpdate,
3266
+ removedSkills
3158
3267
  };
3159
3268
  }
3160
- function stableJson3(value) {
3161
- return `${JSON.stringify(value, null, 2)}
3162
- `;
3163
- }
3164
- function emptyManagedModelState() {
3165
- return {
3166
- version: MANAGED_MODEL_STATE_VERSION,
3167
- models: {}
3168
- };
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");
3169
3283
  }
3170
- function readManagedModelState(path3) {
3171
- if (!existsSync7(path3)) return emptyManagedModelState();
3172
- try {
3173
- const parsed = JSON.parse(readFileSync6(path3, "utf8"));
3174
- if (parsed.version !== MANAGED_MODEL_STATE_VERSION || !parsed.models || typeof parsed.models !== "object" || Array.isArray(parsed.models)) {
3175
- 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);
3176
3301
  }
3177
- return {
3178
- version: MANAGED_MODEL_STATE_VERSION,
3179
- models: Object.fromEntries(
3180
- Object.entries(parsed.models).filter(
3181
- (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
3182
- )
3183
- )
3184
- };
3185
- } catch {
3186
- return emptyManagedModelState();
3187
3302
  }
3188
3303
  }
3189
- function parseRoleTomlModel(content) {
3190
- const match = /^model\s*=\s*"((?:\\.|[^"\\])*)"\s*$/m.exec(content);
3191
- if (!match) return void 0;
3192
- return match[1].replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
3193
- }
3194
- function escapeTomlString2(value) {
3195
- 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;
3196
3314
  }
3197
- function replaceRoleTomlModel(content, model) {
3198
- const rendered = `model = "${escapeTomlString2(model)}"`;
3199
- if (/^model\s*=\s*"(?:\\.|[^"\\])*"\s*$/m.test(content)) {
3200
- 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;
3201
3330
  }
3202
- return `${rendered}
3203
- ${content}`;
3204
3331
  }
3205
- function roleManagedModelStateKey(path3) {
3206
- 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));
3207
3338
  }
3208
- function resolveRoleTomlContent(options) {
3209
- const renderedModel = parseRoleTomlModel(options.renderedContent);
3210
- const key = roleManagedModelStateKey(options.targetPath);
3211
- if (!renderedModel) return options.renderedContent;
3212
- if (options.reset || !existsSync7(options.targetPath)) {
3213
- options.nextState.models[key] = renderedModel;
3214
- 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;
3215
3345
  }
3216
- const currentModel = parseRoleTomlModel(
3217
- readFileSync6(options.targetPath, "utf8")
3218
- );
3219
- const trackedModel = options.state.models[key];
3220
- const isUserOwned = currentModel !== void 0 && (trackedModel === void 0 ? currentModel !== renderedModel : currentModel !== trackedModel);
3221
- 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) {
3222
3614
  if (trackedModel !== void 0)
3223
3615
  options.nextState.models[key] = trackedModel;
3224
3616
  return replaceRoleTomlModel(options.renderedContent, currentModel);
@@ -3254,7 +3646,11 @@ function buildCodexSetupPlan(config) {
3254
3646
  homeDir: config.homeDir,
3255
3647
  codexHome: config.codexHome
3256
3648
  });
3257
- 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
+ });
3258
3654
  const packageArtifacts = render.artifacts.filter(
3259
3655
  (artifact) => artifact.path.startsWith(".codex-plugin/")
3260
3656
  );
@@ -3276,7 +3672,7 @@ function buildCodexSetupPlan(config) {
3276
3672
  action: "write-role-toml",
3277
3673
  targetPath: target.path,
3278
3674
  description: `Materialize Codex role subagent ${target.role}.`,
3279
- requiresBackup: existsSync7(target.path),
3675
+ requiresBackup: existsSync9(target.path),
3280
3676
  role: target.role,
3281
3677
  content: resolveRoleTomlContent({
3282
3678
  renderedContent: roleArtifactContent(target.role, render.artifacts),
@@ -3292,7 +3688,7 @@ function buildCodexSetupPlan(config) {
3292
3688
  action: "write-managed-model-state",
3293
3689
  targetPath: targets.managedModelsPath,
3294
3690
  description: "Record thoth-agents-managed Codex role model ownership state.",
3295
- requiresBackup: existsSync7(targets.managedModelsPath),
3691
+ requiresBackup: existsSync9(targets.managedModelsPath),
3296
3692
  content: stableJson3(nextManagedModelState)
3297
3693
  },
3298
3694
  ...packageArtifacts.map(
@@ -3310,7 +3706,7 @@ function buildCodexSetupPlan(config) {
3310
3706
  action: "merge-marketplace",
3311
3707
  targetPath: targets.personalMarketplacePath,
3312
3708
  description: "Register Personal Codex marketplace entry for the local thoth-agents plugin source.",
3313
- requiresBackup: existsSync7(targets.personalMarketplacePath),
3709
+ requiresBackup: existsSync9(targets.personalMarketplacePath),
3314
3710
  content: targets.personalPluginRoot
3315
3711
  },
3316
3712
  {
@@ -3374,10 +3770,10 @@ function formatRefreshPackageGroup(kind, groups) {
3374
3770
  }
3375
3771
  function commonTargetDirectory(items) {
3376
3772
  if (items.length === 0) return "";
3377
- let common = dirname4(items[0]?.targetPath ?? "");
3773
+ let common = dirname5(items[0]?.targetPath ?? "");
3378
3774
  for (const item of items.slice(1)) {
3379
3775
  while (!isSameOrChildPath(item.targetPath, common)) {
3380
- const parent = dirname4(common);
3776
+ const parent = dirname5(common);
3381
3777
  if (parent === common) return common;
3382
3778
  common = parent;
3383
3779
  }
@@ -3399,7 +3795,7 @@ function applyCodexSetup(plan) {
3399
3795
  if (plan.dryRun) return { success: true, changed, diagnostics };
3400
3796
  try {
3401
3797
  for (const targetPath of managedRefreshRoots(plan)) {
3402
- rmSync(targetPath, { recursive: true, force: true });
3798
+ rmSync2(targetPath, { recursive: true, force: true });
3403
3799
  }
3404
3800
  for (const item of plan.items) {
3405
3801
  if (item.action === "diagnose-only") continue;
@@ -3417,8 +3813,8 @@ function applyCodexSetup(plan) {
3417
3813
  if (item.action === "merge-marketplace") {
3418
3814
  if (item.content === void 0) continue;
3419
3815
  const content2 = mergePersonalMarketplace(
3420
- existsSync7(item.targetPath) ? readFileSync6(item.targetPath, "utf8") : "",
3421
- dirname4(dirname4(dirname4(item.targetPath))),
3816
+ existsSync9(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : "",
3817
+ dirname5(dirname5(dirname5(item.targetPath))),
3422
3818
  item.content
3423
3819
  );
3424
3820
  if (writeTextWithBackup(item.targetPath, content2))
@@ -3427,7 +3823,7 @@ function applyCodexSetup(plan) {
3427
3823
  }
3428
3824
  if (item.content === void 0) continue;
3429
3825
  const content = item.action === "merge-managed-block" ? mergeManagedBlock(
3430
- existsSync7(item.targetPath) ? readFileSync6(item.targetPath, "utf8") : "",
3826
+ existsSync9(item.targetPath) ? readFileSync7(item.targetPath, "utf8") : "",
3431
3827
  item.content
3432
3828
  ) : item.content;
3433
3829
  if (writeTextWithBackup(item.targetPath, content))
@@ -3455,7 +3851,7 @@ function managedRefreshRoots(plan) {
3455
3851
  }
3456
3852
 
3457
3853
  // src/cli/system.ts
3458
- import { statSync as statSync2 } from "fs";
3854
+ import { statSync as statSync4 } from "fs";
3459
3855
 
3460
3856
  // src/utils/subprocess.ts
3461
3857
  import {
@@ -3570,7 +3966,7 @@ function resolveOpenCodePath() {
3570
3966
  for (const opencodePath of paths) {
3571
3967
  if (opencodePath === "opencode") continue;
3572
3968
  try {
3573
- const stat = statSync2(opencodePath);
3969
+ const stat = statSync4(opencodePath);
3574
3970
  if (stat.isFile()) {
3575
3971
  cachedOpenCodePath = opencodePath;
3576
3972
  return opencodePath;
@@ -3619,382 +4015,6 @@ function getOpenCodePath() {
3619
4015
  return path3 === "opencode" ? null : path3;
3620
4016
  }
3621
4017
 
3622
- // src/cli/custom-skills.ts
3623
- import {
3624
- copyFileSync as copyFileSync4,
3625
- existsSync as existsSync9,
3626
- mkdirSync as mkdirSync5,
3627
- readdirSync as readdirSync3,
3628
- rmSync as rmSync2,
3629
- statSync as statSync4
3630
- } from "fs";
3631
- import { dirname as dirname5, join as join8, parse } from "path";
3632
- import { fileURLToPath } from "url";
3633
-
3634
- // src/cli/skill-manifest.ts
3635
- import { createHash as createHash3 } from "crypto";
3636
- import {
3637
- existsSync as existsSync8,
3638
- mkdirSync as mkdirSync4,
3639
- readdirSync as readdirSync2,
3640
- readFileSync as readFileSync7,
3641
- statSync as statSync3,
3642
- writeFileSync as writeFileSync4
3643
- } from "fs";
3644
- import { join as join7, relative as relative3 } from "path";
3645
- var SHARED_SKILL_DIRECTORY = "_shared";
3646
- var SKILLS_SOURCE_ROOT = join7("src", "skills");
3647
- var MANIFEST_FILE_NAME = ".skill-manifest.json";
3648
- function getManifestPath() {
3649
- return join7(getCustomSkillsDir(), MANIFEST_FILE_NAME);
3650
- }
3651
- function listFilesRecursive(dirPath) {
3652
- if (!existsSync8(dirPath)) {
3653
- return [];
3654
- }
3655
- const files = [];
3656
- const entries = readdirSync2(dirPath).sort(
3657
- (left, right) => left.localeCompare(right)
3658
- );
3659
- for (const entry of entries) {
3660
- const entryPath = join7(dirPath, entry);
3661
- const stat = statSync3(entryPath);
3662
- if (stat.isDirectory()) {
3663
- files.push(...listFilesRecursive(entryPath));
3664
- continue;
3665
- }
3666
- files.push(entryPath);
3667
- }
3668
- return files;
3669
- }
3670
- function readPackageVersion(packageRoot) {
3671
- const packageJsonPath = join7(packageRoot, "package.json");
3672
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
3673
- if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
3674
- throw new Error(`Invalid package version in ${packageJsonPath}`);
3675
- }
3676
- return packageJson.version;
3677
- }
3678
- function readManifest() {
3679
- const manifestPath = getManifestPath();
3680
- if (!existsSync8(manifestPath)) {
3681
- return null;
3682
- }
3683
- try {
3684
- return JSON.parse(readFileSync7(manifestPath, "utf-8"));
3685
- } catch (error) {
3686
- console.warn(`Failed to read skill manifest: ${manifestPath}`, error);
3687
- return null;
3688
- }
3689
- }
3690
- function writeManifest(manifest) {
3691
- const manifestPath = getManifestPath();
3692
- mkdirSync4(getCustomSkillsDir(), { recursive: true });
3693
- writeFileSync4(manifestPath, `${JSON.stringify(manifest, null, 2)}
3694
- `);
3695
- }
3696
- function computeSkillHash(skillDirPath) {
3697
- const hash = createHash3("sha256");
3698
- for (const filePath of listFilesRecursive(skillDirPath)) {
3699
- hash.update(`${relative3(skillDirPath, filePath)}
3700
- `);
3701
- hash.update(readFileSync7(filePath));
3702
- hash.update("\n");
3703
- }
3704
- return hash.digest("hex");
3705
- }
3706
- function findRemovedSkills(manifest) {
3707
- const currentSkillNames = new Set(CUSTOM_SKILLS.map((skill) => skill.name));
3708
- return Object.keys(manifest.skills).filter(
3709
- (skillName) => !currentSkillNames.has(skillName)
3710
- );
3711
- }
3712
- function checkSkillsNeedUpdate(packageRoot) {
3713
- const manifest = readManifest();
3714
- const pluginVersion = readPackageVersion(packageRoot);
3715
- const sharedHash = computeSkillHash(
3716
- join7(packageRoot, SKILLS_SOURCE_ROOT, SHARED_SKILL_DIRECTORY)
3717
- );
3718
- const versionChanged = manifest !== null && manifest.pluginVersion !== pluginVersion;
3719
- const sharedChanged = manifest !== null && manifest.sharedHash !== sharedHash;
3720
- const removedSkills = manifest ? findRemovedSkills(manifest) : [];
3721
- const skillsNeedingUpdate = CUSTOM_SKILLS.flatMap((skill) => {
3722
- const sourcePath = join7(packageRoot, skill.sourcePath);
3723
- const targetPath = join7(getCustomSkillsDir(), skill.name);
3724
- const sourceHash = computeSkillHash(sourcePath);
3725
- const manifestEntry = manifest?.skills[skill.name];
3726
- const reasons = [];
3727
- if (!manifest) {
3728
- reasons.push("manifest-missing");
3729
- }
3730
- if (versionChanged) {
3731
- reasons.push("version-change");
3732
- }
3733
- if (sharedChanged) {
3734
- reasons.push("shared-hash-mismatch");
3735
- }
3736
- if (!manifestEntry) {
3737
- reasons.push("new-skill");
3738
- } else if (manifestEntry.hash !== sourceHash) {
3739
- reasons.push("hash-mismatch");
3740
- }
3741
- if (!existsSync8(targetPath)) {
3742
- reasons.push("missing-install");
3743
- }
3744
- if (reasons.length === 0) {
3745
- return [];
3746
- }
3747
- return [
3748
- {
3749
- skill,
3750
- sourceHash,
3751
- targetPath,
3752
- reasons
3753
- }
3754
- ];
3755
- });
3756
- return {
3757
- pluginVersion,
3758
- sharedHash,
3759
- manifest,
3760
- versionChanged,
3761
- sharedChanged,
3762
- needsUpdate: skillsNeedingUpdate.length > 0 || removedSkills.length > 0,
3763
- skillsNeedingUpdate,
3764
- removedSkills
3765
- };
3766
- }
3767
-
3768
- // src/cli/custom-skills.ts
3769
- var SHARED_SKILL_DIRECTORY2 = "_shared";
3770
- var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
3771
- var CUSTOM_SKILLS = BUNDLED_SKILL_REGISTRY.map(
3772
- (skill) => ({
3773
- name: skill.name,
3774
- description: skill.description,
3775
- allowedAgents: [...skill.allowedRoles],
3776
- sourcePath: skill.sourcePath
3777
- })
3778
- );
3779
- function getCustomSkillsDir() {
3780
- return join8(getConfigDir(), "skills");
3781
- }
3782
- function copyDirRecursive(src, dest) {
3783
- if (!existsSync9(dest)) {
3784
- mkdirSync5(dest, { recursive: true });
3785
- }
3786
- const entries = readdirSync3(src);
3787
- for (const entry of entries) {
3788
- const srcPath = join8(src, entry);
3789
- const destPath = join8(dest, entry);
3790
- const stat = statSync4(srcPath);
3791
- if (stat.isDirectory()) {
3792
- copyDirRecursive(srcPath, destPath);
3793
- } else {
3794
- const destDir = dirname5(destPath);
3795
- if (!existsSync9(destDir)) {
3796
- mkdirSync5(destDir, { recursive: true });
3797
- }
3798
- copyFileSync4(srcPath, destPath);
3799
- }
3800
- }
3801
- }
3802
- function installSharedSkillAssets(packageRoot) {
3803
- const sharedSourcePath = join8(packageRoot, SHARED_SKILL_SOURCE_PATH);
3804
- const sharedTargetPath = join8(getCustomSkillsDir(), SHARED_SKILL_DIRECTORY2);
3805
- if (!existsSync9(sharedSourcePath)) {
3806
- console.error(`Custom skill shared assets not found: ${sharedSourcePath}`);
3807
- return false;
3808
- }
3809
- rmSync2(sharedTargetPath, { recursive: true, force: true });
3810
- copyDirRecursive(sharedSourcePath, sharedTargetPath);
3811
- return true;
3812
- }
3813
- function findPackageRoot(startDir) {
3814
- let currentDir = startDir;
3815
- const filesystemRoot = parse(startDir).root;
3816
- while (true) {
3817
- if (existsSync9(join8(currentDir, "package.json")) && existsSync9(join8(currentDir, "src", "skills"))) {
3818
- return currentDir;
3819
- }
3820
- if (currentDir === filesystemRoot) {
3821
- return null;
3822
- }
3823
- const parentDir = dirname5(currentDir);
3824
- if (parentDir === currentDir) {
3825
- return null;
3826
- }
3827
- currentDir = parentDir;
3828
- }
3829
- }
3830
- function resolvePackageRoot(packageRoot) {
3831
- if (packageRoot) {
3832
- return packageRoot;
3833
- }
3834
- const moduleDir = fileURLToPath(new URL(".", import.meta.url));
3835
- return findPackageRoot(moduleDir) ?? fileURLToPath(new URL("../..", import.meta.url));
3836
- }
3837
- function installCustomSkillFiles(skill, packageRoot) {
3838
- const sourcePath = join8(packageRoot, skill.sourcePath);
3839
- const targetPath = join8(getCustomSkillsDir(), skill.name);
3840
- if (!existsSync9(sourcePath)) {
3841
- console.error(`Custom skill source not found: ${sourcePath}`);
3842
- return false;
3843
- }
3844
- rmSync2(targetPath, { recursive: true, force: true });
3845
- copyDirRecursive(sourcePath, targetPath);
3846
- return true;
3847
- }
3848
- function removeObsoleteSkills(removedSkillNames) {
3849
- const removedSkills = [];
3850
- for (const skillName of removedSkillNames) {
3851
- const targetPath = join8(getCustomSkillsDir(), skillName);
3852
- try {
3853
- console.log(`Removing obsolete bundled skill: ${skillName}`);
3854
- rmSync2(targetPath, { recursive: true, force: true });
3855
- removedSkills.push(skillName);
3856
- } catch (error) {
3857
- console.warn(`Failed to remove obsolete bundled skill: ${skillName}`);
3858
- console.warn(error);
3859
- }
3860
- }
3861
- return removedSkills;
3862
- }
3863
- function buildManifest(packageRoot, updatedSkills, previousManifest, pluginVersion, sharedHash) {
3864
- const installedAt = (/* @__PURE__ */ new Date()).toISOString();
3865
- const updatedSkillNames = new Set(
3866
- updatedSkills.map(({ skill }) => skill.name)
3867
- );
3868
- const skills = Object.fromEntries(
3869
- CUSTOM_SKILLS.map((skill) => {
3870
- const previousEntry = previousManifest?.skills[skill.name];
3871
- const nextInstalledAt = updatedSkillNames.has(skill.name) ? installedAt : previousEntry?.installedAt ?? installedAt;
3872
- return [
3873
- skill.name,
3874
- {
3875
- hash: computeSkillHash(join8(packageRoot, skill.sourcePath)),
3876
- installedAt: nextInstalledAt
3877
- }
3878
- ];
3879
- })
3880
- );
3881
- return {
3882
- pluginVersion,
3883
- sharedHash,
3884
- skills
3885
- };
3886
- }
3887
- function pruneRemovedSkillsFromManifest(manifest, removedSkills) {
3888
- const removedSkillNames = new Set(removedSkills);
3889
- return {
3890
- ...manifest,
3891
- skills: Object.fromEntries(
3892
- Object.entries(manifest.skills).filter(
3893
- ([skillName]) => !removedSkillNames.has(skillName)
3894
- )
3895
- )
3896
- };
3897
- }
3898
- function installCustomSkills(packageRoot = resolvePackageRoot()) {
3899
- const updateCheck = checkSkillsNeedUpdate(packageRoot);
3900
- if (!updateCheck.needsUpdate) {
3901
- return {
3902
- success: true,
3903
- updatedSkills: [],
3904
- skippedSkills: [...CUSTOM_SKILLS],
3905
- failedSkills: [],
3906
- removedSkills: []
3907
- };
3908
- }
3909
- const removedSkills = removeObsoleteSkills(updateCheck.removedSkills);
3910
- if (removedSkills.length > 0 && updateCheck.manifest) {
3911
- writeManifest(
3912
- pruneRemovedSkillsFromManifest(updateCheck.manifest, removedSkills)
3913
- );
3914
- }
3915
- if (updateCheck.skillsNeedingUpdate.length === 0) {
3916
- writeManifest(
3917
- buildManifest(
3918
- packageRoot,
3919
- [],
3920
- updateCheck.manifest,
3921
- updateCheck.pluginVersion,
3922
- updateCheck.sharedHash
3923
- )
3924
- );
3925
- return {
3926
- success: true,
3927
- updatedSkills: [],
3928
- skippedSkills: [...CUSTOM_SKILLS],
3929
- failedSkills: [],
3930
- removedSkills
3931
- };
3932
- }
3933
- if (!installSharedSkillAssets(packageRoot)) {
3934
- return {
3935
- success: false,
3936
- updatedSkills: [],
3937
- skippedSkills: [],
3938
- failedSkills: updateCheck.skillsNeedingUpdate.map(
3939
- ({ skill, reasons }) => ({
3940
- skill,
3941
- reasons
3942
- })
3943
- ),
3944
- removedSkills
3945
- };
3946
- }
3947
- const updatesBySkillName = new Map(
3948
- updateCheck.skillsNeedingUpdate.map((entry) => [entry.skill.name, entry])
3949
- );
3950
- const updatedSkills = [];
3951
- const skippedSkills = [];
3952
- const failedSkills = [];
3953
- for (const skill of CUSTOM_SKILLS) {
3954
- const pendingUpdate = updatesBySkillName.get(skill.name);
3955
- if (!pendingUpdate) {
3956
- skippedSkills.push(skill);
3957
- continue;
3958
- }
3959
- if (installCustomSkillFiles(skill, packageRoot)) {
3960
- updatedSkills.push({
3961
- skill,
3962
- reasons: pendingUpdate.reasons
3963
- });
3964
- continue;
3965
- }
3966
- failedSkills.push({
3967
- skill,
3968
- reasons: pendingUpdate.reasons
3969
- });
3970
- }
3971
- if (failedSkills.length > 0) {
3972
- return {
3973
- success: false,
3974
- updatedSkills,
3975
- skippedSkills,
3976
- failedSkills,
3977
- removedSkills
3978
- };
3979
- }
3980
- writeManifest(
3981
- buildManifest(
3982
- packageRoot,
3983
- updatedSkills,
3984
- updateCheck.manifest,
3985
- updateCheck.pluginVersion,
3986
- updateCheck.sharedHash
3987
- )
3988
- );
3989
- return {
3990
- success: true,
3991
- updatedSkills,
3992
- skippedSkills,
3993
- failedSkills,
3994
- removedSkills
3995
- };
3996
- }
3997
-
3998
4018
  // src/cli/skills.ts
3999
4019
  import { spawnSync } from "child_process";
4000
4020
  var RECOMMENDED_SKILLS = [
@@ -2,6 +2,7 @@ import { type PluginConfig } from '../../config';
2
2
  import type { HarnessAdapter, HarnessCapabilities, HarnessRenderContext } from '../types';
3
3
  export interface CodexRenderContext extends HarnessRenderContext {
4
4
  config?: PluginConfig;
5
+ packageRoot?: string;
5
6
  }
6
7
  export declare const CODEX_CAPABILITIES: HarnessCapabilities;
7
8
  export declare function renderCodexRootInstructions(config?: PluginConfig): string;
@@ -51,6 +51,7 @@ export interface CapabilityGapDiagnostic extends HarnessDiagnostic {
51
51
  }
52
52
  export interface HarnessRenderContext {
53
53
  projectRoot: string;
54
+ packageRoot?: string;
54
55
  options?: HarnessRenderOptions;
55
56
  }
56
57
  export interface HarnessRenderResult {
@@ -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.2",
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",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "type": "module",
11
11
  "license": "MIT",
12
+ "packageManager": "pnpm@11.2.2",
12
13
  "engines": {
13
14
  "node": ">=22.13"
14
15
  },
@@ -39,6 +40,22 @@
39
40
  "README.md",
40
41
  "LICENSE"
41
42
  ],
43
+ "scripts": {
44
+ "build": "tsup && tsc --emitDeclarationOnly && pnpm run generate-schema",
45
+ "generate-schema": "tsx scripts/generate-schema.ts",
46
+ "release:notes": "tsx scripts/generate-release-notes.ts",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run",
49
+ "lint": "biome lint .",
50
+ "format": "biome format . --write",
51
+ "check": "biome check --write .",
52
+ "check:ci": "biome check .",
53
+ "dev": "pnpm run build && opencode",
54
+ "prepublishOnly": "pnpm run build",
55
+ "release:patch": "npm version patch && git push --follow-tags",
56
+ "release:minor": "npm version minor && git push --follow-tags",
57
+ "release:major": "npm version major && git push --follow-tags"
58
+ },
42
59
  "dependencies": {
43
60
  "@ast-grep/cli": "^0.40.0",
44
61
  "@modelcontextprotocol/sdk": "^1.26.0",
@@ -63,19 +80,5 @@
63
80
  "hono": "4.12.19",
64
81
  "postcss": "8.5.14",
65
82
  "tsx": "4.22.2"
66
- },
67
- "scripts": {
68
- "build": "tsup && tsc --emitDeclarationOnly && pnpm run generate-schema",
69
- "generate-schema": "tsx scripts/generate-schema.ts",
70
- "typecheck": "tsc --noEmit",
71
- "test": "vitest run",
72
- "lint": "biome lint .",
73
- "format": "biome format . --write",
74
- "check": "biome check --write .",
75
- "check:ci": "biome check .",
76
- "dev": "pnpm run build && opencode",
77
- "release:patch": "npm version patch && git push --follow-tags && npm publish",
78
- "release:minor": "npm version minor && git push --follow-tags && npm publish",
79
- "release:major": "npm version major && git push --follow-tags && npm publish"
80
83
  }
81
- }
84
+ }