@botdocs/cli 0.9.0 → 0.9.1
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/dist/commands/ingest.js +86 -18
- package/dist/index.js +1 -1
- package/dist/lib/auto-detect.js +71 -27
- package/package.json +1 -1
package/dist/commands/ingest.js
CHANGED
|
@@ -280,12 +280,26 @@ export const DETECTORS = {
|
|
|
280
280
|
scanPaths: () => [],
|
|
281
281
|
},
|
|
282
282
|
codex: {
|
|
283
|
+
// Codex skills are nested SKILL.md directories, mirroring claude:
|
|
284
|
+
// ~/.codex/skills/<slug>/SKILL.md (developers.openai.com/codex/skills).
|
|
283
285
|
pathPrefix: 'codex/',
|
|
284
|
-
extensions: ['.md'],
|
|
285
|
-
nested:
|
|
286
|
-
slugFor: (abs) =>
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
extensions: ['/SKILL.md'],
|
|
287
|
+
nested: true,
|
|
288
|
+
slugFor: (abs, root) => {
|
|
289
|
+
const rel = path.relative(root, abs).split(path.sep).join('/');
|
|
290
|
+
if (!rel.endsWith('/SKILL.md'))
|
|
291
|
+
return null;
|
|
292
|
+
const parts = rel.split('/');
|
|
293
|
+
if (parts.length < 2)
|
|
294
|
+
return null;
|
|
295
|
+
return parts[parts.length - 2] ?? null;
|
|
296
|
+
},
|
|
297
|
+
canonicalFilename: (slug) => `codex/${slug}/SKILL.md`,
|
|
298
|
+
// Global only — Codex skills live under ~/.codex/skills/.
|
|
299
|
+
scanPaths: (homeDir) => [path.join(homeDir, '.codex', 'skills')],
|
|
300
|
+
includeAdjacent: true,
|
|
301
|
+
skillRoot: (abs) => path.dirname(abs),
|
|
302
|
+
canonicalAdjacentFilename: (slug, relPath) => `codex/${slug}/${relPath}`,
|
|
289
303
|
},
|
|
290
304
|
copilot: {
|
|
291
305
|
pathPrefix: 'copilot/instructions/',
|
|
@@ -297,36 +311,90 @@ export const DETECTORS = {
|
|
|
297
311
|
scanPaths: (_homeDir, projectRoot, isGitRepo) => isGitRepo ? [path.join(projectRoot, '.github', 'instructions')] : [],
|
|
298
312
|
},
|
|
299
313
|
windsurf: {
|
|
314
|
+
// Windsurf reads project rules from <proj>/.windsurf/rules/<slug>.md
|
|
315
|
+
// (docs.windsurf.com). Flat .md rule files, project-scoped (git repo).
|
|
300
316
|
pathPrefix: 'windsurf/rules/',
|
|
301
317
|
extensions: ['.md'],
|
|
302
318
|
nested: false,
|
|
303
319
|
slugFor: (abs) => path.basename(abs).replace(/\.md$/, '') || null,
|
|
304
320
|
canonicalFilename: (slug) => `windsurf/rules/${slug}.md`,
|
|
305
|
-
scanPaths: (_homeDir, projectRoot, isGitRepo) => isGitRepo ? [path.join(projectRoot, '.
|
|
321
|
+
scanPaths: (_homeDir, projectRoot, isGitRepo) => isGitRepo ? [path.join(projectRoot, '.windsurf', 'rules')] : [],
|
|
306
322
|
},
|
|
307
323
|
gemini: {
|
|
324
|
+
// Gemini CLI has NO per-skill file directory. It uses hierarchical
|
|
325
|
+
// GEMINI.md context files (~/.gemini/GEMINI.md global, ./GEMINI.md
|
|
326
|
+
// project) — there's nothing to auto-discover and nowhere to drop a
|
|
327
|
+
// per-skill file. The entry stays present so the ecosystem still exists
|
|
328
|
+
// for compile/variants, but discovery returns nothing (like chatgpt) and
|
|
329
|
+
// install routes it to `manual` (see detectDestination).
|
|
308
330
|
pathPrefix: 'gemini/instructions/',
|
|
309
331
|
extensions: ['.md'],
|
|
310
332
|
nested: false,
|
|
311
333
|
slugFor: (abs) => path.basename(abs).replace(/\.md$/, '') || null,
|
|
312
334
|
canonicalFilename: (slug) => `gemini/instructions/${slug}.md`,
|
|
313
|
-
|
|
335
|
+
// No canonical on-disk location — nothing to discover.
|
|
336
|
+
scanPaths: () => [],
|
|
314
337
|
},
|
|
315
338
|
antigravity: {
|
|
339
|
+
// Antigravity skills are nested SKILL.md directories, mirroring claude:
|
|
340
|
+
// ~/.gemini/antigravity/skills/<slug>/SKILL.md (global) and
|
|
341
|
+
// <proj>/.agent/skills/<slug>/SKILL.md (project)
|
|
342
|
+
// (antigravity.google/docs/skills + Google Codelabs). Keep the existing
|
|
343
|
+
// `antigravity/skills/` canonical prefix, now nested-with-SKILL.md.
|
|
316
344
|
pathPrefix: 'antigravity/skills/',
|
|
317
|
-
extensions: ['.md'],
|
|
318
|
-
nested:
|
|
319
|
-
slugFor: (abs) =>
|
|
320
|
-
|
|
321
|
-
|
|
345
|
+
extensions: ['/SKILL.md'],
|
|
346
|
+
nested: true,
|
|
347
|
+
slugFor: (abs, root) => {
|
|
348
|
+
const rel = path.relative(root, abs).split(path.sep).join('/');
|
|
349
|
+
if (!rel.endsWith('/SKILL.md'))
|
|
350
|
+
return null;
|
|
351
|
+
const parts = rel.split('/');
|
|
352
|
+
if (parts.length < 2)
|
|
353
|
+
return null;
|
|
354
|
+
return parts[parts.length - 2] ?? null;
|
|
355
|
+
},
|
|
356
|
+
canonicalFilename: (slug) => `antigravity/skills/${slug}/SKILL.md`,
|
|
357
|
+
// Global ~/.gemini/antigravity/skills always; project <proj>/.agent/skills
|
|
358
|
+
// only inside a git repo.
|
|
359
|
+
scanPaths: (homeDir, projectRoot, isGitRepo) => {
|
|
360
|
+
const paths = [path.join(homeDir, '.gemini', 'antigravity', 'skills')];
|
|
361
|
+
if (isGitRepo)
|
|
362
|
+
paths.push(path.join(projectRoot, '.agent', 'skills'));
|
|
363
|
+
return paths;
|
|
364
|
+
},
|
|
365
|
+
includeAdjacent: true,
|
|
366
|
+
skillRoot: (abs) => path.dirname(abs),
|
|
367
|
+
canonicalAdjacentFilename: (slug, relPath) => `antigravity/skills/${slug}/${relPath}`,
|
|
322
368
|
},
|
|
323
369
|
opencode: {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
370
|
+
// OpenCode skills are nested SKILL.md directories, mirroring claude:
|
|
371
|
+
// ~/.config/opencode/skills/<slug>/SKILL.md (global) and
|
|
372
|
+
// <proj>/.opencode/skills/<slug>/SKILL.md (project)
|
|
373
|
+
// (opencode.ai/docs/skills).
|
|
374
|
+
pathPrefix: 'opencode/',
|
|
375
|
+
extensions: ['/SKILL.md'],
|
|
376
|
+
nested: true,
|
|
377
|
+
slugFor: (abs, root) => {
|
|
378
|
+
const rel = path.relative(root, abs).split(path.sep).join('/');
|
|
379
|
+
if (!rel.endsWith('/SKILL.md'))
|
|
380
|
+
return null;
|
|
381
|
+
const parts = rel.split('/');
|
|
382
|
+
if (parts.length < 2)
|
|
383
|
+
return null;
|
|
384
|
+
return parts[parts.length - 2] ?? null;
|
|
385
|
+
},
|
|
386
|
+
canonicalFilename: (slug) => `opencode/${slug}/SKILL.md`,
|
|
387
|
+
// Global ~/.config/opencode/skills always; project <proj>/.opencode/skills
|
|
388
|
+
// only inside a git repo.
|
|
389
|
+
scanPaths: (homeDir, projectRoot, isGitRepo) => {
|
|
390
|
+
const paths = [path.join(homeDir, '.config', 'opencode', 'skills')];
|
|
391
|
+
if (isGitRepo)
|
|
392
|
+
paths.push(path.join(projectRoot, '.opencode', 'skills'));
|
|
393
|
+
return paths;
|
|
394
|
+
},
|
|
395
|
+
includeAdjacent: true,
|
|
396
|
+
skillRoot: (abs) => path.dirname(abs),
|
|
397
|
+
canonicalAdjacentFilename: (slug, relPath) => `opencode/${slug}/${relPath}`,
|
|
330
398
|
},
|
|
331
399
|
};
|
|
332
400
|
export const SUPPORTED_TOOLS = Object.keys(DETECTORS);
|
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ program
|
|
|
106
106
|
});
|
|
107
107
|
program
|
|
108
108
|
.command('install <ref>')
|
|
109
|
-
.description('Install a skill or bundle locally (skills go to ~/.claude/skills/, project files to .cursor/rules/, .
|
|
109
|
+
.description('Install a skill or bundle locally (skills go to ~/.claude/skills/, project files to .cursor/rules/, .github/instructions/, .windsurf/rules/, etc.)')
|
|
110
110
|
.option('--project <dir>', 'Override the project root used for project-local files')
|
|
111
111
|
.option('--flat', 'Skip the {scope} subdirectory in install paths (collision-prone, not recommended)')
|
|
112
112
|
.option('--clean', 'Wipe-and-reinstall instead of additive')
|
package/dist/lib/auto-detect.js
CHANGED
|
@@ -47,10 +47,24 @@ export function detectDestination(srcRelative, ctx) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
if (src.startsWith('codex/')) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
// Codex skills are nested SKILL.md directories at
|
|
51
|
+
// ~/.codex/skills/<slug>/SKILL.md (developers.openai.com/codex/skills).
|
|
52
|
+
// New canonical form: codex/<slug>/SKILL.md (+ adjacent files). Strip the
|
|
53
|
+
// `codex/<slug>/` prefix and keep the relpath under ~/.codex/skills/<slug>/.
|
|
54
|
+
//
|
|
55
|
+
// Backward-compat: BotDocs published before this fix stored flat
|
|
56
|
+
// `codex/<slug>.md`. We detect the flat form (no inner slash) and route it
|
|
57
|
+
// to the new nested destination ~/.codex/skills/<slug>/SKILL.md so already-
|
|
58
|
+
// published docs still install where Codex reads them.
|
|
59
|
+
const remainder = src.slice('codex/'.length);
|
|
60
|
+
const codexBase = path.join(ctx.homeDir, '.codex', 'skills');
|
|
61
|
+
if (!remainder.includes('/')) {
|
|
62
|
+
// Flat legacy form `codex/<slug>.md` → nested SKILL.md.
|
|
63
|
+
const slug = remainder.replace(/\.md$/, '');
|
|
64
|
+
return { kind: 'global', dest: path.join(codexBase, slug, 'SKILL.md') };
|
|
65
|
+
}
|
|
66
|
+
const finalName = remainder.replace(/^[^/]+\//, '');
|
|
67
|
+
return { kind: 'global', dest: path.join(codexBase, ctx.slug, finalName) };
|
|
54
68
|
}
|
|
55
69
|
if (src.startsWith('copilot/instructions/')) {
|
|
56
70
|
// GitHub Copilot custom instructions live in .github/instructions/
|
|
@@ -61,36 +75,66 @@ export function detectDestination(srcRelative, ctx) {
|
|
|
61
75
|
};
|
|
62
76
|
}
|
|
63
77
|
if (src.startsWith('windsurf/rules/')) {
|
|
64
|
-
// Windsurf
|
|
65
|
-
// (
|
|
78
|
+
// Windsurf reads project rules from <proj>/.windsurf/rules/<slug>.md
|
|
79
|
+
// (docs.windsurf.com). Flat .md rule, project-scoped. The canonical form
|
|
80
|
+
// is already flat, so basename() is the right leaf either way.
|
|
66
81
|
return {
|
|
67
82
|
kind: 'project',
|
|
68
|
-
dest: path.join(ctx.projectDir, '.
|
|
83
|
+
dest: path.join(ctx.projectDir, '.windsurf', 'rules', path.basename(src)),
|
|
69
84
|
};
|
|
70
85
|
}
|
|
71
|
-
if (src.startsWith('gemini/
|
|
72
|
-
// Gemini CLI
|
|
73
|
-
// (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
};
|
|
86
|
+
if (src.startsWith('gemini/')) {
|
|
87
|
+
// Gemini CLI has NO per-skill file directory — it uses hierarchical
|
|
88
|
+
// GEMINI.md context files (~/.gemini/GEMINI.md global, ./GEMINI.md
|
|
89
|
+
// project). There's no real path to write to, so route to `manual` (like
|
|
90
|
+
// chatgpt): install surfaces the content for the user to paste into their
|
|
91
|
+
// GEMINI.md or @import it, rather than fabricating ~/.gemini/instructions/.
|
|
92
|
+
return { kind: 'manual', dest: src };
|
|
78
93
|
}
|
|
79
94
|
if (src.startsWith('antigravity/skills/')) {
|
|
80
|
-
//
|
|
81
|
-
// (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
// Antigravity skills are nested SKILL.md directories
|
|
96
|
+
// (antigravity.google/docs/skills). Project: <proj>/.agent/skills/<slug>/…
|
|
97
|
+
// Global mirror: ~/.gemini/antigravity/skills/<slug>/… We prefer the
|
|
98
|
+
// project destination when a project dir applies (matching cursor/codex-
|
|
99
|
+
// commands which default to project).
|
|
100
|
+
//
|
|
101
|
+
// New canonical form: antigravity/skills/<slug>/SKILL.md (+ adjacent).
|
|
102
|
+
// Strip the `antigravity/skills/<slug>/` prefix, keep the relpath.
|
|
103
|
+
//
|
|
104
|
+
// Backward-compat: docs published before this fix stored flat
|
|
105
|
+
// `antigravity/skills/<slug>.md`. Detect the flat form and route it to the
|
|
106
|
+
// new nested destination so already-published docs still install.
|
|
107
|
+
const remainder = src.slice('antigravity/skills/'.length);
|
|
108
|
+
const agentBase = path.join(ctx.projectDir, '.agent', 'skills');
|
|
109
|
+
if (!remainder.includes('/')) {
|
|
110
|
+
const slug = remainder.replace(/\.md$/, '');
|
|
111
|
+
return { kind: 'project', dest: path.join(agentBase, slug, 'SKILL.md') };
|
|
112
|
+
}
|
|
113
|
+
const finalName = remainder.replace(/^[^/]+\//, '');
|
|
114
|
+
return { kind: 'project', dest: path.join(agentBase, ctx.slug, finalName) };
|
|
86
115
|
}
|
|
87
|
-
if (src.startsWith('opencode/
|
|
88
|
-
// OpenCode
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
116
|
+
if (src.startsWith('opencode/')) {
|
|
117
|
+
// OpenCode skills are nested SKILL.md directories (opencode.ai/docs/skills).
|
|
118
|
+
// Project: <proj>/.opencode/skills/<slug>/… Global mirror:
|
|
119
|
+
// ~/.config/opencode/skills/<slug>/… We prefer the project destination
|
|
120
|
+
// when a project dir applies (matching cursor/codex-commands).
|
|
121
|
+
//
|
|
122
|
+
// New canonical form: opencode/<slug>/SKILL.md (+ adjacent). Strip the
|
|
123
|
+
// `opencode/<slug>/` prefix, keep the relpath.
|
|
124
|
+
//
|
|
125
|
+
// Backward-compat: docs published before this fix stored flat
|
|
126
|
+
// `opencode/instructions/<slug>.md`. Detect that legacy prefix and route
|
|
127
|
+
// it to the new nested destination so already-published docs still install.
|
|
128
|
+
const opencodeBase = path.join(ctx.projectDir, '.opencode', 'skills');
|
|
129
|
+
if (src.startsWith('opencode/instructions/')) {
|
|
130
|
+
const slug = path.basename(src).replace(/\.md$/, '');
|
|
131
|
+
return { kind: 'project', dest: path.join(opencodeBase, slug, 'SKILL.md') };
|
|
132
|
+
}
|
|
133
|
+
const remainder = src.slice('opencode/'.length);
|
|
134
|
+
const finalName = remainder.includes('/')
|
|
135
|
+
? remainder.replace(/^[^/]+\//, '')
|
|
136
|
+
: remainder;
|
|
137
|
+
return { kind: 'project', dest: path.join(opencodeBase, ctx.slug, finalName) };
|
|
94
138
|
}
|
|
95
139
|
if (src.startsWith('chatgpt/')) {
|
|
96
140
|
return { kind: 'manual', dest: src };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botdocs/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|