@katipwork/skills 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -139,7 +139,7 @@ async function discoverSkills() {
139
139
  name: parsed.data.name,
140
140
  description: parsed.data.description || "",
141
141
  dirPath: fullPath,
142
- category: category || (0, import_node_path2.basename)((0, import_node_path2.dirname)(fullPath))
142
+ category: category || (dir === skillsRoot ? "general" : (0, import_node_path2.basename)((0, import_node_path2.dirname)(fullPath)))
143
143
  });
144
144
  }
145
145
  } catch {
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tui.ts","../src/agents.ts","../src/skills.ts","../src/installer.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["import { search, checkbox, select, confirm } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { AGENTS, detectInstalledAgents, type AgentConfig } from './agents.js';\nimport { discoverSkills, type SkillMeta } from './skills.js';\nimport { installSkills, type InstallMode, type InstallResult } from './installer.js';\n\n// ─── simple spinner ────────────────────────────────────────────\n\nfunction spinner(message: string) {\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let i = 0;\n let running = true;\n const timer = setInterval(() => {\n process.stderr.write(`\\r${frames[i++ % frames.length]} ${message}`);\n }, 80);\n return {\n succeed(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.green('✓')} ${text}\\n`);\n running = false;\n },\n fail(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.red('✗')} ${text}\\n`);\n running = false;\n },\n stop() {\n clearInterval(timer);\n process.stderr.write('\\r' + ' '.repeat(message.length + 4) + '\\r');\n running = false;\n },\n };\n}\n\n// ─── theme helpers ──────────────────────────────────────────────\n\nconst theme = {\n style: {\n highlight: (text: string) => pc.cyan(text),\n },\n};\n\n// ─── steps ──────────────────────────────────────────────────────\n\nasync function selectSkills(skills: SkillMeta[]): Promise<SkillMeta[]> {\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found in skills/ directory.'));\n return [];\n }\n\n const choices = skills.map((s) => ({\n name: `${pc.bold(s.name)} ${pc.dim(`— ${s.description}`)}`,\n value: s,\n }));\n\n // Add a \"select all\" option\n choices.unshift({\n name: pc.italic('Select all skills'),\n value: '__all__' as unknown as SkillMeta,\n });\n\n const selected = await checkbox({\n message: 'Select skills to install:',\n choices,\n pageSize: 15,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n });\n\n // Handle \"select all\"\n if (selected.some((s: SkillMeta | string) => (s as unknown as string) === '__all__')) {\n return skills;\n }\n\n return selected as SkillMeta[];\n}\n\nasync function selectAgents(): Promise<AgentConfig[]> {\n const detected = detectInstalledAgents();\n const detectedIds = new Set(detected.map((a) => a.id));\n\n const choices = AGENTS.map((a) => ({\n name: detectedIds.has(a.id)\n ? `${a.displayName} ${pc.green('(detected)')}`\n : a.displayName,\n value: a,\n checked: detectedIds.has(a.id),\n }));\n\n return checkbox({\n message: 'Select target agents:',\n choices,\n pageSize: 12,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n validate: (answer) => {\n if (answer.length === 0) return 'Select at least one agent.';\n return true;\n },\n });\n}\n\nasync function selectScope(): Promise<boolean> {\n return select({\n message: 'Install scope:',\n choices: [\n {\n name: `${pc.bold('Project')} — install to ./<agent>/skills/ (shared with your team)`,\n value: false,\n },\n {\n name: `${pc.bold('Global')} — install to ~/<agent>/skills/ (available everywhere)`,\n value: true,\n },\n ],\n default: false,\n });\n}\n\nasync function selectMode(): Promise<InstallMode> {\n return select({\n message: 'Install method:',\n choices: [\n {\n name: `${pc.bold('Symlink')} ${pc.green('(recommended)')} — single source of truth at .agents/skills/, symlinked to agent dirs. Easy updates.`,\n value: 'symlink',\n },\n {\n name: `${pc.bold('Copy')} — independent copies for each agent. Use when symlinks aren't supported.`,\n value: 'copy',\n },\n ],\n default: 'symlink',\n });\n}\n\nasync function confirmInstallation(\n skills: SkillMeta[],\n agents: AgentConfig[],\n global: boolean,\n mode: InstallMode,\n): Promise<boolean> {\n console.log('\\n' + pc.cyan('─── Installation summary ───'));\n console.log(pc.white(` Skills: ${pc.bold(skills.map((s) => s.name).join(', '))}`));\n console.log(pc.white(` Agents: ${pc.bold(agents.map((a) => a.displayName).join(', '))}`));\n console.log(pc.white(` Scope: ${pc.bold(global ? 'Global' : 'Project')}`));\n console.log(pc.white(` Method: ${pc.bold(mode === 'symlink' ? 'Symlink (canonical at .agents/skills/)' : 'Copy')}`));\n console.log('');\n\n return confirm({\n message: 'Proceed with installation?',\n default: true,\n });\n}\n\nfunction printResults(results: InstallResult[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n console.log('');\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} skill(s) installed successfully:`));\n for (const r of succeeded) {\n console.log(pc.dim(` ${r.skill} → ${r.agent} (${r.mode}) → ${r.path}`));\n }\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} skill(s) failed:`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n }\n\n // Post-install hints\n if (succeeded.length > 0) {\n console.log('');\n console.log(pc.cyan('Tip:'));\n console.log(pc.dim(' Skills are installed. Restart your agent for changes to take effect.'));\n console.log(pc.dim(' To update, re-run this installer or git pull + re-link.'));\n }\n}\n\n// ─── main ───────────────────────────────────────────────────────\n\nexport async function runTui(): Promise<void> {\n console.log('');\n console.log(pc.bold(pc.cyan('@katipwork/skills')));\n console.log(pc.dim('Curated agent skills installer'));\n console.log('');\n\n // 1. Discover skills\n const discoverSpinner = spinner('Discovering skills...');\n let skills: SkillMeta[];\n try {\n skills = await discoverSkills();\n discoverSpinner.succeed(`Found ${skills.length} skill(s).`);\n } catch (err) {\n discoverSpinner.fail('Failed to discover skills.');\n console.error(err);\n process.exit(1);\n }\n\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found. Add SKILL.md files under skills/ and try again.'));\n process.exit(0);\n }\n\n // 2. Select skills\n const selectedSkills = await selectSkills(skills);\n if (selectedSkills.length === 0) {\n console.log(pc.yellow('No skills selected. Exiting.'));\n process.exit(0);\n }\n\n // 3. Select agents\n const selectedAgents = await selectAgents();\n\n // 4. Scope\n const global = await selectScope();\n\n // 5. Mode\n const mode = await selectMode();\n\n // 6. Confirm\n const confirmed = await confirmInstallation(selectedSkills, selectedAgents, global, mode);\n if (!confirmed) {\n console.log(pc.yellow('Installation cancelled.'));\n process.exit(0);\n }\n\n // 7. Install\n const installSpinner = spinner('Installing skills...');\n let results: InstallResult[];\n try {\n results = await installSkills(selectedSkills, selectedAgents, { global, mode });\n installSpinner.stop();\n } catch (err) {\n installSpinner.fail('Installation failed.');\n console.error(err);\n process.exit(1);\n }\n\n printResults(results);\n\n if (results.some((r) => !r.success)) {\n process.exit(1);\n }\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync } from 'node:fs';\n\nconst home = homedir();\n\nexport interface AgentConfig {\n /** CLI flag value, e.g. \"claude-code\" */\n id: string;\n /** Human-readable label */\n displayName: string;\n /** Relative path from project root for project-scoped installs */\n projectSkillsDir: string;\n /** Absolute path for global installs */\n globalSkillsDir: string;\n /** Quick check if agent is installed */\n detectInstalled: () => boolean;\n}\n\nexport type AgentId = (typeof AGENTS)[number]['id'];\n\n/**\n * All supported coding agents.\n * Each has a project-skills dir (relative to CWD) and a global-skills dir (absolute).\n */\nexport const AGENTS: AgentConfig[] = [\n {\n id: 'pi',\n displayName: 'Pi',\n projectSkillsDir: '.pi/skills',\n globalSkillsDir: join(home, '.pi/agent/skills'),\n detectInstalled: () => existsSync(join(home, '.pi/agent')),\n },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectSkillsDir: '.claude/skills',\n globalSkillsDir: join(home, '.claude/skills'),\n detectInstalled: () => existsSync(join(home, '.claude')),\n },\n {\n id: 'codex',\n displayName: 'Codex',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.codex/skills'),\n detectInstalled: () => existsSync(join(home, '.codex')),\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.copilot/skills'),\n detectInstalled: () => existsSync(join(home, '.copilot')),\n },\n {\n id: 'hermes-agent',\n displayName: 'Hermes Agent',\n projectSkillsDir: '.hermes/skills',\n globalSkillsDir: join(home, '.hermes/skills'),\n detectInstalled: () => existsSync(join(home, '.hermes')),\n },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.cursor/skills'),\n detectInstalled: () => existsSync(join(home, '.cursor')),\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.config/opencode/skills'),\n detectInstalled: () => existsSync(join(home, '.config/opencode')),\n },\n];\n\n/** Lookup agent config by id */\nexport function getAgent(id: string): AgentConfig | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n\n/** Detect which agents are installed on this machine */\nexport function detectInstalledAgents(): AgentConfig[] {\n return AGENTS.filter((a) => a.detectInstalled());\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { join, basename, dirname } from 'node:path';\n\n// In CJS, __dirname is a global pointing to the directory of the current file.\n// After build, __dirname is dist/ and skills/ is at ../skills.\ndeclare const __dirname: string;\n\nexport interface SkillMeta {\n /** Skill name from SKILL.md frontmatter */\n name: string;\n /** Description from SKILL.md frontmatter */\n description: string;\n /** Path to the skill directory on disk */\n dirPath: string;\n /** Which category/bucket the skill lives in, e.g. \"engineering\" */\n category: string;\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file.\n * Minimal parser that handles `---` delimited frontmatter.\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string> } | null {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!match) return null;\n\n const data: Record<string, string> = {};\n for (const line of match[1].split('\\n')) {\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key && value) data[key] = value;\n }\n return { data };\n}\n\n/**\n * Find the skills/ directory.\n * When built: dist/ → ../skills\n * When running from source (tsx): src/ → ../skills\n * When installed globally: <package>/dist/ → ../skills\n */\nfunction getSkillsRoot(): string {\n // dist/ → ../skills (bundled case)\n const bundledPath = join(__dirname, '..', 'skills');\n if (existsSync(bundledPath)) return bundledPath;\n\n // CWD fallback\n return join(process.cwd(), 'skills');\n}\n\n/**\n * Discover all skills under skills/.\n * Skills can be at skills/<name>/SKILL.md or skills/<category>/<name>/SKILL.md.\n */\nexport async function discoverSkills(): Promise<SkillMeta[]> {\n const skillsRoot = getSkillsRoot();\n const skills: SkillMeta[] = [];\n\n if (!existsSync(skillsRoot)) {\n return skills;\n }\n\n async function walk(dir: string, category: string = ''): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n\n const fullPath = join(dir, entry.name);\n const skillMdPath = join(fullPath, 'SKILL.md');\n\n if (existsSync(skillMdPath)) {\n // This is a skill directory (has SKILL.md)\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const parsed = parseFrontmatter(content);\n if (parsed && parsed.data.name) {\n skills.push({\n name: parsed.data.name,\n description: parsed.data.description || '',\n dirPath: fullPath,\n category: category || basename(dirname(fullPath)),\n });\n }\n } catch {\n // skip broken skills\n }\n } else {\n // Recurse into subdirectories (category level)\n const subCategory = category ? `${category}/${entry.name}` : entry.name;\n await walk(fullPath, category || entry.name);\n }\n }\n }\n\n await walk(skillsRoot);\n return skills.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { existsSync } from 'node:fs';\nimport {\n mkdir,\n cp,\n symlink,\n rm,\n readlink,\n lstat,\n realpath,\n stat,\n} from 'node:fs/promises';\nimport { join, relative, dirname, resolve, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { AgentConfig } from './agents.js';\nimport type { SkillMeta } from './skills.js';\n\nconst home = homedir();\n\nexport type InstallMode = 'symlink' | 'copy';\n\nexport interface InstallResult {\n skill: string;\n agent: string;\n success: boolean;\n path: string;\n mode: InstallMode;\n error?: string;\n}\n\n/** Canonical skills directory: .agents/skills/ */\nexport function getCanonicalDir(global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return join(base, '.agents', 'skills');\n}\n\nfunction getAgentSkillsDir(agent: AgentConfig, global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return global ? agent.globalSkillsDir : join(base, agent.projectSkillsDir);\n}\n\n/**\n * Resolve a symlink's parent directory to its real path.\n * Handles the case where e.g. ~/.claude/skills is a symlink to ~/.agents/skills.\n */\nasync function resolveParentSymlinks(p: string): Promise<string> {\n const d = dirname(p);\n const b = basename(p);\n try {\n return join(await realpath(d), b);\n } catch {\n return p;\n }\n}\n\n/**\n * Create a symlink at `linkPath` pointing to `target`.\n * Returns true on success, false if fallback to copy is needed.\n */\nasync function createSymlink(target: string, linkPath: string): Promise<boolean> {\n try {\n const resolvedTarget = resolve(target);\n const resolvedLinkPath = resolve(linkPath);\n\n // If they resolve to the same physical path, skip\n try {\n const [realTrg, realLnk] = await Promise.all([\n realpath(resolvedTarget),\n realpath(resolvedLinkPath),\n ]);\n if (realTrg === realLnk) return true;\n } catch {\n // paths may not exist yet — continue\n }\n\n // Check parent symlinks\n const realTgtWithParent = await resolveParentSymlinks(target);\n const realLnkWithParent = await resolveParentSymlinks(linkPath);\n if (realTgtWithParent === realLnkWithParent) return true;\n\n // Remove existing link/dir at target\n try {\n const stats = await lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const existing = await readlink(linkPath);\n if (resolve(dirname(linkPath), existing) === resolvedTarget) return true;\n await rm(linkPath);\n } else {\n await rm(linkPath, { recursive: true });\n }\n } catch {\n // Doesn't exist — fine\n }\n\n const linkDir = dirname(linkPath);\n await mkdir(linkDir, { recursive: true });\n\n const realLinkDir = await resolveParentSymlinks(linkDir);\n const relPath = relative(realLinkDir, target);\n\n await symlink(relPath, linkPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively copy a directory, skipping .git and node_modules.\n */\nasync function copyDir(src: string, dest: string): Promise<void> {\n await mkdir(dest, { recursive: true });\n\n // Use cp with recursive\n await cp(src, dest, {\n recursive: true,\n dereference: true,\n filter: (srcPath) => {\n const name = basename(srcPath);\n return name !== '.git' && name !== 'node_modules';\n },\n });\n}\n\n/**\n * Install a single skill for a single agent.\n */\nexport async function installSkill(\n skill: SkillMeta,\n agent: AgentConfig,\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult> {\n const isGlobal = options.global ?? false;\n const cwd = options.cwd ?? process.cwd();\n const mode = options.mode ?? 'symlink';\n\n const skillName = skill.name;\n const canonicalDir = join(getCanonicalDir(isGlobal, cwd), skillName);\n const agentDir = join(getAgentSkillsDir(agent, isGlobal, cwd), skillName);\n\n try {\n if (mode === 'copy') {\n // Copy directly to agent directory\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n return { skill: skillName, agent: agent.id, success: true, path: agentDir, mode: 'copy' };\n }\n\n // Symlink mode: copy to canonical, then symlink from agent dir\n await rm(canonicalDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, canonicalDir);\n\n // For agents that share the universal .agents/skills dir, canonical is already correct\n if (agent.projectSkillsDir === '.agents/skills' && !isGlobal) {\n // Already installed at canonical location, nothing to symlink\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: canonicalDir,\n mode: 'symlink',\n };\n }\n\n const ok = await createSymlink(canonicalDir, agentDir);\n if (!ok) {\n // Fallback to copy\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n }\n\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: ok ? agentDir : agentDir,\n mode: 'symlink',\n };\n } catch (err) {\n return {\n skill: skillName,\n agent: agent.id,\n success: false,\n path: agentDir,\n mode,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Install multiple skills for multiple agents.\n */\nexport async function installSkills(\n skills: SkillMeta[],\n agents: AgentConfig[],\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult[]> {\n const results: InstallResult[] = [];\n for (const skill of skills) {\n for (const agent of agents) {\n results.push(await installSkill(skill, agent, options));\n }\n }\n return results;\n}\n","import pc from 'picocolors';\nimport { AGENTS, getAgent } from './agents.js';\nimport { discoverSkills } from './skills.js';\nimport { installSkills } from './installer.js';\n\nfunction showHelp(): void {\n console.log(`\n${pc.bold(pc.cyan('@katipwork/skills'))} — Curated agent skills installer\n\n${pc.bold('Usage:')}\n npx @katipwork/skills Interactive TUI (default)\n npx @katipwork/skills --list List available skills\n npx @katipwork/skills --install <skills...> [options]\n\n${pc.bold('Options:')}\n --help, -h Show this help\n --list, -l List available skills and exit\n --install, -i Install specific skills (followed by skill names)\n --agent, -a Target agent(s): pi, claude-code, codex, github-copilot,\n hermes-agent, cursor, opencode (can repeat)\n --all-agents Install to all detected agents\n --global, -g Install globally (~/<agent>/skills/)\n --copy Copy files instead of symlinking\n --yes, -y Skip confirmation prompt\n\n${pc.bold('Examples:')}\n npx @katipwork/skills\n npx @katipwork/skills --list\n npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y\n npx @katipwork/skills -i '*' --all-agents --global\n\n${pc.bold('Agents:')}\n ${AGENTS.map((a) => `${a.id} (${a.displayName})`).join(', ')}\n\n${pc.dim('Source: https://github.com/katipwork/skills')}\n`);\n}\n\ninterface ParsedArgs {\n help: boolean;\n list: boolean;\n install: string[];\n agents: string[];\n allAgents: boolean;\n global: boolean;\n copy: boolean;\n yes: boolean;\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const result: ParsedArgs = {\n help: false,\n list: false,\n install: [],\n agents: [],\n allAgents: false,\n global: false,\n copy: false,\n yes: false,\n };\n\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n switch (arg) {\n case '--help':\n case '-h':\n result.help = true;\n break;\n case '--list':\n case '-l':\n result.list = true;\n break;\n case '--install':\n case '-i':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.install.push(args[i]);\n i++;\n }\n i--; // compensate for loop increment\n break;\n case '--agent':\n case '-a':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.agents.push(args[i]);\n i++;\n }\n i--;\n break;\n case '--all-agents':\n result.allAgents = true;\n break;\n case '--global':\n case '-g':\n result.global = true;\n break;\n case '--copy':\n result.copy = true;\n break;\n case '--yes':\n case '-y':\n result.yes = true;\n break;\n default:\n // Treat as skill name if not a flag\n if (!arg.startsWith('-')) {\n result.install.push(arg);\n }\n }\n i++;\n }\n\n return result;\n}\n\nexport async function runCli(rawArgs: string[]): Promise<void> {\n const args = parseArgs(rawArgs);\n\n if (args.help) {\n showHelp();\n process.exit(0);\n }\n\n if (args.list) {\n const skills = await discoverSkills();\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found.'));\n } else {\n console.log(pc.bold(`Available skills (${skills.length}):\\n`));\n for (const s of skills) {\n console.log(` ${pc.bold(pc.cyan(s.name))}`);\n console.log(` ${pc.dim(s.description)}`);\n console.log(` category: ${s.category}`);\n console.log('');\n }\n }\n process.exit(0);\n }\n\n // Install mode\n if (args.install.length > 0) {\n const allSkills = await discoverSkills();\n let skillsToInstall = allSkills;\n\n // Filter by requested names unless '*' is specified\n if (!args.install.includes('*')) {\n skillsToInstall = allSkills.filter((s) =>\n args.install.some((name) => s.name === name),\n );\n const notFound = args.install.filter(\n (name) => !allSkills.some((s) => s.name === name),\n );\n if (notFound.length > 0) {\n console.log(pc.yellow(`Warning: skills not found: ${notFound.join(', ')}`));\n }\n }\n\n if (skillsToInstall.length === 0) {\n console.log(pc.red('No matching skills found to install.'));\n process.exit(1);\n }\n\n // Resolve agents\n let agentsToUse = args.agents\n .map((id) => getAgent(id))\n .filter((a): a is NonNullable<typeof a> => !!a);\n\n if (args.allAgents) {\n // Use all agents\n agentsToUse = AGENTS;\n }\n\n if (agentsToUse.length === 0) {\n console.log(pc.red('No agents specified. Use --agent <id> or --all-agents.'));\n process.exit(1);\n }\n\n const mode = args.copy ? 'copy' : 'symlink';\n\n console.log(pc.cyan('\\n─── Installing ───'));\n console.log(` Skills: ${pc.bold(skillsToInstall.map((s) => s.name).join(', '))}`);\n console.log(` Agents: ${pc.bold(agentsToUse.map((a) => a.displayName).join(', '))}`);\n console.log(` Scope: ${pc.bold(args.global ? 'Global' : 'Project')}`);\n console.log(` Method: ${pc.bold(mode)}`);\n console.log('');\n\n const results = await installSkills(skillsToInstall, agentsToUse, {\n global: args.global,\n mode,\n });\n\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} installed successfully`));\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} failed`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n process.exit(1);\n }\n process.exit(0);\n }\n\n // No recognized mode — show help\n showHelp();\n}\n","// Shebang is added by tsup banner at build time.\nimport { runTui } from './tui.js';\nimport { runCli } from './cli-args.js';\n\n// If CLI arguments are provided (beyond just the binary), run in non-interactive mode.\n// Otherwise, launch the TUI.\nconst args = process.argv.slice(2);\n\nif (args.length > 0) {\n runCli(args).catch((err) => {\n console.error('Error:', err.message);\n process.exit(1);\n });\n} else {\n runTui().catch((err) => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_promises","import_node_path","import_node_os","home","pc","import_picocolors","pc","args"]}
1
+ {"version":3,"sources":["../src/tui.ts","../src/agents.ts","../src/skills.ts","../src/installer.ts","../src/cli-args.ts","../src/cli.ts"],"sourcesContent":["import { search, checkbox, select, confirm } from '@inquirer/prompts';\nimport pc from 'picocolors';\nimport { AGENTS, detectInstalledAgents, type AgentConfig } from './agents.js';\nimport { discoverSkills, type SkillMeta } from './skills.js';\nimport { installSkills, type InstallMode, type InstallResult } from './installer.js';\n\n// ─── simple spinner ────────────────────────────────────────────\n\nfunction spinner(message: string) {\n const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let i = 0;\n let running = true;\n const timer = setInterval(() => {\n process.stderr.write(`\\r${frames[i++ % frames.length]} ${message}`);\n }, 80);\n return {\n succeed(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.green('✓')} ${text}\\n`);\n running = false;\n },\n fail(text: string) {\n clearInterval(timer);\n process.stderr.write(`\\r${pc.red('✗')} ${text}\\n`);\n running = false;\n },\n stop() {\n clearInterval(timer);\n process.stderr.write('\\r' + ' '.repeat(message.length + 4) + '\\r');\n running = false;\n },\n };\n}\n\n// ─── theme helpers ──────────────────────────────────────────────\n\nconst theme = {\n style: {\n highlight: (text: string) => pc.cyan(text),\n },\n};\n\n// ─── steps ──────────────────────────────────────────────────────\n\nasync function selectSkills(skills: SkillMeta[]): Promise<SkillMeta[]> {\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found in skills/ directory.'));\n return [];\n }\n\n const choices = skills.map((s) => ({\n name: `${pc.bold(s.name)} ${pc.dim(`— ${s.description}`)}`,\n value: s,\n }));\n\n // Add a \"select all\" option\n choices.unshift({\n name: pc.italic('Select all skills'),\n value: '__all__' as unknown as SkillMeta,\n });\n\n const selected = await checkbox({\n message: 'Select skills to install:',\n choices,\n pageSize: 15,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n });\n\n // Handle \"select all\"\n if (selected.some((s: SkillMeta | string) => (s as unknown as string) === '__all__')) {\n return skills;\n }\n\n return selected as SkillMeta[];\n}\n\nasync function selectAgents(): Promise<AgentConfig[]> {\n const detected = detectInstalledAgents();\n const detectedIds = new Set(detected.map((a) => a.id));\n\n const choices = AGENTS.map((a) => ({\n name: detectedIds.has(a.id)\n ? `${a.displayName} ${pc.green('(detected)')}`\n : a.displayName,\n value: a,\n checked: detectedIds.has(a.id),\n }));\n\n return checkbox({\n message: 'Select target agents:',\n choices,\n pageSize: 12,\n loop: false,\n theme: {\n ...theme,\n helpMode: 'auto',\n },\n validate: (answer) => {\n if (answer.length === 0) return 'Select at least one agent.';\n return true;\n },\n });\n}\n\nasync function selectScope(): Promise<boolean> {\n return select({\n message: 'Install scope:',\n choices: [\n {\n name: `${pc.bold('Project')} — install to ./<agent>/skills/ (shared with your team)`,\n value: false,\n },\n {\n name: `${pc.bold('Global')} — install to ~/<agent>/skills/ (available everywhere)`,\n value: true,\n },\n ],\n default: false,\n });\n}\n\nasync function selectMode(): Promise<InstallMode> {\n return select({\n message: 'Install method:',\n choices: [\n {\n name: `${pc.bold('Symlink')} ${pc.green('(recommended)')} — single source of truth at .agents/skills/, symlinked to agent dirs. Easy updates.`,\n value: 'symlink',\n },\n {\n name: `${pc.bold('Copy')} — independent copies for each agent. Use when symlinks aren't supported.`,\n value: 'copy',\n },\n ],\n default: 'symlink',\n });\n}\n\nasync function confirmInstallation(\n skills: SkillMeta[],\n agents: AgentConfig[],\n global: boolean,\n mode: InstallMode,\n): Promise<boolean> {\n console.log('\\n' + pc.cyan('─── Installation summary ───'));\n console.log(pc.white(` Skills: ${pc.bold(skills.map((s) => s.name).join(', '))}`));\n console.log(pc.white(` Agents: ${pc.bold(agents.map((a) => a.displayName).join(', '))}`));\n console.log(pc.white(` Scope: ${pc.bold(global ? 'Global' : 'Project')}`));\n console.log(pc.white(` Method: ${pc.bold(mode === 'symlink' ? 'Symlink (canonical at .agents/skills/)' : 'Copy')}`));\n console.log('');\n\n return confirm({\n message: 'Proceed with installation?',\n default: true,\n });\n}\n\nfunction printResults(results: InstallResult[]): void {\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n console.log('');\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} skill(s) installed successfully:`));\n for (const r of succeeded) {\n console.log(pc.dim(` ${r.skill} → ${r.agent} (${r.mode}) → ${r.path}`));\n }\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} skill(s) failed:`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n }\n\n // Post-install hints\n if (succeeded.length > 0) {\n console.log('');\n console.log(pc.cyan('Tip:'));\n console.log(pc.dim(' Skills are installed. Restart your agent for changes to take effect.'));\n console.log(pc.dim(' To update, re-run this installer or git pull + re-link.'));\n }\n}\n\n// ─── main ───────────────────────────────────────────────────────\n\nexport async function runTui(): Promise<void> {\n console.log('');\n console.log(pc.bold(pc.cyan('@katipwork/skills')));\n console.log(pc.dim('Curated agent skills installer'));\n console.log('');\n\n // 1. Discover skills\n const discoverSpinner = spinner('Discovering skills...');\n let skills: SkillMeta[];\n try {\n skills = await discoverSkills();\n discoverSpinner.succeed(`Found ${skills.length} skill(s).`);\n } catch (err) {\n discoverSpinner.fail('Failed to discover skills.');\n console.error(err);\n process.exit(1);\n }\n\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found. Add SKILL.md files under skills/ and try again.'));\n process.exit(0);\n }\n\n // 2. Select skills\n const selectedSkills = await selectSkills(skills);\n if (selectedSkills.length === 0) {\n console.log(pc.yellow('No skills selected. Exiting.'));\n process.exit(0);\n }\n\n // 3. Select agents\n const selectedAgents = await selectAgents();\n\n // 4. Scope\n const global = await selectScope();\n\n // 5. Mode\n const mode = await selectMode();\n\n // 6. Confirm\n const confirmed = await confirmInstallation(selectedSkills, selectedAgents, global, mode);\n if (!confirmed) {\n console.log(pc.yellow('Installation cancelled.'));\n process.exit(0);\n }\n\n // 7. Install\n const installSpinner = spinner('Installing skills...');\n let results: InstallResult[];\n try {\n results = await installSkills(selectedSkills, selectedAgents, { global, mode });\n installSpinner.stop();\n } catch (err) {\n installSpinner.fail('Installation failed.');\n console.error(err);\n process.exit(1);\n }\n\n printResults(results);\n\n if (results.some((r) => !r.success)) {\n process.exit(1);\n }\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { existsSync } from 'node:fs';\n\nconst home = homedir();\n\nexport interface AgentConfig {\n /** CLI flag value, e.g. \"claude-code\" */\n id: string;\n /** Human-readable label */\n displayName: string;\n /** Relative path from project root for project-scoped installs */\n projectSkillsDir: string;\n /** Absolute path for global installs */\n globalSkillsDir: string;\n /** Quick check if agent is installed */\n detectInstalled: () => boolean;\n}\n\nexport type AgentId = (typeof AGENTS)[number]['id'];\n\n/**\n * All supported coding agents.\n * Each has a project-skills dir (relative to CWD) and a global-skills dir (absolute).\n */\nexport const AGENTS: AgentConfig[] = [\n {\n id: 'pi',\n displayName: 'Pi',\n projectSkillsDir: '.pi/skills',\n globalSkillsDir: join(home, '.pi/agent/skills'),\n detectInstalled: () => existsSync(join(home, '.pi/agent')),\n },\n {\n id: 'claude-code',\n displayName: 'Claude Code',\n projectSkillsDir: '.claude/skills',\n globalSkillsDir: join(home, '.claude/skills'),\n detectInstalled: () => existsSync(join(home, '.claude')),\n },\n {\n id: 'codex',\n displayName: 'Codex',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.codex/skills'),\n detectInstalled: () => existsSync(join(home, '.codex')),\n },\n {\n id: 'github-copilot',\n displayName: 'GitHub Copilot',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.copilot/skills'),\n detectInstalled: () => existsSync(join(home, '.copilot')),\n },\n {\n id: 'hermes-agent',\n displayName: 'Hermes Agent',\n projectSkillsDir: '.hermes/skills',\n globalSkillsDir: join(home, '.hermes/skills'),\n detectInstalled: () => existsSync(join(home, '.hermes')),\n },\n {\n id: 'cursor',\n displayName: 'Cursor',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.cursor/skills'),\n detectInstalled: () => existsSync(join(home, '.cursor')),\n },\n {\n id: 'opencode',\n displayName: 'OpenCode',\n projectSkillsDir: '.agents/skills',\n globalSkillsDir: join(home, '.config/opencode/skills'),\n detectInstalled: () => existsSync(join(home, '.config/opencode')),\n },\n];\n\n/** Lookup agent config by id */\nexport function getAgent(id: string): AgentConfig | undefined {\n return AGENTS.find((a) => a.id === id);\n}\n\n/** Detect which agents are installed on this machine */\nexport function detectInstalledAgents(): AgentConfig[] {\n return AGENTS.filter((a) => a.detectInstalled());\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { readdir } from 'node:fs/promises';\nimport { join, basename, dirname } from 'node:path';\n\n// In CJS, __dirname is a global pointing to the directory of the current file.\n// After build, __dirname is dist/ and skills/ is at ../skills.\ndeclare const __dirname: string;\n\nexport interface SkillMeta {\n /** Skill name from SKILL.md frontmatter */\n name: string;\n /** Description from SKILL.md frontmatter */\n description: string;\n /** Path to the skill directory on disk */\n dirPath: string;\n /** Which category/bucket the skill lives in, e.g. \"engineering\" */\n category: string;\n}\n\n/**\n * Parse YAML frontmatter from a SKILL.md file.\n * Minimal parser that handles `---` delimited frontmatter.\n */\nfunction parseFrontmatter(content: string): { data: Record<string, string> } | null {\n const match = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---/);\n if (!match) return null;\n\n const data: Record<string, string> = {};\n for (const line of match[1].split('\\n')) {\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key && value) data[key] = value;\n }\n return { data };\n}\n\n/**\n * Find the skills/ directory.\n * When built: dist/ → ../skills\n * When running from source (tsx): src/ → ../skills\n * When installed globally: <package>/dist/ → ../skills\n */\nfunction getSkillsRoot(): string {\n // dist/ → ../skills (bundled case)\n const bundledPath = join(__dirname, '..', 'skills');\n if (existsSync(bundledPath)) return bundledPath;\n\n // CWD fallback\n return join(process.cwd(), 'skills');\n}\n\n/**\n * Discover all skills under skills/.\n * Skills can be at skills/<name>/SKILL.md or skills/<category>/<name>/SKILL.md.\n */\nexport async function discoverSkills(): Promise<SkillMeta[]> {\n const skillsRoot = getSkillsRoot();\n const skills: SkillMeta[] = [];\n\n if (!existsSync(skillsRoot)) {\n return skills;\n }\n\n async function walk(dir: string, category: string = ''): Promise<void> {\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n\n const fullPath = join(dir, entry.name);\n const skillMdPath = join(fullPath, 'SKILL.md');\n\n if (existsSync(skillMdPath)) {\n // This is a skill directory (has SKILL.md)\n try {\n const content = readFileSync(skillMdPath, 'utf-8');\n const parsed = parseFrontmatter(content);\n if (parsed && parsed.data.name) {\n skills.push({\n name: parsed.data.name,\n description: parsed.data.description || '',\n dirPath: fullPath,\n category: category || (dir === skillsRoot ? 'general' : basename(dirname(fullPath))),\n });\n }\n } catch {\n // skip broken skills\n }\n } else {\n // Recurse into subdirectories (category level)\n const subCategory = category ? `${category}/${entry.name}` : entry.name;\n await walk(fullPath, category || entry.name);\n }\n }\n }\n\n await walk(skillsRoot);\n return skills.sort((a, b) => a.name.localeCompare(b.name));\n}\n","import { existsSync } from 'node:fs';\nimport {\n mkdir,\n cp,\n symlink,\n rm,\n readlink,\n lstat,\n realpath,\n stat,\n} from 'node:fs/promises';\nimport { join, relative, dirname, resolve, basename } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { AgentConfig } from './agents.js';\nimport type { SkillMeta } from './skills.js';\n\nconst home = homedir();\n\nexport type InstallMode = 'symlink' | 'copy';\n\nexport interface InstallResult {\n skill: string;\n agent: string;\n success: boolean;\n path: string;\n mode: InstallMode;\n error?: string;\n}\n\n/** Canonical skills directory: .agents/skills/ */\nexport function getCanonicalDir(global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return join(base, '.agents', 'skills');\n}\n\nfunction getAgentSkillsDir(agent: AgentConfig, global: boolean, cwd?: string): string {\n const base = global ? home : cwd || process.cwd();\n return global ? agent.globalSkillsDir : join(base, agent.projectSkillsDir);\n}\n\n/**\n * Resolve a symlink's parent directory to its real path.\n * Handles the case where e.g. ~/.claude/skills is a symlink to ~/.agents/skills.\n */\nasync function resolveParentSymlinks(p: string): Promise<string> {\n const d = dirname(p);\n const b = basename(p);\n try {\n return join(await realpath(d), b);\n } catch {\n return p;\n }\n}\n\n/**\n * Create a symlink at `linkPath` pointing to `target`.\n * Returns true on success, false if fallback to copy is needed.\n */\nasync function createSymlink(target: string, linkPath: string): Promise<boolean> {\n try {\n const resolvedTarget = resolve(target);\n const resolvedLinkPath = resolve(linkPath);\n\n // If they resolve to the same physical path, skip\n try {\n const [realTrg, realLnk] = await Promise.all([\n realpath(resolvedTarget),\n realpath(resolvedLinkPath),\n ]);\n if (realTrg === realLnk) return true;\n } catch {\n // paths may not exist yet — continue\n }\n\n // Check parent symlinks\n const realTgtWithParent = await resolveParentSymlinks(target);\n const realLnkWithParent = await resolveParentSymlinks(linkPath);\n if (realTgtWithParent === realLnkWithParent) return true;\n\n // Remove existing link/dir at target\n try {\n const stats = await lstat(linkPath);\n if (stats.isSymbolicLink()) {\n const existing = await readlink(linkPath);\n if (resolve(dirname(linkPath), existing) === resolvedTarget) return true;\n await rm(linkPath);\n } else {\n await rm(linkPath, { recursive: true });\n }\n } catch {\n // Doesn't exist — fine\n }\n\n const linkDir = dirname(linkPath);\n await mkdir(linkDir, { recursive: true });\n\n const realLinkDir = await resolveParentSymlinks(linkDir);\n const relPath = relative(realLinkDir, target);\n\n await symlink(relPath, linkPath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Recursively copy a directory, skipping .git and node_modules.\n */\nasync function copyDir(src: string, dest: string): Promise<void> {\n await mkdir(dest, { recursive: true });\n\n // Use cp with recursive\n await cp(src, dest, {\n recursive: true,\n dereference: true,\n filter: (srcPath) => {\n const name = basename(srcPath);\n return name !== '.git' && name !== 'node_modules';\n },\n });\n}\n\n/**\n * Install a single skill for a single agent.\n */\nexport async function installSkill(\n skill: SkillMeta,\n agent: AgentConfig,\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult> {\n const isGlobal = options.global ?? false;\n const cwd = options.cwd ?? process.cwd();\n const mode = options.mode ?? 'symlink';\n\n const skillName = skill.name;\n const canonicalDir = join(getCanonicalDir(isGlobal, cwd), skillName);\n const agentDir = join(getAgentSkillsDir(agent, isGlobal, cwd), skillName);\n\n try {\n if (mode === 'copy') {\n // Copy directly to agent directory\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n return { skill: skillName, agent: agent.id, success: true, path: agentDir, mode: 'copy' };\n }\n\n // Symlink mode: copy to canonical, then symlink from agent dir\n await rm(canonicalDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, canonicalDir);\n\n // For agents that share the universal .agents/skills dir, canonical is already correct\n if (agent.projectSkillsDir === '.agents/skills' && !isGlobal) {\n // Already installed at canonical location, nothing to symlink\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: canonicalDir,\n mode: 'symlink',\n };\n }\n\n const ok = await createSymlink(canonicalDir, agentDir);\n if (!ok) {\n // Fallback to copy\n await rm(agentDir, { recursive: true, force: true });\n await copyDir(skill.dirPath, agentDir);\n }\n\n return {\n skill: skillName,\n agent: agent.id,\n success: true,\n path: ok ? agentDir : agentDir,\n mode: 'symlink',\n };\n } catch (err) {\n return {\n skill: skillName,\n agent: agent.id,\n success: false,\n path: agentDir,\n mode,\n error: err instanceof Error ? err.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Install multiple skills for multiple agents.\n */\nexport async function installSkills(\n skills: SkillMeta[],\n agents: AgentConfig[],\n options: {\n global?: boolean;\n cwd?: string;\n mode?: InstallMode;\n } = {},\n): Promise<InstallResult[]> {\n const results: InstallResult[] = [];\n for (const skill of skills) {\n for (const agent of agents) {\n results.push(await installSkill(skill, agent, options));\n }\n }\n return results;\n}\n","import pc from 'picocolors';\nimport { AGENTS, getAgent } from './agents.js';\nimport { discoverSkills } from './skills.js';\nimport { installSkills } from './installer.js';\n\nfunction showHelp(): void {\n console.log(`\n${pc.bold(pc.cyan('@katipwork/skills'))} — Curated agent skills installer\n\n${pc.bold('Usage:')}\n npx @katipwork/skills Interactive TUI (default)\n npx @katipwork/skills --list List available skills\n npx @katipwork/skills --install <skills...> [options]\n\n${pc.bold('Options:')}\n --help, -h Show this help\n --list, -l List available skills and exit\n --install, -i Install specific skills (followed by skill names)\n --agent, -a Target agent(s): pi, claude-code, codex, github-copilot,\n hermes-agent, cursor, opencode (can repeat)\n --all-agents Install to all detected agents\n --global, -g Install globally (~/<agent>/skills/)\n --copy Copy files instead of symlinking\n --yes, -y Skip confirmation prompt\n\n${pc.bold('Examples:')}\n npx @katipwork/skills\n npx @katipwork/skills --list\n npx @katipwork/skills -i code-review tdd -a pi -a claude-code -y\n npx @katipwork/skills -i '*' --all-agents --global\n\n${pc.bold('Agents:')}\n ${AGENTS.map((a) => `${a.id} (${a.displayName})`).join(', ')}\n\n${pc.dim('Source: https://github.com/katipwork/skills')}\n`);\n}\n\ninterface ParsedArgs {\n help: boolean;\n list: boolean;\n install: string[];\n agents: string[];\n allAgents: boolean;\n global: boolean;\n copy: boolean;\n yes: boolean;\n}\n\nfunction parseArgs(args: string[]): ParsedArgs {\n const result: ParsedArgs = {\n help: false,\n list: false,\n install: [],\n agents: [],\n allAgents: false,\n global: false,\n copy: false,\n yes: false,\n };\n\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n switch (arg) {\n case '--help':\n case '-h':\n result.help = true;\n break;\n case '--list':\n case '-l':\n result.list = true;\n break;\n case '--install':\n case '-i':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.install.push(args[i]);\n i++;\n }\n i--; // compensate for loop increment\n break;\n case '--agent':\n case '-a':\n i++;\n while (i < args.length && !args[i].startsWith('-')) {\n result.agents.push(args[i]);\n i++;\n }\n i--;\n break;\n case '--all-agents':\n result.allAgents = true;\n break;\n case '--global':\n case '-g':\n result.global = true;\n break;\n case '--copy':\n result.copy = true;\n break;\n case '--yes':\n case '-y':\n result.yes = true;\n break;\n default:\n // Treat as skill name if not a flag\n if (!arg.startsWith('-')) {\n result.install.push(arg);\n }\n }\n i++;\n }\n\n return result;\n}\n\nexport async function runCli(rawArgs: string[]): Promise<void> {\n const args = parseArgs(rawArgs);\n\n if (args.help) {\n showHelp();\n process.exit(0);\n }\n\n if (args.list) {\n const skills = await discoverSkills();\n if (skills.length === 0) {\n console.log(pc.yellow('No skills found.'));\n } else {\n console.log(pc.bold(`Available skills (${skills.length}):\\n`));\n for (const s of skills) {\n console.log(` ${pc.bold(pc.cyan(s.name))}`);\n console.log(` ${pc.dim(s.description)}`);\n console.log(` category: ${s.category}`);\n console.log('');\n }\n }\n process.exit(0);\n }\n\n // Install mode\n if (args.install.length > 0) {\n const allSkills = await discoverSkills();\n let skillsToInstall = allSkills;\n\n // Filter by requested names unless '*' is specified\n if (!args.install.includes('*')) {\n skillsToInstall = allSkills.filter((s) =>\n args.install.some((name) => s.name === name),\n );\n const notFound = args.install.filter(\n (name) => !allSkills.some((s) => s.name === name),\n );\n if (notFound.length > 0) {\n console.log(pc.yellow(`Warning: skills not found: ${notFound.join(', ')}`));\n }\n }\n\n if (skillsToInstall.length === 0) {\n console.log(pc.red('No matching skills found to install.'));\n process.exit(1);\n }\n\n // Resolve agents\n let agentsToUse = args.agents\n .map((id) => getAgent(id))\n .filter((a): a is NonNullable<typeof a> => !!a);\n\n if (args.allAgents) {\n // Use all agents\n agentsToUse = AGENTS;\n }\n\n if (agentsToUse.length === 0) {\n console.log(pc.red('No agents specified. Use --agent <id> or --all-agents.'));\n process.exit(1);\n }\n\n const mode = args.copy ? 'copy' : 'symlink';\n\n console.log(pc.cyan('\\n─── Installing ───'));\n console.log(` Skills: ${pc.bold(skillsToInstall.map((s) => s.name).join(', '))}`);\n console.log(` Agents: ${pc.bold(agentsToUse.map((a) => a.displayName).join(', '))}`);\n console.log(` Scope: ${pc.bold(args.global ? 'Global' : 'Project')}`);\n console.log(` Method: ${pc.bold(mode)}`);\n console.log('');\n\n const results = await installSkills(skillsToInstall, agentsToUse, {\n global: args.global,\n mode,\n });\n\n const succeeded = results.filter((r) => r.success);\n const failed = results.filter((r) => !r.success);\n\n if (succeeded.length > 0) {\n console.log(pc.green(`✓ ${succeeded.length} installed successfully`));\n }\n if (failed.length > 0) {\n console.log(pc.red(`✗ ${failed.length} failed`));\n for (const r of failed) {\n console.log(pc.red(` ${r.skill} → ${r.agent}: ${r.error}`));\n }\n process.exit(1);\n }\n process.exit(0);\n }\n\n // No recognized mode — show help\n showHelp();\n}\n","// Shebang is added by tsup banner at build time.\nimport { runTui } from './tui.js';\nimport { runCli } from './cli-args.js';\n\n// If CLI arguments are provided (beyond just the binary), run in non-interactive mode.\n// Otherwise, launch the TUI.\nconst args = process.argv.slice(2);\n\nif (args.length > 0) {\n runCli(args).catch((err) => {\n console.error('Error:', err.message);\n process.exit(1);\n });\n} else {\n runTui().catch((err) => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_promises","import_node_path","import_node_os","home","pc","import_picocolors","pc","args"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@katipwork/skills",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Curated agent skills with an interactive TUI installer",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,534 @@
1
+ ---
2
+ name: ddd-go-scaffold
3
+ description: Scaffold new DDD/CQRS/Event Sourcing domains in Go monorepos. Use when the user wants to create a new bounded context, scaffold a domain, add an event-sourced aggregate, or replicate the project's domain pattern. Covers commands, events (with upcasting), aggregates, projections, queries, policies, Kafka consumers, HTTP handlers, CLI wiring, and the deep-module principle.
4
+ ---
5
+
6
+ # DDD Go Scaffold
7
+
8
+ Scaffolds a complete DDD/CQRS/Event Sourcing domain in a Go workspace monorepo. Every domain follows the same 13-file pattern — 4 files with business logic (domain-specific), 9 files that are purely mechanical (copy + sed).
9
+
10
+ Reference implementations in this project:
11
+ - `domains/ordering/` — canonical example with all patterns
12
+ - `domains/inventory/` — cross-domain consumer example
13
+ - `domains/billing/` — minimal domain example
14
+ - `SCAFFOLD.md` — condensed architecture reference
15
+
16
+ ## Deep Module Invariant (NON-NEGOTIABLE)
17
+
18
+ Every package MUST hide complexity behind a small interface. The deletion test: remove the module and count the duplicated lines.
19
+
20
+ | Module | Interface methods | Lines hidden |
21
+ |--------|------------------|-------------|
22
+ | `eventstore` | Load, Append, AppendWithSnapshot (3) | ~200 |
23
+ | `outbox` | Append (1) | ~80 |
24
+ | `uow` | Execute (1) | ~120 |
25
+ | `cache` | Get, Set, Invalidate (3) | ~60 |
26
+ | `db` | Register (1) | ~300 |
27
+ | `cli` | New (1), Run (1) | ~200 |
28
+ | Aggregate | Handle (1) | All business rules |
29
+
30
+ NEVER put business logic outside the aggregate. Projections, policies, and HTTP handlers are stateless adapters.
31
+
32
+ ## File Checklist (13 files per domain)
33
+
34
+ | # | File | Type | Customization |
35
+ |---|------|------|--------------|
36
+ | 1 | `commands.go` | DOMAIN | Command structs + marker interface |
37
+ | 2 | `events.go` | DOMAIN | Event structs + V1→V2 UnmarshalJSON upcasting |
38
+ | 3 | `aggregate.go` | DOMAIN | Handle(cmd)→[]Event, ApplyEvent, Snapshot |
39
+ | 4 | `aggregate_test.go` | DOMAIN | Pure tests (no I/O, no mocks) |
40
+ | 5 | `setup.go` | MECHANICAL | NewDomain wiring, ExecuteCommand adapter |
41
+ | 6 | `queries.go` | MECHANICAL | QueryHandler with read-through cache |
42
+ | 7 | `projections.go` | MECHANICAL | Stateless (event, DB) → updated projection row |
43
+ | 8 | `projection_consumer.go` | MECHANICAL | Routes own-domain events → projections |
44
+ | 9 | `kafka_consumer.go` | MECHANICAL | Cross-domain event → local command translation |
45
+ | 10 | `policies.go` | MECHANICAL | Pre-command DB validations + event-driven policies |
46
+ | 11 | `http_handler.go` | MECHANICAL | Gin handlers → ExecuteCommand or QueryHandler |
47
+ | 12 | `cmd/server/main.go` | MECHANICAL | Binary: cli.New() + runServer() |
48
+ | 13 | `migrations/001_create.sql` | MECHANICAL | Schema: events, snapshots, outbox, projections |
49
+
50
+ ## Workflow
51
+
52
+ ### Step 1: Create the module
53
+
54
+ ```bash
55
+ mkdir -p domains/<name>/cmd/server domains/<name>/migrations
56
+ ```
57
+
58
+ Create `domains/<name>/go.mod`:
59
+ ```
60
+ module github.com/company/ddd-monorepo/domains/<name>
61
+
62
+ go 1.22
63
+
64
+ require (
65
+ github.com/company/ddd-monorepo/pkg v0.0.0
66
+ github.com/gin-gonic/gin v1.9.0
67
+ github.com/google/uuid v1.6.0
68
+ github.com/lib/pq v1.10.9
69
+ go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.53.0
70
+ go.opentelemetry.io/otel/trace v1.28.0
71
+ )
72
+
73
+ replace github.com/company/ddd-monorepo/pkg => ../../pkg
74
+ ```
75
+
76
+ Add to `go.work`:
77
+ ```
78
+ use ./domains/<name>
79
+ ```
80
+
81
+ ### Step 2: Commands
82
+
83
+ File: `domains/<name>/commands.go`
84
+
85
+ ```go
86
+ package <name>
87
+
88
+ type Command interface {
89
+ is<Name>Command()
90
+ }
91
+
92
+ type DoSomething struct {
93
+ ID string
94
+ Data string
95
+ }
96
+ func (DoSomething) is<Name>Command() {}
97
+
98
+ // Add idempotency notes: "returns nil, nil if already done"
99
+ ```
100
+
101
+ ### Step 3: Events (with on-read upcasting)
102
+
103
+ File: `domains/<name>/events.go`
104
+
105
+ ```go
106
+ package <name>
107
+
108
+ type Event interface {
109
+ eventstore.Event
110
+ is<Name>Event()
111
+ }
112
+
113
+ // Always create V2 even for the first version — makes the upcasting
114
+ // path exist from day one. If you don't need V2 yet, it's identical
115
+ // to what V1 would have been.
116
+ type SomethingHappenedV2 struct {
117
+ ID string `json:"id"`
118
+ NewField string `json:"new_field"` // V2 addition
119
+ }
120
+
121
+ func (*SomethingHappenedV2) EventType() string { return "<name>.SomethingHappened" }
122
+ func (*SomethingHappenedV2) is<Name>Event() {}
123
+
124
+ func (e *SomethingHappenedV2) UnmarshalJSON(data []byte) error {
125
+ type v2 SomethingHappenedV2
126
+ if err := json.Unmarshal(data, (*v2)(e)); err == nil {
127
+ return nil
128
+ }
129
+ var v1 struct { ID string `json:"id"` }
130
+ if err := json.Unmarshal(data, &v1); err != nil {
131
+ return err
132
+ }
133
+ e.ID = v1.ID
134
+ e.NewField = "" // default for V1→V2
135
+ return nil
136
+ }
137
+ ```
138
+
139
+ ### Step 4: Aggregate (THE deep module)
140
+
141
+ File: `domains/<name>/aggregate.go`
142
+
143
+ This is where ALL business rules live. One entry point: `Handle(cmd)→[]Event`.
144
+
145
+ ```go
146
+ package <name>
147
+
148
+ type <Name>State int
149
+ const (
150
+ StateDraft <Name>State = iota
151
+ StateDone
152
+ )
153
+
154
+ var (
155
+ ErrAlreadyDone = errors.New("<name>: already done")
156
+ ErrInvalidTransition = errors.New("<name>: invalid state transition")
157
+ )
158
+
159
+ type <Name> struct {
160
+ id string
161
+ version int
162
+ state <Name>State
163
+ }
164
+
165
+ var _ eventstore.Aggregate = (*<Name>)(nil)
166
+
167
+ func New<Name>(id string) *<Name> {
168
+ return &<Name>{id: id, state: StateDraft}
169
+ }
170
+
171
+ func (a *<Name>) ID() string { return a.id }
172
+ func (a *<Name>) Version() int { return a.version }
173
+
174
+ // Handle is the ONLY write entry point. No other public mutators.
175
+ func (a *<Name>) Handle(cmd Command) ([]eventstore.Event, error) {
176
+ switch c := cmd.(type) {
177
+ case DoSomething:
178
+ return a.handleDoSomething(c)
179
+ default:
180
+ return nil, errors.New("<name>: unknown command type")
181
+ }
182
+ }
183
+
184
+ func (a *<Name>) handleDoSomething(cmd DoSomething) ([]eventstore.Event, error) {
185
+ if a.state != StateDraft {
186
+ return nil, ErrAlreadyDone
187
+ }
188
+ return []eventstore.Event{
189
+ &SomethingHappenedV2{ID: a.id, NewField: cmd.Data},
190
+ }, nil
191
+ }
192
+
193
+ // ApplyEvent rebuilds state. Called during Load and after Handle.
194
+ func (a *<Name>) ApplyEvent(e eventstore.Event) error {
195
+ switch e := e.(type) {
196
+ case *SomethingHappenedV2:
197
+ a.state = StateDone
198
+ a.version++
199
+ }
200
+ return nil
201
+ }
202
+
203
+ // Snapshot + ApplySnapshot (always implement, even if empty).
204
+ func (a *<Name>) ApplySnapshot(data []byte) error {
205
+ var snap <name>Snapshot
206
+ json.Unmarshal(data, &snap)
207
+ a.id = snap.ID
208
+ a.version = snap.Version
209
+ a.state = snap.State
210
+ return nil
211
+ }
212
+
213
+ func (a *<Name>) Snapshot() ([]byte, error) {
214
+ return json.Marshal(<name>Snapshot{ID: a.id, Version: a.version, State: a.state})
215
+ }
216
+ ```
217
+
218
+ **Idempotency rule**: return `nil, nil` (not an error) for duplicate commands. The UoW short-circuits when events are empty.
219
+
220
+ ### Step 5: Tests
221
+
222
+ File: `domains/<name>/aggregate_test.go`
223
+
224
+ Uses `github.com/stretchr/testify` — `require` for fatal assertions, `assert` for non-fatal.
225
+
226
+ Five test patterns to cover for every command:
227
+ 1. Happy path: command → correct event type and fields
228
+ 2. Invalid transition: wrong state → error
229
+ 3. Idempotent: apply event, send same command → 0 events, no error
230
+ 4. Event sourcing rebuild: apply N events → correct version
231
+ 5. Snapshot round-trip: snapshot → rebuild → re-snapshot → identical
232
+
233
+ ```go
234
+ package <name>_test
235
+
236
+ import (
237
+ "testing"
238
+
239
+ "github.com/stretchr/testify/assert"
240
+ "github.com/stretchr/testify/require"
241
+
242
+ "github.com/company/ddd-monorepo/domains/<name>"
243
+ )
244
+
245
+ func TestDoSomething(t *testing.T) {
246
+ agg := <name>.New<Name>("id-1")
247
+
248
+ events, err := agg.Handle(<name>.DoSomething{ID: "id-1", Data: "hello"})
249
+ require.NoError(t, err)
250
+ require.Len(t, events, 1)
251
+
252
+ // Type check + cast pattern (IsType doesn't return the value).
253
+ require.IsType(t, &<name>.SomethingHappenedV2{}, events[0])
254
+ e := events[0].(*<name>.SomethingHappenedV2)
255
+ assert.Equal(t, "hello", e.NewField)
256
+ }
257
+
258
+ func TestDoSomething_Idempotent(t *testing.T) {
259
+ agg := <name>.New<Name>("id-1")
260
+
261
+ _, err := agg.Handle(<name>.DoSomething{ID: "id-1"})
262
+ require.NoError(t, err)
263
+ require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
264
+
265
+ events, err := agg.Handle(<name>.DoSomething{ID: "id-1"})
266
+ require.NoError(t, err)
267
+ assert.Empty(t, events)
268
+ }
269
+
270
+ func TestDoSomething_InvalidTransition(t *testing.T) {
271
+ agg := <name>.New<Name>("id-1")
272
+ require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
273
+
274
+ _, err := agg.Handle(<name>.DoSomething{ID: "id-1"})
275
+ require.Error(t, err)
276
+ }
277
+
278
+ func TestEventSourcing_RebuildState(t *testing.T) {
279
+ agg := <name>.New<Name>("id-1")
280
+ require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
281
+ assert.Equal(t, 1, agg.Version())
282
+ }
283
+
284
+ func TestSnapshot_RoundTrip(t *testing.T) {
285
+ agg := <name>.New<Name>("id-1")
286
+ require.NoError(t, agg.ApplyEvent(&<name>.SomethingHappenedV2{ID: "id-1"}))
287
+
288
+ data, err := agg.Snapshot()
289
+ require.NoError(t, err)
290
+
291
+ agg2 := <name>.New<Name>("id-1")
292
+ require.NoError(t, agg2.ApplySnapshot(data))
293
+ assert.Equal(t, 1, agg2.Version())
294
+
295
+ data2, err := agg2.Snapshot()
296
+ require.NoError(t, err)
297
+ assert.Equal(t, string(data), string(data2))
298
+ }
299
+ ```
300
+
301
+ ### Step 6: Domain wiring
302
+
303
+ File: `domains/<name>/setup.go`
304
+
305
+ ```go
306
+ package <name>
307
+
308
+ type Domain struct {
309
+ DB *sql.DB
310
+ Store eventstore.EventStore
311
+ Outbox outbox.Outbox
312
+ Registry eventstore.Registry
313
+ Poller *messaging.Poller
314
+ QueryHandler *QueryHandler
315
+ UoW *uow.UnitOfWork
316
+ }
317
+
318
+ func NewDomain(db *sql.DB, kafka messaging.Publisher, c cache.Cache, tracer trace.Tracer) *Domain {
319
+ d := &Domain{DB: db, QueryHandler: &QueryHandler{DB: db, Cache: c}}
320
+
321
+ d.Registry = eventstore.Registry{
322
+ "<name>.SomethingHappened": func() eventstore.Event { return &SomethingHappenedV2{} },
323
+ }
324
+
325
+ baseStore := eventstore.NewPostgres(db, d.Registry, eventstore.Config{SnapshotFrequency: 100})
326
+ d.Store = eventstore.NewTracingStore(baseStore, tracer)
327
+ d.Outbox = outbox.NewPostgres(db)
328
+ tracedKafka := messaging.NewTracingPublisher(kafka, tracer)
329
+ d.Poller = messaging.NewPoller(db, tracedKafka)
330
+ d.UoW = uow.New(db, d.Store, d.Outbox, tracer)
331
+ return d
332
+ }
333
+
334
+ // ExecuteCommand is a THIN adapter. All complexity is in uow.Execute.
335
+ func (d *Domain) ExecuteCommand(ctx context.Context, cmd Command) ([]eventstore.Event, error) {
336
+ streamID := extractStreamID(cmd)
337
+ return d.UoW.Execute(ctx, streamID,
338
+ func() eventstore.Aggregate { return New<Name>(streamID) },
339
+ func(agg eventstore.Aggregate) ([]eventstore.Event, error) {
340
+ return agg.(*<Name>).Handle(cmd)
341
+ },
342
+ func(events []eventstore.Event) ([]outbox.Record, error) {
343
+ return d.eventsToOutboxRecords(events), nil
344
+ },
345
+ )
346
+ }
347
+
348
+ // streamIDer interface: implement on every command and event.
349
+ type streamIDer interface { streamID() string }
350
+
351
+ func (c DoSomething) streamID() string { return c.ID }
352
+ // ... repeat for every command and event
353
+
354
+ func extractStreamID(cmd Command) string {
355
+ if s, ok := cmd.(streamIDer); ok { return s.streamID() }
356
+ return ""
357
+ }
358
+
359
+ func (d *Domain) eventsToOutboxRecords(events []eventstore.Event) []outbox.Record {
360
+ // Marshal event → outbox.Record with Topic=e.EventType(), Key=u.UUID, Payload=json
361
+ // Copy the pattern from domains/ordering/setup.go
362
+ }
363
+ ```
364
+
365
+ ### Step 7: Projections + Queries
366
+
367
+ **Projections** (`projections.go`): stateless (event, DB) → updated row. One method per event type.
368
+
369
+ ```go
370
+ type <Name>Projection struct { DB *sql.DB }
371
+
372
+ func (p *<Name>Projection) OnSomethingHappened(ctx context.Context, msg messaging.Message) error {
373
+ var e SomethingHappenedV2
374
+ json.Unmarshal(msg.Payload, &e)
375
+ _, err := p.DB.ExecContext(ctx,
376
+ `INSERT INTO <name>_summaries (id, data, updated_at)
377
+ VALUES ($1, $2, now()) ON CONFLICT (id) DO UPDATE SET data = $2, updated_at = now()`,
378
+ e.ID, e.NewField)
379
+ return err
380
+ }
381
+ ```
382
+
383
+ **Queries** (`queries.go`): read from projection tables with read-through cache.
384
+
385
+ ```go
386
+ type QueryHandler struct {
387
+ DB *sql.DB
388
+ Cache cache.Cache
389
+ }
390
+
391
+ func (q *QueryHandler) GetByID(ctx context.Context, id string) (*Summary, error) {
392
+ // 1. Cache hit? return
393
+ // 2. Cache miss → DB query → cache set → return
394
+ }
395
+ ```
396
+
397
+ ### Step 8: Consumers
398
+
399
+ **ProjectionConsumer** (`projection_consumer.go`): consumes OWN domain's topic, routes to projections.
400
+
401
+ ```go
402
+ type ProjectionConsumer struct { domain *Domain; proj *<Name>Projection }
403
+
404
+ func (c *ProjectionConsumer) Handle(ctx context.Context, msg messaging.Message) error {
405
+ switch msg.Headers["event_type"] {
406
+ case "<name>.SomethingHappened":
407
+ c.proj.OnSomethingHappened(ctx, msg)
408
+ return c.invalidateCache(ctx, msg)
409
+ }
410
+ return nil
411
+ }
412
+ ```
413
+
414
+ **KafkaEventConsumer** (`kafka_consumer.go`): consumes OTHER domains' topics, translates to local commands.
415
+
416
+ ```go
417
+ func (c *KafkaEventConsumer) HandleOtherDomainEvent(ctx context.Context, msg messaging.Message) error {
418
+ switch msg.Headers["event_type"] {
419
+ case "otherdomain.EventName":
420
+ var event struct { ID string `json:"id"` }
421
+ json.Unmarshal(msg.Payload, &event)
422
+ _, err := c.domain.ExecuteCommand(ctx, DoSomething{ID: event.ID})
423
+ return err
424
+ }
425
+ return nil
426
+ }
427
+ ```
428
+
429
+ ### Step 9: HTTP handlers
430
+
431
+ File: `domains/<name>/http_handler.go`
432
+
433
+ Gin handlers that call ExecuteCommand (write) or QueryHandler (read). Map domain errors to HTTP status codes (422, 409, 500). See `domains/ordering/http_handler.go` for the exact pattern.
434
+
435
+ ### Step 10: main.go
436
+
437
+ File: `domains/<name>/cmd/server/main.go`
438
+
439
+ ```go
440
+ package main
441
+
442
+ import (
443
+ "context"
444
+ "github.com/company/ddd-monorepo/domains/<name>"
445
+ "github.com/company/ddd-monorepo/pkg/cli"
446
+ "github.com/company/ddd-monorepo/pkg/messaging"
447
+ )
448
+
449
+ func main() {
450
+ cli.New(cli.Opts{
451
+ Name: "<name>",
452
+ Description: "<Name> domain service",
453
+ MigrationsDir: "domains/<name>/migrations",
454
+ Serve: runServer,
455
+ }).Run()
456
+ }
457
+
458
+ func runServer(ctx context.Context, deps cli.Dependencies) error {
459
+ domain := <name>.NewDomain(deps.DB, deps.Kafka, nil, deps.Tracer)
460
+
461
+ go func() { domain.Poller.Start(ctx) }()
462
+
463
+ // Own-domain projection consumer
464
+ projConsumer := messaging.NewKafkaConsumer(deps.Config.KafkaBrokers, "<name>.events", "<name>-projections")
465
+ tracedProj := messaging.NewTracedConsumer(projConsumer, deps.Tracer)
466
+ projHandler := <name>.NewProjectionConsumer(domain)
467
+ go func() { tracedProj.Consume(ctx, projHandler.Handle) }()
468
+
469
+ // Cross-domain consumers (one per external domain)
470
+
471
+ // Gin HTTP server with otelgin middleware
472
+
473
+ <-ctx.Done()
474
+ // Graceful shutdown
475
+ }
476
+ ```
477
+
478
+ ### Step 11: Migration
479
+
480
+ File: `domains/<name>/migrations/001_create_<name>.sql`
481
+
482
+ ```sql
483
+ CREATE TABLE IF NOT EXISTS events (...) -- same schema as other domains
484
+ CREATE TABLE IF NOT EXISTS snapshots (...)
485
+ CREATE TABLE IF NOT EXISTS outbox (...)
486
+ CREATE TABLE IF NOT EXISTS <name>_summaries (...) -- domain-specific projection
487
+ ```
488
+
489
+ Copy the DDL from `domains/ordering/migrations/` or `domains/inventory/migrations/001_create_inventory.sql`. The events, snapshots, and outbox tables are identical across domains.
490
+
491
+ ### Step 12: Add to Makefile
492
+
493
+ ```
494
+ run-<name>:
495
+ cd domains/<name> && ... go run ./cmd/server serve
496
+
497
+ migrate-<name>:
498
+ cd domains/<name> && ... go run ./cmd/server migrate --dir migrations
499
+ ```
500
+
501
+ ## Cross-Domain Communication Pattern
502
+
503
+ ```
504
+ Domain A emits events → outbox table → Poller → Kafka topic "A.events"
505
+ → Domain B's KafkaEventConsumer.HandleAEvent()
506
+ → deserialize foreign event → translate to local command
507
+ → domain.ExecuteCommand → aggregate.Handle → event store + outbox
508
+ ```
509
+
510
+ The consumer is a pure adapter: deserialize → map → ExecuteCommand. No business logic.
511
+
512
+ ## Wiring Order in main.go (INVARIANT)
513
+
514
+ The `cli` package handles this via `runServe`. If bypassing cli, the order is:
515
+ 1. telemetry.Init
516
+ 2. db.Register + sql.Open
517
+ 3. messaging.NewKafkaPublisher
518
+ 4. domain.NewDomain
519
+ 5. Poller.Start (goroutine)
520
+ 6. Own-domain projection consumer (goroutine)
521
+ 7. Cross-domain consumers (goroutines)
522
+ 8. Policies consumer (goroutine)
523
+ 9. Gin HTTP server with otelgin middleware
524
+ 10. signal.NotifyContext → graceful shutdown
525
+
526
+ ## Guardrails
527
+
528
+ - **No business logic outside the aggregate**. If you write `if state == ...` in a projection, policy, or handler, move it to `aggregate.go`.
529
+ - **ExecuteCommand is always a thin adapter**. It delegates to `uow.Execute`. Never put tx logic there.
530
+ - **Projections are stateless**. `(event, DB) → updated row`. No if-else on business state.
531
+ - **Idempotency returns nil, nil**. Never return an error for a duplicate command.
532
+ - **Every event type must be in the Registry**. Missing types cause "unknown event type" at runtime.
533
+ - **Snapshot + ApplySnapshot always implemented**. Even if empty initially.
534
+ - **Upcasting on read**. Always deserialize into the latest version (V2). V1→V2 in UnmarshalJSON.
@@ -1,20 +0,0 @@
1
- ---
2
- name: code-review
3
- description: Review changes since a fixed point (commit, branch, or tag) along two axes — Standards (does the code follow this repo's conventions?) and Spec (does it match the originating issue/PRD?). Use when reviewing a branch, PR, or work-in-progress changes.
4
- ---
5
-
6
- Review changes between `HEAD` and a fixed point the user supplies.
7
-
8
- ## Process
9
-
10
- 1. **Pin the fixed point** — ask the user for a commit SHA, branch, tag, or `main`. Resolve it and confirm the diff is non-empty.
11
-
12
- 2. **Identify the spec** — look for issue references in commit messages (`#123`, `Closes #45`), PRD files under `docs/` or `specs/`, or ask the user.
13
-
14
- 3. **Review on two axes in parallel:**
15
- - **Standards** — does the diff follow documented coding conventions?
16
- - **Spec** — does it implement what was asked?
17
-
18
- Run both as sub-agents so they don't pollute each other's context.
19
-
20
- 4. **Report** — present findings under `## Standards` and `## Spec` headings. Keep axes separate — a change can pass one and fail the other.
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "Code Review"
3
- short_description: "Review a diff on standards and spec"
@@ -1,32 +0,0 @@
1
- ---
2
- name: diagnosing-bugs
3
- description: Systematic diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose," "debug this," or reports something broken, throwing, failing, or slow.
4
- ---
5
-
6
- ## Diagnosis Loop
7
-
8
- Follow this cycle until the root cause is identified:
9
-
10
- ### 1. Reproduce
11
- - Confirm the bug is reproducible.
12
- - Find the minimal reproduction steps.
13
- - Note the exact observed vs expected behavior.
14
-
15
- ### 2. Isolate
16
- - Bisect the codebase — when did this start?
17
- - Narrow the scope: which module, function, or line?
18
- - Use `git bisect` for regressions.
19
-
20
- ### 3. Hypothesize
21
- - Propose a specific root-cause hypothesis.
22
- - Design a test or probe that would confirm or refute it.
23
-
24
- ### 4. Test
25
- - Run the probe (log, breakpoint, test).
26
- - If confirmed → fix. If refuted → return to step 3.
27
-
28
- ### Guidelines
29
- - **One hypothesis at a time.** Don't shotgun-debug.
30
- - **Read the error message** — it often contains the answer.
31
- - **Check recent changes first** — most bugs come from the last change.
32
- - **Don't guess and check** — each probe should be deliberate.
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "Diagnosing Bugs"
3
- short_description: "Systematic diagnosis for bugs and performance issues"
@@ -1,27 +0,0 @@
1
- ---
2
- name: tdd
3
- description: Test-driven development loop. Write a failing test first, then implement the minimal code to make it pass, then refactor. Use when building features or fixing bugs test-first, or when the user mentions "red-green-refactor".
4
- ---
5
-
6
- ## Red-Green-Refactor Loop
7
-
8
- ### 1. Red — Write a failing test
9
- - Write the smallest test that captures the desired behavior.
10
- - The test must fail before any implementation code exists.
11
- - Use the project's existing test framework and conventions.
12
-
13
- ### 2. Green — Make it pass
14
- - Write the **minimum** code to make the test pass.
15
- - Don't over-engineer — no refactoring, no "future-proofing."
16
- - If the test still fails, diagnose and fix the implementation.
17
-
18
- ### 3. Refactor
19
- - Clean up both test and production code.
20
- - Remove duplication, improve names, extract helpers.
21
- - Run tests to confirm nothing broke.
22
-
23
- ### Guidelines
24
- - **Test behavior, not implementation.** Avoid coupling tests to internal details.
25
- - **One assertion per test** where practical.
26
- - **Tests are documentation** — name them clearly.
27
- - **Fast feedback** — keep tests quick; mock external dependencies.
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "TDD"
3
- short_description: "Test-driven development with red-green-refactor loop"
@@ -1,25 +0,0 @@
1
- ---
2
- name: grilling
3
- description: Grill the user relentlessly about a plan, decision, or idea to stress-test their thinking. Use when the user wants to pressure-test an idea, or mentions "grill me," "stress test," or "poke holes in this."
4
- ---
5
-
6
- ## Grilling Session
7
-
8
- The goal is to find flaws, gaps, and unstated assumptions in the user's plan — not to be contrarian but to make the plan stronger.
9
-
10
- ### Rules
11
- 1. **Ask one question at a time.** Let the user answer before moving to the next.
12
- 2. **Probe the edges.** What happens at scale? Under failure? With bad data?
13
- 3. **Challenge assumptions.** "What makes you sure that...?"
14
- 4. **Surface trade-offs.** "If you optimize for X, what happens to Y?"
15
- 5. **Don't offer solutions yet.** The grilling phase is for finding problems.
16
-
17
- ### Areas to probe
18
- - **Scope:** What's in and out? What's the MVP vs nice-to-have?
19
- - **Failure modes:** What breaks? What's the rollback plan?
20
- - **Dependencies:** What does this depend on? What depends on this?
21
- - **Alternatives:** What else did you consider? Why was this chosen?
22
- - **Success criteria:** How will you know it worked?
23
-
24
- ### End condition
25
- Stop when the user says "that's enough" or when you've asked 5-8 questions without finding new gaps.
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "Grilling"
3
- short_description: "Relentlessly question a plan to find gaps and assumptions"
@@ -1,22 +0,0 @@
1
- ---
2
- name: handoff
3
- description: Create a structured handoff document summarizing current work, decisions made, and next steps. Use when the user wants to hand off work to another developer or agent, or save context before switching tasks.
4
- ---
5
-
6
- ## Handoff Document
7
-
8
- Create a concise document that captures everything the next person (human or agent) needs to continue the work.
9
-
10
- ### Structure
11
-
12
- 1. **What we're building** — one sentence on the goal.
13
- 2. **Current state** — what's done, what's in progress, what's blocked.
14
- 3. **Key decisions** — what was chosen, why, and what alternatives were considered.
15
- 4. **Files to look at** — the 3-5 most important files.
16
- 5. **Next steps** — the immediate next action, clearly scoped.
17
- 6. **Gotchas** — surprising constraints, workarounds, or known issues.
18
-
19
- ### Guidelines
20
- - Write for someone with context about the project but not this specific task.
21
- - Be honest about what's messy or incomplete.
22
- - Don't bury the lede — put the most important info first.
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "Handoff"
3
- short_description: "Create a structured handoff document for context transfer"
@@ -1,32 +0,0 @@
1
- ---
2
- name: prototype
3
- description: Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
4
- ---
5
-
6
- ## Throwaway Prototyping
7
-
8
- The prototype exists to answer **one question**. It should be built as fast as possible and thrown away after the question is answered.
9
-
10
- ### 1. Define the question
11
- - What exactly are we trying to learn?
12
- - What would a "yes" or "no" answer look like?
13
-
14
- ### 2. Build the minimum
15
- - Only the code needed to answer the question.
16
- - No error handling, no tests, no polish.
17
- - Fake anything that isn't the thing being tested.
18
-
19
- ### 3. Run the experiment
20
- - Exercise the prototype with the specific scenario.
21
- - Record what happened.
22
-
23
- ### 4. Decide
24
- - Question answered? → Report findings, throw away code.
25
- - Question not answered? → Refine the prototype or the question.
26
-
27
- ### Red flags (abort)
28
- - "Let me just add one more feature..."
29
- - "This prototype is actually pretty good, maybe we should..."
30
- - More than 30 minutes spent on the prototype.
31
-
32
- **Reminder:** The prototype is garbage by design. The value is the learning, not the code.
@@ -1,3 +0,0 @@
1
- interface:
2
- display_name: "Prototype"
3
- short_description: "Build a throwaway prototype to answer one design question"