agdocs 1.0.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 ADDED
@@ -0,0 +1,26 @@
1
+ # agdocs
2
+
3
+ English | [简体中文](./README.zh-CN.md)
4
+
5
+ Bootstrap agent-oriented docs in your project.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ npx agdocs init
11
+ ```
12
+
13
+ See `npx agdocs -h` for all commands and flags.
14
+
15
+ ## Layout
16
+
17
+ ```
18
+ .
19
+ ├── AGENTS.md # Agent rules
20
+ ├── CLAUDE.md # Agent rules for Claude Code
21
+ ├── CONTEXT.md # Domain glossary
22
+ ├── DESIGN.md # UI / interaction conventions
23
+ └── docs/
24
+ ├── adr/ # Architecture decision records (0001-<slug>.md)
25
+ └── prd/ # Product requirements (<feature-slug>.md)
26
+ ```
@@ -0,0 +1,26 @@
1
+ # agdocs
2
+
3
+ [English](./README.md) | 简体中文
4
+
5
+ 在项目中引导搭建面向 agent 的文档。
6
+
7
+ ## 用法
8
+
9
+ ```bash
10
+ npx agdocs init
11
+ ```
12
+
13
+ 全部命令与 flags 见 `npx agdocs -h`。
14
+
15
+ ## 目录结构
16
+
17
+ ```
18
+ .
19
+ ├── AGENTS.md # Agent 规则
20
+ ├── CLAUDE.md # Claude Code 的 Agent 规则
21
+ ├── CONTEXT.md # 领域词汇表
22
+ ├── DESIGN.md # UI / 交互约定
23
+ └── docs/
24
+ ├── adr/ # 架构决策记录(0001-<slug>.md)
25
+ └── prd/ # 产品需求(<feature-slug>.md)
26
+ ```
package/dist/cli.mjs ADDED
@@ -0,0 +1,567 @@
1
+ #!/usr/bin/env node
2
+ import { Command, InvalidArgumentError } from "commander";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { cp, lstat, mkdir, readFile, readlink, rm, symlink, writeFile } from "node:fs/promises";
7
+ import { cancel, confirm, isCancel, select } from "@clack/prompts";
8
+ //#region src/content/agents-bootstrap.ts
9
+ /** Injected into the target repo's AGENTS.md by `agdocs init`. */
10
+ const AGENTS_BOOTSTRAP_START = "<!-- agdocs:begin -->";
11
+ const AGENTS_BOOTSTRAP_END = "<!-- agdocs:end -->";
12
+ const agentsBootstrapBody = `${AGENTS_BOOTSTRAP_START}
13
+
14
+ ## CONTEXT.md
15
+
16
+ \`CONTEXT.md\` should be totally devoid of implementation details. Do not treat \`CONTEXT.md\` as a spec, a scratch pad, or a repository for implementation decisions. It is a glossary and nothing else.
17
+
18
+ Use its canonical terms, and avoid the listed aliases, wherever a domain concept appears in docs, tests, or implementation. Update it whenever a term is added, renamed, or clarified.
19
+
20
+ **File structure.** Single context: a root \`CONTEXT.md\` and \`docs/adr/\`.
21
+
22
+ **Format.**
23
+
24
+ \`\`\`md
25
+ # {Context Name}
26
+
27
+ {One or two sentence description of what this context is and why it exists.}
28
+
29
+ ## Language
30
+
31
+ **Order**:
32
+ {A one or two sentence description of the term}
33
+ _Avoid_: Purchase, transaction
34
+ \`\`\`
35
+
36
+ **Rules.**
37
+
38
+ - **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.
39
+ - **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution.
40
+ - **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
41
+ - **Show relationships.** Use bold term names and express cardinality where obvious.
42
+ - **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
43
+ - **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
44
+ - **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.
45
+
46
+ ## docs/adr/
47
+
48
+ ADRs live in \`docs/adr/\` and use sequential numbering: \`0001-slug.md\`, \`0002-slug.md\`, etc. Create the directory lazily — only when the first ADR is needed. Scan \`docs/adr/\` for the highest existing number and increment by one. Read the ADRs touching an area before changing it, and add or update one when a qualifying decision changes.
49
+
50
+ **When to write one.**
51
+
52
+ An ADR records a decision that is costly to reverse. Skip everyday implementation choices. Write one when the choice is:
53
+
54
+ 1. **Consequential** — it constrains future work, shapes the architecture, or would be expensive to undo
55
+ 2. **Surprising without context** — a future reader will look at the code and wonder "why on earth did they do it this way?"
56
+
57
+ If both apply, write the ADR. If only one applies, use judgment. If neither applies, skip it.
58
+
59
+ **Optional.**
60
+
61
+ - **Status** frontmatter (\`proposed | accepted | deprecated | superseded by ADR-NNNN\`) — useful when decisions are revisited
62
+ - **Alternatives considered** — useful when the rejected options are non-obvious
63
+
64
+ ## PRD (\`docs/prd/<feature-slug>.md\`)
65
+
66
+ One PRD file per feature. Synthesize the PRD from the current conversation and codebase understanding — do NOT interview the user. Use the project's domain glossary vocabulary throughout, and respect any ADRs in the area you're touching. This repo keeps PRDs only: do not create issue or task files, and do not run a triage step, unless the user explicitly asks. Update the PRD when product scope, user flows, implementation decisions, or testing decisions change.
67
+
68
+ \`\`\`md
69
+ # <feature-slug>
70
+
71
+ **<Feature Name>**
72
+
73
+ ## Problem Statement
74
+
75
+ The problem that the user is facing.
76
+
77
+ ## Solution
78
+
79
+ The solution that the user is proposing.
80
+
81
+ ## User Stories
82
+
83
+ How the user will interact with the feature.
84
+
85
+ ## Implementation Decisions
86
+
87
+ Key decisions that the user has made about the feature.
88
+
89
+ ## Testing Decisions
90
+
91
+ Key decisions that the user has made about testing the feature.
92
+
93
+ ## Out of Scope
94
+
95
+ The things that are out of scope for this PRD.
96
+
97
+ ## Further Notes
98
+
99
+ Any further notes about the feature.
100
+ \`\`\`
101
+
102
+ ## DESIGN.md
103
+
104
+ The current workspace's visual and interaction direction. Read it before changing UI, and update it when those conventions stably change.
105
+
106
+ ${AGENTS_BOOTSTRAP_END}`;
107
+ //#endregion
108
+ //#region src/agents-md.ts
109
+ function replaceRange(source, start, end, replacement) {
110
+ return `${source.slice(0, start)}${replacement}${source.slice(end)}`;
111
+ }
112
+ function upsertAgentsMd(existing) {
113
+ const block = `${agentsBootstrapBody}\n`;
114
+ if (!existing.trim()) return {
115
+ content: block,
116
+ action: "created"
117
+ };
118
+ const startIdx = existing.indexOf(AGENTS_BOOTSTRAP_START);
119
+ const endIdx = existing.indexOf(AGENTS_BOOTSTRAP_END);
120
+ const hasStart = startIdx !== -1;
121
+ const hasEnd = endIdx !== -1;
122
+ if (hasStart && hasEnd && startIdx < endIdx) {
123
+ const next = replaceRange(existing, startIdx, endIdx + 19, agentsBootstrapBody);
124
+ if (next === existing) return {
125
+ content: existing,
126
+ action: "unchanged"
127
+ };
128
+ return {
129
+ content: next,
130
+ action: "updated"
131
+ };
132
+ }
133
+ if (hasStart && !hasEnd) return {
134
+ content: replaceRange(existing, startIdx, existing.length, block),
135
+ action: "updated"
136
+ };
137
+ if (!hasStart && hasEnd) return {
138
+ content: replaceRange(existing, endIdx, endIdx + 19, agentsBootstrapBody),
139
+ action: "updated"
140
+ };
141
+ return {
142
+ content: `${existing}${existing.endsWith("\n") ? "\n" : "\n\n"}${block}`,
143
+ action: "updated"
144
+ };
145
+ }
146
+ //#endregion
147
+ //#region src/fs/io.ts
148
+ async function exists(path) {
149
+ try {
150
+ await lstat(path);
151
+ return true;
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+ async function readText(path) {
157
+ try {
158
+ return await readFile(path, "utf8");
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+ async function writeText(path, content) {
164
+ await mkdir(dirname(path), { recursive: true });
165
+ if (await isSymlink(path)) await rm(path, { force: true });
166
+ await writeFile(path, content, "utf8");
167
+ }
168
+ async function isSymlink(path) {
169
+ try {
170
+ return (await lstat(path)).isSymbolicLink();
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+ async function isDirectory(path) {
176
+ try {
177
+ const stat = await lstat(path);
178
+ return stat.isDirectory() && !stat.isSymbolicLink();
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
183
+ async function isFile(path) {
184
+ try {
185
+ const stat = await lstat(path);
186
+ return stat.isFile() && !stat.isSymbolicLink();
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+ /** True when `path` is a symlink whose target equals `expected` (as stored). */
192
+ async function isSymlinkTo(path, expected) {
193
+ try {
194
+ if (!(await lstat(path)).isSymbolicLink()) return false;
195
+ return await readlink(path) === expected;
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+ async function ensureDir(path) {
201
+ await mkdir(path, { recursive: true });
202
+ }
203
+ async function copyDir(src, dest) {
204
+ await rm(dest, {
205
+ force: true,
206
+ recursive: true
207
+ });
208
+ await cp(src, dest, {
209
+ recursive: true,
210
+ dereference: true
211
+ });
212
+ }
213
+ async function replaceWithSymlink(path, target) {
214
+ await rm(path, {
215
+ force: true,
216
+ recursive: true
217
+ });
218
+ await mkdir(dirname(path), { recursive: true });
219
+ await symlink(target, path);
220
+ }
221
+ function isInteractive() {
222
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
223
+ }
224
+ //#endregion
225
+ //#region src/fs/paths.ts
226
+ function agentsMd(cwd) {
227
+ return join(cwd, "AGENTS.md");
228
+ }
229
+ function claudeMd(cwd) {
230
+ return join(cwd, "CLAUDE.md");
231
+ }
232
+ function agentsSkills(cwd) {
233
+ return join(cwd, ".agents", "skills");
234
+ }
235
+ function claudeSkills(cwd) {
236
+ return join(cwd, ".claude", "skills");
237
+ }
238
+ //#endregion
239
+ //#region src/link/detect-md.ts
240
+ function sameText(a, b) {
241
+ return a.trim() === b.trim();
242
+ }
243
+ /** Empty / whitespace-only files count as missing. */
244
+ async function readPresentFile(path) {
245
+ if (!await exists(path) || !await isFile(path)) return null;
246
+ const text = await readText(path) ?? "";
247
+ return text.trim() ? text : null;
248
+ }
249
+ async function detectMd(cwd) {
250
+ const agentsPath = agentsMd(cwd);
251
+ const claudePath = claudeMd(cwd);
252
+ if (await isSymlinkTo(claudePath, "AGENTS.md")) return { kind: "correct-link" };
253
+ if (await exists(claudePath) && await isSymlink(claudePath)) return { kind: "bad-link" };
254
+ if (await exists(agentsPath) && await isSymlink(agentsPath)) return { kind: "other" };
255
+ const agents = await readPresentFile(agentsPath);
256
+ const claude = await readPresentFile(claudePath);
257
+ if (!agents && !claude) return { kind: "missing-both" };
258
+ if (agents && !claude) return {
259
+ kind: "agents-only",
260
+ agents
261
+ };
262
+ if (!agents && claude) return {
263
+ kind: "claude-only",
264
+ claude
265
+ };
266
+ if (agents && claude) {
267
+ if (sameText(agents, claude)) return {
268
+ kind: "same-content",
269
+ content: agents
270
+ };
271
+ return {
272
+ kind: "conflict",
273
+ agents,
274
+ claude
275
+ };
276
+ }
277
+ return { kind: "other" };
278
+ }
279
+ //#endregion
280
+ //#region src/link/prompt.ts
281
+ async function askLink(message, initialValue = true) {
282
+ const value = await confirm({
283
+ message,
284
+ initialValue
285
+ });
286
+ if (isCancel(value)) {
287
+ cancel("Cancelled.");
288
+ process.exit(0);
289
+ }
290
+ return value;
291
+ }
292
+ async function askKeep(message = "Keep which content?") {
293
+ const value = await select({
294
+ message,
295
+ options: [{
296
+ value: "agents",
297
+ label: "AGENTS.md"
298
+ }, {
299
+ value: "claude",
300
+ label: "CLAUDE.md"
301
+ }]
302
+ });
303
+ if (isCancel(value)) {
304
+ cancel("Cancelled.");
305
+ process.exit(0);
306
+ }
307
+ return value;
308
+ }
309
+ async function askKeepSkills() {
310
+ const value = await select({
311
+ message: "Keep which skills?",
312
+ options: [{
313
+ value: "agents",
314
+ label: ".agents/skills"
315
+ }, {
316
+ value: "claude",
317
+ label: ".claude/skills"
318
+ }]
319
+ });
320
+ if (isCancel(value)) {
321
+ cancel("Cancelled.");
322
+ process.exit(0);
323
+ }
324
+ return value;
325
+ }
326
+ //#endregion
327
+ //#region src/link/resolve.ts
328
+ var LinkConflictError = class extends Error {
329
+ constructor(target) {
330
+ super(`Conflict in ${target}: pass --keep-* or -y, or run in a TTY`);
331
+ this.name = "LinkConflictError";
332
+ }
333
+ };
334
+ function modeToBool(mode) {
335
+ if (mode === "link") return true;
336
+ if (mode === "skip") return false;
337
+ }
338
+ async function resolveWantLink(options, message, ask, choice) {
339
+ const explicit = modeToBool(choice);
340
+ if (explicit !== void 0) return explicit;
341
+ if (options.yes) return true;
342
+ if (isInteractive()) return ask(message);
343
+ return options.command === "link";
344
+ }
345
+ async function resolveKeep(options, ask, target, choice) {
346
+ if (choice) return choice;
347
+ if (options.yes) return "agents";
348
+ if (isInteractive()) return ask();
349
+ throw new LinkConflictError(target);
350
+ }
351
+ //#endregion
352
+ //#region src/link/run-md.ts
353
+ async function bodies(cwd, state) {
354
+ switch (state.kind) {
355
+ case "missing-both": return {
356
+ agents: "",
357
+ claude: ""
358
+ };
359
+ case "agents-only": return {
360
+ agents: state.agents,
361
+ claude: ""
362
+ };
363
+ case "claude-only": return {
364
+ agents: "",
365
+ claude: state.claude
366
+ };
367
+ case "same-content": return {
368
+ agents: state.content,
369
+ claude: state.content
370
+ };
371
+ case "conflict": return {
372
+ agents: state.agents,
373
+ claude: state.claude
374
+ };
375
+ case "bad-link": return {
376
+ agents: await readText(agentsMd(cwd)) ?? "",
377
+ claude: ""
378
+ };
379
+ }
380
+ }
381
+ async function planMd(options) {
382
+ const state = await detectMd(options.cwd);
383
+ if (state.kind === "correct-link") return { action: "skip" };
384
+ if (state.kind === "other") throw new Error("Cannot link AGENTS.md / CLAUDE.md in the current layout");
385
+ const want = await resolveWantLink(options, "Link CLAUDE.md → AGENTS.md?", askLink, options.md);
386
+ const { agents, claude } = await bodies(options.cwd, state);
387
+ if (!want) {
388
+ if (options.command === "link") return { action: "skip" };
389
+ return {
390
+ action: "separate",
391
+ agents,
392
+ claude
393
+ };
394
+ }
395
+ if (state.kind === "conflict") {
396
+ const kept = await resolveKeep(options, askKeep, "AGENTS.md / CLAUDE.md", options.keepMd);
397
+ return {
398
+ action: "link",
399
+ content: kept === "agents" ? agents : claude,
400
+ kept
401
+ };
402
+ }
403
+ return {
404
+ action: "link",
405
+ content: agents || claude
406
+ };
407
+ }
408
+ /** `map` transforms each file body (e.g. bootstrap upsert). */
409
+ async function applyMdPlan(cwd, plan, map = (c) => c) {
410
+ if (plan.action === "skip") return { status: "skipped" };
411
+ if (plan.action === "link") {
412
+ await writeText(agentsMd(cwd), map(plan.content));
413
+ await replaceWithSymlink(claudeMd(cwd), "AGENTS.md");
414
+ return {
415
+ status: "linked",
416
+ kept: plan.kept
417
+ };
418
+ }
419
+ await writeText(agentsMd(cwd), map(plan.agents));
420
+ await writeText(claudeMd(cwd), map(plan.claude));
421
+ return { status: "unlinked" };
422
+ }
423
+ //#endregion
424
+ //#region src/commands/init.ts
425
+ async function writeIfMissing(path, content) {
426
+ if (await exists(path)) return false;
427
+ await writeText(path, content);
428
+ return true;
429
+ }
430
+ async function upsertAgentsInPlace(cwd) {
431
+ const { content, action } = upsertAgentsMd(await readText(agentsMd(cwd)) ?? "");
432
+ if (action !== "unchanged") await writeText(agentsMd(cwd), content);
433
+ console.log(action === "created" ? "Created AGENTS.md" : action === "updated" ? "Updated AGENTS.md" : "AGENTS.md up to date");
434
+ }
435
+ async function runInit(options) {
436
+ const opts = {
437
+ ...options,
438
+ command: "init"
439
+ };
440
+ const plan = await planMd(opts);
441
+ if (plan.action === "skip") await upsertAgentsInPlace(opts.cwd);
442
+ else if ((await applyMdPlan(opts.cwd, plan, (body) => upsertAgentsMd(body).content)).status === "linked") console.log("Linked CLAUDE.md → AGENTS.md");
443
+ else console.log("Wrote AGENTS.md and CLAUDE.md");
444
+ const created = [];
445
+ const skipped = [];
446
+ for (const file of ["CONTEXT.md", "DESIGN.md"]) if (await writeIfMissing(join(opts.cwd, file), "")) created.push(file);
447
+ else skipped.push(file);
448
+ await mkdir(join(opts.cwd, "docs", "adr"), { recursive: true });
449
+ await mkdir(join(opts.cwd, "docs", "prd"), { recursive: true });
450
+ created.push("docs/adr/", "docs/prd/");
451
+ if (created.length) console.log(`Created: ${created.join(", ")}`);
452
+ if (skipped.length) console.log(`Skipped: ${skipped.join(", ")}`);
453
+ }
454
+ //#endregion
455
+ //#region src/link/detect-skills.ts
456
+ async function detectSkills(cwd) {
457
+ const agentsPath = agentsSkills(cwd);
458
+ const claudePath = claudeSkills(cwd);
459
+ const agentsExists = await exists(agentsPath);
460
+ const claudeExists = await exists(claudePath);
461
+ if (await isSymlinkTo(claudePath, "../.agents/skills")) return { kind: "correct-link" };
462
+ if (!agentsExists && !claudeExists) return { kind: "missing-both" };
463
+ if (claudeExists && await isSymlink(claudePath)) return { kind: "bad-link" };
464
+ if (agentsExists && await isSymlink(agentsPath)) return { kind: "other" };
465
+ const agentsDir = agentsExists && await isDirectory(agentsPath);
466
+ const claudeDir = claudeExists && await isDirectory(claudePath);
467
+ if (agentsDir && !claudeExists) return { kind: "agents-only" };
468
+ if (claudeDir && !agentsExists) return { kind: "claude-only" };
469
+ if (agentsDir && claudeDir) return { kind: "both-dirs" };
470
+ return { kind: "other" };
471
+ }
472
+ //#endregion
473
+ //#region src/link/run-skills.ts
474
+ async function runSkills(options) {
475
+ const state = await detectSkills(options.cwd);
476
+ if (state.kind === "correct-link") return { status: "skipped" };
477
+ if (state.kind === "other") throw new Error("Cannot link .agents/skills / .claude/skills in the current layout");
478
+ if (!await resolveWantLink(options, "Link .claude/skills → .agents/skills?", askLink, options.skills)) return { status: "skipped" };
479
+ if (state.kind === "both-dirs") {
480
+ const kept = await resolveKeep(options, askKeepSkills, ".agents/skills / .claude/skills", options.keepSkills);
481
+ if (kept === "claude") await copyDir(claudeSkills(options.cwd), agentsSkills(options.cwd));
482
+ await replaceWithSymlink(claudeSkills(options.cwd), "../.agents/skills");
483
+ return {
484
+ status: "linked",
485
+ kept
486
+ };
487
+ }
488
+ if (state.kind === "claude-only") await copyDir(claudeSkills(options.cwd), agentsSkills(options.cwd));
489
+ else await ensureDir(agentsSkills(options.cwd));
490
+ await replaceWithSymlink(claudeSkills(options.cwd), "../.agents/skills");
491
+ return { status: "linked" };
492
+ }
493
+ //#endregion
494
+ //#region src/commands/link.ts
495
+ async function runLink(options) {
496
+ const opts = {
497
+ ...options,
498
+ command: "link"
499
+ };
500
+ const plan = await planMd(opts);
501
+ const md = await applyMdPlan(opts.cwd, plan);
502
+ if (md.status === "linked") console.log("Linked CLAUDE.md → AGENTS.md");
503
+ else if (md.status === "skipped") console.log("AGENTS.md / CLAUDE.md unchanged");
504
+ const skills = await runSkills(opts);
505
+ if (skills.status === "linked") console.log("Linked .claude/skills → .agents/skills");
506
+ else if (skills.status === "skipped") console.log(".agents/skills / .claude/skills unchanged");
507
+ }
508
+ //#endregion
509
+ //#region src/cli.ts
510
+ function readPackageVersion() {
511
+ const pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
512
+ return JSON.parse(readFileSync(pkgPath, "utf8")).version ?? "0.0.0";
513
+ }
514
+ function parseMode(value) {
515
+ if (value === "link" || value === "skip") return value;
516
+ throw new InvalidArgumentError("Expected link|skip");
517
+ }
518
+ function parseKeep(value) {
519
+ if (value === "agents" || value === "claude") return value;
520
+ throw new InvalidArgumentError("Expected agents|claude");
521
+ }
522
+ function toOptions(flags, command) {
523
+ return {
524
+ cwd: resolve(flags.cwd ?? process.cwd()),
525
+ command,
526
+ yes: flags.yes,
527
+ md: flags.md,
528
+ keepMd: flags.keepMd,
529
+ skills: flags.skills,
530
+ keepSkills: flags.keepSkills
531
+ };
532
+ }
533
+ const program = new Command().name("agdocs").description("Bootstrap agent-oriented docs and link Claude ↔ Agents").version(readPackageVersion()).addHelpText("after", `
534
+ Flags (init & link):
535
+ -y, --yes Link without prompts; keep agents on conflict
536
+ --md <link|skip> Answer the MD link prompt
537
+ --keep-md <agents|claude>
538
+ Keep which content on MD conflict
539
+ -C, --cwd <dir> Target directory (default: cwd)
540
+
541
+ Flags (link only):
542
+ --skills <link|skip> Answer the skills link prompt
543
+ --keep-skills <agents|claude>
544
+ Keep which skills on conflict
545
+ `);
546
+ function addSharedOptions(cmd) {
547
+ return cmd.option("-C, --cwd <dir>", "target directory").option("-y, --yes", "link without prompts; keep agents on conflict").option("--md <mode>", "link|skip", parseMode).option("--keep-md <side>", "agents|claude", parseKeep);
548
+ }
549
+ addSharedOptions(program.command("init").description("Bootstrap docs; optionally link CLAUDE.md (not skills)")).action(async (flags) => {
550
+ try {
551
+ await runInit(toOptions(flags, "init"));
552
+ } catch (error) {
553
+ console.error(error instanceof Error ? error.message : error);
554
+ process.exitCode = 1;
555
+ }
556
+ });
557
+ addSharedOptions(program.command("link").description("Link CLAUDE.md and .claude/skills to Agents").option("--skills <mode>", "link|skip", parseMode).option("--keep-skills <side>", "agents|claude", parseKeep)).action(async (flags) => {
558
+ try {
559
+ await runLink(toOptions(flags, "link"));
560
+ } catch (error) {
561
+ console.error(error instanceof Error ? error.message : error);
562
+ process.exitCode = 1;
563
+ }
564
+ });
565
+ program.parse();
566
+ //#endregion
567
+ export {};
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "agdocs",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Initialize AGENTS.md and docs scaffolding for AI-assisted development",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/WBBB0730/agdocs.git"
10
+ },
11
+ "packageManager": "pnpm@10.30.2",
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsdown",
20
+ "dev": "tsdown --watch",
21
+ "test": "vitest",
22
+ "play": "tsx scripts/play.ts",
23
+ "play:clean": "tsx scripts/play-clean.ts",
24
+ "typecheck": "tsc --noEmit",
25
+ "release": "bumpp",
26
+ "prepublishOnly": "pnpm run build"
27
+ },
28
+ "dependencies": {
29
+ "@clack/prompts": "^1.7.0",
30
+ "commander": "^14.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.6.2",
34
+ "@typescript/native-preview": "7.0.0-dev.20260509.2",
35
+ "bumpp": "^11.1.0",
36
+ "tsdown": "^0.22.0",
37
+ "tsx": "^4.20.3",
38
+ "typescript": "^6.0.3",
39
+ "vitest": "^4.1.5"
40
+ },
41
+ "bin": {
42
+ "agdocs": "dist/cli.mjs"
43
+ }
44
+ }