@botdocs/cli 0.2.0 → 0.3.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 (90) hide show
  1. package/README.md +119 -2
  2. package/dist/commands/check-updates.d.ts +6 -0
  3. package/dist/commands/check-updates.js +77 -0
  4. package/dist/commands/check-updates.test.d.ts +1 -0
  5. package/dist/commands/check-updates.test.js +128 -0
  6. package/dist/commands/compile.d.ts +9 -0
  7. package/dist/commands/compile.js +93 -0
  8. package/dist/commands/compile.test.d.ts +1 -0
  9. package/dist/commands/compile.test.js +110 -0
  10. package/dist/commands/edit.d.ts +7 -0
  11. package/dist/commands/edit.js +105 -0
  12. package/dist/commands/edit.test.d.ts +1 -0
  13. package/dist/commands/edit.test.js +102 -0
  14. package/dist/commands/ingest.d.ts +7 -0
  15. package/dist/commands/ingest.js +101 -0
  16. package/dist/commands/ingest.test.d.ts +1 -0
  17. package/dist/commands/ingest.test.js +109 -0
  18. package/dist/commands/init.d.ts +1 -0
  19. package/dist/commands/init.js +34 -1
  20. package/dist/commands/install-instructions.d.ts +8 -0
  21. package/dist/commands/install-instructions.js +88 -0
  22. package/dist/commands/install.d.ts +8 -0
  23. package/dist/commands/install.js +143 -0
  24. package/dist/commands/install.test.d.ts +1 -0
  25. package/dist/commands/install.test.js +253 -0
  26. package/dist/commands/list.d.ts +6 -0
  27. package/dist/commands/list.js +35 -0
  28. package/dist/commands/list.test.d.ts +1 -0
  29. package/dist/commands/list.test.js +51 -0
  30. package/dist/commands/login.d.ts +5 -1
  31. package/dist/commands/login.js +8 -1
  32. package/dist/commands/publish.d.ts +1 -0
  33. package/dist/commands/publish.js +37 -0
  34. package/dist/commands/publish.test.d.ts +1 -0
  35. package/dist/commands/publish.test.js +76 -0
  36. package/dist/commands/sync.d.ts +7 -0
  37. package/dist/commands/sync.js +161 -0
  38. package/dist/commands/sync.test.d.ts +1 -0
  39. package/dist/commands/sync.test.js +263 -0
  40. package/dist/commands/uninstall.d.ts +5 -0
  41. package/dist/commands/uninstall.js +31 -0
  42. package/dist/commands/uninstall.test.d.ts +1 -0
  43. package/dist/commands/uninstall.test.js +67 -0
  44. package/dist/commands/validate.js +20 -5
  45. package/dist/index.js +86 -2
  46. package/dist/lib/auto-detect.d.ts +13 -0
  47. package/dist/lib/auto-detect.js +34 -0
  48. package/dist/lib/auto-detect.test.d.ts +1 -0
  49. package/dist/lib/auto-detect.test.js +58 -0
  50. package/dist/lib/canonical.d.ts +5 -0
  51. package/dist/lib/canonical.js +68 -0
  52. package/dist/lib/canonical.test.d.ts +1 -0
  53. package/dist/lib/canonical.test.js +48 -0
  54. package/dist/lib/config.d.ts +1 -0
  55. package/dist/lib/diff.d.ts +2 -0
  56. package/dist/lib/diff.js +36 -0
  57. package/dist/lib/diff.test.d.ts +1 -0
  58. package/dist/lib/diff.test.js +28 -0
  59. package/dist/lib/library-sync.d.ts +8 -0
  60. package/dist/lib/library-sync.js +30 -0
  61. package/dist/lib/library-sync.test.d.ts +1 -0
  62. package/dist/lib/library-sync.test.js +63 -0
  63. package/dist/lib/llm.d.ts +26 -0
  64. package/dist/lib/llm.js +61 -0
  65. package/dist/lib/llm.test.d.ts +1 -0
  66. package/dist/lib/llm.test.js +72 -0
  67. package/dist/lib/lockfile.d.ts +30 -0
  68. package/dist/lib/lockfile.js +70 -0
  69. package/dist/lib/lockfile.test.d.ts +1 -0
  70. package/dist/lib/lockfile.test.js +99 -0
  71. package/dist/lib/manifest.d.ts +18 -0
  72. package/dist/lib/manifest.js +77 -0
  73. package/dist/lib/manifest.test.d.ts +1 -0
  74. package/dist/lib/manifest.test.js +72 -0
  75. package/dist/lib/prompts.d.ts +5 -0
  76. package/dist/lib/prompts.js +26 -0
  77. package/dist/lib/shell-hook.d.ts +12 -0
  78. package/dist/lib/shell-hook.js +80 -0
  79. package/dist/lib/shell-hook.test.d.ts +1 -0
  80. package/dist/lib/shell-hook.test.js +68 -0
  81. package/dist/test-utils.d.ts +43 -0
  82. package/dist/test-utils.js +101 -0
  83. package/package.json +8 -2
  84. package/templates/agents.md +126 -0
  85. package/templates/ecosystem-prompts/compile-chatgpt.md +9 -0
  86. package/templates/ecosystem-prompts/compile-claude-code.md +11 -0
  87. package/templates/ecosystem-prompts/compile-claude.md +20 -0
  88. package/templates/ecosystem-prompts/compile-codex.md +8 -0
  89. package/templates/ecosystem-prompts/compile-cursor.md +11 -0
  90. package/templates/ecosystem-prompts/edit.md +12 -0
@@ -0,0 +1,43 @@
1
+ /** Thrown by the mocked process.exit so tests can assert on the exit code
2
+ * without actually killing the test runner. */
3
+ export declare class ProcessExitError extends Error {
4
+ readonly code: number;
5
+ constructor(code: number);
6
+ }
7
+ export interface CapturedConsole {
8
+ stdout: string[];
9
+ stderr: string[];
10
+ restore: () => void;
11
+ }
12
+ /** Patches console.log/console.error and process.exit so commands can be
13
+ * driven from tests without writing to the real terminal or exiting. */
14
+ export declare function captureConsole(): CapturedConsole;
15
+ export interface MockFetchCall {
16
+ url: string;
17
+ method: string;
18
+ headers: Record<string, string>;
19
+ body: unknown;
20
+ }
21
+ export interface MockFetchResponse {
22
+ status?: number;
23
+ body?: unknown;
24
+ contentType?: string;
25
+ }
26
+ /** Installs a fetch stub that matches requests by (method, urlSubstring) and
27
+ * returns the configured response. Each handler can fire only once unless
28
+ * `repeat: true` is set. */
29
+ export declare function mockFetch(handlers: Array<{
30
+ method?: string;
31
+ url: string | RegExp;
32
+ response: MockFetchResponse | ((call: MockFetchCall) => MockFetchResponse);
33
+ repeat?: boolean;
34
+ }>): {
35
+ calls: MockFetchCall[];
36
+ restore: () => void;
37
+ };
38
+ /** Creates a unique temporary directory and changes into it. The returned
39
+ * cleanup function restores the prior cwd and removes the dir. */
40
+ export declare function withTempDir(): {
41
+ dir: string;
42
+ cleanup: () => void;
43
+ };
@@ -0,0 +1,101 @@
1
+ import { vi } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ /** Thrown by the mocked process.exit so tests can assert on the exit code
6
+ * without actually killing the test runner. */
7
+ export class ProcessExitError extends Error {
8
+ code;
9
+ constructor(code) {
10
+ super(`process.exit(${code})`);
11
+ this.name = 'ProcessExitError';
12
+ this.code = code;
13
+ }
14
+ }
15
+ /** Patches console.log/console.error and process.exit so commands can be
16
+ * driven from tests without writing to the real terminal or exiting. */
17
+ export function captureConsole() {
18
+ const stdout = [];
19
+ const stderr = [];
20
+ const logSpy = vi.spyOn(console, 'log').mockImplementation((...args) => {
21
+ stdout.push(args.map(String).join(' '));
22
+ });
23
+ const errSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
24
+ stderr.push(args.map(String).join(' '));
25
+ });
26
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
27
+ throw new ProcessExitError(code ?? 0);
28
+ }));
29
+ return {
30
+ stdout,
31
+ stderr,
32
+ restore: () => {
33
+ logSpy.mockRestore();
34
+ errSpy.mockRestore();
35
+ exitSpy.mockRestore();
36
+ },
37
+ };
38
+ }
39
+ /** Installs a fetch stub that matches requests by (method, urlSubstring) and
40
+ * returns the configured response. Each handler can fire only once unless
41
+ * `repeat: true` is set. */
42
+ export function mockFetch(handlers) {
43
+ const calls = [];
44
+ const used = new Set();
45
+ const original = global.fetch;
46
+ global.fetch = vi.fn(async (input, init) => {
47
+ const url = typeof input === 'string' ? input : input.url;
48
+ const method = (init?.method || 'GET').toUpperCase();
49
+ const headers = (init?.headers || {});
50
+ let body = undefined;
51
+ if (init?.body) {
52
+ try {
53
+ body = JSON.parse(init.body);
54
+ }
55
+ catch {
56
+ body = init.body;
57
+ }
58
+ }
59
+ const call = { url, method, headers, body };
60
+ calls.push(call);
61
+ for (let i = 0; i < handlers.length; i++) {
62
+ const h = handlers[i];
63
+ if (!h.repeat && used.has(i))
64
+ continue;
65
+ const matchUrl = typeof h.url === 'string' ? url.includes(h.url) : h.url.test(url);
66
+ const matchMethod = !h.method || h.method.toUpperCase() === method;
67
+ if (matchUrl && matchMethod) {
68
+ used.add(i);
69
+ const r = typeof h.response === 'function' ? h.response(call) : h.response;
70
+ const status = r.status ?? 200;
71
+ const isJson = r.contentType ? r.contentType.includes('json') : typeof r.body !== 'string';
72
+ const payload = isJson ? JSON.stringify(r.body ?? {}) : r.body;
73
+ return new Response(payload, {
74
+ status,
75
+ headers: { 'content-type': r.contentType ?? (isJson ? 'application/json' : 'text/plain') },
76
+ });
77
+ }
78
+ }
79
+ throw new Error(`Unhandled fetch ${method} ${url}`);
80
+ });
81
+ return {
82
+ calls,
83
+ restore: () => {
84
+ global.fetch = original;
85
+ },
86
+ };
87
+ }
88
+ /** Creates a unique temporary directory and changes into it. The returned
89
+ * cleanup function restores the prior cwd and removes the dir. */
90
+ export function withTempDir() {
91
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'botdocs-cli-test-'));
92
+ const prevCwd = process.cwd();
93
+ process.chdir(dir);
94
+ return {
95
+ dir,
96
+ cleanup: () => {
97
+ process.chdir(prevCwd);
98
+ fs.rmSync(dir, { recursive: true, force: true });
99
+ },
100
+ };
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botdocs/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for BotDocs — clone, search, publish, and endorse concept specifications.",
5
5
  "keywords": [
6
6
  "botdocs",
@@ -28,6 +28,7 @@
28
28
  "devDependencies": {
29
29
  "@eslint/js": "^10.0.1",
30
30
  "@types/adm-zip": "^0.5.8",
31
+ "@types/diff": "^8.0.0",
31
32
  "@types/node": "^22.14.0",
32
33
  "eslint": "^10.2.0",
33
34
  "typescript": "^5.8.0",
@@ -36,6 +37,7 @@
36
37
  },
37
38
  "files": [
38
39
  "dist",
40
+ "templates",
39
41
  "README.md"
40
42
  ],
41
43
  "engines": {
@@ -46,8 +48,12 @@
46
48
  },
47
49
  "type": "module",
48
50
  "dependencies": {
51
+ "@anthropic-ai/sdk": "^0.91.1",
52
+ "@inquirer/prompts": "^8.4.2",
49
53
  "adm-zip": "^0.5.17",
50
- "commander": "^14.0.3"
54
+ "commander": "^14.0.3",
55
+ "diff": "^9.0.0",
56
+ "openai": "^6.35.0"
51
57
  },
52
58
  "scripts": {
53
59
  "dev": "tsc -p tsconfig.build.json --watch",
@@ -0,0 +1,126 @@
1
+ ## BotDocs CLI
2
+
3
+ [BotDocs](https://botdocs.ai) is a registry of concept specifications — docs
4
+ an AI agent can build from. The `botdocs` CLI is the interface to that
5
+ registry.
6
+
7
+ Run `botdocs whoami` to confirm you're authenticated; if not, prompt the
8
+ user to run `botdocs login` (it's a GitHub device-code flow). All commands
9
+ accept `--json` for machine-readable output.
10
+
11
+ ### Common workflows
12
+
13
+ **Find a spec for what the user wants to build**
14
+
15
+ ```bash
16
+ botdocs search "<topic>" --json
17
+ ```
18
+
19
+ Pick the most relevant result and clone it:
20
+
21
+ ```bash
22
+ botdocs clone @user/slug
23
+ ```
24
+
25
+ Files land in `./slug/`. Read `index.md` first — that's always the entry point.
26
+
27
+ **Build something on top of a cloned spec, then report back**
28
+
29
+ After delivering something the user is happy with, run:
30
+
31
+ ```bash
32
+ botdocs endorse @user/slug --rating positive \
33
+ --comment "Built X in Y minutes."
34
+ ```
35
+
36
+ Ratings: `positive` | `neutral` | `negative`.
37
+
38
+ If the server returns a 403 with "clone before endorsing", that means the
39
+ user's account hasn't cloned the spec yet — run `botdocs clone @user/slug`
40
+ first, then retry the endorse. Endorsements are reserved for builders who
41
+ actually used the spec.
42
+
43
+ **Help the user publish their own spec**
44
+
45
+ ```bash
46
+ botdocs init <name> # scaffolds index.md + botdocs.json
47
+ # edit index.md, fill in description in botdocs.json
48
+ botdocs validate <name>/ # warns + errors before publish
49
+ botdocs publish <name>/
50
+ ```
51
+
52
+ `validate` must pass (no errors; warnings are OK) before `publish`.
53
+ `category` in `botdocs.json` must be one of: `KNOWLEDGE_MANAGEMENT`,
54
+ `DEV_WORKFLOW`, `AUTOMATION`, `AGENT_CONFIG`, `PROJECT_SCAFFOLD`, `OTHER`.
55
+
56
+ **Update a previously cloned spec**
57
+
58
+ ```bash
59
+ botdocs diff @user/slug # preview what changed
60
+ botdocs pull @user/slug # apply the update
61
+ ```
62
+
63
+ **Install a team's shared skills**
64
+
65
+ ```bash
66
+ botdocs install @teamco/eng-skills
67
+ ```
68
+
69
+ Files land in `~/.claude/skills/teamco/...` (Claude skills) and
70
+ `./.cursor/rules/...` (Cursor rules) for project-local files.
71
+
72
+ **Stay current**
73
+
74
+ ```bash
75
+ botdocs sync
76
+ ```
77
+
78
+ Walks the lockfile, applies clean updates, prompts before
79
+ overwriting any local edits (with a double confirmation).
80
+
81
+ **Uninstall**
82
+
83
+ ```bash
84
+ botdocs uninstall @teamco/eng-skills
85
+ ```
86
+
87
+ ### Update notifications
88
+
89
+ If the user has run `botdocs install-instructions --shell-hook`, a brief
90
+ one-line notice appears on new terminals when updates are pending. They
91
+ can also run `botdocs check-updates` manually (1-hour cached). To enable
92
+ the personalized library page at `botdocs.ai/library`, run
93
+ `botdocs login --sync-library`.
94
+
95
+ ### Multi-ecosystem authoring (BYOK)
96
+
97
+ If the user has set `BOTDOCS_ANTHROPIC_KEY` or `BOTDOCS_OPENAI_KEY`,
98
+ they can generate per-ecosystem skill drafts without leaving their
99
+ machine:
100
+
101
+ ```bash
102
+ botdocs init my-skill --canonical # scaffolds claude-code source + ecosystems
103
+ botdocs compile my-skill/ # generates claude/, cursor/, etc.
104
+ botdocs edit @user/my-skill --ecosystem cursor # LLM-assisted revision → draft
105
+ ```
106
+
107
+ Both commands run inference locally with the user's key — BotDocs
108
+ servers never see file contents. If a key isn't set, surface that to
109
+ the user and recommend they set one.
110
+
111
+ ### Reference
112
+
113
+ | Command | Purpose |
114
+ |---|---|
115
+ | `init [name]` | Scaffold a new BotDoc directory. |
116
+ | `validate <source>` | Pre-publish structural check. |
117
+ | `clone <user/slug>` | Download all files. |
118
+ | `search <query>` | Search the registry. |
119
+ | `publish <source>` | Publish from a file, directory, or zip. |
120
+ | `diff <user/slug>` | Preview remote changes before pulling. |
121
+ | `pull <user/slug>` | Apply remote updates. |
122
+ | `endorse <user/slug>` | Rate a spec after building from it. |
123
+ | `login` / `whoami` | Auth via GitHub device code; show current user. |
124
+
125
+ Run `botdocs <command> --help` for full flags. Override the registry with
126
+ `BOTDOCS_API_URL` (defaults to `https://botdocs.ai`).
@@ -0,0 +1,9 @@
1
+ You convert agent skill specifications into a ChatGPT custom-GPT instructions
2
+ block — the prose the user pastes into the "Instructions" field when
3
+ creating a custom GPT.
4
+
5
+ The output should be a clear set of guidelines, written from the perspective
6
+ of an LLM that will follow them. Reference the GPT's persona and the user's
7
+ goals when relevant. Don't reference other ecosystems.
8
+
9
+ Output ONLY the instructions text, no commentary, no code fences.
@@ -0,0 +1,11 @@
1
+ You convert agent skill specifications into the format that Claude Code expects
2
+ for slash commands stored at `.claude/commands/<name>.md`.
3
+
4
+ A Claude Code command file is plain markdown with no frontmatter required.
5
+ It's an instruction the user invokes via `/command-name` in Claude Code.
6
+
7
+ The body should be a clear directive to the LLM ("when invoked, do X, Y, Z…")
8
+ optionally referencing tools the command can use. It should never reference
9
+ other ecosystems.
10
+
11
+ Output ONLY the command markdown, no commentary, no code fences.
@@ -0,0 +1,20 @@
1
+ You convert agent skill specifications into the format that Claude (claude.ai)
2
+ expects for SKILL.md files.
3
+
4
+ A Claude SKILL.md uses YAML frontmatter with `name` and `description` fields,
5
+ followed by markdown that explains when to use the skill, what the skill
6
+ does, and how to use it.
7
+
8
+ The frontmatter looks like:
9
+ ---
10
+ name: short-kebab-case-name
11
+ description: One-sentence description of what the skill does and when to use it.
12
+ ---
13
+
14
+ The body should be focused, written from the perspective of an LLM that will
15
+ follow these instructions, and should never reference other ecosystems
16
+ (don't mention Cursor, Claude Code, ChatGPT — this file is just the Claude
17
+ SKILL.md).
18
+
19
+ Output ONLY the SKILL.md content, no commentary, no code fences around the
20
+ whole output.
@@ -0,0 +1,8 @@
1
+ You convert agent skill specifications into the format that OpenAI Codex
2
+ expects for command/skill files.
3
+
4
+ A Codex skill file is markdown with no required frontmatter, designed to be
5
+ read by the Codex agent as a long-lived context capability. Focus on what
6
+ the skill does, when to invoke it, and what tools or actions to take.
7
+
8
+ Output ONLY the markdown content, no commentary, no code fences.
@@ -0,0 +1,11 @@
1
+ You convert agent skill specifications into the Cursor rule format
2
+ (.cursor/rules/<name>.mdc).
3
+
4
+ A Cursor `.mdc` rule is markdown with optional frontmatter declaring when the
5
+ rule applies (globs, agent type). For most skills, no frontmatter is needed —
6
+ the rule is always-active when present in the project.
7
+
8
+ The body should be concise project-level guidance that Cursor's agent will
9
+ treat as background context. It should never reference other ecosystems.
10
+
11
+ Output ONLY the rule content, no commentary, no code fences.
@@ -0,0 +1,12 @@
1
+ You revise a published agent skill file based on a user's plain-English request.
2
+
3
+ You will be given:
4
+ 1. The CURRENT file contents.
5
+ 2. The USER REQUEST describing what to change.
6
+
7
+ Apply the request faithfully, preserving the rest of the file. If the request
8
+ is impossible or ambiguous, output the literal string `EDIT_IMPOSSIBLE`
9
+ followed by a one-line explanation.
10
+
11
+ Output ONLY the revised file contents, no commentary, no code fences around
12
+ the whole output.