@ner076/prd-toolkit 0.1.4

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Claude Code adapter.
3
+ *
4
+ * Installs skills to:
5
+ * - user scope: ~/.claude/skills/<skill-name>/SKILL.md
6
+ * - project scope: ./.claude/skills/<skill-name>/SKILL.md
7
+ *
8
+ * Claude Code expects a SKILL.md file with YAML frontmatter containing
9
+ * at minimum `name` and `description`. Our source SKILL.md files already
10
+ * conform, so this adapter mostly copies content and writes a manifest.
11
+ */
12
+
13
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { join } from 'node:path';
16
+ import { getPackageVersion } from '../version.js';
17
+
18
+ function resolveBasePath(scope) {
19
+ if (scope === 'user') return join(homedir(), '.claude', 'skills');
20
+ return join(process.cwd(), '.claude', 'skills');
21
+ }
22
+
23
+ function validateFrontmatter(content, skillName) {
24
+ // Normalize line endings: CRLF โ†’ LF (Windows git checkout may produce CRLF)
25
+ const normalized = content.replace(/\r\n/g, '\n');
26
+ const match = normalized.match(/^---\n([\s\S]*?)\n---/);
27
+ if (!match) {
28
+ throw new Error(`Skill "${skillName}" is missing YAML frontmatter โ€” required by Claude Code`);
29
+ }
30
+ const body = match[1];
31
+ if (!/^name:\s*\S+/m.test(body)) throw new Error(`Skill "${skillName}" frontmatter missing 'name'`);
32
+ if (!/^description:\s*\S+/m.test(body)) throw new Error(`Skill "${skillName}" frontmatter missing 'description'`);
33
+ }
34
+
35
+ export async function installClaudeCode({ skills, opts }) {
36
+ const basePath = resolveBasePath(opts.scope);
37
+ const manifest = {
38
+ tool: '@ner076/prd-toolkit',
39
+ version: await getPackageVersion(),
40
+ target: 'claude-code',
41
+ scope: opts.scope,
42
+ installedAt: new Date().toISOString(),
43
+ skills: []
44
+ };
45
+
46
+ if (!opts.dryRun) mkdirSync(basePath, { recursive: true });
47
+
48
+ for (const skill of skills) {
49
+ validateFrontmatter(skill.content, skill.name);
50
+ const targetDir = join(basePath, skill.name);
51
+ const targetFile = join(targetDir, 'SKILL.md');
52
+
53
+ if (!opts.dryRun) {
54
+ if (existsSync(targetFile) && !opts.force) {
55
+ const existing = readFileSync(targetFile, 'utf8');
56
+ if (existing === skill.content) {
57
+ manifest.skills.push({ name: skill.name, path: targetFile, status: 'unchanged' });
58
+ continue;
59
+ }
60
+ // Overwrite policy: for non-force, keep existing; log warning
61
+ console.warn(` ! ${skill.name} exists at ${targetFile} โ€” skipping (use --force to overwrite)`);
62
+ manifest.skills.push({ name: skill.name, path: targetFile, status: 'skipped' });
63
+ continue;
64
+ }
65
+ mkdirSync(targetDir, { recursive: true });
66
+ writeFileSync(targetFile, skill.content, 'utf8');
67
+ }
68
+ manifest.skills.push({ name: skill.name, path: targetFile, status: 'installed' });
69
+ }
70
+
71
+ if (!opts.dryRun) {
72
+ writeFileSync(
73
+ join(basePath, '.prd-toolkit.manifest.json'),
74
+ JSON.stringify(manifest, null, 2),
75
+ 'utf8'
76
+ );
77
+ }
78
+
79
+ return { installPath: basePath, manifest };
80
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Opencode adapter.
3
+ *
4
+ * Installs skills as custom commands to:
5
+ * - user scope: ~/.config/opencode/command/<name>.md
6
+ * - project scope: ./.opencode/command/<name>.md
7
+ *
8
+ * Opencode custom commands use YAML frontmatter with a `description` field
9
+ * (and optionally `agent`, `model`). We transform our source SKILL.md by:
10
+ * 1. Extracting the source frontmatter
11
+ * 2. Emitting Opencode-flavored frontmatter
12
+ * 3. Preserving the entire body verbatim
13
+ *
14
+ * The result: user can invoke via `/prd-discovery` or `/prd-generator` inside Opencode.
15
+ */
16
+
17
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ import { getPackageVersion } from '../version.js';
21
+
22
+ function resolveBasePath(scope) {
23
+ if (scope === 'user') return join(homedir(), '.config', 'opencode', 'command');
24
+ return join(process.cwd(), '.opencode', 'command');
25
+ }
26
+
27
+ function parseSourceFrontmatter(content) {
28
+ // Normalize line endings: CRLF โ†’ LF (Windows git checkout may produce CRLF)
29
+ const normalized = content.replace(/\r\n/g, '\n');
30
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
31
+ if (!match) throw new Error('Source SKILL.md is missing frontmatter');
32
+ const meta = {};
33
+ match[1].split('\n').forEach((line) => {
34
+ const kv = line.match(/^(\w+):\s*(.+)$/);
35
+ if (kv) meta[kv[1]] = kv[2].trim();
36
+ });
37
+ return { meta, body: match[2] };
38
+ }
39
+
40
+ function toOpencodeFrontmatter(meta) {
41
+ // Opencode commands accept: description, agent, model, subtask
42
+ const lines = ['---'];
43
+ if (meta.description) lines.push(`description: ${meta.description}`);
44
+ lines.push(`agent: general`); // reasonable default
45
+ lines.push(`subtask: true`); // multi-turn workflows run best as subtask
46
+ lines.push('---');
47
+ return lines.join('\n');
48
+ }
49
+
50
+ export async function installOpencode({ skills, opts }) {
51
+ const basePath = resolveBasePath(opts.scope);
52
+ const manifest = {
53
+ tool: '@ner076/prd-toolkit',
54
+ version: await getPackageVersion(),
55
+ target: 'opencode',
56
+ scope: opts.scope,
57
+ installedAt: new Date().toISOString(),
58
+ skills: []
59
+ };
60
+
61
+ if (!opts.dryRun) mkdirSync(basePath, { recursive: true });
62
+
63
+ for (const skill of skills) {
64
+ const { meta, body } = parseSourceFrontmatter(skill.content);
65
+ const transformed = `${toOpencodeFrontmatter(meta)}\n\n${body}`;
66
+ const targetFile = join(basePath, `${skill.name}.md`);
67
+
68
+ if (!opts.dryRun) {
69
+ if (existsSync(targetFile) && !opts.force) {
70
+ const existing = readFileSync(targetFile, 'utf8');
71
+ if (existing === transformed) {
72
+ manifest.skills.push({ name: skill.name, path: targetFile, status: 'unchanged' });
73
+ continue;
74
+ }
75
+ console.warn(` ! ${skill.name} exists at ${targetFile} โ€” skipping (use --force to overwrite)`);
76
+ manifest.skills.push({ name: skill.name, path: targetFile, status: 'skipped' });
77
+ continue;
78
+ }
79
+ writeFileSync(targetFile, transformed, 'utf8');
80
+ }
81
+ manifest.skills.push({ name: skill.name, path: targetFile, status: 'installed' });
82
+ }
83
+
84
+ if (!opts.dryRun) {
85
+ writeFileSync(
86
+ join(basePath, '.prd-toolkit.manifest.json'),
87
+ JSON.stringify(manifest, null, 2),
88
+ 'utf8'
89
+ );
90
+ }
91
+
92
+ return { installPath: basePath, manifest };
93
+ }
package/src/detect.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Auto-detect which target environment (Claude Code / Opencode) is available.
3
+ *
4
+ * Detection signals (any = present):
5
+ * Claude Code:
6
+ * - ~/.claude/ exists
7
+ * - `claude` executable in PATH
8
+ * - CLAUDE_CONFIG env var set
9
+ * Opencode:
10
+ * - ~/.config/opencode/ exists
11
+ * - `opencode` executable in PATH
12
+ * - .opencode/ in CWD
13
+ */
14
+
15
+ import { existsSync } from 'node:fs';
16
+ import { homedir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { execSync } from 'node:child_process';
19
+
20
+ function hasBinary(name) {
21
+ try {
22
+ execSync(process.platform === 'win32' ? `where ${name}` : `which ${name}`, {
23
+ stdio: 'ignore'
24
+ });
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ export function detectClaudeCode() {
32
+ const signals = {
33
+ homeDir: existsSync(join(homedir(), '.claude')),
34
+ binary: hasBinary('claude'),
35
+ envVar: !!process.env.CLAUDE_CONFIG
36
+ };
37
+ return {
38
+ detected: Object.values(signals).some(Boolean),
39
+ signals
40
+ };
41
+ }
42
+
43
+ export function detectOpencode() {
44
+ const signals = {
45
+ configDir: existsSync(join(homedir(), '.config', 'opencode')),
46
+ binary: hasBinary('opencode'),
47
+ projectDir: existsSync(join(process.cwd(), '.opencode'))
48
+ };
49
+ return {
50
+ detected: Object.values(signals).some(Boolean),
51
+ signals
52
+ };
53
+ }
54
+
55
+ export function detectAll() {
56
+ return {
57
+ 'claude-code': detectClaudeCode(),
58
+ opencode: detectOpencode()
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Resolve target arg into concrete list of adapters to run.
64
+ * - 'auto' โ†’ whichever is detected (both if both)
65
+ * - explicit target โ†’ just that one
66
+ * - 'all' โ†’ both regardless
67
+ */
68
+ export function resolveTargets(targetArg) {
69
+ if (targetArg === 'all') return ['claude-code', 'opencode'];
70
+ if (targetArg === 'claude-code' || targetArg === 'opencode') return [targetArg];
71
+
72
+ // auto
73
+ const detected = detectAll();
74
+ const active = Object.entries(detected)
75
+ .filter(([, v]) => v.detected)
76
+ .map(([k]) => k);
77
+
78
+ if (active.length === 0) {
79
+ throw new Error(
80
+ 'No supported environment detected.\n' +
81
+ 'Install Claude Code (https://claude.ai/code) or Opencode (https://opencode.ai),\n' +
82
+ 'or pass --target=claude-code|opencode|all explicitly.'
83
+ );
84
+ }
85
+ return active;
86
+ }
package/src/doctor.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Doctor: diagnose environment + install health.
3
+ */
4
+
5
+ import { detectAll } from './detect.js';
6
+ import { getPackageVersion } from './version.js';
7
+
8
+ export async function doctor() {
9
+ const version = await getPackageVersion();
10
+ const detected = detectAll();
11
+
12
+ console.log('\n๐Ÿฉบ @ner076/prd-toolkit โ€” doctor\n');
13
+ console.log(` Toolkit version: ${version}`);
14
+ console.log(` Node version: ${process.version}`);
15
+ console.log(` Platform: ${process.platform}\n`);
16
+
17
+ for (const [target, res] of Object.entries(detected)) {
18
+ const status = res.detected ? 'โœ“ detected' : 'โœ— not detected';
19
+ console.log(` ${target}: ${status}`);
20
+ for (const [sig, val] of Object.entries(res.signals)) {
21
+ console.log(` - ${sig}: ${val}`);
22
+ }
23
+ console.log('');
24
+ }
25
+
26
+ const anyDetected = Object.values(detected).some((v) => v.detected);
27
+ if (!anyDetected) {
28
+ console.log(' โš  No target environment detected.');
29
+ console.log(' Install Claude Code (https://claude.ai/code) or Opencode (https://opencode.ai) first.\n');
30
+ } else {
31
+ console.log(' Ready to install. Run: npx @ner076/prd-toolkit install\n');
32
+ }
33
+ }
package/src/help.js ADDED
@@ -0,0 +1,41 @@
1
+ import { getPackageVersion } from './version.js';
2
+
3
+ export function printHelp() {
4
+ console.log(`
5
+ ๐Ÿ“ฆ @ner076/prd-toolkit โ€” AI-powered PRD toolkit
6
+ Guided discovery + auto-dev ready PRD generation for Claude Code & Opencode
7
+
8
+ Usage:
9
+ npx @ner076/prd-toolkit <command> [flags]
10
+
11
+ Commands:
12
+ install Install skills to target environment(s)
13
+ uninstall Remove installed skills (tracked via manifest)
14
+ update Re-install to pick up latest version (implies --force)
15
+ list Show installed skills across all targets
16
+ doctor Diagnose environment & install health
17
+ help Show this help
18
+
19
+ Flags:
20
+ --target=<t> claude-code | opencode | all | auto (default: auto)
21
+ --scope=<s> user | project (default: project)
22
+ -y, --yes Skip confirmation prompts
23
+ -f, --force Overwrite existing files
24
+ --dryRun Show what would happen without writing files
25
+ -v, --version Print version
26
+ -h, --help Print this help
27
+
28
+ Examples:
29
+ npx @ner076/prd-toolkit install # auto-detect, project scope
30
+ npx @ner076/prd-toolkit install --target=all --scope=user
31
+ npx @ner076/prd-toolkit install --target=opencode -y
32
+ npx @ner076/prd-toolkit uninstall --target=claude-code
33
+ npx @ner076/prd-toolkit doctor
34
+
35
+ Learn more: https://github.com/ner076/prd-toolkit
36
+ `);
37
+ }
38
+
39
+ export async function printVersion() {
40
+ console.log(await getPackageVersion());
41
+ }
package/src/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @ner076/prd-toolkit โ€” programmatic API
3
+ *
4
+ * Use this when you want to script the toolkit instead of the CLI:
5
+ *
6
+ * import { install, doctor, detectAll } from '@ner076/prd-toolkit';
7
+ * await doctor();
8
+ * await install({ target: 'claude-code', scope: 'project', yes: true });
9
+ */
10
+
11
+ // โ”€โ”€ Install / uninstall / list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
12
+
13
+ export { install } from './install.js';
14
+ export { uninstall } from './uninstall.js';
15
+ export { list } from './list.js';
16
+
17
+ // โ”€โ”€ Doctor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18
+
19
+ export { doctor } from './doctor.js';
20
+
21
+ // โ”€โ”€ Detection helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
22
+
23
+ export { detectClaudeCode, detectOpencode, detectAll, resolveTargets } from './detect.js';
24
+
25
+ // โ”€โ”€ Display helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
26
+
27
+ export { printHelp, printVersion } from './help.js';
28
+
29
+ // โ”€โ”€ Version โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
30
+
31
+ export { getPackageVersion } from './version.js';
package/src/install.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Install orchestrator.
3
+ * Resolves targets, calls appropriate adapters, writes manifests.
4
+ */
5
+
6
+ import { readdirSync, readFileSync } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { dirname, join } from 'node:path';
9
+ import { resolveTargets } from './detect.js';
10
+ import { installClaudeCode } from './adapters/claude-code.js';
11
+ import { installOpencode } from './adapters/opencode.js';
12
+ import { confirm } from '@inquirer/prompts';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ const SKILLS_ROOT = join(__dirname, '..', 'skills');
16
+
17
+ const ADAPTERS = {
18
+ 'claude-code': installClaudeCode,
19
+ 'opencode': installOpencode
20
+ };
21
+
22
+ function loadSkills() {
23
+ const names = readdirSync(SKILLS_ROOT, { withFileTypes: true })
24
+ .filter((d) => d.isDirectory())
25
+ .map((d) => d.name);
26
+
27
+ return names.map((name) => ({
28
+ name,
29
+ sourcePath: join(SKILLS_ROOT, name, 'SKILL.md'),
30
+ content: readFileSync(join(SKILLS_ROOT, name, 'SKILL.md'), 'utf8')
31
+ }));
32
+ }
33
+
34
+ export async function install(opts) {
35
+ const targets = resolveTargets(opts.target);
36
+ const skills = loadSkills();
37
+
38
+ console.log(`\n๐Ÿ“ฆ @ner076/prd-toolkit โ€” install`);
39
+ console.log(` Targets: ${targets.join(', ')}`);
40
+ console.log(` Scope: ${opts.scope}`);
41
+ console.log(` Skills: ${skills.map((s) => s.name).join(', ')}\n`);
42
+
43
+ if (opts.dryRun) {
44
+ console.log(' (dry run โ€” no files will be written)\n');
45
+ }
46
+
47
+ if (!opts.yes && !opts.dryRun) {
48
+ const ok = await confirm({
49
+ message: 'Proceed with install?',
50
+ default: true
51
+ });
52
+ if (!ok) {
53
+ console.log('Cancelled.');
54
+ return;
55
+ }
56
+ }
57
+
58
+ const results = [];
59
+ for (const target of targets) {
60
+ const adapter = ADAPTERS[target];
61
+ if (!adapter) {
62
+ console.warn(` โš  No adapter for target: ${target}`);
63
+ continue;
64
+ }
65
+ try {
66
+ const result = await adapter({ skills, opts });
67
+ results.push({ target, ok: true, ...result });
68
+ console.log(` โœ“ ${target} โ†’ ${result.installPath}`);
69
+ } catch (err) {
70
+ results.push({ target, ok: false, error: err.message });
71
+ console.error(` โœ— ${target} โ†’ ${err.message}`);
72
+ }
73
+ }
74
+
75
+ console.log('\nDone.\n');
76
+ const successCount = results.filter((r) => r.ok).length;
77
+ if (successCount === 0) throw new Error('All installs failed.');
78
+
79
+ console.log('Try it out:');
80
+ console.log(' โ€ข In Claude Code: run `claude` in your project, then ask "run prd-discovery"');
81
+ console.log(' โ€ข In Opencode: run `opencode`, then use `/prd-discovery` command\n');
82
+ }
package/src/list.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * List installed skills across all known targets & scopes.
3
+ */
4
+
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+
9
+ const LOCATIONS = [
10
+ { target: 'claude-code', scope: 'user', path: join(homedir(), '.claude', 'skills') },
11
+ { target: 'claude-code', scope: 'project', path: join(process.cwd(), '.claude', 'skills') },
12
+ { target: 'opencode', scope: 'user', path: join(homedir(), '.config', 'opencode', 'command') },
13
+ { target: 'opencode', scope: 'project', path: join(process.cwd(), '.opencode', 'command') }
14
+ ];
15
+
16
+ export async function list() {
17
+ console.log('\n๐Ÿ“ฆ @ner076/prd-toolkit โ€” installed skills\n');
18
+ let found = 0;
19
+ for (const loc of LOCATIONS) {
20
+ const manifestPath = join(loc.path, '.prd-toolkit.manifest.json');
21
+ if (!existsSync(manifestPath)) continue;
22
+ const m = JSON.parse(readFileSync(manifestPath, 'utf8'));
23
+ console.log(` ${loc.target} ยท ${loc.scope} ยท v${m.version} ยท ${new Date(m.installedAt).toLocaleString()}`);
24
+ for (const s of m.skills) {
25
+ console.log(` - ${s.name} (${s.status})`);
26
+ }
27
+ console.log('');
28
+ found++;
29
+ }
30
+ if (found === 0) console.log(' (nothing installed yet โ€” run `install`)\n');
31
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Uninstall: read manifest per target, remove tracked files, keep everything else.
3
+ */
4
+
5
+ import { existsSync, readFileSync, rmSync, readdirSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { dirname, join } from 'node:path';
8
+ import { confirm } from '@inquirer/prompts';
9
+ import { resolveTargets } from './detect.js';
10
+
11
+ const PATHS = {
12
+ 'claude-code': (scope) =>
13
+ scope === 'user' ? join(homedir(), '.claude', 'skills') : join(process.cwd(), '.claude', 'skills'),
14
+ 'opencode': (scope) =>
15
+ scope === 'user' ? join(homedir(), '.config', 'opencode', 'command') : join(process.cwd(), '.opencode', 'command')
16
+ };
17
+
18
+ export async function uninstall(opts) {
19
+ const targets = resolveTargets(opts.target);
20
+
21
+ console.log(`\n๐Ÿ“ฆ @ner076/prd-toolkit โ€” uninstall`);
22
+ console.log(` Targets: ${targets.join(', ')}`);
23
+ console.log(` Scope: ${opts.scope}\n`);
24
+
25
+ if (!opts.yes) {
26
+ const ok = await confirm({ message: 'Proceed with uninstall?', default: false });
27
+ if (!ok) return console.log('Cancelled.');
28
+ }
29
+
30
+ for (const target of targets) {
31
+ const base = PATHS[target](opts.scope);
32
+ const manifestPath = join(base, '.prd-toolkit.manifest.json');
33
+
34
+ if (!existsSync(manifestPath)) {
35
+ console.warn(` โš  ${target}: no manifest at ${base} โ€” nothing to remove`);
36
+ continue;
37
+ }
38
+
39
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
40
+ for (const skill of manifest.skills) {
41
+ if (!existsSync(skill.path)) continue;
42
+ rmSync(skill.path, { force: true });
43
+
44
+ // Claude Code puts each skill in its own dir; clean up if empty
45
+ const parentDir = dirname(skill.path);
46
+ if (parentDir !== base && existsSync(parentDir) && readdirSync(parentDir).length === 0) {
47
+ rmSync(parentDir, { recursive: true, force: true });
48
+ }
49
+ }
50
+ rmSync(manifestPath, { force: true });
51
+ console.log(` โœ“ ${target} โ†’ removed ${manifest.skills.length} skill(s)`);
52
+ }
53
+
54
+ console.log('\nDone.\n');
55
+ }
package/src/version.js ADDED
@@ -0,0 +1,13 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ let cached;
8
+ export async function getPackageVersion() {
9
+ if (cached) return cached;
10
+ const pkg = JSON.parse(await readFile(join(__dirname, '..', 'package.json'), 'utf8'));
11
+ cached = pkg.version;
12
+ return cached;
13
+ }