ai-workflow-kit
Advanced tools
+117
-80
@@ -8,2 +8,4 @@ #!/usr/bin/env node | ||
| * npx ai-workflow-kit --yes → install everything without prompting | ||
| * npx ai-workflow-kit --local → install into .claude/ of the current project | ||
| * npx ai-workflow-kit --global → install into ~/.claude/ (default) | ||
| * npx ai-workflow-kit --skills → all skills and agents only | ||
@@ -38,11 +40,2 @@ * npx ai-workflow-kit --hooks → all hooks only | ||
| // ─── Paths ─────────────────────────────────────────────────────────────────── | ||
| const __dir = path.dirname(fileURLToPath(import.meta.url)) | ||
| const REPO_ROOT = path.resolve(__dir, '..') | ||
| const CLAUDE = path.join(os.homedir(), '.claude') | ||
| const SKILLS_DST = path.join(CLAUDE, 'skills') | ||
| const AGENTS_DST = path.join(CLAUDE, 'agents') | ||
| const HOOKS_DST = path.join(CLAUDE, 'hooks') | ||
| const SETTINGS = path.join(CLAUDE, 'settings.json') | ||
| // ─── Args ──────────────────────────────────────────────────────────────────── | ||
@@ -55,3 +48,22 @@ const args = process.argv.slice(2) | ||
| const LIST = args.includes('--list') | ||
| const FORCE_LOCAL = args.includes('--local') | ||
| const FORCE_GLOBAL = args.includes('--global') | ||
| // ─── Paths ─────────────────────────────────────────────────────────────────── | ||
| const __dir = path.dirname(fileURLToPath(import.meta.url)) | ||
| const REPO_ROOT = path.resolve(__dir, '..') | ||
| function resolvePaths(isLocal) { | ||
| const base = isLocal | ||
| ? path.join(process.cwd(), '.claude') | ||
| : path.join(os.homedir(), '.claude') | ||
| return { | ||
| SKILLS_DST: path.join(base, 'skills'), | ||
| AGENTS_DST: path.join(base, 'agents'), | ||
| HOOKS_DST: path.join(base, 'hooks'), | ||
| SETTINGS: path.join(base, 'settings.json'), | ||
| base, | ||
| } | ||
| } | ||
| // ─── Discovery ─────────────────────────────────────────────────────────────── | ||
@@ -128,83 +140,90 @@ | ||
| // ─── Selection menu ────────────────────────────────────────────────────────── | ||
| // ─── Selection — step 1: categories ───────────────────────────────────────── | ||
| function renderMenu(allSkills, allAgents, allHooks) { | ||
| const numWidth = String(allSkills.length + allAgents.length + allHooks.length).length | ||
| async function selectCategories(allSkills, allAgents, allHooks) { | ||
| const skillNames = allSkills.map(s => s.name).join(', ') | ||
| const agentNames = allAgents.map(a => a.name).join(', ') | ||
| const hookNames = allHooks.map(h => path.basename(h, '.sh')).join(', ') | ||
| function grid(items, startNum) { | ||
| const cols = 3 | ||
| const colW = 14 | ||
| for (let i = 0; i < items.length; i += cols) { | ||
| const row = items.slice(i, i + cols) | ||
| const line = row.map(({ num, name }) => { | ||
| const label = `${String(num).padStart(numWidth)} ${c.cyan}${name}${c.reset}` | ||
| // strip ANSI for length calculation | ||
| const rawLen = String(num).padStart(numWidth).length + 2 + name.length | ||
| return label + ' '.repeat(Math.max(1, colW - rawLen)) | ||
| }).join(' ') | ||
| console.log(' ' + line) | ||
| } | ||
| console.log(`\n${c.bold} What would you like to install?${c.reset}\n`) | ||
| console.log(` ${c.cyan}s${c.reset} Skills ${c.dim}${skillNames}${c.reset}`) | ||
| console.log(` ${c.cyan}ag${c.reset} Agents ${c.dim}${agentNames}${c.reset}`) | ||
| console.log(` ${c.cyan}h${c.reset} Hooks ${c.dim}${hookNames}${c.reset}`) | ||
| console.log(` ${c.cyan}a${c.reset} All of the above`) | ||
| console.log() | ||
| const input = await prompt(` Enter categories ${c.dim}(e.g. "s h" or "a")${c.reset}: `) | ||
| const tokens = input.toLowerCase().split(/[\s,]+/).filter(Boolean) | ||
| if (!tokens.length) return { wantSkills: false, wantAgents: false, wantHooks: false } | ||
| if (tokens.includes('a') || tokens.includes('all')) { | ||
| return { wantSkills: true, wantAgents: true, wantHooks: true } | ||
| } | ||
| let n = 1 | ||
| const skillItems = allSkills.map(s => ({ num: n++, name: s.name, item: s, type: 'skill' })) | ||
| const agentItems = allAgents.map(a => ({ num: n++, name: a.name, item: a, type: 'agent' })) | ||
| const hookItems = allHooks.map(h => ({ num: n++, name: path.basename(h, '.sh'), item: h, type: 'hook' })) | ||
| return { | ||
| wantSkills: tokens.includes('s') || tokens.includes('skills'), | ||
| wantAgents: tokens.includes('ag') || tokens.includes('agents'), | ||
| wantHooks: tokens.includes('h') || tokens.includes('hooks'), | ||
| } | ||
| } | ||
| console.log(`\n${c.bold} What would you like to install?${c.reset}\n`) | ||
| // ─── Selection — step 2: items within a category ──────────────────────────── | ||
| console.log(` ${c.bold}SKILLS${c.reset}`) | ||
| grid(skillItems) | ||
| console.log() | ||
| async function selectItemsInCategory(items, getName) { | ||
| const numWidth = String(items.length).length | ||
| const cols = 3 | ||
| const colW = 16 | ||
| console.log(` ${c.bold}AGENTS${c.reset}`) | ||
| grid(agentItems) | ||
| console.log() | ||
| console.log(` ${c.bold}HOOKS${c.reset}`) | ||
| grid(hookItems) | ||
| for (let i = 0; i < items.length; i += cols) { | ||
| const row = items.slice(i, i + cols) | ||
| const line = row.map((item, j) => { | ||
| const n = i + j + 1 | ||
| const name = getName(item) | ||
| const label = ` ${c.dim}${String(n).padStart(numWidth)}${c.reset} ${c.cyan}${name}${c.reset}` | ||
| const rawLen = 2 + String(n).padStart(numWidth).length + 2 + name.length | ||
| return label + ' '.repeat(Math.max(1, colW - rawLen)) | ||
| }).join('') | ||
| console.log(line) | ||
| } | ||
| console.log() | ||
| console.log(` ${c.dim}Shortcuts: [a] all [s] all skills [ag] all agents [h] all hooks${c.reset}`) | ||
| console.log() | ||
| const input = await prompt(` Enter numbers or ${c.cyan}Enter${c.reset} for all: `) | ||
| return { skillItems, agentItems, hookItems } | ||
| if (!input.trim()) return items | ||
| const selected = new Set() | ||
| for (const token of input.split(/[\s,]+/).filter(Boolean)) { | ||
| const n = parseInt(token, 10) | ||
| if (n >= 1 && n <= items.length) selected.add(items[n - 1]) | ||
| } | ||
| return [...selected] | ||
| } | ||
| async function selectItems(allSkills, allAgents, allHooks) { | ||
| const { skillItems, agentItems, hookItems } = renderMenu(allSkills, allAgents, allHooks) | ||
| const allItems = [...skillItems, ...agentItems, ...hookItems] | ||
| const { wantSkills, wantAgents, wantHooks } = await selectCategories(allSkills, allAgents, allHooks) | ||
| const input = await prompt(` Enter numbers or shortcuts ${c.dim}(e.g. "1 3 5" or "s h")${c.reset}: `) | ||
| if (!wantSkills && !wantAgents && !wantHooks) return { skills: [], agents: [], hooks: [] } | ||
| if (!input) return { skills: [], agents: [], hooks: [] } | ||
| let skills = [] | ||
| let agents = [] | ||
| let hooks = [] | ||
| const tokens = input.toLowerCase().split(/[\s,]+/).filter(Boolean) | ||
| const skills = new Set() | ||
| const agents = new Set() | ||
| const hooks = new Set() | ||
| if (wantSkills && allSkills.length > 0) { | ||
| console.log(`\n ${c.bold}SKILLS${c.reset} — pick specific or Enter for all`) | ||
| skills = await selectItemsInCategory(allSkills, s => s.name) | ||
| } | ||
| for (const token of tokens) { | ||
| if (token === 'a' || token === 'all') { | ||
| return { skills: allSkills, agents: allAgents, hooks: allHooks } | ||
| } | ||
| if (token === 's' || token === 'skills') { | ||
| allSkills.forEach(s => skills.add(s)); continue | ||
| } | ||
| if (token === 'ag' || token === 'agents') { | ||
| allAgents.forEach(a => agents.add(a)); continue | ||
| } | ||
| if (token === 'h' || token === 'hooks') { | ||
| allHooks.forEach(h => hooks.add(h)); continue | ||
| } | ||
| const num = parseInt(token, 10) | ||
| const found = allItems.find(i => i.num === num) | ||
| if (found) { | ||
| if (found.type === 'skill') skills.add(found.item) | ||
| if (found.type === 'agent') agents.add(found.item) | ||
| if (found.type === 'hook') hooks.add(found.item) | ||
| } | ||
| if (wantAgents && allAgents.length > 0) { | ||
| console.log(`\n ${c.bold}AGENTS${c.reset} — pick specific or Enter for all`) | ||
| agents = await selectItemsInCategory(allAgents, a => a.name) | ||
| } | ||
| return { skills: [...skills], agents: [...agents], hooks: [...hooks] } | ||
| if (wantHooks && allHooks.length > 0) { | ||
| console.log(`\n ${c.bold}HOOKS${c.reset} — pick specific or Enter for all`) | ||
| hooks = await selectItemsInCategory(allHooks, h => path.basename(h, '.sh')) | ||
| } | ||
| return { skills, agents, hooks } | ||
| } | ||
@@ -215,3 +234,3 @@ | ||
| console.log(`${c.bold} AI Workflow Kit${c.reset} ${c.dim}v${JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf8')).version}${c.reset}`) | ||
| console.log(` ${c.dim}Skills · Agents · Hooks for Claude Code, Cursor & Copilot${c.reset}`) | ||
| console.log(` ${c.dim}Skills · Agents · Hooks for Claude Code, Cursor, Antigravity & Copilot${c.reset}`) | ||
| console.log() | ||
@@ -237,2 +256,27 @@ | ||
| // ─── Resolve scope (local vs global) ───────────────────────────────────────── | ||
| let isLocal | ||
| if (FORCE_LOCAL) { | ||
| isLocal = true | ||
| } else if (FORCE_GLOBAL) { | ||
| isLocal = false | ||
| } else { | ||
| // Interactive scope question | ||
| console.log(` ${c.bold}Where do you want to install?${c.reset}`) | ||
| console.log(` ${c.cyan}g${c.reset} Global ${c.dim}~/.claude/ — available in all projects${c.reset}`) | ||
| console.log(` ${c.cyan}l${c.reset} Local ${c.dim}.claude/ (here) — this project only${c.reset}`) | ||
| console.log() | ||
| const scopeAns = YES ? 'g' : await prompt(` ${c.dim}[g/l]${c.reset} `) | ||
| isLocal = scopeAns.toLowerCase() === 'l' | ||
| console.log() | ||
| } | ||
| const { SKILLS_DST, AGENTS_DST, HOOKS_DST, SETTINGS, base: CLAUDE_BASE } = resolvePaths(isLocal) | ||
| const scopeLabel = isLocal | ||
| ? `local ${c.dim}(.claude/)${c.reset}` | ||
| : `global ${c.dim}(~/.claude/)${c.reset}` | ||
| info(`Scope: ${scopeLabel}`) | ||
| // ─── Uninstall ─────────────────────────────────────────────────────────────── | ||
@@ -285,10 +329,7 @@ if (UNINSTALL) { | ||
| if (SKILLS_ONLY) { | ||
| // --skills wins over --yes: scope to skills + agents only | ||
| selectedSkills = allSkills | ||
| selectedAgents = allAgents | ||
| } else if (HOOKS_ONLY) { | ||
| // --hooks wins over --yes: scope to hooks only | ||
| selectedHooks = allHooks | ||
| } else if (YES) { | ||
| // --yes with no scope flag: install everything | ||
| selectedSkills = allSkills | ||
@@ -298,3 +339,2 @@ selectedAgents = allAgents | ||
| } else { | ||
| // Interactive selection menu | ||
| const sel = await selectItems(allSkills, allAgents, allHooks) | ||
@@ -316,3 +356,2 @@ selectedSkills = sel.skills | ||
| // Skills → ~/.claude/skills/ | ||
| if (selectedSkills.length > 0) { | ||
@@ -338,3 +377,2 @@ step('Installing skills...') | ||
| // Agents → ~/.claude/agents/ (flat .md files) | ||
| if (selectedAgents.length > 0) { | ||
@@ -358,3 +396,2 @@ step('Installing agents...') | ||
| // Hooks → ~/.claude/hooks/ + configure settings.json | ||
| if (selectedHooks.length > 0) { | ||
@@ -375,5 +412,5 @@ step('Installing hooks...') | ||
| if (!fs.existsSync(SETTINGS)) { | ||
| const template = fs.readFileSync(path.join(REPO_ROOT, 'hooks', 'settings.template.json'), 'utf8') | ||
| const template = fs.readFileSync(path.join(REPO_ROOT, 'hooks', 'settings.template.json'), 'utf8') | ||
| const hooksDstJson = HOOKS_DST.replace(/\\/g, '/') | ||
| const configured = template.replace(/~\/\.claude\/hooks/g, hooksDstJson) | ||
| const configured = template.replace(/~\/\.claude\/hooks/g, hooksDstJson) | ||
| try { | ||
@@ -380,0 +417,0 @@ const cleaned = configured |
+1
-1
| { | ||
| "name": "ai-workflow-kit", | ||
| "version": "2.2.0-beta.1", | ||
| "version": "2.2.0-beta.2", | ||
| "description": "Skills, agents & hooks for Claude Code, Cursor, GitHub Copilot, and Google Antigravity", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+9
-0
@@ -14,5 +14,14 @@ # AI Workflow Kit | ||
| Or pin a specific version as a dev dependency (it's a dev tool, not a runtime dependency): | ||
| ```bash | ||
| npm i -D ai-workflow-kit@2.2.0-beta.1 | ||
| npx ai-workflow-kit | ||
| ``` | ||
| Restart Claude Code. You'll have `/ak:commit`, `/ak:pr`, `/ak:plan`, `/ak:debug`, `/ak:review`, `/ak:vibe-audit`, `/ak:frontend`, `/ak:api`, `/ak:test`, `/ak:refactor`, and `/ak:docs` available — plus 5 automatic hooks. | ||
| ```bash | ||
| npx ai-workflow-kit --global # install into ~/.claude/ — all projects (default) | ||
| npx ai-workflow-kit --local # install into .claude/ — this project only | ||
| npx ai-workflow-kit --skills # skills and agents only | ||
@@ -19,0 +28,0 @@ npx ai-workflow-kit --hooks # hooks only |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
535156
0.33%1820
1.56%203
4.64%15
-6.25%