@botdocs/cli 0.10.2 → 0.11.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.
@@ -4,6 +4,18 @@ export class ManifestError extends Error {
4
4
  this.name = 'ManifestError';
5
5
  }
6
6
  }
7
+ /**
8
+ * The placeholder description `botdocs init` writes into a fresh manifest.
9
+ * Duplicated here (kept in sync with the constant in `commands/init.ts`) so
10
+ * the validator can reject the exact string without commands/init.ts and
11
+ * lib/manifest.ts having a circular dependency (init imports validate
12
+ * indirectly through the publish flow; manifest is upstream of both).
13
+ *
14
+ * If you change this, change `INIT_PLACEHOLDER_DESCRIPTION` in
15
+ * `commands/init.ts` to match — a string-literal diff in either file
16
+ * silently breaks the "you left the placeholder" detection.
17
+ */
18
+ export const INIT_PLACEHOLDER_DESCRIPTION = 'TODO: describe your skill in one sentence.';
7
19
  const SEMVER = /^\d+\.\d+\.\d+(-[\w.-]+)?(\+[\w.-]+)?$/;
8
20
  function parseSkillRef(raw) {
9
21
  const cleaned = raw.startsWith('@') ? raw.slice(1) : raw;
@@ -38,8 +50,23 @@ export function parseManifest(input) {
38
50
  if (type !== 'SPEC' && !SEMVER.test(version)) {
39
51
  throw new ManifestError(`Version must be semver (e.g. 1.0.0), got: "${version}"`);
40
52
  }
41
- if (typeof data.title !== 'string' || typeof data.description !== 'string' || !data.title || !data.description) {
42
- throw new ManifestError('title and description are required');
53
+ // Split title and description validation into separate errors so the
54
+ // author sees exactly which field is missing. The old combined check
55
+ // ("title and description are required") was generic — a friend who'd
56
+ // filled in the title still saw the same line and had to guess.
57
+ if (typeof data.title !== 'string' || !data.title.trim()) {
58
+ throw new ManifestError('title is required in botdocs.json');
59
+ }
60
+ if (typeof data.description !== 'string' || !data.description.trim()) {
61
+ throw new ManifestError('description is required in botdocs.json');
62
+ }
63
+ // Reject the unchanged placeholder that `botdocs init` writes. Without
64
+ // this, an author who scaffolded but never edited the manifest would
65
+ // publish a skill called "TODO: describe your skill in one sentence." —
66
+ // a recurring source of registry noise. The message names the file so
67
+ // the fix is obvious.
68
+ if (data.description.trim() === INIT_PLACEHOLDER_DESCRIPTION) {
69
+ throw new ManifestError(`Description still says "${INIT_PLACEHOLDER_DESCRIPTION}" — edit botdocs.json before publishing.`);
43
70
  }
44
71
  const title = data.title;
45
72
  const description = data.description;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Pure Node-version check. Lives in its own module so the inline preflight at
3
+ * the top of `src/index.ts` can be unit-tested without having to spawn a
4
+ * subprocess with a doctored `process.versions.node`.
5
+ *
6
+ * Returns `{ ok: true }` when the version satisfies the floor; otherwise
7
+ * `{ ok: false, message }` where `message` is the exact stderr line we want
8
+ * the CLI to print. Keeping the wording in one place means tests pin the
9
+ * message and we can't accidentally diverge between runtime and docs.
10
+ *
11
+ * Safe to import before any Node-20-only globals are touched — this file
12
+ * uses only `Number.parseInt` and string ops that exist on every supported
13
+ * Node version, including the legacy runtimes we're trying to reject.
14
+ */
15
+ export interface NodePreflightResult {
16
+ ok: boolean;
17
+ message?: string;
18
+ }
19
+ export declare const MIN_NODE_MAJOR = 20;
20
+ export declare function checkNodeVersion(nodeVersion: string): NodePreflightResult;
@@ -0,0 +1,11 @@
1
+ export const MIN_NODE_MAJOR = 20;
2
+ export function checkNodeVersion(nodeVersion) {
3
+ const major = Number.parseInt(nodeVersion.split('.')[0] ?? '0', 10);
4
+ if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
5
+ return {
6
+ ok: false,
7
+ message: `BotDocs CLI requires Node.js ${MIN_NODE_MAJOR} or newer. You're on Node ${nodeVersion}. Install from https://nodejs.org`,
8
+ };
9
+ }
10
+ return { ok: true };
11
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Client-side mirror of the server's per-skill size and count caps. The server
3
+ * enforces these on every ingest + publish-from-source request — duplicating
4
+ * the constants here lets `validate` flag violations locally so authors don't
5
+ * blow through the publish flow only to get a 413 back from the API.
6
+ *
7
+ * MUST stay in sync with apps/web/src/lib/skill-caps.ts. A shared package
8
+ * would eliminate the duplication long-term; for now the constants are tiny
9
+ * enough that mirroring is the simplest path. If you change one, change the
10
+ * other.
11
+ */
12
+ export declare const PER_FILE_BYTE_CAP: number;
13
+ export declare const PER_SKILL_BYTE_CAP: number;
14
+ export declare const PER_SKILL_FILE_CAP = 25;
15
+ /** Pretty-print a byte count as KB with one decimal. Used in validate error
16
+ * messages so authors see "71.3 KB" instead of "73012 bytes". */
17
+ export declare function formatKB(bytes: number): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Client-side mirror of the server's per-skill size and count caps. The server
3
+ * enforces these on every ingest + publish-from-source request — duplicating
4
+ * the constants here lets `validate` flag violations locally so authors don't
5
+ * blow through the publish flow only to get a 413 back from the API.
6
+ *
7
+ * MUST stay in sync with apps/web/src/lib/skill-caps.ts. A shared package
8
+ * would eliminate the duplication long-term; for now the constants are tiny
9
+ * enough that mirroring is the simplest path. If you change one, change the
10
+ * other.
11
+ */
12
+ export const PER_FILE_BYTE_CAP = 64 * 1024;
13
+ export const PER_SKILL_BYTE_CAP = 512 * 1024;
14
+ export const PER_SKILL_FILE_CAP = 25;
15
+ /** Pretty-print a byte count as KB with one decimal. Used in validate error
16
+ * messages so authors see "71.3 KB" instead of "73012 bytes". */
17
+ export function formatKB(bytes) {
18
+ return `${(bytes / 1024).toFixed(1)} KB`;
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botdocs/cli",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "CLI for BotDocs — author, publish, install, and sync agent skills across Claude, Claude Code, Cursor, Codex, ChatGPT, Windsurf, Copilot, Gemini, Antigravity, and OpenCode.",
5
5
  "keywords": [
6
6
  "botdocs",
@@ -33,7 +33,7 @@
33
33
  "license": "MIT",
34
34
  "author": "BotDocs contributors",
35
35
  "bin": {
36
- "botdocs": "./dist/index.js"
36
+ "botdocs": "./bin/botdocs.cjs"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@eslint/js": "^10.0.1",
@@ -49,6 +49,7 @@
49
49
  "vitest": "^3.1.0"
50
50
  },
51
51
  "files": [
52
+ "bin",
52
53
  "dist",
53
54
  "templates",
54
55
  "README.md"