agdex 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agdex
2
2
 
3
- Embed compressed documentation indexes into `AGENTS.md` or `CLAUDE.md` for AI coding agents.
3
+ Embed compressed documentation indexes into local agent instruction files.
4
4
 
5
5
  This package helps AI coding agents (Claude, Cursor, etc.) work with version-matched framework documentation by embedding a compressed docs index directly into your project's markdown file. Based on [Vercel's research](https://vercel.com/blog/teaching-ai-agents-how-to-use-nextjs) showing that embedded docs achieve 100% pass rates compared to 79% for skills.
6
6
 
@@ -10,8 +10,9 @@ AI coding agents rely on training data that becomes outdated. When agents don't
10
10
 
11
11
  1. Downloads version-matched documentation from GitHub
12
12
  2. Creates a compressed index (~8KB for Next.js)
13
- 3. Embeds it in your `AGENTS.md` or `CLAUDE.md`
14
- 4. Agents can then retrieve specific docs on demand
13
+ 3. Stores the docs in local `.agdex/` cache
14
+ 4. Embeds an index in your local agent instruction file
15
+ 5. Agents can then retrieve specific docs on demand
15
16
 
16
17
  The key instruction embedded tells agents to **prefer retrieval-led reasoning over pre-training-led reasoning**.
17
18
 
@@ -38,7 +39,7 @@ Create a `.agdexrc.json` file in your project root:
38
39
 
39
40
  ```json
40
41
  {
41
- "output": "CLAUDE.md"
42
+ "output": "CLAUDE.local.md"
42
43
  }
43
44
  ```
44
45
 
@@ -50,7 +51,7 @@ Add an `agdex` field to your `package.json`:
50
51
  {
51
52
  "name": "my-project",
52
53
  "agdex": {
53
- "output": "CLAUDE.md"
54
+ "output": "CLAUDE.local.md"
54
55
  }
55
56
  }
56
57
  ```
@@ -61,7 +62,7 @@ Add an `agdex` field to your `package.json`:
61
62
 
62
63
  | Option | Type | Default | Description |
63
64
  |----------|--------|-------------|-------------|
64
- | `output` | string | `CLAUDE.md` | Default output file for indexes |
65
+ | `output` | string | `CLAUDE.local.md` | Default output file for indexes |
65
66
 
66
67
  ## CLI Usage
67
68
 
@@ -112,10 +113,10 @@ npx agdex --provider nextjs --description "Project uses App Router only"
112
113
  ```bash
113
114
  -p, --provider <name> Documentation provider (nextjs, react, etc.)
114
115
  --fw-version <version> Framework version (auto-detected if not provided)
115
- -o, --output <file> Target file (default: from config or CLAUDE.md)
116
+ -o, --output <file> Target file (default: from config or CLAUDE.local.md)
116
117
  -d, --description <text> Additional description to include in the index
117
- -g, --global Use global cache ~/.cache/agdex/ (default)
118
- -l, --local Use local .agdex/ instead
118
+ -g, --global Use global cache ~/.cache/agdex/ instead of local .agdex/
119
+ -l, --local Use local .agdex/ (default)
119
120
  ```
120
121
 
121
122
  ### Custom GitHub Repository
@@ -209,6 +210,35 @@ npx agdex remove --docs
209
210
  npx agdex remove --skills
210
211
  ```
211
212
 
213
+ ### Maintaining Indexes
214
+
215
+ Successful embeds write local maintenance state to `.agdex/agdex.lock`. The lockfile records the target agent instruction file, marker, source metadata, cache path, and display command for each last-known-good index. It is generated local state and should stay unversioned with `.agdex/`.
216
+
217
+ Inspect index health:
218
+
219
+ ```bash
220
+ npx agdex status
221
+ npx agdex status --check
222
+ npx agdex status --json
223
+ npx agdex status --output AGENTS.md
224
+ ```
225
+
226
+ Refresh lockfile-backed indexes:
227
+
228
+ ```bash
229
+ npx agdex refresh
230
+ npx agdex refresh --force
231
+ npx agdex refresh --repair
232
+ npx agdex refresh --provider nextjs
233
+ ```
234
+
235
+ Create lockfile entries for existing embedded docs markers when agdex can safely infer marker and cache metadata:
236
+
237
+ ```bash
238
+ npx agdex migrate
239
+ npx agdex migrate --output AGENTS.md
240
+ ```
241
+
212
242
  ### List Available Providers
213
243
 
214
244
  ```bash
@@ -224,7 +254,7 @@ import { embed, nextjsProvider, createProvider } from 'agdex'
224
254
  const result = await embed({
225
255
  cwd: process.cwd(),
226
256
  provider: nextjsProvider,
227
- output: 'AGENTS.md'
257
+ output: 'CLAUDE.local.md'
228
258
  })
229
259
 
230
260
  // Create custom provider
@@ -240,7 +270,7 @@ await embed({
240
270
  cwd: process.cwd(),
241
271
  provider: myProvider,
242
272
  version: '1.0.0',
243
- output: 'CLAUDE.md'
273
+ output: 'CLAUDE.local.md'
244
274
  })
245
275
  ```
246
276
 
package/dist/cli/index.js CHANGED
@@ -6,11 +6,14 @@ import {
6
6
  collectAllSkills,
7
7
  collectDocFiles,
8
8
  convexProvider,
9
+ createIndexId,
9
10
  createProvider,
11
+ createStatusReport,
10
12
  deltaRsProvider,
11
13
  discoverSkillsShRepo,
12
14
  embed,
13
15
  embedSkills,
16
+ ensureGitignoreEntry,
14
17
  fetchSkillsShSearch,
15
18
  ffmpegProvider,
16
19
  generateIndex,
@@ -32,19 +35,21 @@ import {
32
35
  pixiProvider,
33
36
  polarsProvider,
34
37
  rattlerBuildProvider,
38
+ readIndexLockfile,
35
39
  removeDocsIndex,
36
40
  removeSkillsIndex,
37
41
  ruffProvider,
38
42
  svelteProvider,
39
43
  tailwindProvider,
40
44
  tauriProvider,
41
- tyProvider
42
- } from "../index-cm3qmz9v.js";
45
+ tyProvider,
46
+ upsertIndexLockEntry
47
+ } from "../index-38fpcrpr.js";
43
48
  import {
44
49
  __commonJS,
45
50
  __require,
46
51
  __toESM
47
- } from "../index-dtcewfnz.js";
52
+ } from "../url-scraper-fme0jdje.js";
48
53
 
49
54
  // node_modules/commander/lib/error.js
50
55
  var require_error = __commonJS((exports) => {
@@ -2109,7 +2114,7 @@ var require_clear = __commonJS((exports, module) => {
2109
2114
  if (it)
2110
2115
  o = it;
2111
2116
  var i = 0;
2112
- var F = function F2() {};
2117
+ var F = function F() {};
2113
2118
  return { s: F, n: function n() {
2114
2119
  if (i >= o.length)
2115
2120
  return { done: true };
@@ -4441,7 +4446,7 @@ var require_dist = __commonJS((exports, module) => {
4441
4446
  if (it)
4442
4447
  o = it;
4443
4448
  var i = 0;
4444
- var F = function F2() {};
4449
+ var F = function F() {};
4445
4450
  return { s: F, n: function n() {
4446
4451
  if (i >= o.length)
4447
4452
  return { done: true };
@@ -4544,7 +4549,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
4544
4549
  }
4545
4550
  return question2.format ? yield question2.format(answer2, answers) : answer2;
4546
4551
  });
4547
- return function getFormattedAnswer2(_x, _x2) {
4552
+ return function getFormattedAnswer(_x, _x2) {
4548
4553
  return _ref.apply(this, arguments);
4549
4554
  };
4550
4555
  }();
@@ -7048,12 +7053,21 @@ function onCancel() {
7048
7053
  Cancelled.`));
7049
7054
  process.exit(0);
7050
7055
  }
7056
+ function resolveGlobalCacheOption(options) {
7057
+ if (options.global && options.local) {
7058
+ console.error(import_picocolors2.default.red("Choose either --global or --local, not both."));
7059
+ process.exit(1);
7060
+ }
7061
+ return options.global ? true : false;
7062
+ }
7051
7063
  async function runEmbed(options) {
7052
7064
  const cwd = process.cwd();
7065
+ const globalCache = resolveGlobalCacheOption(options);
7053
7066
  if (options.url) {
7054
7067
  await runUrl(options.url, {
7055
7068
  output: options.output,
7056
- name: options.description
7069
+ name: options.description,
7070
+ global: globalCache
7057
7071
  });
7058
7072
  return;
7059
7073
  }
@@ -7085,8 +7099,7 @@ async function runEmbed(options) {
7085
7099
  provider = result.provider;
7086
7100
  version = result.version;
7087
7101
  output = result.output;
7088
- const useGlobal = options.local ? false : undefined;
7089
- await executeEmbed(cwd, provider, version, output, useGlobal, result.description);
7102
+ await executeEmbed(cwd, provider, version, output, globalCache, result.description, version ? "auto" : "unknown");
7090
7103
  return;
7091
7104
  }
7092
7105
  } else {
@@ -7094,8 +7107,7 @@ async function runEmbed(options) {
7094
7107
  provider = result.provider;
7095
7108
  version = result.version;
7096
7109
  output = result.output;
7097
- const useGlobal = options.local ? false : undefined;
7098
- await executeEmbed(cwd, provider, version, output, useGlobal, result.description);
7110
+ await executeEmbed(cwd, provider, version, output, globalCache, result.description, version ? "auto" : "unknown");
7099
7111
  return;
7100
7112
  }
7101
7113
  output = options.output || getDefaultOutput();
@@ -7103,10 +7115,9 @@ async function runEmbed(options) {
7103
7115
  console.error(import_picocolors2.default.red(`Provider ${provider.displayName} requires --version flag since auto-detection is not supported.`));
7104
7116
  process.exit(1);
7105
7117
  }
7106
- const useGlobalCache = options.local ? false : undefined;
7107
- await executeEmbed(cwd, provider, version, output, useGlobalCache, options.description);
7118
+ await executeEmbed(cwd, provider, version, output, globalCache, options.description, options.fwVersion ? "pinned" : version ? "auto" : "unknown");
7108
7119
  }
7109
- async function executeEmbed(cwd, provider, version, output, globalCache, description) {
7120
+ async function executeEmbed(cwd, provider, version, output, globalCache, description, versionMode) {
7110
7121
  let resolvedVersion = version;
7111
7122
  let usingDefaultBranch = false;
7112
7123
  if (!resolvedVersion && provider.urlConfig) {
@@ -7122,8 +7133,10 @@ async function executeEmbed(cwd, provider, version, output, globalCache, descrip
7122
7133
  `));
7123
7134
  resolvedVersion = fallbackBranch;
7124
7135
  usingDefaultBranch = true;
7136
+ versionMode = versionMode || "default-branch";
7125
7137
  } else {
7126
7138
  resolvedVersion = detected.version;
7139
+ versionMode = versionMode || "auto";
7127
7140
  }
7128
7141
  }
7129
7142
  const versionLabel = usingDefaultBranch ? "latest" : resolvedVersion;
@@ -7133,6 +7146,7 @@ Embedding ${import_picocolors2.default.cyan(provider.displayName)} ${import_pico
7133
7146
  cwd,
7134
7147
  provider,
7135
7148
  version: resolvedVersion,
7149
+ versionMode,
7136
7150
  output,
7137
7151
  globalCache,
7138
7152
  description
@@ -7347,15 +7361,17 @@ No providers selected.
7347
7361
  const output = await promptForOutputFile();
7348
7362
  for (const item of selected) {
7349
7363
  const provider = getProvider(item.value);
7350
- await executeEmbed(cwd, provider, item.version, output, undefined, item.description || undefined);
7364
+ await executeEmbed(cwd, provider, item.version, output, undefined, item.description || undefined, item.version ? "pinned" : "auto");
7351
7365
  }
7352
7366
  process.exit(0);
7353
7367
  }
7354
7368
  async function promptForOutputFile() {
7355
7369
  const defaultOutput = getDefaultOutput();
7356
7370
  const choices = [
7357
- { title: "AGENTS.md", value: "AGENTS.md" },
7371
+ { title: "CLAUDE.local.md", value: "CLAUDE.local.md" },
7372
+ { title: "AGENTS.local.md", value: "AGENTS.local.md" },
7358
7373
  { title: "CLAUDE.md", value: "CLAUDE.md" },
7374
+ { title: "AGENTS.md", value: "AGENTS.md" },
7359
7375
  { title: "Custom...", value: "__custom__" }
7360
7376
  ];
7361
7377
  const defaultIndex = choices.findIndex((c) => c.value === defaultOutput);
@@ -7562,6 +7578,21 @@ Building index from ${import_picocolors2.default.cyan(docsPath)}...`);
7562
7578
  const newContent = injectIndex(existingContent, indexContent, providerName);
7563
7579
  fs.writeFileSync(targetPath, newContent, "utf-8");
7564
7580
  const sizeAfter = Buffer.byteLength(newContent, "utf-8");
7581
+ upsertIndexLockEntry(cwd, {
7582
+ id: createIndexId("docs", providerName, output),
7583
+ kind: "docs",
7584
+ source: {
7585
+ type: "local-docs",
7586
+ name: providerName,
7587
+ displayName: name,
7588
+ docsPath,
7589
+ versionMode: "unknown"
7590
+ },
7591
+ targetFile: output,
7592
+ marker: providerName,
7593
+ cachePath: absoluteDocsPath,
7594
+ command: `npx agdex local ${docsPath} --name "${name}" --output ${output}`
7595
+ });
7565
7596
  const action = isNewFile ? "Created" : "Updated";
7566
7597
  const sizeInfo = isNewFile ? formatSize(sizeAfter) : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
7567
7598
  console.log(`${import_picocolors2.default.green("✓")} ${action} ${import_picocolors2.default.bold(output)} (${sizeInfo})`);
@@ -7569,13 +7600,15 @@ Building index from ${import_picocolors2.default.cyan(docsPath)}...`);
7569
7600
  }
7570
7601
  async function runUrl(url, options) {
7571
7602
  const cwd = process.cwd();
7572
- const { createUrlProvider } = await import("../url-scraper-5sj8c56t.js");
7573
- const { pullDocsFromUrl } = await import("../url-scraper-5sj8c56t.js");
7603
+ const { createUrlProvider } = await import("../url-scraper-fme0jdje.js");
7604
+ const { pullDocsFromUrl } = await import("../url-scraper-fme0jdje.js");
7574
7605
  const name = options.name || new URL(url).hostname.replace(/^docs\./, "").replace(/\.\w+$/, "");
7575
7606
  const providerName = name.toLowerCase().replace(/\s+/g, "-");
7576
7607
  const output = options.output || getDefaultOutput();
7577
- const cacheBase = path.join(os.homedir(), ".cache", "agdex");
7578
- const docsPath = path.join(cacheBase, providerName);
7608
+ const globalCache = resolveGlobalCacheOption(options);
7609
+ const docsDir = globalCache ? path.join(os.homedir(), ".cache", "agdex", providerName) : path.join(".agdex", providerName);
7610
+ const docsPath = path.isAbsolute(docsDir) ? docsDir : path.join(cwd, docsDir);
7611
+ const docsLinkPath = globalCache ? docsPath : `./${docsDir}`;
7579
7612
  console.log(`
7580
7613
  Scraping documentation from ${import_picocolors2.default.cyan(url)}...`);
7581
7614
  const cacheHit = fs.existsSync(docsPath) && fs.readdirSync(docsPath).length > 0;
@@ -7614,19 +7647,41 @@ ${import_picocolors2.default.green("✓")} Downloaded docs to ${import_picocolor
7614
7647
  const docFiles = collectDocFiles(docsPath, { extensions: [".md"] });
7615
7648
  const sections = buildDocTree(docFiles);
7616
7649
  const indexContent = generateIndex({
7617
- docsPath,
7650
+ docsPath: docsLinkPath,
7618
7651
  sections,
7619
7652
  outputFile: output,
7620
7653
  providerName: name,
7621
7654
  instruction: `IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any ${name} tasks.`,
7622
- regenerateCommand: `npx agdex url "${url}" --name "${name}" --output ${output}`
7655
+ regenerateCommand: `npx agdex url "${url}" --name "${name}" --output ${output}${globalCache ? " --global" : ""}`
7623
7656
  });
7624
7657
  const newContent = injectIndex(existingContent, indexContent, providerName);
7625
7658
  fs.writeFileSync(targetPath, newContent, "utf-8");
7626
7659
  const sizeAfter = Buffer.byteLength(newContent, "utf-8");
7660
+ upsertIndexLockEntry(cwd, {
7661
+ id: createIndexId("docs", providerName, output),
7662
+ kind: "docs",
7663
+ source: {
7664
+ type: "url-docs",
7665
+ name: providerName,
7666
+ displayName: name,
7667
+ url,
7668
+ version: "latest",
7669
+ versionMode: "default-branch"
7670
+ },
7671
+ targetFile: output,
7672
+ marker: providerName,
7673
+ cachePath: docsPath,
7674
+ command: `npx agdex url "${url}" --name "${name}" --output ${output}${globalCache ? " --global" : ""}`
7675
+ });
7627
7676
  const action = isNewFile ? "Created" : "Updated";
7628
7677
  const sizeInfo = isNewFile ? formatSize(sizeAfter) : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
7629
7678
  console.log(`${import_picocolors2.default.green("✓")} ${action} ${import_picocolors2.default.bold(output)} (${sizeInfo})`);
7679
+ if (!globalCache) {
7680
+ const gitignoreResult = ensureGitignoreEntry(cwd, ".agdex");
7681
+ if (gitignoreResult.updated) {
7682
+ console.log(`${import_picocolors2.default.green("✓")} Added ${import_picocolors2.default.bold(".agdex")} to .gitignore`);
7683
+ }
7684
+ }
7630
7685
  console.log("");
7631
7686
  }
7632
7687
  function runList() {
@@ -7675,10 +7730,191 @@ Sources you can index:
7675
7730
  • Claude Code skills
7676
7731
 
7677
7732
  Run 'agdex' without arguments for interactive mode.`).version("0.4.2");
7678
- program2.command("embed", { isDefault: true }).description("Embed documentation index into AGENTS.md/CLAUDE.md").option("-p, --provider <name>", "Documentation provider (nextjs, react, etc.)").option("--fw-version <version>", "Framework version (auto-detected if not provided)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.md)").option("--repo <owner/repo>", "Custom GitHub repository").option("--docs-path <path>", "Path to docs folder in repository").option("-g, --global", "Store docs in global cache (~/.cache/agdex/) (default)").option("-l, --local", "Store docs in local .agdex/ instead of global cache").option("-d, --description <text>", "Additional description to include in the index").option("-u, --url <url>", "Scrape documentation from a website URL").action(runEmbed);
7679
- program2.command("local <docs-path>").description("Build index from local documentation directory").option("-n, --name <name>", "Display name for the documentation").option("-o, --output <file>", "Target file (default: from config or CLAUDE.md)").option("-e, --extensions <exts>", "File extensions to include (comma-separated, default: .md,.mdx)").action(runLocal);
7680
- program2.command("url <url>").description("Scrape documentation from a website URL and build index").option("-n, --name <name>", "Display name for the documentation (default: derived from URL)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.md)").option("-s, --selector <css>", "CSS selector for main content (default: main#main-content, main, article)").option("-c, --concurrency <n>", "Max concurrent fetches (default: 5)").option("--delay <ms>", "Delay between fetch batches in ms (default: 200)").action(runUrl);
7733
+ program2.command("embed", { isDefault: true }).description("Embed documentation index into AGENTS.md/CLAUDE.md").option("-p, --provider <name>", "Documentation provider (nextjs, react, etc.)").option("--fw-version <version>", "Framework version (auto-detected if not provided)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("--repo <owner/repo>", "Custom GitHub repository").option("--docs-path <path>", "Path to docs folder in repository").option("-g, --global", "Store docs in global cache (~/.cache/agdex/) instead of local .agdex/").option("-l, --local", "Store docs in local .agdex/ (default)").option("-d, --description <text>", "Additional description to include in the index").option("-u, --url <url>", "Scrape documentation from a website URL").action(runEmbed);
7734
+ program2.command("local <docs-path>").description("Build index from local documentation directory").option("-n, --name <name>", "Display name for the documentation").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("-e, --extensions <exts>", "File extensions to include (comma-separated, default: .md,.mdx)").action(runLocal);
7735
+ program2.command("url <url>").description("Scrape documentation from a website URL and build index").option("-n, --name <name>", "Display name for the documentation (default: derived from URL)").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("-s, --selector <css>", "CSS selector for main content (default: main#main-content, main, article)").option("-c, --concurrency <n>", "Max concurrent fetches (default: 5)").option("--delay <ms>", "Delay between fetch batches in ms (default: 200)").option("-g, --global", "Store docs in global cache (~/.cache/agdex/) instead of local .agdex/").option("-l, --local", "Store docs in local .agdex/ (default)").action(runUrl);
7681
7736
  program2.command("list").description("List available documentation providers").action(runList);
7737
+ function runStatus(options) {
7738
+ const report = createStatusReport({
7739
+ cwd: process.cwd(),
7740
+ targetFile: options.output
7741
+ });
7742
+ const unhealthy = report.indexes.filter((index) => index.health !== "ok");
7743
+ if (options.json) {
7744
+ console.log(JSON.stringify(report, null, 2));
7745
+ } else {
7746
+ printStatusReport(report.indexes, report.scannedFiles);
7747
+ }
7748
+ if (options.check && unhealthy.length > 0) {
7749
+ process.exit(1);
7750
+ }
7751
+ }
7752
+ function printStatusReport(indexes, scannedFiles) {
7753
+ console.log(import_picocolors2.default.cyan(`
7754
+ agdex status
7755
+ `));
7756
+ console.log(import_picocolors2.default.gray(` Scanned: ${scannedFiles.length > 0 ? scannedFiles.join(", ") : "no agent instruction files found"}`));
7757
+ if (indexes.length === 0) {
7758
+ console.log(import_picocolors2.default.yellow(`
7759
+ No indexes found.
7760
+ `));
7761
+ return;
7762
+ }
7763
+ console.log("");
7764
+ for (const index of indexes) {
7765
+ const symbol = index.health === "ok" ? import_picocolors2.default.green("✓") : import_picocolors2.default.yellow("!");
7766
+ const source = index.source?.displayName || index.source?.name || index.marker;
7767
+ console.log(` ${symbol} ${import_picocolors2.default.bold(index.kind)} ${source} ${import_picocolors2.default.gray(`(${index.targetFile})`)}`);
7768
+ console.log(` health: ${formatHealth(index.health)}`);
7769
+ if (index.cachePath) {
7770
+ console.log(` cache: ${index.cachePath}`);
7771
+ }
7772
+ if (index.health !== "ok") {
7773
+ console.log(` action: ${index.suggestedAction}`);
7774
+ }
7775
+ }
7776
+ console.log("");
7777
+ }
7778
+ function formatHealth(health) {
7779
+ if (health === "ok")
7780
+ return import_picocolors2.default.green(health);
7781
+ return import_picocolors2.default.yellow(health);
7782
+ }
7783
+ program2.command("status").description("Inspect lockfile-backed index health").option("-o, --output <file>", "Scope status to one agent instruction file").option("--json", "Print machine-readable JSON").option("--check", "Exit non-zero when any discovered index is unhealthy").action(runStatus);
7784
+ async function runRefresh(options) {
7785
+ const cwd = process.cwd();
7786
+ const lockfile = readIndexLockfile(cwd);
7787
+ const repairIds = options.repair ? new Set(createStatusReport({ cwd, targetFile: options.output }).indexes.filter((index) => index.lockfileEntry && index.health !== "ok").map((index) => index.id)) : null;
7788
+ const entries = lockfile.indexes.filter((entry) => {
7789
+ if (options.output && entry.targetFile !== options.output)
7790
+ return false;
7791
+ if (options.kind && entry.kind !== options.kind)
7792
+ return false;
7793
+ if (options.provider && entry.source.name !== options.provider && entry.marker !== options.provider)
7794
+ return false;
7795
+ if (repairIds && !repairIds.has(entry.id))
7796
+ return false;
7797
+ return true;
7798
+ });
7799
+ if (entries.length === 0) {
7800
+ console.log(import_picocolors2.default.yellow(`
7801
+ No lockfile-backed indexes matched refresh filters.
7802
+ `));
7803
+ return;
7804
+ }
7805
+ for (const entry of entries) {
7806
+ if (options.force && entry.kind === "docs") {
7807
+ const cachePath = path.isAbsolute(entry.cachePath) ? entry.cachePath : path.join(cwd, entry.cachePath);
7808
+ if (fs.existsSync(cachePath)) {
7809
+ fs.rmSync(cachePath, { recursive: true, force: true });
7810
+ }
7811
+ }
7812
+ if (entry.kind === "skills") {
7813
+ if (entry.source.type === "skills-sh" && entry.source.repo) {
7814
+ await runSkillsEmbed({ repo: entry.source.repo, output: entry.targetFile });
7815
+ } else {
7816
+ await runSkillsEmbed({ output: entry.targetFile });
7817
+ }
7818
+ continue;
7819
+ }
7820
+ if (entry.source.type === "local-docs" && entry.source.docsPath) {
7821
+ await runLocal(entry.source.docsPath, {
7822
+ name: entry.source.displayName || entry.source.name,
7823
+ output: entry.targetFile
7824
+ });
7825
+ continue;
7826
+ }
7827
+ if (entry.source.type === "url-docs" && entry.source.url) {
7828
+ await runUrl(entry.source.url, {
7829
+ name: entry.source.displayName || entry.source.name,
7830
+ output: entry.targetFile,
7831
+ global: path.isAbsolute(entry.cachePath)
7832
+ });
7833
+ continue;
7834
+ }
7835
+ const provider = entry.source.type === "builtin-provider" ? getProvider(entry.source.name) : entry.source.repo && entry.source.docsPath ? createProvider({
7836
+ name: entry.source.name,
7837
+ displayName: entry.source.displayName || entry.source.name,
7838
+ repo: entry.source.repo,
7839
+ docsPath: entry.source.docsPath
7840
+ }) : null;
7841
+ if (!provider) {
7842
+ console.log(import_picocolors2.default.yellow(`Skipped ${entry.id}: source metadata is incomplete.`));
7843
+ continue;
7844
+ }
7845
+ const version = entry.source.versionMode === "pinned" ? entry.source.version : undefined;
7846
+ await executeEmbed(cwd, provider, version, entry.targetFile, path.isAbsolute(entry.cachePath), undefined, entry.source.versionMode);
7847
+ }
7848
+ }
7849
+ program2.command("refresh").description("Refresh lockfile-backed indexes").option("-o, --output <file>", "Refresh indexes in one agent instruction file").option("-p, --provider <name>", "Refresh one provider or marker name").option("--kind <kind>", "Refresh only docs or skills indexes").option("--force", "Delete existing documentation caches before refreshing").option("--repair", "Refresh only unhealthy lockfile-backed indexes").action(runRefresh);
7850
+ function runMigrate(options) {
7851
+ const cwd = process.cwd();
7852
+ const targets = options.output ? [options.output] : ["AGENTS.md", "AGENTS.local.md", "CLAUDE.md", "CLAUDE.local.md"];
7853
+ const migrated = [];
7854
+ const skipped = [];
7855
+ for (const targetFile of targets) {
7856
+ const targetPath = path.join(cwd, targetFile);
7857
+ if (!fs.existsSync(targetPath))
7858
+ continue;
7859
+ const content = fs.readFileSync(targetPath, "utf-8");
7860
+ const regex = /<!-- AGENTS-MD-EMBED-START:(\S+?) -->\n([\s\S]*?)\n<!-- AGENTS-MD-EMBED-END:\1 -->/g;
7861
+ let match;
7862
+ while ((match = regex.exec(content)) !== null) {
7863
+ const marker = match[1];
7864
+ const block = match[2];
7865
+ const rootMatch = block.match(/(?:^|\|)root:\s*([^|]+)/);
7866
+ if (!rootMatch) {
7867
+ skipped.push({ targetFile, marker, reason: "missing root metadata" });
7868
+ continue;
7869
+ }
7870
+ const cachePath = rootMatch[1].trim().replace(/^\.\//, "");
7871
+ const provider = getProvider(marker);
7872
+ const source = provider ? {
7873
+ type: "builtin-provider",
7874
+ name: provider.name,
7875
+ displayName: provider.displayName,
7876
+ repo: provider.repo,
7877
+ docsPath: provider.docsPath,
7878
+ versionMode: "unknown"
7879
+ } : {
7880
+ type: "local-docs",
7881
+ name: marker,
7882
+ displayName: marker,
7883
+ docsPath: cachePath,
7884
+ versionMode: "unknown"
7885
+ };
7886
+ const entry = upsertIndexLockEntry(cwd, {
7887
+ id: createIndexId("docs", marker, targetFile),
7888
+ kind: "docs",
7889
+ source,
7890
+ targetFile,
7891
+ marker,
7892
+ cachePath,
7893
+ command: `npx agdex --provider ${marker} --output ${targetFile}`
7894
+ });
7895
+ migrated.push(entry.id);
7896
+ }
7897
+ }
7898
+ const result = { migrated, skipped };
7899
+ if (options.json) {
7900
+ console.log(JSON.stringify(result, null, 2));
7901
+ return;
7902
+ }
7903
+ console.log(import_picocolors2.default.cyan(`
7904
+ agdex migrate
7905
+ `));
7906
+ for (const id of migrated) {
7907
+ console.log(`${import_picocolors2.default.green("✓")} Created lockfile entry ${import_picocolors2.default.bold(id)}`);
7908
+ }
7909
+ for (const item of skipped) {
7910
+ console.log(`${import_picocolors2.default.yellow("!")} Skipped ${item.marker} in ${item.targetFile}: ${item.reason}`);
7911
+ }
7912
+ if (migrated.length === 0 && skipped.length === 0) {
7913
+ console.log(import_picocolors2.default.yellow(" No migratable indexes found."));
7914
+ }
7915
+ console.log("");
7916
+ }
7917
+ program2.command("migrate").description("Create lockfile entries from existing embedded markers when safe").option("-o, --output <file>", "Migrate one agent instruction file").option("--json", "Print machine-readable JSON").action(runMigrate);
7682
7918
  async function runRemove(options) {
7683
7919
  const cwd = process.cwd();
7684
7920
  const output = options.output || getDefaultOutput();
@@ -7778,7 +8014,7 @@ No indices found to remove.
7778
8014
  console.log(import_picocolors2.default.gray(` (${formatSize(sizeBefore)} → ${formatSize(sizeAfter)})`));
7779
8015
  console.log("");
7780
8016
  }
7781
- program2.command("remove").description("Remove embedded indices from AGENTS.md/CLAUDE.md").option("-o, --output <file>", "Target file (default: from config or CLAUDE.md)").option("--docs", "Remove only docs index").option("--skills", "Remove only skills index").option("-p, --provider <name>", "Remove only a specific provider's docs index").action(runRemove);
8017
+ program2.command("remove").description("Remove embedded indices from AGENTS.md/CLAUDE.md").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("--docs", "Remove only docs index").option("--skills", "Remove only skills index").option("-p, --provider <name>", "Remove only a specific provider's docs index").action(runRemove);
7782
8018
  var skillsCommand = program2.command("skills").description("Manage Claude Code skills indexing");
7783
8019
  async function runSkillsEmbed(options) {
7784
8020
  const cwd = process.cwd();
@@ -7838,6 +8074,21 @@ Fetching skills from ${import_picocolors2.default.cyan(repoName)}...`);
7838
8074
  const newContent = injectSkillsIndex(existingContent, indexContent);
7839
8075
  fs.writeFileSync(targetPath, newContent, "utf-8");
7840
8076
  const sizeAfter = Buffer.byteLength(newContent, "utf-8");
8077
+ upsertIndexLockEntry(cwd, {
8078
+ id: createIndexId("skills", `skills-sh:${repoName}`, output),
8079
+ kind: "skills",
8080
+ source: {
8081
+ type: "skills-sh",
8082
+ name: `skills-sh:${repoName}`,
8083
+ displayName: repoName,
8084
+ repo: repoName,
8085
+ versionMode: "default-branch"
8086
+ },
8087
+ targetFile: output,
8088
+ marker: "skills",
8089
+ cachePath: cacheDir,
8090
+ command: `npx agdex skills embed --repo ${repoName}`
8091
+ });
7841
8092
  const action2 = isNewFile ? "Created" : "Updated";
7842
8093
  const sizeInfo2 = isNewFile ? formatSize(sizeAfter) : `${formatSize(sizeBefore)} → ${formatSize(sizeAfter)}`;
7843
8094
  console.log(`${import_picocolors2.default.green("✓")} ${action2} ${import_picocolors2.default.bold(output)} (${sizeInfo2})`);
@@ -7860,6 +8111,20 @@ Discovering skills from ${import_picocolors2.default.cyan(sources.length.toStrin
7860
8111
  const sizeInfo = result.isNewFile ? formatSize(result.sizeAfter) : `${formatSize(result.sizeBefore)} → ${formatSize(result.sizeAfter)}`;
7861
8112
  console.log(`${import_picocolors2.default.green("✓")} ${action} ${import_picocolors2.default.bold(result.targetFile)} (${sizeInfo})`);
7862
8113
  console.log(`${import_picocolors2.default.green("✓")} Indexed ${import_picocolors2.default.bold(result.skillCount.toString())} skills`);
8114
+ upsertIndexLockEntry(cwd, {
8115
+ id: createIndexId("skills", "local-skills", output),
8116
+ kind: "skills",
8117
+ source: {
8118
+ type: "skills-local",
8119
+ name: "local-skills",
8120
+ displayName: "Local skills",
8121
+ versionMode: "unknown"
8122
+ },
8123
+ targetFile: output,
8124
+ marker: "skills",
8125
+ cachePath: cwd,
8126
+ command: "npx agdex skills embed"
8127
+ });
7863
8128
  if (result.sourceBreakdown) {
7864
8129
  const breakdown = [];
7865
8130
  if (result.sourceBreakdown.plugin > 0) {
@@ -8015,8 +8280,8 @@ Selected ${import_picocolors2.default.bold(selected.name)} from ${import_picocol
8015
8280
  process.exit(1);
8016
8281
  }
8017
8282
  }
8018
- skillsCommand.command("embed").description("Embed skills index into AGENTS.md").option("-o, --output <file>", "Target file (default: from config or CLAUDE.md)").option("--plugin <path...>", "Additional plugin repo paths (with plugins/ structure)").option("--plugins", "Include enabled plugins from settings.json (default: true)").option("--no-plugins", "Exclude enabled plugins from settings.json").option("--user", "Include ~/.claude/skills (default: true)").option("--no-user", "Exclude ~/.claude/skills").option("--project", "Include .claude/skills (default: true)").option("--no-project", "Exclude .claude/skills").option("--repo <owner/repo>", "Fetch and index skills from a skills.sh-compatible GitHub repository").action(runSkillsEmbed);
8283
+ skillsCommand.command("embed").description("Embed skills index into AGENTS.md").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("--plugin <path...>", "Additional plugin repo paths (with plugins/ structure)").option("--plugins", "Include enabled plugins from settings.json (default: true)").option("--no-plugins", "Exclude enabled plugins from settings.json").option("--user", "Include ~/.claude/skills (default: true)").option("--no-user", "Exclude ~/.claude/skills").option("--project", "Include .claude/skills (default: true)").option("--no-project", "Exclude .claude/skills").option("--repo <owner/repo>", "Fetch and index skills from a skills.sh-compatible GitHub repository").action(runSkillsEmbed);
8019
8284
  skillsCommand.command("list").description("List discovered skills").option("--plugin <path...>", "Additional plugin repo paths (with plugins/ structure)").option("--plugins", "Include enabled plugins from settings.json (default: true)").option("--no-plugins", "Exclude enabled plugins from settings.json").option("--user", "Include ~/.claude/skills (default: true)").option("--no-user", "Exclude ~/.claude/skills").option("--project", "Include .claude/skills (default: true)").option("--no-project", "Exclude .claude/skills").action(runSkillsList);
8020
- skillsCommand.command("local <skills-path>").description("Index skills from a local path").option("-o, --output <file>", "Target file (default: from config or CLAUDE.md)").option("-n, --name <name>", "Label for this skill source").action(runSkillsLocal);
8285
+ skillsCommand.command("local <skills-path>").description("Index skills from a local path").option("-o, --output <file>", "Target file (default: from config or CLAUDE.local.md)").option("-n, --name <name>", "Label for this skill source").action(runSkillsLocal);
8021
8286
  skillsCommand.command("find [query]").description("Search skills.sh for agent skills").option("-l, --limit <n>", "Max results (default: 20)", parseInt).option("-o, --output <file>", "Target file for embedding").action(runSkillsFind);
8022
8287
  program2.parse();