@schalkneethling/toolkit 0.5.3 → 0.6.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * toolkit — personal CLI for managing Claude Code hooks, skills, and commands.\n *\n * Commands:\n * toolkit add hook <name>\n * toolkit add skill <name> [--link <target>...]\n * toolkit add command <name>\n * toolkit add collections <name>\n * toolkit update [--force]\n * toolkit list hook\n * toolkit list skill\n * toolkit list command\n * toolkit list collections\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n cpSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n symlinkSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createInterface } from \"node:readline/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nconst TOOLKIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst HOOKS_SRC = join(TOOLKIT_ROOT, \"hooks\");\nconst SKILLS_SRC = join(TOOLKIT_ROOT, \"skills\");\nconst COMMANDS_SRC = join(TOOLKIT_ROOT, \"commands\");\nconst CONFIG_PATH = join(TOOLKIT_ROOT, \"config.json\");\n\nconst PROJECT_ROOT = process.cwd();\nconst CLAUDE_DIR = join(PROJECT_ROOT, \".claude\");\nconst TOOLKIT_DIR = join(PROJECT_ROOT, \".claude-toolkit\");\nconst MANIFEST_PATH = join(CLAUDE_DIR, \"toolkit-manifest.json\");\n\ntype HookEntry = { hash: string; installedAt: string };\ntype SkillEntry = { hash: string; installedAt: string; linkedTo: string[] };\ntype CommandEntry = { hash: string; installedAt: string };\ntype Manifest = {\n commands: Record<string, CommandEntry>;\n hooks: Record<string, HookEntry>;\n skills: Record<string, SkillEntry>;\n};\ntype CollectionItemKind = \"command\" | \"hook\" | \"skill\";\ntype CollectionItemConfig = {\n type: CollectionItemKind | `${CollectionItemKind}s`;\n src: string;\n};\ntype CollectionConfig = {\n name: string;\n items: CollectionItemConfig[];\n};\ntype ResolvedCollectionItem = {\n collection: string;\n sourcePath: string;\n sourceName: string;\n type: CollectionItemKind;\n};\n\n// ---------- helpers ----------\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction shortHash(content: string | Buffer): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 7);\n}\n\nfunction readManifest(): Manifest {\n if (!existsSync(MANIFEST_PATH)) {\n return { commands: {}, hooks: {}, skills: {} };\n }\n\n try {\n const parsed = JSON.parse(readFileSync(MANIFEST_PATH, \"utf8\")) as Partial<Manifest>;\n return {\n commands: parsed.commands ?? {},\n hooks: parsed.hooks ?? {},\n skills: parsed.skills ?? {},\n };\n } catch {\n return { commands: {}, hooks: {}, skills: {} };\n }\n}\n\nfunction writeManifest(m: Manifest): void {\n mkdirSync(CLAUDE_DIR, { recursive: true });\n writeFileSync(MANIFEST_PATH, JSON.stringify(m, null, 2) + \"\\n\");\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction deepMerge<T>(target: T, source: T): T {\n if (Array.isArray(target) && Array.isArray(source)) {\n return [...target, ...source] as T;\n }\n if (isPlainObject(target) && isPlainObject(source)) {\n const out: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(source)) {\n out[k] = k in out ? deepMerge(out[k], v) : v;\n }\n return out as T;\n }\n return source;\n}\n\nfunction hashCommandSource(name: string): string {\n const p = join(COMMANDS_SRC, `${name}.md`);\n return shortHash(readFileSync(p));\n}\n\nfunction hashHookSource(name: string): string {\n const p = join(HOOKS_SRC, name, \"hook.mjs\");\n return shortHash(readFileSync(p));\n}\n\nfunction hashSkillSource(name: string): string {\n const dir = join(SKILLS_SRC, name);\n const files = collectFiles(dir).sort();\n const h = createHash(\"sha256\");\n for (const f of files) {\n h.update(relative(dir, f));\n h.update(\"\\0\");\n h.update(readFileSync(f));\n h.update(\"\\0\");\n }\n return h.digest(\"hex\").slice(0, 7);\n}\n\nfunction collectFiles(dir: string): string[] {\n const out: string[] = [];\n if (!existsSync(dir)) {\n return out;\n }\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === \".gitkeep\") {\n continue;\n }\n\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...collectFiles(full));\n } else if (entry.isFile()) {\n out.push(full);\n }\n }\n return out;\n}\n\nasync function confirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n rl.close();\n return answer === \"y\" || answer === \"yes\";\n}\n\nfunction diffLines(oldStr: string, newStr: string): string {\n const a = oldStr.split(\"\\n\");\n const b = newStr.split(\"\\n\");\n const out: string[] = [];\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n if (a[i] === b[i]) {\n continue;\n }\n\n if (a[i] !== undefined) {\n out.push(`- ${a[i]}`);\n }\n\n if (b[i] !== undefined) {\n out.push(`+ ${b[i]}`);\n }\n }\n return out.join(\"\\n\");\n}\n\n// ---------- commands ----------\n\nfunction sanitizeName(name: string, kind: string): string {\n name = basename(name);\n if (!name) {\n console.error(`Invalid ${kind} name`);\n process.exit(1);\n }\n return name;\n}\n\nfunction normalizeCollectionItemType(\n type: CollectionItemConfig[\"type\"],\n collectionName: string,\n): CollectionItemKind {\n if (type === \"command\" || type === \"commands\") {\n return \"command\";\n }\n if (type === \"hook\" || type === \"hooks\") {\n return \"hook\";\n }\n if (type === \"skill\" || type === \"skills\") {\n return \"skill\";\n }\n\n throw new Error(`Collection \"${collectionName}\" has unsupported item type \"${type}\"`);\n}\n\nfunction resolveSourcePath(src: string, kind: string, collectionName: string): string {\n const sourcePath = resolve(TOOLKIT_ROOT, src);\n if (!sourcePath.startsWith(TOOLKIT_ROOT + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${kind} source must stay within the toolkit root: ${src}`,\n );\n }\n return sourcePath;\n}\n\nfunction inferItemNameFromSource(\n type: CollectionItemKind,\n sourcePath: string,\n collectionName: string,\n): string {\n if (type === \"command\") {\n if (\n dirname(sourcePath) !== COMMANDS_SRC ||\n !sourcePath.startsWith(COMMANDS_SRC + sep) ||\n !sourcePath.endsWith(\".md\")\n ) {\n throw new Error(\n `Collection \"${collectionName}\" command source must point to a markdown file directly under commands/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n return basename(sourcePath, \".md\");\n }\n\n const expectedRoot = type === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (dirname(sourcePath) !== expectedRoot || !sourcePath.startsWith(expectedRoot + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${type} source must point to a top-level entry under ${relative(TOOLKIT_ROOT, expectedRoot)}/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n\n return basename(sourcePath);\n}\n\nfunction readCollectionsConfig(): CollectionConfig[] {\n if (!existsSync(CONFIG_PATH)) {\n throw new Error(`Collections config not found: ${relative(TOOLKIT_ROOT, CONFIG_PATH)}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(CONFIG_PATH, \"utf8\"));\n } catch (error) {\n throw new Error(\n `Invalid collections config in ${relative(TOOLKIT_ROOT, CONFIG_PATH)}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n if (!Array.isArray(parsed)) {\n throw new Error(\"Collections config must be an array\");\n }\n\n const names = new Set<string>();\n return parsed.map((entry, index) => {\n if (!isPlainObject(entry)) {\n throw new Error(`Collection at index ${index} must be an object`);\n }\n\n const { name, items } = entry;\n if (typeof name !== \"string\" || name.trim().length === 0) {\n throw new Error(`Collection at index ${index} must have a non-empty name`);\n }\n if (names.has(name)) {\n throw new Error(`Duplicate collection name: ${name}`);\n }\n names.add(name);\n\n if (!Array.isArray(items)) {\n throw new Error(`Collection \"${name}\" must have an items array`);\n }\n\n const validatedItems = items.map((item, itemIndex) => {\n if (!isPlainObject(item)) {\n throw new Error(`Collection \"${name}\" item at index ${itemIndex} must be an object`);\n }\n if (typeof item.type !== \"string\" || item.type.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty type`,\n );\n }\n if (typeof item.src !== \"string\" || item.src.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty src`,\n );\n }\n\n return {\n type: item.type as CollectionItemConfig[\"type\"],\n src: item.src,\n };\n });\n\n return {\n name,\n items: validatedItems,\n };\n });\n}\n\nfunction resolveCollection(name: string): ResolvedCollectionItem[] {\n const collectionName = sanitizeName(name, \"collection\");\n const collections = readCollectionsConfig();\n const collection = collections.find((entry) => entry.name === collectionName);\n\n if (!collection) {\n throw new Error(`Collection not found: ${collectionName}`);\n }\n\n const deduped = new Map<string, ResolvedCollectionItem>();\n\n for (const item of collection.items) {\n const type = normalizeCollectionItemType(item.type, collection.name);\n const sourcePath = resolveSourcePath(item.src, type, collection.name);\n const sourceName = inferItemNameFromSource(type, sourcePath, collection.name);\n const key = `${type}:${sourceName}`;\n\n if (!deduped.has(key)) {\n deduped.set(key, {\n collection: collection.name,\n sourcePath,\n sourceName,\n type,\n });\n }\n }\n\n return [...deduped.values()];\n}\n\nfunction installCommand(name: string, src: string): void {\n if (!existsSync(src)) {\n console.error(`Command not found: ${name}`);\n process.exit(1);\n }\n\n const commandsDir = join(CLAUDE_DIR, \"commands\");\n mkdirSync(commandsDir, { recursive: true });\n const dest = resolve(commandsDir, `${name}.md`);\n if (!dest.startsWith(commandsDir + sep)) {\n console.error(\"Invalid command name\");\n process.exit(1);\n }\n writeFileSync(dest, readFileSync(src));\n\n const manifest = readManifest();\n manifest.commands[name] = {\n hash: hashCommandSource(name),\n installedAt: today(),\n };\n writeManifest(manifest);\n\n console.log(`Installed command: ${name} → ${relative(PROJECT_ROOT, dest)}`);\n}\n\nfunction addCommand(name: string): void {\n name = sanitizeName(name, \"command\");\n installCommand(name, join(COMMANDS_SRC, `${name}.md`));\n}\n\nfunction installHook(name: string, srcDir: string): void {\n if (!existsSync(srcDir)) {\n console.error(`Hook not found: ${name}`);\n process.exit(1);\n }\n\n const hookSrc = join(srcDir, \"hook.mjs\");\n const fragmentPath = join(srcDir, \"settings-fragment.json\");\n\n const hooksDir = join(CLAUDE_DIR, \"hooks\");\n mkdirSync(hooksDir, { recursive: true });\n const destHook = resolve(hooksDir, `${name}.mjs`);\n if (!destHook.startsWith(hooksDir + sep)) {\n console.error(\"Invalid hook name\");\n process.exit(1);\n }\n writeFileSync(destHook, readFileSync(hookSrc));\n\n if (existsSync(fragmentPath)) {\n const fragment = JSON.parse(readFileSync(fragmentPath, \"utf8\"));\n const settingsPath = join(CLAUDE_DIR, \"settings.json\");\n const current = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, \"utf8\")) : {};\n const merged = deepMerge(current, fragment);\n writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + \"\\n\");\n }\n\n const manifest = readManifest();\n manifest.hooks[name] = { hash: hashHookSource(name), installedAt: today() };\n writeManifest(manifest);\n\n console.log(`Installed hook: ${name} → ${relative(PROJECT_ROOT, destHook)}`);\n}\n\nfunction addHook(name: string): void {\n name = sanitizeName(name, \"hook\");\n installHook(name, join(HOOKS_SRC, name));\n}\n\nfunction installSkill(name: string, srcDir: string, links: string[]): void {\n if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) {\n console.error(`Skill not found: ${name}`);\n process.exit(1);\n }\n\n const destDir = resolve(TOOLKIT_DIR, \"skills\", name);\n if (!destDir.startsWith(join(TOOLKIT_DIR, \"skills\") + sep)) {\n console.error(\"Invalid skill name\");\n process.exit(1);\n }\n mkdirSync(dirname(destDir), { recursive: true });\n cpSync(srcDir, destDir, { recursive: true });\n\n const resolvedLinks = links.length > 0 ? links : [join(\".claude\", \"skills\")];\n for (const link of resolvedLinks) {\n const linkDir = resolve(PROJECT_ROOT, link);\n mkdirSync(linkDir, { recursive: true });\n\n const linkPath = join(linkDir, name);\n if (existsSync(linkPath) || lstatExists(linkPath)) {\n unlinkSync(linkPath);\n }\n\n const relTarget = relative(linkDir, destDir);\n symlinkSync(relTarget, linkPath, \"dir\");\n }\n\n const manifest = readManifest();\n manifest.skills[name] = {\n hash: hashSkillSource(name),\n installedAt: today(),\n linkedTo: resolvedLinks,\n };\n writeManifest(manifest);\n\n console.log(`Installed skill: ${name} → ${relative(PROJECT_ROOT, destDir)}`);\n for (const l of resolvedLinks) {\n console.log(` linked: ${join(l, name)}`);\n }\n}\n\nfunction addSkill(name: string, links: string[]): void {\n name = sanitizeName(name, \"skill\");\n installSkill(name, join(SKILLS_SRC, name), links);\n}\n\nfunction addCollection(name: string): void {\n const items = resolveCollection(name);\n for (const item of items) {\n if (!existsSync(item.sourcePath)) {\n throw new Error(\n `Collection \"${item.collection}\" references missing ${item.type} source: ${relative(TOOLKIT_ROOT, item.sourcePath)}`,\n );\n }\n\n const itemStats = statSync(item.sourcePath);\n const actualKind = itemStats.isFile()\n ? \"file\"\n : itemStats.isDirectory()\n ? \"directory\"\n : \"other\";\n\n if (item.type === \"command\") {\n if (!itemStats.isFile()) {\n throw new Error(\n `Collection \"${item.collection}\" expected command source \"${item.sourcePath}\" to be a file, found ${actualKind}`,\n );\n }\n installCommand(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (item.type === \"hook\") {\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected hook source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installHook(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected skill source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installSkill(item.sourceName, item.sourcePath, []);\n }\n}\n\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function update(force: boolean): Promise<void> {\n const manifest = readManifest();\n let changed = false;\n\n for (const [name, entry] of Object.entries(manifest.hooks)) {\n const srcDir = join(HOOKS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashHookSource(name);\n const installedPath = join(CLAUDE_DIR, \"hooks\", `${name}.mjs`);\n const installedHash = existsSync(installedPath) ? shortHash(readFileSync(installedPath)) : null;\n\n const sourceChanged = sourceHash !== entry.hash;\n const locallyModified = installedHash !== null && installedHash !== entry.hash;\n\n if (!sourceChanged && !locallyModified) {\n continue;\n }\n\n changed = true;\n\n if (locallyModified && !force) {\n console.warn(\n `! hook \"${name}\" was modified locally (installed=${installedHash}, manifest=${entry.hash}). Use --force to overwrite.`,\n );\n continue;\n }\n\n if (sourceChanged) {\n const oldSrc = existsSync(installedPath) ? readFileSync(installedPath, \"utf8\") : \"\";\n const newSrc = readFileSync(join(srcDir, \"hook.mjs\"), \"utf8\");\n console.log(`\\n~ hook: ${name} (${entry.hash} → ${sourceHash})`);\n console.log(diffLines(oldSrc, newSrc));\n const ok = force || (await confirm(`Update hook \"${name}\"?`));\n\n if (!ok) {\n continue;\n }\n\n writeFileSync(installedPath, newSrc);\n manifest.hooks[name] = { hash: sourceHash, installedAt: today() };\n }\n }\n\n for (const [name, entry] of Object.entries(manifest.skills)) {\n const srcDir = join(SKILLS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashSkillSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ skill: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update skill \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const destDir = join(TOOLKIT_DIR, \"skills\", name);\n cpSync(srcDir, destDir, { recursive: true, force: true });\n manifest.skills[name] = {\n hash: sourceHash,\n installedAt: today(),\n linkedTo: entry.linkedTo,\n };\n }\n\n for (const [name, entry] of Object.entries(manifest.commands)) {\n const src = join(COMMANDS_SRC, `${name}.md`);\n if (!existsSync(src)) {\n continue;\n }\n\n const sourceHash = hashCommandSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ command: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update command \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const dest = join(CLAUDE_DIR, \"commands\", `${name}.md`);\n writeFileSync(dest, readFileSync(src));\n manifest.commands[name] = { hash: sourceHash, installedAt: today() };\n }\n\n if (changed) {\n writeManifest(manifest);\n }\n}\n\nfunction list(kind: \"hook\" | \"skill\" | \"command\"): void {\n if (kind === \"command\") {\n if (!existsSync(COMMANDS_SRC)) {\n console.log(\"(no commands available)\");\n return;\n }\n const files = readdirSync(COMMANDS_SRC)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n if (files.length === 0) {\n console.log(\"(no commands available)\");\n return;\n }\n for (const name of files) {\n console.log(`${name} ${hashCommandSource(name)}`);\n }\n return;\n }\n\n const dir = kind === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (!existsSync(dir)) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n const entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory() || (kind === \"skill\" && e.isSymbolicLink()))\n .map((e) => e.name);\n\n if (entries.length === 0) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n\n for (const name of entries) {\n const hash = kind === \"hook\" ? hashHookSource(name) : hashSkillSource(name);\n console.log(`${name} ${hash}`);\n }\n}\n\nfunction listCollections(): void {\n const collections = readCollectionsConfig();\n if (collections.length === 0) {\n console.log(\"(no collections available)\");\n return;\n }\n\n for (const collection of collections) {\n console.log(`${collection.name} ${collection.items.length} item(s)`);\n }\n}\n\n// ---------- argv ----------\n\nfunction usage(): never {\n console.error(\n `Usage:\n toolkit add hook <name>\n toolkit add skill <name> [--link <target>]...\n toolkit add command <name>\n toolkit add collections <name>\n toolkit update [--force]\n toolkit list hook\n toolkit list skill\n toolkit list command\n toolkit list collections`,\n );\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n options: {\n force: {\n default: false,\n type: \"boolean\",\n },\n links: {\n multiple: true,\n type: \"string\",\n },\n },\n allowPositionals: true,\n });\n\n const { force, links } = values;\n const [command, resource, name] = positionals;\n\n if (command === \"add\" && resource === \"hook\") {\n if (!name) {\n usage();\n }\n\n addHook(name);\n return;\n }\n\n if (command === \"add\" && resource === \"skill\") {\n if (!name) {\n usage();\n }\n\n addSkill(name, links ? links : []);\n return;\n }\n\n if (command === \"add\" && resource === \"command\") {\n if (!name) {\n usage();\n }\n\n addCommand(name);\n return;\n }\n\n if (command === \"add\" && (resource === \"collection\" || resource === \"collections\")) {\n if (!name) {\n usage();\n }\n\n addCollection(name);\n return;\n }\n\n if (command === \"update\") {\n await update(force);\n return;\n }\n\n if (\n command === \"list\" &&\n (resource === \"hook\" || resource === \"skill\" || resource === \"command\")\n ) {\n list(resource as \"hook\" | \"skill\" | \"command\");\n return;\n }\n\n if (command === \"list\" && (resource === \"collection\" || resource === \"collections\")) {\n listCollections();\n return;\n }\n\n usage();\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * toolkit — personal CLI for managing Claude Code hooks, skills, and commands.\n *\n * Commands:\n * toolkit add hook <name>\n * toolkit add skill <name> [--link <target>...]\n * toolkit add command <name>\n * toolkit add collections <name>\n * toolkit update [--force]\n * toolkit list hook\n * toolkit list skill\n * toolkit list command\n * toolkit list collections\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n cpSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n symlinkSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createInterface } from \"node:readline/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseArgs } from \"node:util\";\n\nconst TOOLKIT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst HOOKS_SRC = join(TOOLKIT_ROOT, \"hooks\");\nconst SKILLS_SRC = join(TOOLKIT_ROOT, \"skills\");\nconst COMMANDS_SRC = join(TOOLKIT_ROOT, \"commands\");\nconst CONFIG_PATH = join(TOOLKIT_ROOT, \"config.json\");\n\nconst PROJECT_ROOT = process.cwd();\nconst CLAUDE_DIR = join(PROJECT_ROOT, \".claude\");\nconst TOOLKIT_DIR = join(PROJECT_ROOT, \".claude-toolkit\");\nconst MANIFEST_PATH = join(CLAUDE_DIR, \"toolkit-manifest.json\");\n\ntype HookEntry = { hash: string; installedAt: string };\ntype SkillEntry = { hash: string; installedAt: string; linkedTo: string[] };\ntype CommandEntry = { hash: string; installedAt: string };\ntype Manifest = {\n commands: Record<string, CommandEntry>;\n hooks: Record<string, HookEntry>;\n skills: Record<string, SkillEntry>;\n};\ntype CollectionItemKind = \"command\" | \"hook\" | \"skill\";\ntype CollectionItemConfig = {\n type: CollectionItemKind | `${CollectionItemKind}s`;\n src: string;\n};\ntype CollectionConfig = {\n name: string;\n items: CollectionItemConfig[];\n};\ntype ResolvedCollectionItem = {\n collection: string;\n sourcePath: string;\n sourceName: string;\n type: CollectionItemKind;\n};\n\n// ---------- helpers ----------\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction shortHash(content: string | Buffer): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 7);\n}\n\nfunction readManifest(): Manifest {\n if (!existsSync(MANIFEST_PATH)) {\n return { commands: {}, hooks: {}, skills: {} };\n }\n\n try {\n const parsed = JSON.parse(readFileSync(MANIFEST_PATH, \"utf8\")) as Partial<Manifest>;\n return {\n commands: parsed.commands ?? {},\n hooks: parsed.hooks ?? {},\n skills: parsed.skills ?? {},\n };\n } catch {\n return { commands: {}, hooks: {}, skills: {} };\n }\n}\n\nfunction writeManifest(m: Manifest): void {\n mkdirSync(CLAUDE_DIR, { recursive: true });\n writeFileSync(MANIFEST_PATH, JSON.stringify(m, null, 2) + \"\\n\");\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction deepMerge<T>(target: T, source: T): T {\n if (Array.isArray(target) && Array.isArray(source)) {\n return [...target, ...source] as T;\n }\n if (isPlainObject(target) && isPlainObject(source)) {\n const out: Record<string, unknown> = { ...target };\n for (const [k, v] of Object.entries(source)) {\n out[k] = k in out ? deepMerge(out[k], v) : v;\n }\n return out as T;\n }\n return source;\n}\n\nfunction hashCommandSource(name: string): string {\n const p = join(COMMANDS_SRC, `${name}.md`);\n return shortHash(readFileSync(p));\n}\n\nfunction hashHookSource(name: string): string {\n const p = join(HOOKS_SRC, name, \"hook.mjs\");\n return shortHash(readFileSync(p));\n}\n\nfunction hashSkillSource(name: string): string {\n const dir = join(SKILLS_SRC, name);\n const files = collectFiles(dir).sort();\n const h = createHash(\"sha256\");\n for (const f of files) {\n h.update(relative(dir, f));\n h.update(\"\\0\");\n h.update(readFileSync(f));\n h.update(\"\\0\");\n }\n return h.digest(\"hex\").slice(0, 7);\n}\n\nfunction collectFiles(dir: string): string[] {\n const out: string[] = [];\n if (!existsSync(dir)) {\n return out;\n }\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === \".gitkeep\") {\n continue;\n }\n\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...collectFiles(full));\n } else if (entry.isFile()) {\n out.push(full);\n }\n }\n return out;\n}\n\nasync function confirm(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n rl.close();\n return answer === \"y\" || answer === \"yes\";\n}\n\nfunction diffLines(oldStr: string, newStr: string): string {\n const a = oldStr.split(\"\\n\");\n const b = newStr.split(\"\\n\");\n const out: string[] = [];\n const max = Math.max(a.length, b.length);\n for (let i = 0; i < max; i++) {\n if (a[i] === b[i]) {\n continue;\n }\n\n if (a[i] !== undefined) {\n out.push(`- ${a[i]}`);\n }\n\n if (b[i] !== undefined) {\n out.push(`+ ${b[i]}`);\n }\n }\n return out.join(\"\\n\");\n}\n\n// ---------- commands ----------\n\nfunction sanitizeName(name: string, kind: string): string {\n name = basename(name);\n if (!name) {\n console.error(`Invalid ${kind} name`);\n process.exit(1);\n }\n return name;\n}\n\nfunction normalizeCollectionItemType(\n type: CollectionItemConfig[\"type\"],\n collectionName: string,\n): CollectionItemKind {\n if (type === \"command\" || type === \"commands\") {\n return \"command\";\n }\n if (type === \"hook\" || type === \"hooks\") {\n return \"hook\";\n }\n if (type === \"skill\" || type === \"skills\") {\n return \"skill\";\n }\n\n throw new Error(`Collection \"${collectionName}\" has unsupported item type \"${type}\"`);\n}\n\nfunction resolveSourcePath(src: string, kind: string, collectionName: string): string {\n const sourcePath = resolve(TOOLKIT_ROOT, src);\n if (!sourcePath.startsWith(TOOLKIT_ROOT + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${kind} source must stay within the toolkit root: ${src}`,\n );\n }\n return sourcePath;\n}\n\nfunction inferItemNameFromSource(\n type: CollectionItemKind,\n sourcePath: string,\n collectionName: string,\n): string {\n if (type === \"command\") {\n if (\n dirname(sourcePath) !== COMMANDS_SRC ||\n !sourcePath.startsWith(COMMANDS_SRC + sep) ||\n !sourcePath.endsWith(\".md\")\n ) {\n throw new Error(\n `Collection \"${collectionName}\" command source must point to a markdown file directly under commands/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n return basename(sourcePath, \".md\");\n }\n\n const expectedRoot = type === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (dirname(sourcePath) !== expectedRoot || !sourcePath.startsWith(expectedRoot + sep)) {\n throw new Error(\n `Collection \"${collectionName}\" ${type} source must point to a top-level entry under ${relative(TOOLKIT_ROOT, expectedRoot)}/: ${relative(TOOLKIT_ROOT, sourcePath)}`,\n );\n }\n\n return basename(sourcePath);\n}\n\nfunction readCollectionsConfig(): CollectionConfig[] {\n if (!existsSync(CONFIG_PATH)) {\n throw new Error(`Collections config not found: ${relative(TOOLKIT_ROOT, CONFIG_PATH)}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(CONFIG_PATH, \"utf8\"));\n } catch (error) {\n throw new Error(\n `Invalid collections config in ${relative(TOOLKIT_ROOT, CONFIG_PATH)}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n if (!Array.isArray(parsed)) {\n throw new Error(\"Collections config must be an array\");\n }\n\n const names = new Set<string>();\n return parsed.map((entry, index) => {\n if (!isPlainObject(entry)) {\n throw new Error(`Collection at index ${index} must be an object`);\n }\n\n const { name, items } = entry;\n if (typeof name !== \"string\" || name.trim().length === 0) {\n throw new Error(`Collection at index ${index} must have a non-empty name`);\n }\n if (names.has(name)) {\n throw new Error(`Duplicate collection name: ${name}`);\n }\n names.add(name);\n\n if (!Array.isArray(items)) {\n throw new Error(`Collection \"${name}\" must have an items array`);\n }\n\n const validatedItems = items.map((item, itemIndex) => {\n if (!isPlainObject(item)) {\n throw new Error(`Collection \"${name}\" item at index ${itemIndex} must be an object`);\n }\n if (typeof item.type !== \"string\" || item.type.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty type`,\n );\n }\n if (typeof item.src !== \"string\" || item.src.trim().length === 0) {\n throw new Error(\n `Collection \"${name}\" item at index ${itemIndex} must have a non-empty src`,\n );\n }\n\n return {\n type: item.type as CollectionItemConfig[\"type\"],\n src: item.src,\n };\n });\n\n return {\n name,\n items: validatedItems,\n };\n });\n}\n\nfunction resolveCollection(name: string): ResolvedCollectionItem[] {\n const collectionName = sanitizeName(name, \"collection\");\n const collections = readCollectionsConfig();\n const collection = collections.find((entry) => entry.name === collectionName);\n\n if (!collection) {\n throw new Error(`Collection not found: ${collectionName}`);\n }\n\n const deduped = new Map<string, ResolvedCollectionItem>();\n\n for (const item of collection.items) {\n const type = normalizeCollectionItemType(item.type, collection.name);\n const sourcePath = resolveSourcePath(item.src, type, collection.name);\n const sourceName = inferItemNameFromSource(type, sourcePath, collection.name);\n const key = `${type}:${sourceName}`;\n\n if (!deduped.has(key)) {\n deduped.set(key, {\n collection: collection.name,\n sourcePath,\n sourceName,\n type,\n });\n }\n }\n\n return [...deduped.values()];\n}\n\nfunction installCommand(name: string, src: string): void {\n if (!existsSync(src)) {\n console.error(`Command not found: ${name}`);\n process.exit(1);\n }\n\n const commandsDir = join(CLAUDE_DIR, \"commands\");\n mkdirSync(commandsDir, { recursive: true });\n const dest = resolve(commandsDir, `${name}.md`);\n if (!dest.startsWith(commandsDir + sep)) {\n console.error(\"Invalid command name\");\n process.exit(1);\n }\n writeFileSync(dest, readFileSync(src));\n\n const manifest = readManifest();\n manifest.commands[name] = {\n hash: hashCommandSource(name),\n installedAt: today(),\n };\n writeManifest(manifest);\n\n console.log(`Installed command: ${name} → ${relative(PROJECT_ROOT, dest)}`);\n}\n\nfunction addCommand(name: string): void {\n name = sanitizeName(name, \"command\");\n installCommand(name, join(COMMANDS_SRC, `${name}.md`));\n}\n\nfunction installHook(name: string, srcDir: string): void {\n if (!existsSync(srcDir)) {\n console.error(`Hook not found: ${name}`);\n process.exit(1);\n }\n\n const hookSrc = join(srcDir, \"hook.mjs\");\n const fragmentPath = join(srcDir, \"settings-fragment.json\");\n\n const hooksDir = join(CLAUDE_DIR, \"hooks\");\n mkdirSync(hooksDir, { recursive: true });\n const destHook = resolve(hooksDir, `${name}.mjs`);\n if (!destHook.startsWith(hooksDir + sep)) {\n console.error(\"Invalid hook name\");\n process.exit(1);\n }\n writeFileSync(destHook, readFileSync(hookSrc));\n\n if (existsSync(fragmentPath)) {\n const fragment = JSON.parse(readFileSync(fragmentPath, \"utf8\"));\n const settingsPath = join(CLAUDE_DIR, \"settings.json\");\n const current = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, \"utf8\")) : {};\n const merged = deepMerge(current, fragment);\n writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + \"\\n\");\n }\n\n const manifest = readManifest();\n manifest.hooks[name] = { hash: hashHookSource(name), installedAt: today() };\n writeManifest(manifest);\n\n console.log(`Installed hook: ${name} → ${relative(PROJECT_ROOT, destHook)}`);\n}\n\nfunction addHook(name: string): void {\n name = sanitizeName(name, \"hook\");\n installHook(name, join(HOOKS_SRC, name));\n}\n\nfunction installSkill(name: string, srcDir: string, links: string[]): void {\n if (!existsSync(srcDir) || !statSync(srcDir).isDirectory()) {\n console.error(`Skill not found: ${name}`);\n process.exit(1);\n }\n\n const destDir = resolve(TOOLKIT_DIR, \"skills\", name);\n if (!destDir.startsWith(join(TOOLKIT_DIR, \"skills\") + sep)) {\n console.error(\"Invalid skill name\");\n process.exit(1);\n }\n mkdirSync(dirname(destDir), { recursive: true });\n cpSync(srcDir, destDir, { recursive: true });\n\n const resolvedLinks = links.length > 0 ? links : [join(\".claude\", \"skills\")];\n for (const link of resolvedLinks) {\n const linkDir = resolve(PROJECT_ROOT, link);\n mkdirSync(linkDir, { recursive: true });\n\n const linkPath = join(linkDir, name);\n if (existsSync(linkPath) || lstatExists(linkPath)) {\n unlinkSync(linkPath);\n }\n\n const relTarget = relative(linkDir, destDir);\n symlinkSync(relTarget, linkPath, \"dir\");\n }\n\n const manifest = readManifest();\n manifest.skills[name] = {\n hash: hashSkillSource(name),\n installedAt: today(),\n linkedTo: resolvedLinks,\n };\n writeManifest(manifest);\n\n console.log(`Installed skill: ${name} → ${relative(PROJECT_ROOT, destDir)}`);\n for (const l of resolvedLinks) {\n console.log(` linked: ${join(l, name)}`);\n }\n}\n\nfunction addSkill(name: string, links: string[]): void {\n name = sanitizeName(name, \"skill\");\n installSkill(name, join(SKILLS_SRC, name), links);\n}\n\nfunction addCollection(name: string): void {\n const items = resolveCollection(name);\n for (const item of items) {\n if (!existsSync(item.sourcePath)) {\n throw new Error(\n `Collection \"${item.collection}\" references missing ${item.type} source: ${relative(TOOLKIT_ROOT, item.sourcePath)}`,\n );\n }\n\n const itemStats = statSync(item.sourcePath);\n const actualKind = itemStats.isFile()\n ? \"file\"\n : itemStats.isDirectory()\n ? \"directory\"\n : \"other\";\n\n if (item.type === \"command\") {\n if (!itemStats.isFile()) {\n throw new Error(\n `Collection \"${item.collection}\" expected command source \"${item.sourcePath}\" to be a file, found ${actualKind}`,\n );\n }\n installCommand(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (item.type === \"hook\") {\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected hook source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installHook(item.sourceName, item.sourcePath);\n continue;\n }\n\n if (!itemStats.isDirectory()) {\n throw new Error(\n `Collection \"${item.collection}\" expected skill source \"${item.sourcePath}\" to be a directory, found ${actualKind}`,\n );\n }\n installSkill(item.sourceName, item.sourcePath, []);\n }\n}\n\nfunction lstatExists(p: string): boolean {\n try {\n lstatSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function update(force: boolean): Promise<void> {\n const manifest = readManifest();\n let changed = false;\n\n for (const [name, entry] of Object.entries(manifest.hooks)) {\n const srcDir = join(HOOKS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashHookSource(name);\n const installedPath = join(CLAUDE_DIR, \"hooks\", `${name}.mjs`);\n const installedHash = existsSync(installedPath) ? shortHash(readFileSync(installedPath)) : null;\n\n const sourceChanged = sourceHash !== entry.hash;\n const locallyModified = installedHash !== null && installedHash !== entry.hash;\n\n if (!sourceChanged && !locallyModified) {\n continue;\n }\n\n changed = true;\n\n if (locallyModified && !force) {\n console.warn(\n `! hook \"${name}\" was modified locally (installed=${installedHash}, manifest=${entry.hash}). Use --force to overwrite.`,\n );\n continue;\n }\n\n if (sourceChanged) {\n const oldSrc = existsSync(installedPath) ? readFileSync(installedPath, \"utf8\") : \"\";\n const newSrc = readFileSync(join(srcDir, \"hook.mjs\"), \"utf8\");\n console.log(`\\n~ hook: ${name} (${entry.hash} → ${sourceHash})`);\n console.log(diffLines(oldSrc, newSrc));\n const ok = force || (await confirm(`Update hook \"${name}\"?`));\n\n if (!ok) {\n continue;\n }\n\n writeFileSync(installedPath, newSrc);\n manifest.hooks[name] = { hash: sourceHash, installedAt: today() };\n }\n }\n\n for (const [name, entry] of Object.entries(manifest.skills)) {\n const srcDir = join(SKILLS_SRC, name);\n if (!existsSync(srcDir)) {\n continue;\n }\n\n const sourceHash = hashSkillSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ skill: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update skill \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const destDir = join(TOOLKIT_DIR, \"skills\", name);\n cpSync(srcDir, destDir, { recursive: true, force: true });\n manifest.skills[name] = {\n hash: sourceHash,\n installedAt: today(),\n linkedTo: entry.linkedTo,\n };\n }\n\n for (const [name, entry] of Object.entries(manifest.commands)) {\n const src = join(COMMANDS_SRC, `${name}.md`);\n if (!existsSync(src)) {\n continue;\n }\n\n const sourceHash = hashCommandSource(name);\n if (sourceHash === entry.hash) {\n continue;\n }\n\n changed = true;\n console.log(`\\n~ command: ${name} (${entry.hash} → ${sourceHash})`);\n const ok = force || (await confirm(`Update command \"${name}\"?`));\n if (!ok) {\n continue;\n }\n\n const dest = join(CLAUDE_DIR, \"commands\", `${name}.md`);\n writeFileSync(dest, readFileSync(src));\n manifest.commands[name] = { hash: sourceHash, installedAt: today() };\n }\n\n if (changed) {\n writeManifest(manifest);\n }\n}\n\nfunction list(kind: \"hook\" | \"skill\" | \"command\"): void {\n if (kind === \"command\") {\n if (!existsSync(COMMANDS_SRC)) {\n console.log(\"(no commands available)\");\n return;\n }\n const files = readdirSync(COMMANDS_SRC)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n if (files.length === 0) {\n console.log(\"(no commands available)\");\n return;\n }\n for (const name of files) {\n console.log(`${name} ${hashCommandSource(name)}`);\n }\n return;\n }\n\n const dir = kind === \"hook\" ? HOOKS_SRC : SKILLS_SRC;\n if (!existsSync(dir)) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n const entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory() || (kind === \"skill\" && e.isSymbolicLink()))\n .map((e) => e.name);\n\n if (entries.length === 0) {\n console.log(`(no ${kind}s available)`);\n return;\n }\n\n for (const name of entries) {\n const hash = kind === \"hook\" ? hashHookSource(name) : hashSkillSource(name);\n console.log(`${name} ${hash}`);\n }\n}\n\nfunction listCollections(): void {\n const collections = readCollectionsConfig();\n if (collections.length === 0) {\n console.log(\"(no collections available)\");\n return;\n }\n\n for (const collection of collections) {\n console.log(`${collection.name} ${collection.items.length} item(s)`);\n }\n}\n\n// ---------- argv ----------\n\nfunction usage(): never {\n console.error(\n `Usage:\n toolkit add hook <name>\n toolkit add skill <name> [--link <target>]...\n toolkit add command <name>\n toolkit add collections <name>\n toolkit update [--force]\n toolkit list hook\n toolkit list skill\n toolkit list command\n toolkit list collections`,\n );\n process.exit(1);\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n options: {\n force: {\n default: false,\n type: \"boolean\",\n },\n links: {\n multiple: true,\n type: \"string\",\n },\n },\n allowPositionals: true,\n });\n\n const { force, links } = values;\n const [command, resource, name] = positionals;\n\n if (command === \"add\" && resource === \"hook\") {\n if (!name) {\n usage();\n }\n\n addHook(name);\n return;\n }\n\n if (command === \"add\" && resource === \"skill\") {\n if (!name) {\n usage();\n }\n\n addSkill(name, links ? links : []);\n return;\n }\n\n if (command === \"add\" && resource === \"command\") {\n if (!name) {\n usage();\n }\n\n addCommand(name);\n return;\n }\n\n if (command === \"add\" && (resource === \"collection\" || resource === \"collections\")) {\n if (!name) {\n usage();\n }\n\n addCollection(name);\n return;\n }\n\n if (command === \"update\") {\n await update(force);\n return;\n }\n\n if (\n command === \"list\" &&\n (resource === \"hook\" || resource === \"skill\" || resource === \"command\")\n ) {\n list(resource as \"hook\" | \"skill\" | \"command\");\n return;\n }\n\n if (command === \"list\" && (resource === \"collection\" || resource === \"collections\")) {\n listCollections();\n return;\n }\n\n usage();\n}\n\nmain().catch((err) => {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schalkneethling/toolkit",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for managing Claude Code hooks and skills across projects.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -20,16 +20,14 @@
20
20
  "publishConfig": {
21
21
  "access": "public"
22
22
  },
23
- "scripts": {
24
- "toolkit": "tsx src/index.ts",
25
- "prepare": "vp pack && vp run build:hooks",
26
- "build:hooks": "tsc --project tsconfig.hooks.json"
27
- },
28
23
  "devDependencies": {
29
24
  "@types/node": "^25.6.0",
30
25
  "tsx": "^4.21.0",
31
26
  "typescript": "^6.0.2",
32
27
  "vite-plus": "^0.1.18"
33
28
  },
34
- "packageManager": "pnpm@11.1.1"
35
- }
29
+ "scripts": {
30
+ "toolkit": "tsx src/index.ts",
31
+ "build:hooks": "tsc --project tsconfig.hooks.json"
32
+ }
33
+ }
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: dependabot-config
3
+ description: >
4
+ Generate or update Dependabot configuration files for projects. Use this skill
5
+ whenever the user asks to add, create, update, configure, or fix Dependabot for
6
+ a project — including phrases like "set up Dependabot", "add Dependabot config",
7
+ "update my dependabot.yml", "enable Dependabot updates", or "configure automated
8
+ dependency updates". Always apply this skill even if the user only mentions one
9
+ ecosystem (e.g. "add Dependabot for npm") — the canonical config covers all
10
+ required ecosystems.
11
+ ---
12
+
13
+ # Dependabot Configuration Skill
14
+
15
+ Produces the canonical `.github/dependabot.yml` configuration for any project.
16
+ Always emit the complete canonical config, never a partial one, unless the user
17
+ explicitly overrides a specific field after reviewing it.
18
+
19
+ ---
20
+
21
+ ## Canonical Configuration
22
+
23
+ The authoritative configuration to emit is:
24
+
25
+ ```yaml
26
+ version: 2
27
+ updates:
28
+ - package-ecosystem: "npm"
29
+ directory: "/"
30
+ schedule:
31
+ interval: "daily"
32
+ cooldown:
33
+ default-days: 7
34
+ semver-major-days: 7
35
+ semver-minor-days: 3
36
+ semver-patch-days: 2
37
+ include:
38
+ - "*"
39
+
40
+ - package-ecosystem: "github-actions"
41
+ directory: "/"
42
+ schedule:
43
+ interval: "daily"
44
+ cooldown:
45
+ default-days: 7
46
+ semver-major-days: 7
47
+ semver-minor-days: 3
48
+ semver-patch-days: 2
49
+ include:
50
+ - "*"
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Cooldown Rationale
56
+
57
+ Document this to the user when relevant:
58
+
59
+ | Field | Value | Reasoning |
60
+ | ------------------- | ----- | ------------------------------------------------------ |
61
+ | `default-days` | 7 | Catch-all safety buffer for uncategorised updates |
62
+ | `semver-major-days` | 7 | Breaking changes warrant the longest review window |
63
+ | `semver-minor-days` | 3 | New features; moderate confidence, shorter delay |
64
+ | `semver-patch-days` | 2 | Bug/security fixes; high confidence, fast-track |
65
+ | `include: ["*"]` | — | Apply cooldown rules to all packages without exception |
66
+
67
+ ---
68
+
69
+ ## Workflow
70
+
71
+ ### 1. Identify context
72
+
73
+ - Determine whether a `.github/dependabot.yml` already exists in the project.
74
+ - **Exists**: read the file, explain any differences from the canonical config,
75
+ then overwrite it with the canonical config using a file tool.
76
+ - **Does not exist**: create `.github/dependabot.yml` with the canonical config
77
+ using a file tool.
78
+
79
+ ### 2. Check for non-npm ecosystems
80
+
81
+ If the project uses additional package ecosystems (e.g. `pip`, `cargo`, `bundler`,
82
+ `docker`, `composer`), add an additional `updates` block per ecosystem using the
83
+ same `schedule` and `cooldown` values. Keep `npm` and `github-actions` blocks
84
+ unchanged. Use the references file for additional ecosystem identifiers if needed.
85
+
86
+ ### 3. Handle directory variations
87
+
88
+ If the user's project has packages in subdirectories (e.g. a monorepo), ask which
89
+ directories need coverage and emit one block per directory per ecosystem, keeping
90
+ all other fields from the canonical config intact.
91
+
92
+ ### 4. Write the file
93
+
94
+ Write the config directly to `.github/dependabot.yml` in the project root using a
95
+ file tool — do not present it as a fenced code block for the user to copy manually.
96
+ Ensure the `.github/` directory exists before writing.
97
+
98
+ After writing, confirm the file path to the user and follow with a concise
99
+ explanation of what was configured and why — particularly the cooldown strategy —
100
+ without repeating every field verbatim.
101
+
102
+ ---
103
+
104
+ ## Constraints
105
+
106
+ - **Never omit the `cooldown` block** from any ecosystem entry.
107
+ - **Never change the canonical values** unless the user explicitly requests it and
108
+ provides a reason (e.g. a monorepo with a stricter release cadence).
109
+ - **Always include `github-actions`** as an ecosystem, even if the user only asked
110
+ about npm or another runtime ecosystem.
111
+ - **YAML formatting**: two-space indentation, string values quoted, list items with
112
+ `- ` prefix. Validate indentation before emitting — malformed YAML is a silent
113
+ failure in Dependabot.
114
+
115
+ ---
116
+
117
+ ## Reference
118
+
119
+ See `references/ecosystems.md` for the full list of Dependabot-supported
120
+ `package-ecosystem` identifiers and their directory conventions.
@@ -0,0 +1,35 @@
1
+ # Dependabot Package Ecosystem Identifiers
2
+
3
+ Reference: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
4
+
5
+ | Identifier | Ecosystem | Manifest file(s) |
6
+ | ---------------- | -------------------------- | ------------------------------------ |
7
+ | `npm` | npm / Yarn / pnpm | `package.json` |
8
+ | `pip` | pip / Poetry / pip-compile | `requirements.txt`, `pyproject.toml` |
9
+ | `cargo` | Rust / Cargo | `Cargo.toml` |
10
+ | `bundler` | Ruby / Bundler | `Gemfile` |
11
+ | `composer` | PHP / Composer | `composer.json` |
12
+ | `docker` | Docker | `Dockerfile` |
13
+ | `gradle` | Java / Gradle | `build.gradle`, `build.gradle.kts` |
14
+ | `maven` | Java / Maven | `pom.xml` |
15
+ | `gomod` | Go modules | `go.mod` |
16
+ | `nuget` | .NET / NuGet | `*.csproj`, `packages.config` |
17
+ | `github-actions` | GitHub Actions workflows | `.github/workflows/*.yml` |
18
+ | `terraform` | Terraform | `*.tf` |
19
+ | `hex` | Elixir / Hex | `mix.exs` |
20
+ | `elm` | Elm | `elm.json` |
21
+ | `pub` | Dart / Flutter | `pubspec.yaml` |
22
+ | `swift` | Swift Package Manager | `Package.swift` |
23
+
24
+ ## Directory conventions
25
+
26
+ - Single-package project: `directory: "/"`
27
+ - Monorepo with packages in subdirectories: one block per directory, e.g.
28
+ `directory: "/packages/ui"`, `directory: "/packages/api"`
29
+ - Docker images referenced in a subdirectory: `directory: "/docker"`
30
+
31
+ ## Notes
32
+
33
+ - `github-actions` scans `.github/workflows/` regardless of the `directory` value;
34
+ `"/"` is the correct and conventional value.
35
+ - Multiple ecosystems in the same directory each require their own `updates` block.