@takazudo/zudo-doc 5.8.0 → 5.9.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/config.d.ts +18 -0
  3. package/dist/config.js +1 -0
  4. package/dist/i18n-defaults/index.js +3 -0
  5. package/dist/plugins/codex-resources.d.ts +4 -0
  6. package/dist/plugins/codex-resources.js +31 -0
  7. package/dist/plugins/internal/claude-resources/generate.js +20 -291
  8. package/dist/plugins/internal/codex-resources/agents-md.d.ts +6 -0
  9. package/dist/plugins/internal/codex-resources/agents-md.js +70 -0
  10. package/dist/plugins/internal/codex-resources/agents.d.ts +7 -0
  11. package/dist/plugins/internal/codex-resources/agents.js +104 -0
  12. package/dist/plugins/internal/codex-resources/config.d.ts +6 -0
  13. package/dist/plugins/internal/codex-resources/config.js +111 -0
  14. package/dist/plugins/internal/codex-resources/generate.d.ts +20 -0
  15. package/dist/plugins/internal/codex-resources/generate.js +34 -0
  16. package/dist/plugins/internal/codex-resources/hooks.d.ts +6 -0
  17. package/dist/plugins/internal/codex-resources/hooks.js +174 -0
  18. package/dist/plugins/internal/codex-resources/index.d.ts +24 -0
  19. package/dist/plugins/internal/codex-resources/index.js +21 -0
  20. package/dist/plugins/internal/codex-resources/overview.d.ts +10 -0
  21. package/dist/plugins/internal/codex-resources/overview.js +33 -0
  22. package/dist/plugins/internal/codex-resources/rules.d.ts +6 -0
  23. package/dist/plugins/internal/codex-resources/rules.js +255 -0
  24. package/dist/plugins/internal/codex-resources/skills.d.ts +2 -0
  25. package/dist/plugins/internal/codex-resources/skills.js +84 -0
  26. package/dist/plugins/internal/codex-resources/utils.d.ts +14 -0
  27. package/dist/plugins/internal/codex-resources/utils.js +80 -0
  28. package/dist/plugins/internal/resource-docs-shared/fs.d.ts +3 -0
  29. package/dist/plugins/internal/resource-docs-shared/fs.js +19 -0
  30. package/dist/plugins/internal/resource-docs-shared/index.d.ts +7 -0
  31. package/dist/plugins/internal/resource-docs-shared/index.js +44 -0
  32. package/dist/plugins/internal/resource-docs-shared/links.d.ts +22 -0
  33. package/dist/plugins/internal/resource-docs-shared/links.js +44 -0
  34. package/dist/plugins/internal/resource-docs-shared/markdown-structure.d.ts +9 -0
  35. package/dist/plugins/internal/resource-docs-shared/markdown-structure.js +22 -0
  36. package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +23 -0
  37. package/dist/plugins/internal/resource-docs-shared/mdx.js +63 -0
  38. package/dist/plugins/internal/resource-docs-shared/skills.d.ts +37 -0
  39. package/dist/plugins/internal/resource-docs-shared/skills.js +318 -0
  40. package/dist/plugins/internal/resource-docs-shared/walk.d.ts +20 -0
  41. package/dist/plugins/internal/resource-docs-shared/walk.js +41 -0
  42. package/dist/preset.d.ts +8 -0
  43. package/dist/preset.js +11 -0
  44. package/dist/safelist.css +1 -1
  45. package/dist/settings.d.ts +5 -0
  46. package/package.json +7 -2
  47. /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.d.ts +0 -0
  48. /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.js +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
4
4
 
5
5
  The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
6
6
 
7
+ ## [5.9.0] - 2026-08-20
8
+
9
+ ### Features
10
+
11
+ - Added Codex Resources generation for `AGENTS.md`, Codex configuration, custom agents, hooks, rules, and skills, with dedicated parsing and rendering for each resource type. (`666c12059`)
12
+ - Added the public Codex Resources plugin and wired it through package settings, presets, `create-zudo-doc`, and generated project configuration. (`752a3fe5a`, `a7fa47906`, `5831cd73e`)
13
+ - Added Codex Resources showcase data, navigation, localization, development watching, and complete English and Japanese documentation. (`a4dca1e40`, `1a2e6d5bc`, `f1396e7a1`)
14
+
15
+ ### Bug Fixes
16
+
17
+ - Added type validation and deterministic fallbacks for malformed skill frontmatter values. (`710a1d225`)
18
+ - Made generated Codex Resources frontmatter formatter-stable while preserving YAML string semantics and existing Claude Resources output.
19
+
20
+ ### Other Changes
21
+
22
+ - Extracted shared resource-documentation helpers while preserving existing Claude Resources output byte-for-byte. (`c9165d5cb`)
23
+ - Expanded generator, scaffold, build, and responsive navigation regression coverage for Codex Resources. (`142c11aa4`)
24
+
7
25
  ## [5.8.0] - 2026-08-20
8
26
 
9
27
  ### Features
package/dist/config.d.ts CHANGED
@@ -72,6 +72,12 @@ type ClaudeResourcesConfig = {
72
72
  projectRoot?: string;
73
73
  scanRoot?: string;
74
74
  } | false;
75
+ /** The `settings.codexResources` block (or `false` when disabled). */
76
+ type CodexResourcesConfig = {
77
+ codexDir: string;
78
+ projectRoot?: string;
79
+ scanRoot?: string;
80
+ } | false;
75
81
  export declare const DEFAULT_SETTINGS: Settings;
76
82
  export interface ZudoDocConfig {
77
83
  /**
@@ -372,6 +378,18 @@ export interface ZudoDocConfig {
372
378
  * @default false
373
379
  */
374
380
  claudeResources?: ClaudeResourcesConfig;
381
+ /**
382
+ * Codex-resources ingestion config, or `false` to disable.
383
+ * `codexDir` is the `.codex/` directory, resolved against `projectRoot`.
384
+ * `projectRoot` anchors relative paths and the output base. `scanRoot` is
385
+ * the repo-wide discovery root for the `AGENTS.md` / `AGENTS.override.md`
386
+ * walk (including its relPath base for titles/slugs) and the repo-level
387
+ * `.agents/skills/` root; it defaults to `projectRoot`. This lets a doc site
388
+ * in a repo subdirectory scan repo-wide instructions and team skills while
389
+ * writing into its own collection (#2558).
390
+ * @default false
391
+ */
392
+ codexResources?: CodexResourcesConfig;
375
393
  /**
376
394
  * Route prefixes served only in the default locale (never locale-prefixed).
377
395
  * @default []
package/dist/config.js CHANGED
@@ -70,6 +70,7 @@ const DEFAULT_SETTINGS = {
70
70
  htmlPreview: void 0,
71
71
  versions: false,
72
72
  claudeResources: false,
73
+ codexResources: false,
73
74
  defaultLocaleOnlyPrefixes: [],
74
75
  footer: false,
75
76
  headerNav: [],
@@ -6,6 +6,7 @@ const defaultTranslations = {
6
6
  "nav.components": "Components",
7
7
  "nav.reference": "Reference",
8
8
  "nav.claude": "Claude",
9
+ "nav.codex": "Codex",
9
10
  "nav.changelog": "Changelog",
10
11
  "nav.develop": "Develop",
11
12
  "nav.previous": "Previous",
@@ -65,6 +66,7 @@ const defaultTranslations = {
65
66
  "nav.components": "\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8",
66
67
  "nav.reference": "\u30EA\u30D5\u30A1\u30EC\u30F3\u30B9",
67
68
  "nav.claude": "Claude",
69
+ "nav.codex": "Codex",
68
70
  "nav.changelog": "\u5909\u66F4\u5C65\u6B74",
69
71
  "nav.develop": "\u958B\u767A",
70
72
  "nav.previous": "\u524D\u3078",
@@ -124,6 +126,7 @@ const defaultTranslations = {
124
126
  "nav.components": "Komponenten",
125
127
  "nav.reference": "Referenz",
126
128
  "nav.claude": "Claude",
129
+ "nav.codex": "Codex",
127
130
  "nav.changelog": "Changelog",
128
131
  "nav.develop": "Entwicklung",
129
132
  "nav.previous": "Zur\xFCck",
@@ -0,0 +1,4 @@
1
+ import type { ZfbPlugin } from "@takazudo/zfb/plugins";
2
+ export { runCodexResourcesPreStep } from "./internal/codex-resources/index.js";
3
+ declare const plugin: ZfbPlugin;
4
+ export default plugin;
@@ -0,0 +1,31 @@
1
+ import { runCodexResourcesPreStep } from "./internal/codex-resources/index.js";
2
+ import { runCodexResourcesPreStep as runCodexResourcesPreStep2 } from "./internal/codex-resources/index.js";
3
+ const PLUGIN_NAME = "@takazudo/zudo-doc-codex-resources";
4
+ const plugin = {
5
+ name: PLUGIN_NAME,
6
+ async preBuild(ctx) {
7
+ const codexDir = ctx.options["codexDir"];
8
+ if (typeof codexDir !== "string" || codexDir.length === 0) {
9
+ throw new Error(
10
+ `[${PLUGIN_NAME}] preBuild: options.codexDir must be a non-empty string (got ${JSON.stringify(codexDir)})`
11
+ );
12
+ }
13
+ const projectRootOpt = ctx.options["projectRoot"];
14
+ const scanRootOpt = ctx.options["scanRoot"];
15
+ const docsDirOpt = ctx.options["docsDir"];
16
+ const result = runCodexResourcesPreStep({
17
+ codexDir,
18
+ projectRoot: typeof projectRootOpt === "string" ? projectRootOpt : ctx.projectRoot,
19
+ scanRoot: typeof scanRootOpt === "string" ? scanRootOpt : void 0,
20
+ docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs"
21
+ });
22
+ ctx.logger.info(
23
+ `codex-resources: ${result.agentsMd} AGENTS.md, ${result.config} config, ${result.agents} agents, ${result.hooks} hooks, ${result.rules} rules, ${result.skills} skills`
24
+ );
25
+ }
26
+ };
27
+ var codex_resources_default = plugin;
28
+ export {
29
+ codex_resources_default as default,
30
+ runCodexResourcesPreStep2 as runCodexResourcesPreStep
31
+ };
@@ -1,133 +1,17 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import matter from "gray-matter";
4
- import { escapeForMdx } from "./escape-for-mdx.js";
5
- function ensureDir(dir) {
6
- if (!fs.existsSync(dir)) {
7
- fs.mkdirSync(dir, { recursive: true });
8
- }
9
- }
10
- function cleanDir(dir) {
11
- if (!fs.existsSync(dir)) return;
12
- fs.rmSync(dir, { recursive: true, force: true });
13
- }
14
- function parseFrontmatter(content) {
15
- try {
16
- return matter(content);
17
- } catch {
18
- return null;
19
- }
20
- }
21
- function escapeTitle(s) {
22
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
23
- }
24
- function listFiles(dir) {
25
- if (!fs.existsSync(dir)) return [];
26
- return fs.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isFile()).map((d) => d.name).sort();
27
- }
28
- function writeCategoryIndex(outputDir, label, position, description) {
29
- const mdx = `---
30
- title: "${escapeTitle(label)}"
31
- description: "${escapeTitle(description)}"
32
- sidebar_position: ${position}
33
- category_no_page: true
34
- generated: true
35
- ---
36
- `;
37
- fs.writeFileSync(path.join(outputDir, "index.mdx"), mdx);
38
- }
39
- function writeUnlistedSubPage(outputPath, title, body) {
40
- fs.writeFileSync(
41
- outputPath,
42
- `---
43
- title: "${escapeTitle(title)}"
44
- unlisted: true
45
- generated: true
46
- ---
47
-
48
- ${body}
49
- `
50
- );
51
- }
52
- function assertNotIndexReserved(nameOrSlug, errorMessage) {
53
- if (nameOrSlug === "index") {
54
- throw new Error(errorMessage);
55
- }
56
- }
57
- function isRepoRelativeLink(url) {
58
- const trimmed = url.trim();
59
- if (trimmed === "") return false;
60
- if (trimmed.startsWith("#")) return false;
61
- if (trimmed.startsWith("/")) return false;
62
- if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return false;
63
- return true;
64
- }
65
- function downgradeRepoRelativeLinks(content) {
66
- const blockPlaceholder = "\0CRLINK_BLOCK_";
67
- const inlinePlaceholder = "\0CRLINK_INLINE_";
68
- const codeBlocks = [];
69
- const withBlocks = content.replace(/(`{3,}|~{3,})[^\n]*\n[\s\S]*?\1/g, (match) => {
70
- codeBlocks.push(match);
71
- return `${blockPlaceholder}${codeBlocks.length - 1}\0`;
72
- });
73
- const transformed = withBlocks.split(new RegExp(`(${blockPlaceholder}\\d+\0)`, "g")).map((part) => {
74
- if (new RegExp(`^${blockPlaceholder}\\d+\0$`).test(part)) return part;
75
- const inlineCodes = [];
76
- const withInline = part.replace(
77
- /(`{1,3})(?!`)([\s\S]*?[^`])\1(?!`)/g,
78
- (match) => {
79
- inlineCodes.push(match);
80
- return `${inlinePlaceholder}${inlineCodes.length - 1}\0`;
81
- }
82
- );
83
- const rewritten = withInline.replace(
84
- /!?\[([^\]]*)\]\(([^)]+)\)/g,
85
- (match, text, url) => isRepoRelativeLink(url) ? `\`${text}\`` : match
86
- );
87
- return rewritten.replace(
88
- new RegExp(`${inlinePlaceholder}(\\d+)\0`, "g"),
89
- (_, idx) => inlineCodes[Number(idx)] ?? ""
90
- );
91
- }).join("");
92
- return transformed.replace(
93
- new RegExp(`${blockPlaceholder}(\\d+)\0`, "g"),
94
- (_, idx) => codeBlocks[Number(idx)] ?? ""
95
- );
96
- }
97
- const EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([
98
- "node_modules",
99
- "worktrees",
100
- "dist",
101
- "out",
102
- "public",
103
- "__inbox",
104
- "test-results"
105
- ]);
106
- function findClaudeMdFiles(dir, excludeDirs) {
107
- const results = [];
108
- if (!fs.existsSync(dir)) return results;
109
- const excludes = excludeDirs.map(
110
- (d) => d.endsWith(path.sep) ? d.slice(0, -path.sep.length) : d
111
- );
112
- for (const item of fs.readdirSync(dir)) {
113
- if (EXCLUDED_DIR_NAMES.has(item)) continue;
114
- if (item.startsWith(".")) continue;
115
- const itemPath = path.join(dir, item);
116
- if (excludes.some((d) => itemPath === d || itemPath.startsWith(d + path.sep))) continue;
117
- let stat;
118
- try {
119
- stat = fs.lstatSync(itemPath);
120
- } catch {
121
- continue;
122
- }
123
- if (stat.isDirectory()) {
124
- results.push(...findClaudeMdFiles(itemPath, excludes));
125
- } else if (stat.isFile() && item === "CLAUDE.md") {
126
- results.push(itemPath);
127
- }
128
- }
129
- return results;
130
- }
3
+ import {
4
+ assertNotIndexReserved,
5
+ cleanDir,
6
+ downgradeRepoRelativeLinks,
7
+ ensureDir,
8
+ escapeForMdx,
9
+ escapeTitle,
10
+ findNamedFiles,
11
+ generateSkillsCategory,
12
+ parseFrontmatter,
13
+ writeCategoryIndex
14
+ } from "../resource-docs-shared/index.js";
131
15
  function generateClaudemdDocs(config) {
132
16
  const projectRoot = config.projectRoot ?? config.claudeDir;
133
17
  const scanRoot = config.scanRoot ?? projectRoot;
@@ -138,7 +22,7 @@ function generateClaudemdDocs(config) {
138
22
  path.join(projectRoot, "e2e", "fixtures"),
139
23
  path.join(config.docsDir)
140
24
  ];
141
- const files = findClaudeMdFiles(scanRoot, excludeDirs);
25
+ const files = findNamedFiles(scanRoot, excludeDirs, ["CLAUDE.md"]);
142
26
  if (files.length === 0) return [];
143
27
  ensureDir(outputDir);
144
28
  const items = [];
@@ -220,170 +104,15 @@ ${escapeForMdx(parsed.content.trim())}
220
104
  writeCategoryIndex(outputDir, "Commands", 901, "Custom slash commands");
221
105
  return items;
222
106
  }
223
- function getSkillFileTree(skillDir, subDirs) {
224
- const lines = [`${skillDir}/`];
225
- const entries = [{ isDir: false, name: "SKILL.md" }];
226
- for (const sub of subDirs) {
227
- entries.push({ isDir: true, name: sub.name, children: sub.files });
228
- }
229
- for (let i = 0; i < entries.length; i++) {
230
- const entry = entries[i];
231
- if (!entry) continue;
232
- const isLast = i === entries.length - 1;
233
- const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
234
- if (!entry.isDir) {
235
- lines.push(`${prefix}${entry.name}`);
236
- } else {
237
- lines.push(`${prefix}${entry.name}/`);
238
- for (let j = 0; j < entry.children.length; j++) {
239
- const child = entry.children[j];
240
- if (!child) continue;
241
- const childIsLast = j === entry.children.length - 1;
242
- const continuation = isLast ? " " : "\u2502 ";
243
- const childPrefix = childIsLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
244
- lines.push(`${continuation}${childPrefix}${child}`);
245
- }
246
- }
247
- }
248
- return lines.join("\n");
249
- }
250
- function getScriptDescription(filePath) {
251
- try {
252
- const topLines = fs.readFileSync(filePath, "utf8").split("\n", 2);
253
- const firstLine = topLines[0] ?? "";
254
- const commentLine = firstLine.startsWith("#!") ? topLines[1] ?? "" : firstLine;
255
- const match = commentLine.match(/^(?:#|\/\/)\s*(.+)/);
256
- return match ? ` \u2014 ${match[1]}` : "";
257
- } catch {
258
- return "";
259
- }
260
- }
261
- function getSkillReferences(skillsDir, skillDir) {
262
- const refsDir = path.join(skillsDir, skillDir, "references");
263
- if (!fs.existsSync(refsDir)) return [];
264
- return fs.readdirSync(refsDir).filter((f) => f.endsWith(".md")).map((f) => {
265
- const content = fs.readFileSync(path.join(refsDir, f), "utf8");
266
- const name = f.replace(/\.md$/, "");
267
- const h1Match = content.match(/^#\s+(.+)$/m);
268
- const title = h1Match?.[1] ?? name;
269
- return { name, title, content };
270
- }).sort((a, b) => a.name.localeCompare(b.name));
271
- }
272
107
  function generateSkillsDocs(config) {
273
- const skillsDir = path.join(config.claudeDir, "skills");
274
- const outputDir = path.join(config.docsDir, "claude-skills");
275
- cleanDir(outputDir);
276
- if (!fs.existsSync(skillsDir)) return [];
277
- const dirs = fs.readdirSync(skillsDir).filter((d) => {
278
- const skillPath = path.join(skillsDir, d);
279
- return fs.statSync(skillPath).isDirectory() && fs.existsSync(path.join(skillPath, "SKILL.md"));
108
+ return generateSkillsCategory({
109
+ skillsDirs: [path.join(config.claudeDir, "skills")],
110
+ outputDir: path.join(config.docsDir, "claude-skills"),
111
+ label: "Skills",
112
+ position: 902,
113
+ description: "Skill packages",
114
+ sourceLabel: ".claude/skills"
280
115
  });
281
- if (dirs.length === 0) return [];
282
- ensureDir(outputDir);
283
- const items = [];
284
- for (const dir of dirs) {
285
- assertNotIndexReserved(
286
- dir,
287
- `claude-resources: skill directory ".claude/skills/index/" uses the reserved name "index", which is used for the category metadata file. Rename the skill directory to resolve the conflict.`
288
- );
289
- const content = fs.readFileSync(
290
- path.join(skillsDir, dir, "SKILL.md"),
291
- "utf8"
292
- );
293
- const parsed = parseFrontmatter(content);
294
- if (!parsed) continue;
295
- const name = parsed.data.name || dir;
296
- const description = parsed.data.description || "";
297
- const references = getSkillReferences(skillsDir, dir);
298
- items.push({ name, dir, description, references });
299
- const scriptFiles = listFiles(path.join(skillsDir, dir, "scripts"));
300
- const assetFiles = listFiles(path.join(skillsDir, dir, "assets"));
301
- const refFiles = references.map((r) => `${r.name}.md`);
302
- const subDirs = [];
303
- if (scriptFiles.length > 0) subDirs.push({ name: "scripts", files: scriptFiles });
304
- if (refFiles.length > 0) subDirs.push({ name: "references", files: refFiles });
305
- if (assetFiles.length > 0) subDirs.push({ name: "assets", files: assetFiles });
306
- let fileStructureSection = "";
307
- if (subDirs.length > 0) {
308
- const tree = `\`\`\`
309
- ${getSkillFileTree(dir, subDirs)}
310
- \`\`\``;
311
- const links = [];
312
- for (const ref of references) {
313
- links.push(`- [references/${ref.name}.md](./ref-${ref.name})`);
314
- }
315
- for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
316
- const slug = f.replace(/\.md$/, "");
317
- links.push(`- [scripts/${f}](./script-${slug})`);
318
- }
319
- for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
320
- const slug = f.replace(/\.md$/, "");
321
- links.push(`- [assets/${f}](./asset-${slug})`);
322
- }
323
- const linkList = links.length > 0 ? `
324
-
325
- ${links.join("\n")}` : "";
326
- fileStructureSection = `## File Structure
327
-
328
- ${tree}${linkList}`;
329
- }
330
- const shortDesc = description.length > 200 ? description.substring(0, 200) + "..." : description;
331
- let skillBody = parsed.content.trim();
332
- skillBody = skillBody.replace(/\]\(references\/([^)]+)\.md\)/g, "](./ref-$1)").replace(/\]\(scripts\/([^)]+)\.md\)/g, "](./script-$1)").replace(/\]\(assets\/([^)]+)\.md\)/g, "](./asset-$1)");
333
- const body = [
334
- fileStructureSection,
335
- escapeForMdx(skillBody)
336
- ].filter(Boolean).join("\n\n");
337
- const mdx = `---
338
- title: "${escapeTitle(name)}"
339
- description: "${escapeTitle(shortDesc)}"
340
- sidebar_label: "${escapeTitle(name)}"
341
- generated: true
342
- ---
343
-
344
- ${body}`;
345
- const skillDirOut = path.join(outputDir, dir);
346
- ensureDir(skillDirOut);
347
- fs.writeFileSync(path.join(skillDirOut, "index.mdx"), mdx);
348
- for (const ref of references) {
349
- writeUnlistedSubPage(
350
- path.join(skillDirOut, `ref-${ref.name}.mdx`),
351
- ref.title,
352
- escapeForMdx(ref.content.trim())
353
- );
354
- }
355
- for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
356
- const slug = f.replace(/\.md$/, "");
357
- const raw = fs.readFileSync(
358
- path.join(skillsDir, dir, "scripts", f),
359
- "utf8"
360
- );
361
- const h1Match = raw.match(/^#\s+(.+)$/m);
362
- const title = h1Match?.[1] ?? slug;
363
- writeUnlistedSubPage(
364
- path.join(skillDirOut, `script-${slug}.mdx`),
365
- title,
366
- escapeForMdx(raw.trim())
367
- );
368
- }
369
- for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
370
- const slug = f.replace(/\.md$/, "");
371
- const raw = fs.readFileSync(
372
- path.join(skillsDir, dir, "assets", f),
373
- "utf8"
374
- );
375
- const h1Match = raw.match(/^#\s+(.+)$/m);
376
- const title = h1Match?.[1] ?? slug;
377
- writeUnlistedSubPage(
378
- path.join(skillDirOut, `asset-${slug}.mdx`),
379
- title,
380
- escapeForMdx(raw.trim())
381
- );
382
- }
383
- }
384
- items.sort((a, b) => a.name.localeCompare(b.name));
385
- writeCategoryIndex(outputDir, "Skills", 902, "Skill packages");
386
- return items;
387
116
  }
388
117
  function generateAgentsDocs(config) {
389
118
  const agentsDir = path.join(config.claudeDir, "agents");
@@ -0,0 +1,6 @@
1
+ import type { CodexResourcesConfig } from "./generate.js";
2
+ export interface AgentsMdItem {
3
+ relPath: string;
4
+ slug: string;
5
+ }
6
+ export declare function generateAgentsMdCategory(config: CodexResourcesConfig): AgentsMdItem[];
@@ -0,0 +1,70 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ formatFrontmatterString,
5
+ cleanDir,
6
+ downgradeRepoRelativeLinks,
7
+ ensureDir,
8
+ escapeForMdx,
9
+ escapeMarkdownTableCell,
10
+ findNamedFiles,
11
+ writeCategoryIndex
12
+ } from "../resource-docs-shared/index.js";
13
+ import { assertUniqueSlug, writeGeneratedPage } from "./utils.js";
14
+ function generateAgentsMdCategory(config) {
15
+ const projectRoot = config.projectRoot ?? config.codexDir;
16
+ const scanRoot = config.scanRoot ?? projectRoot;
17
+ const outputDir = path.join(config.docsDir, "codex-agents-md");
18
+ cleanDir(outputDir);
19
+ const excludeDirs = [
20
+ path.join(scanRoot, "e2e", "fixtures"),
21
+ path.join(projectRoot, "e2e", "fixtures"),
22
+ config.docsDir
23
+ ];
24
+ const files = findNamedFiles(scanRoot, excludeDirs, [
25
+ "AGENTS.md",
26
+ "AGENTS.override.md"
27
+ ]);
28
+ if (files.length === 0) return [];
29
+ const items = files.map((absPath) => {
30
+ const relPath = path.relative(scanRoot, absPath);
31
+ const dirPart = path.dirname(relPath);
32
+ const baseSlug = dirPart === "." ? "root" : dirPart.split(path.sep).join("--");
33
+ const slug = path.basename(absPath) === "AGENTS.override.md" ? `${baseSlug}--override` : baseSlug;
34
+ return { absPath, relPath, slug };
35
+ });
36
+ items.sort((a, b) => {
37
+ if (a.slug === "root") return -1;
38
+ if (b.slug === "root") return 1;
39
+ return a.relPath.localeCompare(b.relPath);
40
+ });
41
+ ensureDir(outputDir);
42
+ const emitted = /* @__PURE__ */ new Map();
43
+ items.forEach((item, index) => {
44
+ assertUniqueSlug("AGENTS.md", item.slug, item.relPath, emitted);
45
+ const content = fs.readFileSync(item.absPath, "utf8");
46
+ writeGeneratedPage({
47
+ outputPath: path.join(outputDir, `${item.slug}.mdx`),
48
+ title: `/${item.relPath}`,
49
+ description: `Codex instructions at /${item.relPath}`,
50
+ sidebarPosition: index + 1,
51
+ sidebarLabel: item.relPath,
52
+ body: `**Path:** ${escapeMarkdownTableCell(item.relPath)}
53
+
54
+ ${escapeForMdx(
55
+ downgradeRepoRelativeLinks(content.trim())
56
+ )}`
57
+ });
58
+ });
59
+ writeCategoryIndex(
60
+ outputDir,
61
+ "AGENTS.md",
62
+ 905,
63
+ "Project instructions for Codex",
64
+ formatFrontmatterString
65
+ );
66
+ return items.map(({ relPath, slug }) => ({ relPath, slug }));
67
+ }
68
+ export {
69
+ generateAgentsMdCategory
70
+ };
@@ -0,0 +1,7 @@
1
+ import type { CodexResourcesConfig } from "./generate.js";
2
+ export interface AgentItem {
3
+ filename: string;
4
+ slug: string;
5
+ name: string;
6
+ }
7
+ export declare function generateAgentsCategory(config: CodexResourcesConfig): AgentItem[];
@@ -0,0 +1,104 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse } from "smol-toml";
4
+ import {
5
+ formatFrontmatterString,
6
+ cleanDir,
7
+ ensureDir,
8
+ escapeForMdx,
9
+ escapeMarkdownTableCell,
10
+ renderCodeFence,
11
+ writeCategoryIndex
12
+ } from "../resource-docs-shared/index.js";
13
+ import {
14
+ assertUniqueSlug,
15
+ filenameSlug,
16
+ isRecord,
17
+ warn,
18
+ writeGeneratedPage
19
+ } from "./utils.js";
20
+ function optionalString(record, field, filePath) {
21
+ const value = record[field];
22
+ if (value === void 0) return void 0;
23
+ if (typeof value === "string") return value;
24
+ warn(filePath, `${field} must be a string; omitting it`);
25
+ return void 0;
26
+ }
27
+ function generateAgentsCategory(config) {
28
+ const agentsDir = path.join(config.codexDir, "agents");
29
+ const outputDir = path.join(config.docsDir, "codex-agents");
30
+ cleanDir(outputDir);
31
+ if (!fs.existsSync(agentsDir)) return [];
32
+ let files;
33
+ try {
34
+ files = fs.readdirSync(agentsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".toml")).map((entry) => entry.name).sort();
35
+ } catch (error) {
36
+ warn(agentsDir, `unable to read agents directory: ${String(error)}`);
37
+ return [];
38
+ }
39
+ if (files.length === 0) return [];
40
+ ensureDir(outputDir);
41
+ const items = [];
42
+ const emitted = /* @__PURE__ */ new Map();
43
+ for (const filename of files) {
44
+ const filePath = path.join(agentsDir, filename);
45
+ let source;
46
+ let parsed;
47
+ try {
48
+ source = fs.readFileSync(filePath, "utf8");
49
+ parsed = parse(source);
50
+ } catch (error) {
51
+ warn(filePath, `unable to parse TOML, skipping file: ${String(error)}`);
52
+ continue;
53
+ }
54
+ if (!isRecord(parsed)) {
55
+ warn(filePath, "parsed TOML root was not an object, skipping file");
56
+ continue;
57
+ }
58
+ const slug = filenameSlug(filename.replace(/\.toml$/, ""));
59
+ assertUniqueSlug("agent", slug, filename, emitted);
60
+ const name = optionalString(parsed, "name", filePath) ?? slug;
61
+ const description = optionalString(parsed, "description", filePath) ?? "";
62
+ const model = optionalString(parsed, "model", filePath);
63
+ const reasoning = optionalString(parsed, "model_reasoning_effort", filePath);
64
+ const sandbox = optionalString(parsed, "sandbox_mode", filePath);
65
+ const instructions = optionalString(parsed, "developer_instructions", filePath);
66
+ const badges = [
67
+ model ? `**Model:** ${escapeMarkdownTableCell(model)}` : "",
68
+ reasoning ? `**Reasoning effort:** ${escapeMarkdownTableCell(reasoning)}` : "",
69
+ sandbox ? `**Sandbox:** ${escapeMarkdownTableCell(sandbox)}` : ""
70
+ ].filter(Boolean);
71
+ const sections = [badges.join("\n")];
72
+ if (instructions !== void 0) {
73
+ sections.push(
74
+ `## Developer instructions
75
+
76
+ ${escapeForMdx(instructions.trim())}`
77
+ );
78
+ }
79
+ sections.push(`## Source
80
+
81
+ ${renderCodeFence(source, "toml")}`);
82
+ writeGeneratedPage({
83
+ outputPath: path.join(outputDir, `${slug}.mdx`),
84
+ title: name,
85
+ description,
86
+ sidebarLabel: name,
87
+ body: sections.filter(Boolean).join("\n\n")
88
+ });
89
+ items.push({ filename, slug, name });
90
+ }
91
+ if (items.length > 0) {
92
+ writeCategoryIndex(
93
+ outputDir,
94
+ "Agents",
95
+ 907,
96
+ "Custom subagents",
97
+ formatFrontmatterString
98
+ );
99
+ }
100
+ return items;
101
+ }
102
+ export {
103
+ generateAgentsCategory
104
+ };
@@ -0,0 +1,6 @@
1
+ import type { CodexResourcesConfig } from "./generate.js";
2
+ export interface ConfigItem {
3
+ filename: string;
4
+ slug: string;
5
+ }
6
+ export declare function generateConfigCategory(config: CodexResourcesConfig): ConfigItem[];