claude-agent-skills 1.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 (153) hide show
  1. package/README.md +65 -0
  2. package/bundled-skills/ask-matt/SKILL.md +61 -0
  3. package/bundled-skills/brainstorming/SKILL.md +159 -0
  4. package/bundled-skills/brainstorming/scripts/frame-template.html +213 -0
  5. package/bundled-skills/brainstorming/scripts/helper.js +167 -0
  6. package/bundled-skills/brainstorming/scripts/server.cjs +723 -0
  7. package/bundled-skills/brainstorming/scripts/start-server.sh +209 -0
  8. package/bundled-skills/brainstorming/scripts/stop-server.sh +120 -0
  9. package/bundled-skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  10. package/bundled-skills/brainstorming/visual-companion.md +298 -0
  11. package/bundled-skills/cavecrew/README.md +41 -0
  12. package/bundled-skills/cavecrew/SKILL.md +82 -0
  13. package/bundled-skills/caveman/README.md +48 -0
  14. package/bundled-skills/caveman/SKILL.md +78 -0
  15. package/bundled-skills/caveman-commit/README.md +44 -0
  16. package/bundled-skills/caveman-commit/SKILL.md +65 -0
  17. package/bundled-skills/caveman-compress/README.md +163 -0
  18. package/bundled-skills/caveman-compress/SECURITY.md +31 -0
  19. package/bundled-skills/caveman-compress/SKILL.md +111 -0
  20. package/bundled-skills/caveman-compress/scripts/__init__.py +9 -0
  21. package/bundled-skills/caveman-compress/scripts/__main__.py +3 -0
  22. package/bundled-skills/caveman-compress/scripts/benchmark.py +80 -0
  23. package/bundled-skills/caveman-compress/scripts/cli.py +85 -0
  24. package/bundled-skills/caveman-compress/scripts/compress.py +342 -0
  25. package/bundled-skills/caveman-compress/scripts/detect.py +121 -0
  26. package/bundled-skills/caveman-compress/scripts/validate.py +213 -0
  27. package/bundled-skills/caveman-help/README.md +38 -0
  28. package/bundled-skills/caveman-help/SKILL.md +63 -0
  29. package/bundled-skills/caveman-review/README.md +33 -0
  30. package/bundled-skills/caveman-review/SKILL.md +55 -0
  31. package/bundled-skills/caveman-stats/README.md +30 -0
  32. package/bundled-skills/caveman-stats/SKILL.md +10 -0
  33. package/bundled-skills/codebase-design/DEEPENING.md +37 -0
  34. package/bundled-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  35. package/bundled-skills/codebase-design/SKILL.md +114 -0
  36. package/bundled-skills/council/SKILL.md +77 -0
  37. package/bundled-skills/diagnosing-bugs/SKILL.md +134 -0
  38. package/bundled-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  39. package/bundled-skills/dispatching-parallel-agents/SKILL.md +185 -0
  40. package/bundled-skills/domain-modeling/ADR-FORMAT.md +47 -0
  41. package/bundled-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  42. package/bundled-skills/domain-modeling/SKILL.md +74 -0
  43. package/bundled-skills/edit-article/SKILL.md +15 -0
  44. package/bundled-skills/executing-plans/SKILL.md +70 -0
  45. package/bundled-skills/finishing-a-development-branch/SKILL.md +241 -0
  46. package/bundled-skills/git-guardrails-claude-code/SKILL.md +95 -0
  47. package/bundled-skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +25 -0
  48. package/bundled-skills/grill-me/SKILL.md +7 -0
  49. package/bundled-skills/grill-with-docs/SKILL.md +7 -0
  50. package/bundled-skills/grilling/SKILL.md +10 -0
  51. package/bundled-skills/handoff/SKILL.md +16 -0
  52. package/bundled-skills/i-am-dumb/SKILL.md +57 -0
  53. package/bundled-skills/implement/SKILL.md +15 -0
  54. package/bundled-skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  55. package/bundled-skills/improve-codebase-architecture/SKILL.md +66 -0
  56. package/bundled-skills/migrate-to-shoehorn/SKILL.md +118 -0
  57. package/bundled-skills/obsidian-vault/SKILL.md +59 -0
  58. package/bundled-skills/ponytail/SKILL.md +117 -0
  59. package/bundled-skills/ponytail-audit/SKILL.md +50 -0
  60. package/bundled-skills/ponytail-debt/SKILL.md +59 -0
  61. package/bundled-skills/ponytail-gain/SKILL.md +51 -0
  62. package/bundled-skills/ponytail-help/SKILL.md +43 -0
  63. package/bundled-skills/ponytail-review/SKILL.md +51 -0
  64. package/bundled-skills/prototype/LOGIC.md +79 -0
  65. package/bundled-skills/prototype/SKILL.md +31 -0
  66. package/bundled-skills/prototype/UI.md +112 -0
  67. package/bundled-skills/receiving-code-review/SKILL.md +213 -0
  68. package/bundled-skills/requesting-code-review/SKILL.md +103 -0
  69. package/bundled-skills/requesting-code-review/code-reviewer.md +172 -0
  70. package/bundled-skills/resolving-merge-conflicts/SKILL.md +14 -0
  71. package/bundled-skills/scaffold-exercises/SKILL.md +106 -0
  72. package/bundled-skills/setup-matt-pocock-skills/SKILL.md +127 -0
  73. package/bundled-skills/setup-matt-pocock-skills/domain.md +51 -0
  74. package/bundled-skills/setup-matt-pocock-skills/issue-tracker-github.md +34 -0
  75. package/bundled-skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +35 -0
  76. package/bundled-skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
  77. package/bundled-skills/setup-matt-pocock-skills/triage-labels.md +15 -0
  78. package/bundled-skills/setup-pre-commit/SKILL.md +91 -0
  79. package/bundled-skills/subagent-driven-development/SKILL.md +418 -0
  80. package/bundled-skills/subagent-driven-development/implementer-prompt.md +139 -0
  81. package/bundled-skills/subagent-driven-development/scripts/review-package +44 -0
  82. package/bundled-skills/subagent-driven-development/scripts/sdd-workspace +22 -0
  83. package/bundled-skills/subagent-driven-development/scripts/task-brief +40 -0
  84. package/bundled-skills/subagent-driven-development/task-reviewer-prompt.md +188 -0
  85. package/bundled-skills/systematic-debugging/CREATION-LOG.md +119 -0
  86. package/bundled-skills/systematic-debugging/SKILL.md +296 -0
  87. package/bundled-skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  88. package/bundled-skills/systematic-debugging/condition-based-waiting.md +115 -0
  89. package/bundled-skills/systematic-debugging/defense-in-depth.md +122 -0
  90. package/bundled-skills/systematic-debugging/find-polluter.sh +63 -0
  91. package/bundled-skills/systematic-debugging/root-cause-tracing.md +169 -0
  92. package/bundled-skills/systematic-debugging/test-academic.md +14 -0
  93. package/bundled-skills/systematic-debugging/test-pressure-1.md +58 -0
  94. package/bundled-skills/systematic-debugging/test-pressure-2.md +68 -0
  95. package/bundled-skills/systematic-debugging/test-pressure-3.md +69 -0
  96. package/bundled-skills/tdd/SKILL.md +108 -0
  97. package/bundled-skills/tdd/mocking.md +59 -0
  98. package/bundled-skills/tdd/refactoring.md +10 -0
  99. package/bundled-skills/tdd/tests.md +61 -0
  100. package/bundled-skills/teach/GLOSSARY-FORMAT.md +35 -0
  101. package/bundled-skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
  102. package/bundled-skills/teach/MISSION-FORMAT.md +31 -0
  103. package/bundled-skills/teach/RESOURCES-FORMAT.md +32 -0
  104. package/bundled-skills/teach/SKILL.md +140 -0
  105. package/bundled-skills/test-driven-development/SKILL.md +371 -0
  106. package/bundled-skills/test-driven-development/testing-anti-patterns.md +299 -0
  107. package/bundled-skills/to-issues/SKILL.md +84 -0
  108. package/bundled-skills/to-prd/SKILL.md +75 -0
  109. package/bundled-skills/triage/AGENT-BRIEF.md +207 -0
  110. package/bundled-skills/triage/OUT-OF-SCOPE.md +105 -0
  111. package/bundled-skills/triage/SKILL.md +112 -0
  112. package/bundled-skills/using-git-worktrees/SKILL.md +202 -0
  113. package/bundled-skills/using-superpowers/SKILL.md +121 -0
  114. package/bundled-skills/using-superpowers/references/antigravity-tools.md +96 -0
  115. package/bundled-skills/using-superpowers/references/claude-code-tools.md +50 -0
  116. package/bundled-skills/using-superpowers/references/codex-tools.md +72 -0
  117. package/bundled-skills/using-superpowers/references/copilot-tools.md +49 -0
  118. package/bundled-skills/using-superpowers/references/gemini-tools.md +63 -0
  119. package/bundled-skills/using-superpowers/references/pi-tools.md +28 -0
  120. package/bundled-skills/verification-before-completion/SKILL.md +139 -0
  121. package/bundled-skills/writing-great-skills/GLOSSARY.md +195 -0
  122. package/bundled-skills/writing-great-skills/SKILL.md +82 -0
  123. package/bundled-skills/writing-plans/SKILL.md +174 -0
  124. package/bundled-skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  125. package/bundled-skills/writing-skills/SKILL.md +689 -0
  126. package/bundled-skills/writing-skills/anthropic-best-practices.md +1150 -0
  127. package/bundled-skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  128. package/bundled-skills/writing-skills/graphviz-conventions.dot +172 -0
  129. package/bundled-skills/writing-skills/persuasion-principles.md +187 -0
  130. package/bundled-skills/writing-skills/render-graphs.js +168 -0
  131. package/bundled-skills/writing-skills/testing-skills-with-subagents.md +384 -0
  132. package/commands/add.js +97 -0
  133. package/commands/check.js +54 -0
  134. package/commands/exportSkills.js +30 -0
  135. package/commands/hub.js +52 -0
  136. package/commands/importSkills.js +68 -0
  137. package/commands/list.js +37 -0
  138. package/commands/remove.js +59 -0
  139. package/commands/sync.js +66 -0
  140. package/commands/update.js +70 -0
  141. package/index.js +100 -0
  142. package/lib/banner.js +108 -0
  143. package/lib/constants.js +10 -0
  144. package/lib/deps.js +51 -0
  145. package/lib/hash.js +26 -0
  146. package/lib/install.js +31 -0
  147. package/lib/lockfile.js +37 -0
  148. package/lib/prompts.js +50 -0
  149. package/lib/scope.js +19 -0
  150. package/lib/summary.js +108 -0
  151. package/lib/theme.js +11 -0
  152. package/package.json +43 -0
  153. package/skills.json +164 -0
@@ -0,0 +1,30 @@
1
+ import { note, outro } from '@clack/prompts';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { showIntro } from '../lib/banner.js';
4
+ import { readLock } from '../lib/lockfile.js';
5
+ import { resolveScope } from '../lib/scope.js';
6
+ import { pickScope } from '../lib/prompts.js';
7
+
8
+ export async function runExport(opts = {}) {
9
+ await showIntro({ skip: opts.skipIntro ?? true });
10
+
11
+ const scopeFlags = await pickScope(opts);
12
+ const scope = resolveScope({ global: scopeFlags.global });
13
+ const lock = await readLock(scope.lockPath);
14
+
15
+ if (!lock || !Object.keys(lock.skills).length) {
16
+ note(`No skills installed at ${scope.lockPath}.`, 'Nothing to export');
17
+ outro('');
18
+ return;
19
+ }
20
+
21
+ const json = `${JSON.stringify(lock, null, 2)}\n`;
22
+
23
+ if (opts.file) {
24
+ writeFileSync(opts.file, json, 'utf8');
25
+ note(`Wrote ${Object.keys(lock.skills).length} skill(s) to ${opts.file}\nShare this file with teammates and have them run:\n claude-agent-skills import --file ${opts.file}`, 'Exported');
26
+ outro('Done.');
27
+ } else {
28
+ process.stdout.write(json);
29
+ }
30
+ }
@@ -0,0 +1,52 @@
1
+ import { select, isCancel, cancel, outro } from '@clack/prompts';
2
+ import updateNotifier from 'update-notifier';
3
+ import { createRequire } from 'node:module';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+ import { showIntro } from '../lib/banner.js';
7
+ import { CliCancel } from '../lib/prompts.js';
8
+
9
+ const req = createRequire(import.meta.url);
10
+ const pkg = req(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'));
11
+ updateNotifier({ pkg }).notify();
12
+ import { runAdd } from './add.js';
13
+ import { runUpdate } from './update.js';
14
+ import { runRemove } from './remove.js';
15
+ import { runList } from './list.js';
16
+ import { runSync } from './sync.js';
17
+ import { runCheck } from './check.js';
18
+
19
+ const SKIP = { skipIntro: true };
20
+
21
+ const MENU = [
22
+ { value: 'add', label: 'Add Skill(s)' },
23
+ { value: 'update', label: 'Update Existing Skill(s)' },
24
+ { value: 'remove', label: 'Remove Existing Skill(s)' },
25
+ { value: 'list', label: 'List Installed Skill(s)' },
26
+ { value: 'sync', label: 'Sync/Restore Skills from Lockfile' },
27
+ { value: 'check', label: 'Check Skill(s)' },
28
+ { value: 'quit', label: 'Quit' },
29
+ ];
30
+
31
+ export async function runHub() {
32
+ await showIntro();
33
+
34
+ for (;;) {
35
+ const choice = await select({ message: 'What do you want to do?', options: MENU });
36
+
37
+ if (isCancel(choice)) { cancel('Cancelled.'); return; }
38
+ if (choice === 'quit') { outro('Goodbye.'); return; }
39
+
40
+ try {
41
+ if (choice === 'add') await runAdd(SKIP);
42
+ if (choice === 'update') await runUpdate(SKIP);
43
+ if (choice === 'remove') await runRemove(SKIP);
44
+ if (choice === 'list') await runList(SKIP);
45
+ if (choice === 'sync') await runSync(SKIP);
46
+ if (choice === 'check') await runCheck(SKIP);
47
+ } catch (e) {
48
+ if (e instanceof CliCancel) continue;
49
+ throw e;
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,68 @@
1
+ import { note, outro } from '@clack/prompts';
2
+ import { readFileSync, existsSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { showIntro } from '../lib/banner.js';
6
+ import { readLock, writeLock, upsertSkill } from '../lib/lockfile.js';
7
+ import { resolveScope, ensureDirs } from '../lib/scope.js';
8
+ import { pickScope, confirmProceed } from '../lib/prompts.js';
9
+ import { materialize } from '../lib/install.js';
10
+ import { hashSkill } from '../lib/hash.js';
11
+
12
+ const BUNDLE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'bundled-skills');
13
+
14
+ export async function runImport(opts = {}) {
15
+ await showIntro({ skip: opts.skipIntro ?? true });
16
+
17
+ const file = opts.file;
18
+ if (!file) {
19
+ console.error('Specify a file: claude-agent-skills import --file <lockfile.json>');
20
+ return;
21
+ }
22
+
23
+ let imported;
24
+ try {
25
+ imported = JSON.parse(readFileSync(file, 'utf8'));
26
+ } catch (e) {
27
+ console.error(`Could not read ${file}: ${e.message}`);
28
+ return;
29
+ }
30
+
31
+ const skills = Object.keys(imported.skills ?? {});
32
+ if (!skills.length) {
33
+ note('No skills found in the imported lockfile.', 'Empty');
34
+ outro('');
35
+ return;
36
+ }
37
+
38
+ const scopeFlags = await pickScope(opts);
39
+ const scope = resolveScope({ global: scopeFlags.global });
40
+
41
+ note(`${skills.length} skill(s) from ${file}:\n${skills.join(', ')}`, 'Ready to import');
42
+
43
+ const proceed = await confirmProceed(`Install ${skills.length} skill(s)?`);
44
+ if (!proceed) { outro('Cancelled. No changes made.'); return; }
45
+
46
+ await ensureDirs(scope);
47
+ const lock = (await readLock(scope.lockPath)) ?? { version: 1, skills: {} };
48
+ const installed = [], skipped = [];
49
+
50
+ for (const name of skills.sort()) {
51
+ const src = join(BUNDLE_DIR, name);
52
+ if (!existsSync(src)) { skipped.push(name); continue; }
53
+
54
+ const entry = imported.skills[name];
55
+ const copy = entry?.linkType !== 'symlink';
56
+ await materialize({ src, dest: join(scope.skillsDir, name), copy });
57
+ if (scope.agentsSkillsDir) {
58
+ await materialize({ src, dest: join(scope.agentsSkillsDir, name), copy });
59
+ }
60
+ upsertSkill(lock, name, { computedHash: await hashSkill(src), linkType: copy ? 'copy' : 'symlink' });
61
+ installed.push(name);
62
+ }
63
+
64
+ await writeLock(scope.lockPath, lock);
65
+
66
+ if (skipped.length) note(`Skipped (not in bundle): ${skipped.join(', ')}`, 'Warning');
67
+ outro(`Imported ${installed.length} skill(s). Restart Claude Code.`);
68
+ }
@@ -0,0 +1,37 @@
1
+ import { note, outro } from '@clack/prompts';
2
+ import { join } from 'node:path';
3
+ import { showIntro } from '../lib/banner.js';
4
+ import { readLock } from '../lib/lockfile.js';
5
+ import { resolveScope } from '../lib/scope.js';
6
+ import { pickScope } from '../lib/prompts.js';
7
+ import { pathExists, isBroken } from '../lib/install.js';
8
+ import { renderListSummary } from '../lib/summary.js';
9
+
10
+ export async function runList(opts = {}) {
11
+ await showIntro({ skip: opts.skipIntro });
12
+
13
+ const scopeFlags = await pickScope(opts);
14
+ const scope = resolveScope({ global: scopeFlags.global });
15
+ const lock = await readLock(scope.lockPath);
16
+
17
+ if (!lock || !Object.keys(lock.skills).length) {
18
+ console.log(`No skills installed. Lockfile not found at: ${scope.lockPath}`);
19
+ outro('');
20
+ return;
21
+ }
22
+
23
+ const rows = await Promise.all(
24
+ Object.entries(lock.skills).sort().map(async ([name, entry]) => {
25
+ const dest = join(scope.skillsDir, name);
26
+ const ex = await pathExists(dest);
27
+ const broken = ex && await isBroken(dest);
28
+ const status = ex && !broken ? 'ok' : broken ? 'broken' : 'missing';
29
+ return { name, linkType: entry.linkType, hash: entry.computedHash.slice(0, 8), status, healthy: status === 'ok' };
30
+ }),
31
+ );
32
+
33
+ note(renderListSummary({
34
+ scope: scope.scope, skillsDir: scope.skillsDir, lockPath: scope.lockPath, rows,
35
+ }), 'Installed skills');
36
+ outro('');
37
+ }
@@ -0,0 +1,59 @@
1
+ import { multiselect, note, outro, isCancel } from '@clack/prompts';
2
+ import { rm } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { showIntro } from '../lib/banner.js';
5
+ import { readLock, writeLock, removeSkill } from '../lib/lockfile.js';
6
+ import { resolveScope } from '../lib/scope.js';
7
+ import { pickScope, confirmProceed, CliCancel } from '../lib/prompts.js';
8
+ import { pathExists } from '../lib/install.js';
9
+ import { renderRemoveSummary } from '../lib/summary.js';
10
+
11
+ export async function runRemove(opts = {}) {
12
+ await showIntro({ skip: opts.skipIntro });
13
+
14
+ const scopeFlags = await pickScope(opts);
15
+ const scope = resolveScope({ global: scopeFlags.global });
16
+ const lock = await readLock(scope.lockPath);
17
+
18
+ if (!lock || !Object.keys(lock.skills).length) {
19
+ console.log(`No skills installed. Lockfile not found at: ${scope.lockPath}`);
20
+ outro('');
21
+ return;
22
+ }
23
+
24
+ const installed = Object.keys(lock.skills).sort();
25
+
26
+ const selected = await multiselect({
27
+ message: 'Select skills to remove:',
28
+ options: installed.map(n => ({ value: n, label: n })),
29
+ required: false,
30
+ });
31
+
32
+ if (isCancel(selected)) throw new CliCancel();
33
+ if (!selected?.length) { outro('Nothing selected.'); return; }
34
+
35
+ if (!opts.yes) {
36
+ const proceed = await confirmProceed(`Remove ${selected.length} skill(s)?`);
37
+ if (!proceed) { outro('Cancelled. No changes made.'); return; }
38
+ }
39
+
40
+ const removed = [], failed = [];
41
+ for (const name of selected) {
42
+ try {
43
+ const dest = join(scope.skillsDir, name);
44
+ if (await pathExists(dest)) await rm(dest, { recursive: true, force: true });
45
+ if (scope.agentsSkillsDir) {
46
+ const agentDest = join(scope.agentsSkillsDir, name);
47
+ if (await pathExists(agentDest)) await rm(agentDest, { recursive: true, force: true });
48
+ }
49
+ removeSkill(lock, name);
50
+ removed.push(name);
51
+ } catch (e) {
52
+ failed.push({ name, error: e.message });
53
+ }
54
+ }
55
+ await writeLock(scope.lockPath, lock);
56
+
57
+ note(renderRemoveSummary({ scope: scope.scope, skillsDir: scope.skillsDir, lockPath: scope.lockPath, removed, failed }), 'Removed');
58
+ outro(removed.length > 0 ? `Removed ${removed.length} skill(s). Restart Claude Code.` : 'Nothing removed.');
59
+ }
@@ -0,0 +1,66 @@
1
+ import { note, outro } from '@clack/prompts';
2
+ import { existsSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { showIntro } from '../lib/banner.js';
6
+ import { readLock, writeLock, upsertSkill } from '../lib/lockfile.js';
7
+ import { resolveScope, ensureDirs } from '../lib/scope.js';
8
+ import { pickScope } from '../lib/prompts.js';
9
+ import { materialize, pathExists, isBroken, diskType } from '../lib/install.js';
10
+ import { hashSkill } from '../lib/hash.js';
11
+ import { renderSyncSummary } from '../lib/summary.js';
12
+
13
+ const BUNDLE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'bundled-skills');
14
+
15
+ export async function runSync(opts = {}) {
16
+ await showIntro({ skip: opts.skipIntro });
17
+
18
+ const scopeFlags = await pickScope(opts);
19
+ const scope = resolveScope({ global: scopeFlags.global });
20
+ const lock = await readLock(scope.lockPath);
21
+
22
+ if (!lock || !Object.keys(lock.skills).length) {
23
+ note(`No lockfile found at ${scope.lockPath}.\nRun "Add Skill(s)" first.`, 'Nothing to sync');
24
+ outro('');
25
+ return;
26
+ }
27
+
28
+ await ensureDirs(scope);
29
+ const synced = [], ok = [];
30
+
31
+ for (const [name, entry] of Object.entries(lock.skills).sort()) {
32
+ const dest = join(scope.skillsDir, name);
33
+ const src = join(BUNDLE_DIR, name);
34
+
35
+ if (!existsSync(src)) {
36
+ console.warn(` Warning: "${name}" in lockfile but missing from bundle — skipping.`);
37
+ ok.push(name);
38
+ continue;
39
+ }
40
+
41
+ const ex = await pathExists(dest);
42
+ const broken = ex && await isBroken(dest);
43
+ const onDisk = await diskType(dest);
44
+ const wantCopy = entry.linkType === 'copy';
45
+ const typeMismatch = onDisk !== 'missing' &&
46
+ ((wantCopy && onDisk === 'symlink') || (!wantCopy && onDisk === 'copy'));
47
+
48
+ if (ex && !broken && !typeMismatch) { ok.push(name); continue; }
49
+
50
+ const lt = await materialize({ src, dest, copy: wantCopy });
51
+ if (scope.agentsSkillsDir) {
52
+ await materialize({ src, dest: join(scope.agentsSkillsDir, name), copy: wantCopy });
53
+ }
54
+ upsertSkill(lock, name, { computedHash: await hashSkill(src), linkType: lt });
55
+ synced.push(name);
56
+ }
57
+
58
+ await writeLock(scope.lockPath, lock);
59
+
60
+ note(renderSyncSummary({
61
+ scope: scope.scope, skillsDir: scope.skillsDir, lockPath: scope.lockPath,
62
+ synced, ok,
63
+ }), 'Sync summary');
64
+
65
+ outro(synced.length > 0 ? `Synced ${synced.length} skill(s).` : 'All skills present.');
66
+ }
@@ -0,0 +1,70 @@
1
+ import { multiselect, note, outro, isCancel } from '@clack/prompts';
2
+ import { existsSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { showIntro } from '../lib/banner.js';
6
+ import { readLock, writeLock, upsertSkill } from '../lib/lockfile.js';
7
+ import { resolveScope, ensureDirs } from '../lib/scope.js';
8
+ import { pickScope, confirmProceed, CliCancel } from '../lib/prompts.js';
9
+ import { materialize } from '../lib/install.js';
10
+ import { hashSkill } from '../lib/hash.js';
11
+ import { renderUpdateSummary } from '../lib/summary.js';
12
+
13
+ const BUNDLE_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'bundled-skills');
14
+
15
+ export async function runUpdate(opts = {}) {
16
+ await showIntro({ skip: opts.skipIntro });
17
+
18
+ const scopeFlags = await pickScope(opts);
19
+ const scope = resolveScope({ global: scopeFlags.global });
20
+ const lock = await readLock(scope.lockPath);
21
+
22
+ if (!lock || !Object.keys(lock.skills).length) {
23
+ console.log(`No skills installed. Run "add" first.`);
24
+ outro('');
25
+ return;
26
+ }
27
+
28
+ const updatable = [];
29
+ for (const [name, entry] of Object.entries(lock.skills).sort()) {
30
+ const bundledPath = join(BUNDLE_DIR, name);
31
+ if (!existsSync(bundledPath)) continue;
32
+ const bundledHash = await hashSkill(bundledPath);
33
+ if (bundledHash !== entry.computedHash) updatable.push({ name, bundledPath, linkType: entry.linkType });
34
+ }
35
+
36
+ if (!updatable.length) {
37
+ note('All installed skills are already up to date.', 'No updates available');
38
+ outro('Done.');
39
+ return;
40
+ }
41
+
42
+ const selected = await multiselect({
43
+ message: `Select skills to update (${updatable.length} available):`,
44
+ options: updatable.map(s => ({ value: s.name, label: s.name })),
45
+ required: false,
46
+ });
47
+
48
+ if (isCancel(selected)) throw new CliCancel();
49
+ if (!selected?.length) { outro('Nothing selected.'); return; }
50
+
51
+ if (!opts.yes) {
52
+ const proceed = await confirmProceed(`Update ${selected.length} skill(s)?`);
53
+ if (!proceed) { outro('Cancelled. No changes made.'); return; }
54
+ }
55
+
56
+ await ensureDirs(scope);
57
+ for (const name of selected) {
58
+ const skill = updatable.find(s => s.name === name);
59
+ const hash = await hashSkill(skill.bundledPath);
60
+ const lt = await materialize({ src: skill.bundledPath, dest: join(scope.skillsDir, name), copy: skill.linkType === 'copy' });
61
+ if (scope.agentsSkillsDir) {
62
+ await materialize({ src: skill.bundledPath, dest: join(scope.agentsSkillsDir, name), copy: skill.linkType === 'copy' });
63
+ }
64
+ upsertSkill(lock, name, { computedHash: hash, linkType: lt });
65
+ }
66
+ await writeLock(scope.lockPath, lock);
67
+
68
+ note(renderUpdateSummary({ scope: scope.scope, skillsDir: scope.skillsDir, lockPath: scope.lockPath, updated: selected }), 'Updated');
69
+ outro(`Done! ${selected.length} skill(s) updated. Restart Claude Code.`);
70
+ }
package/index.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { createRequire } from 'node:module';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+ import { runHub } from './commands/hub.js';
7
+ import { runAdd } from './commands/add.js';
8
+ import { runUpdate } from './commands/update.js';
9
+ import { runRemove } from './commands/remove.js';
10
+ import { runList } from './commands/list.js';
11
+ import { runSync } from './commands/sync.js';
12
+ import { runCheck } from './commands/check.js';
13
+ import { runExport } from './commands/exportSkills.js';
14
+ import { runImport } from './commands/importSkills.js';
15
+ import { CliCancel } from './lib/prompts.js';
16
+
17
+ const req = createRequire(import.meta.url);
18
+ const { version } = req(join(dirname(fileURLToPath(import.meta.url)), 'package.json'));
19
+
20
+ const program = new Command()
21
+ .name('claude-skills')
22
+ .description("Install and manage Pavi's Claude Code skills")
23
+ .version(version);
24
+
25
+ program
26
+ .command('hub', { isDefault: true })
27
+ .description('Interactive menu (default) — loops until Quit or Ctrl+C')
28
+ .action(() => runHub());
29
+
30
+ program
31
+ .command('add')
32
+ .description('Install skills')
33
+ .option('-g, --global', 'Install globally (~/.claude/skills)')
34
+ .option('-p, --project', 'Install to current project (.claude/skills)')
35
+ .option('--all', 'Install all skills without prompting')
36
+ .option('--skill <name...>', 'Specific skill(s) to install')
37
+ .option('--copy', 'Copy files (safe for npx)')
38
+ .option('--symlink', 'Create symlinks (persistent install only)')
39
+ .option('-y, --yes', 'Skip confirmation prompt')
40
+ .action(opts => runAdd(opts));
41
+
42
+ program
43
+ .command('update')
44
+ .description('Update installed skills to latest bundled version')
45
+ .option('-g, --global', 'Update global skills')
46
+ .option('-p, --project', 'Update project skills')
47
+ .option('-y, --yes', 'Skip confirmation prompt')
48
+ .action(opts => runUpdate(opts));
49
+
50
+ program
51
+ .command('remove')
52
+ .description('Remove installed skills')
53
+ .option('-g, --global', 'Remove from global')
54
+ .option('-p, --project', 'Remove from project')
55
+ .option('-y, --yes', 'Skip confirmation prompt')
56
+ .action(opts => runRemove(opts));
57
+
58
+ program
59
+ .command('list')
60
+ .description('List installed skills and their health status')
61
+ .option('-g, --global', 'List global skills')
62
+ .option('-p, --project', 'List project skills')
63
+ .action(opts => runList(opts));
64
+
65
+ program
66
+ .command('sync')
67
+ .description('Restore skills from lockfile')
68
+ .option('-g, --global', 'Sync global skills')
69
+ .option('-p, --project', 'Sync project skills')
70
+ .action(opts => runSync(opts));
71
+
72
+ program
73
+ .command('check')
74
+ .description('Check health and update status of installed skills')
75
+ .option('-g, --global', 'Check global skills')
76
+ .option('-p, --project', 'Check project skills')
77
+ .action(opts => runCheck(opts));
78
+
79
+ program
80
+ .command('export')
81
+ .description('Export lockfile for sharing with teammates')
82
+ .option('-g, --global', 'Export global lockfile')
83
+ .option('-p, --project', 'Export project lockfile')
84
+ .option('--file <path>', 'Write to file instead of stdout')
85
+ .action(opts => runExport(opts));
86
+
87
+ program
88
+ .command('import')
89
+ .description('Import a shared lockfile and install those skills')
90
+ .option('-g, --global', 'Install into global scope')
91
+ .option('-p, --project', 'Install into project scope')
92
+ .option('--file <path>', 'Lockfile to import (required)')
93
+ .option('-y, --yes', 'Skip confirmation')
94
+ .action(opts => runImport(opts));
95
+
96
+ program.parseAsync().catch(err => {
97
+ if (err instanceof CliCancel) process.exit(0);
98
+ console.error(`\nError: ${err.message}`);
99
+ process.exit(1);
100
+ });
package/lib/banner.js ADDED
@@ -0,0 +1,108 @@
1
+ import ansis from 'ansis';
2
+ import { muted } from './theme.js';
3
+ import { REPO } from './constants.js';
4
+
5
+ const CLAUDE_LINES = [
6
+ ' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗',
7
+ '██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝',
8
+ '██║ ██║ ███████║██║ ██║██║ ██║█████╗ ',
9
+ '██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ',
10
+ '╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗',
11
+ ' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝',
12
+ ];
13
+
14
+ const SKILLS_LINES = [
15
+ '███████╗██╗ ██╗██╗██╗ ██╗ ███████╗',
16
+ '██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝',
17
+ '███████╗█████╔╝ ██║██║ ██║ ███████╗',
18
+ '╚════██║██╔═██╗ ██║██║ ██║ ╚════██║',
19
+ '███████║██║ ██╗██║███████╗███████╗███████║',
20
+ '╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝',
21
+ ];
22
+
23
+ const ART = [...CLAUDE_LINES, ...SKILLS_LINES];
24
+ const MAX_WIDTH = Math.max(...ART.map(l => l.length));
25
+
26
+ // One pride stripe per art line (12 lines → 2 lines per colour)
27
+ const PRIDE = [
28
+ [220, 30, 30], // red
29
+ [220, 30, 30],
30
+ [255, 140, 0], // orange
31
+ [255, 140, 0],
32
+ [255, 210, 0], // yellow
33
+ [255, 210, 0],
34
+ [ 30, 185, 30], // green
35
+ [ 30, 185, 30],
36
+ [ 30, 100, 255], // blue
37
+ [ 30, 100, 255],
38
+ [150, 30, 230], // purple
39
+ [150, 30, 230],
40
+ ];
41
+
42
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
43
+ const col = (r, g, b) => s => ansis.rgb(Math.round(r), Math.round(g), Math.round(b))(s);
44
+ const up = n => process.stdout.write(`\x1b[${n}A`);
45
+ const wline = s => process.stdout.write(`\x1b[2K${s}\n`);
46
+
47
+ function clamp(v) { return Math.max(0, Math.min(255, Math.round(v))); }
48
+
49
+ /** Sheen sweep: all chars glow pride → white at the core → pride → dim */
50
+ function sheenColor(_ch, dist, [r, g, b]) {
51
+ if (dist <= 1) return col(255, 255, 255); // white-hot core
52
+ if (dist <= 3) return col(clamp(r*1.1+60), clamp(g*1.1+60), clamp(b*1.1+60)); // bright pride
53
+ if (dist <= 8) return col(r, g, b); // pure pride
54
+ if (dist <= 15) return col(r * 0.7, g * 0.7, b * 0.7); // dimmed
55
+ return col(r * 0.45, g * 0.45, b * 0.45); // dark base
56
+ }
57
+
58
+ /** Static settled state: sine-arc so centre is brightest (metallic highlight) */
59
+ function staticColor(_ch, i, len, [r, g, b]) {
60
+ const arc = Math.sin((i / (len - 1 || 1)) * Math.PI); // 0 at edges, 1 at centre
61
+ const f = 0.38 + arc * 0.62; // 0.38 → 1.0
62
+ return col(r * f, g * f, b * f);
63
+ }
64
+
65
+ function renderFrame(getColor) {
66
+ ART.forEach((line, li) => {
67
+ const pride = PRIDE[li] ?? PRIDE[PRIDE.length - 1];
68
+ let out = '';
69
+ for (let i = 0; i < line.length; i++) {
70
+ const ch = line[i];
71
+ out += ch === ' ' ? ' ' : getColor(ch, i, line.length, pride)(ch);
72
+ }
73
+ wline(out);
74
+ });
75
+ }
76
+
77
+ async function runSweep({ step = 3, fps = 55 } = {}) {
78
+ const delay = Math.round(1000 / fps);
79
+ for (let sx = -20; sx <= MAX_WIDTH + 20; sx += step) {
80
+ up(ART.length);
81
+ renderFrame((ch, i, _len, pride) => sheenColor(ch, Math.abs(i - sx), pride));
82
+ await sleep(delay);
83
+ }
84
+ }
85
+
86
+ async function renderStatic() {
87
+ up(ART.length);
88
+ renderFrame(staticColor);
89
+ }
90
+
91
+ /**
92
+ * showIntro() — full two-sweep entrance + subtitle (call ONCE at startup)
93
+ * showIntro({ skip:true }) — no-op (used inside sub-commands called from hub)
94
+ */
95
+ export async function showIntro({ skip = false } = {}) {
96
+ if (skip) return;
97
+
98
+ process.stdout.write('\n'.repeat(ART.length)); // reserve block
99
+
100
+ await runSweep({ step: 3, fps: 55 }); // first sweep (~0.8 s)
101
+ await runSweep({ step: 3, fps: 55 }); // second sweep (~0.8 s)
102
+ await renderStatic(); // settle to metallic pride
103
+
104
+ const silver = s => ansis.rgb(190, 190, 190)(s);
105
+ process.stdout.write('\n');
106
+ process.stdout.write(silver('Agent Skills for Claude Code by Pavithran Francis') + '\n\n');
107
+ process.stdout.write(muted('Repository: ') + silver(REPO) + '\n\n');
108
+ }
@@ -0,0 +1,10 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const req = createRequire(import.meta.url);
6
+ const pkg = req(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'));
7
+
8
+ export const VERSION = pkg.version;
9
+ export const PACKAGE_NAME = pkg.name;
10
+ export const REPO = 'https://github.com/Pavithran-Francis/skills';
package/lib/deps.js ADDED
@@ -0,0 +1,51 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const req = createRequire(import.meta.url);
6
+
7
+ export function loadManifest() {
8
+ const manifestPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'skills.json');
9
+ try {
10
+ return req(manifestPath);
11
+ } catch {
12
+ return { skills: [], dependsOn: {} };
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Topological expansion: given selected skill names, resolves all transitive
18
+ * dependencies and returns them in install order (deps before dependents).
19
+ * Returns { ordered, addedBy } where addedBy maps auto-added names → who pulled them in.
20
+ */
21
+ export function expandDependencies(manifest, selected) {
22
+ const known = new Set(manifest.skills ?? []);
23
+ const selectedSet = new Set(selected);
24
+ const addedBy = new Map();
25
+ const visiting = new Set();
26
+ const visited = new Set();
27
+ const ordered = [];
28
+
29
+ function visit(name, parent) {
30
+ if (visited.has(name)) return;
31
+ if (visiting.has(name)) throw new Error(`Circular skill dependency involving: ${name}`);
32
+ visiting.add(name);
33
+
34
+ for (const dep of (manifest.dependsOn ?? {})[name] ?? []) {
35
+ if (!selectedSet.has(dep) && !addedBy.has(dep)) {
36
+ addedBy.set(dep, name);
37
+ }
38
+ visit(dep, name);
39
+ }
40
+
41
+ visiting.delete(name);
42
+ visited.add(name);
43
+ ordered.push(name);
44
+ }
45
+
46
+ for (const name of selected) {
47
+ visit(name, null);
48
+ }
49
+
50
+ return { ordered, addedBy };
51
+ }