@chris1807/claude-kit 2.0.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.
- package/LICENSE +21 -0
- package/README.md +821 -0
- package/bin/cli.js +521 -0
- package/package.json +50 -0
- package/templates/agents/global/api-tester.md +75 -0
- package/templates/agents/global/azure-ops.md +59 -0
- package/templates/agents/global/backend.md +245 -0
- package/templates/agents/global/build-validator.md +50 -0
- package/templates/agents/global/frontend.md +254 -0
- package/templates/agents/global/legacy.md +218 -0
- package/templates/agents/global/lint-checker.md +86 -0
- package/templates/agents/global/manager.md +138 -0
- package/templates/agents/global/mockup.md +95 -0
- package/templates/agents/global/reviewer.md +149 -0
- package/templates/agents/global/security-auditor.md +74 -0
- package/templates/agents/global/test-runner.md +98 -0
- package/templates/agents/global/uat-generator.md +107 -0
- package/templates/agents/project/db-admin.md +106 -0
- package/templates/agents/project/deployer.md +113 -0
- package/templates/agents/project/devops-tracker.md +101 -0
- package/templates/commands/add-to-release.md +55 -0
- package/templates/commands/cherry-pick.md +96 -0
- package/templates/commands/cleanup-branches.md +73 -0
- package/templates/commands/create-release.md +65 -0
- package/templates/commands/deploy-release.md +147 -0
- package/templates/commands/deploy.md +65 -0
- package/templates/commands/explain.md +49 -0
- package/templates/commands/implement.md +170 -0
- package/templates/commands/promote.md +71 -0
- package/templates/commands/quote.md +39 -0
- package/templates/commands/review.md +32 -0
- package/templates/commands/rework.md +158 -0
- package/templates/commands/rollback.md +106 -0
- package/templates/commands/status.md +111 -0
- package/templates/hooks/auto-format.sh +46 -0
- package/templates/hooks/protected-files.sh +52 -0
- package/templates/hooks/secret-blocker.sh +68 -0
- package/templates/hooks/self-improve.sh +7 -0
- package/templates/hooks/sensitive-data-blocker.sh +43 -0
- package/templates/hooks/sensitive-data-mcp-blocker.sh +40 -0
- package/templates/hooks/sensitive-data-output-blocker.sh +63 -0
- package/templates/hooks/test-on-change.sh +46 -0
- package/templates/hooks/uat-reminder.sh +9 -0
- package/templates/infrastructure/CLAUDE-WORKFLOW.md +274 -0
- package/templates/infrastructure/azure-pipelines-template.yml +199 -0
- package/templates/infrastructure/mcp.json +35 -0
- package/templates/infrastructure/settings.json +94 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join, basename, resolve } from 'path';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
13
|
+
const GLOBAL_CLAUDE_DIR = join(process.env.HOME || process.env.USERPROFILE, '.claude');
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// CLI Arguments
|
|
17
|
+
// ============================================================================
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const showHelp = args.includes('--help') || args.includes('-h');
|
|
20
|
+
const globalOnly = args.includes('--global-only');
|
|
21
|
+
const installAll = args.includes('--all');
|
|
22
|
+
const dbFlag = args.find(a => a.startsWith('--db='))?.split('=')[1] || null;
|
|
23
|
+
const targetArg = args.find(a => !a.startsWith('--') && a !== 'init');
|
|
24
|
+
|
|
25
|
+
if (showHelp) {
|
|
26
|
+
console.log(`
|
|
27
|
+
${chalk.blue('Claude Kit')}
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
npx @chris1807/claude-kit init [target-dir] Interactive install
|
|
31
|
+
npx @chris1807/claude-kit init --all Install everything
|
|
32
|
+
npx @chris1807/claude-kit init --global-only Global agents only
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--all Install all components (skips identical files, still asks DB type)
|
|
36
|
+
--all --db=mongo Install all with MongoDB (no prompts at all)
|
|
37
|
+
--all --db=mssql Install all with SQL Server
|
|
38
|
+
--all --db=azuresql Install all with Azure SQL
|
|
39
|
+
--all --db=postgres Install all with PostgreSQL
|
|
40
|
+
--global-only Only install global agents to ~/.claude/agents/
|
|
41
|
+
--help, -h Show this help
|
|
42
|
+
`);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Banner
|
|
48
|
+
// ============================================================================
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.blue('╔══════════════════════════════════════════════════╗'));
|
|
51
|
+
console.log(chalk.blue('║') + chalk.bold.white(' Claude Kit Installer ') + chalk.blue('║'));
|
|
52
|
+
console.log(chalk.blue('╚══════════════════════════════════════════════════╝'));
|
|
53
|
+
console.log('');
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Helper: install file with overwrite check
|
|
57
|
+
// ============================================================================
|
|
58
|
+
async function installFile(src, dest, label) {
|
|
59
|
+
if (await fs.pathExists(dest)) {
|
|
60
|
+
const srcContent = await fs.readFile(src, 'utf8');
|
|
61
|
+
const destContent = await fs.readFile(dest, 'utf8');
|
|
62
|
+
if (srcContent === destContent) {
|
|
63
|
+
console.log(chalk.gray(` = ${label} (identical, skipped)`));
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (installAll) {
|
|
67
|
+
// --all mode: overwrite non-identical files silently
|
|
68
|
+
await fs.ensureDir(dirname(dest));
|
|
69
|
+
await fs.copy(src, dest);
|
|
70
|
+
console.log(chalk.green(` ✓ ${label} (updated)`));
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const { action } = await inquirer.prompt([{
|
|
74
|
+
type: 'list',
|
|
75
|
+
name: 'action',
|
|
76
|
+
message: `${label} already exists:`,
|
|
77
|
+
choices: [
|
|
78
|
+
{ name: 'Overwrite', value: 'overwrite' },
|
|
79
|
+
{ name: 'Skip', value: 'skip' },
|
|
80
|
+
],
|
|
81
|
+
}]);
|
|
82
|
+
if (action === 'skip') {
|
|
83
|
+
console.log(chalk.gray(` ⊘ ${label} (skipped)`));
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await fs.ensureDir(dirname(dest));
|
|
88
|
+
await fs.copy(src, dest);
|
|
89
|
+
console.log(chalk.green(` ✓ ${label}`));
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Step 1: Global Agents (always)
|
|
95
|
+
// ============================================================================
|
|
96
|
+
async function installGlobalAgents() {
|
|
97
|
+
console.log(chalk.yellow.bold('\n📦 Global Agents → ~/.claude/agents/\n'));
|
|
98
|
+
|
|
99
|
+
const globalAgentsDir = join(GLOBAL_CLAUDE_DIR, 'agents');
|
|
100
|
+
await fs.ensureDir(globalAgentsDir);
|
|
101
|
+
|
|
102
|
+
const agents = await fs.readdir(join(TEMPLATES_DIR, 'agents', 'global'));
|
|
103
|
+
for (const file of agents) {
|
|
104
|
+
if (file.endsWith('.md')) {
|
|
105
|
+
await installFile(
|
|
106
|
+
join(TEMPLATES_DIR, 'agents', 'global', file),
|
|
107
|
+
join(globalAgentsDir, file),
|
|
108
|
+
file
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Main Interactive Installer
|
|
116
|
+
// ============================================================================
|
|
117
|
+
async function main() {
|
|
118
|
+
// Install global agents first
|
|
119
|
+
await installGlobalAgents();
|
|
120
|
+
|
|
121
|
+
if (globalOnly) {
|
|
122
|
+
console.log(chalk.green('\n✅ Global agents installed!\n'));
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Target Directory ──────────────────────────────────────────────────
|
|
127
|
+
let targetDir;
|
|
128
|
+
if (targetArg) {
|
|
129
|
+
targetDir = resolve(targetArg);
|
|
130
|
+
} else if (installAll) {
|
|
131
|
+
targetDir = process.cwd();
|
|
132
|
+
} else {
|
|
133
|
+
const { dir } = await inquirer.prompt([{
|
|
134
|
+
type: 'input',
|
|
135
|
+
name: 'dir',
|
|
136
|
+
message: 'Target project directory:',
|
|
137
|
+
default: process.cwd(),
|
|
138
|
+
}]);
|
|
139
|
+
targetDir = resolve(dir);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!await fs.pathExists(targetDir)) {
|
|
143
|
+
console.log(chalk.red(`\n ✗ Directory does not exist: ${targetDir}\n`));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.gray(`\n Target: ${targetDir}\n`));
|
|
148
|
+
|
|
149
|
+
// ── Component Selection ───────────────────────────────────────────────
|
|
150
|
+
let components;
|
|
151
|
+
if (installAll) {
|
|
152
|
+
components = ['agents', 'hooks', 'commands', 'mcp', 'settings', 'workflow', 'gitignore'];
|
|
153
|
+
} else {
|
|
154
|
+
const { selected } = await inquirer.prompt([{
|
|
155
|
+
type: 'checkbox',
|
|
156
|
+
name: 'selected',
|
|
157
|
+
message: 'Select components to install:',
|
|
158
|
+
choices: [
|
|
159
|
+
{ name: 'Project Agents (deployer, db-admin, devops-tracker)', value: 'agents', checked: true },
|
|
160
|
+
{ name: 'Hooks (secret blocker, auto-format, test suggestions)', value: 'hooks', checked: true },
|
|
161
|
+
{ name: 'Slash Commands (11 commands — implement, review, deploy, releases, cherry-pick, promote, rollback, status, cleanup)', value: 'commands', checked: true },
|
|
162
|
+
{ name: 'MCP Servers (Playwright, DB, Teams, Stripe, Azure)', value: 'mcp', checked: true },
|
|
163
|
+
{ name: 'Settings (hook registration)', value: 'settings', checked: true },
|
|
164
|
+
{ name: 'CLAUDE.md Workflow Section', value: 'workflow', checked: true },
|
|
165
|
+
{ name: '.gitignore Updates', value: 'gitignore', checked: true },
|
|
166
|
+
],
|
|
167
|
+
}]);
|
|
168
|
+
components = selected;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Project Agents ────────────────────────────────────────────────────
|
|
172
|
+
if (components.includes('agents')) {
|
|
173
|
+
console.log(chalk.yellow.bold('\n📦 Project Agents → .claude/agents/\n'));
|
|
174
|
+
const agentsDir = join(targetDir, '.claude', 'agents');
|
|
175
|
+
await fs.ensureDir(agentsDir);
|
|
176
|
+
const agents = await fs.readdir(join(TEMPLATES_DIR, 'agents', 'project'));
|
|
177
|
+
for (const file of agents) {
|
|
178
|
+
if (file.endsWith('.md')) {
|
|
179
|
+
await installFile(
|
|
180
|
+
join(TEMPLATES_DIR, 'agents', 'project', file),
|
|
181
|
+
join(agentsDir, file),
|
|
182
|
+
file
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Hooks ─────────────────────────────────────────────────────────────
|
|
189
|
+
if (components.includes('hooks')) {
|
|
190
|
+
console.log(chalk.yellow.bold('\n🪝 Hooks → .claude/hooks/\n'));
|
|
191
|
+
const hooksDir = join(targetDir, '.claude', 'hooks');
|
|
192
|
+
await fs.ensureDir(hooksDir);
|
|
193
|
+
const hooks = await fs.readdir(join(TEMPLATES_DIR, 'hooks'));
|
|
194
|
+
for (const file of hooks) {
|
|
195
|
+
if (file.endsWith('.sh')) {
|
|
196
|
+
await installFile(
|
|
197
|
+
join(TEMPLATES_DIR, 'hooks', file),
|
|
198
|
+
join(hooksDir, file),
|
|
199
|
+
file
|
|
200
|
+
);
|
|
201
|
+
await fs.chmod(join(hooksDir, file), 0o755);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Commands ──────────────────────────────────────────────────────────
|
|
207
|
+
if (components.includes('commands')) {
|
|
208
|
+
console.log(chalk.yellow.bold('\n⚡ Slash Commands → .claude/commands/\n'));
|
|
209
|
+
const cmdsDir = join(targetDir, '.claude', 'commands');
|
|
210
|
+
await fs.ensureDir(cmdsDir);
|
|
211
|
+
const cmds = await fs.readdir(join(TEMPLATES_DIR, 'commands'));
|
|
212
|
+
for (const file of cmds) {
|
|
213
|
+
if (file.endsWith('.md')) {
|
|
214
|
+
await installFile(
|
|
215
|
+
join(TEMPLATES_DIR, 'commands', file),
|
|
216
|
+
join(cmdsDir, file),
|
|
217
|
+
file
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── MCP Servers (interactive selection) ───────────────────────────────
|
|
224
|
+
let dbType = 'none';
|
|
225
|
+
if (components.includes('mcp')) {
|
|
226
|
+
console.log(chalk.yellow.bold('\n🔌 MCP Servers → .mcp.json\n'));
|
|
227
|
+
|
|
228
|
+
let mcpChoices;
|
|
229
|
+
if (installAll) {
|
|
230
|
+
// --all mode: use --db flag or prompt just for database type
|
|
231
|
+
let db = dbFlag;
|
|
232
|
+
if (!db) {
|
|
233
|
+
const { dbAnswer } = await inquirer.prompt([{
|
|
234
|
+
type: 'list',
|
|
235
|
+
name: 'dbAnswer',
|
|
236
|
+
message: 'What database does this project use?',
|
|
237
|
+
choices: [
|
|
238
|
+
{ name: 'MongoDB', value: 'mongo' },
|
|
239
|
+
{ name: 'SQL Server (local/VM)', value: 'mssql' },
|
|
240
|
+
{ name: 'Azure SQL', value: 'azuresql' },
|
|
241
|
+
{ name: 'PostgreSQL', value: 'postgres' },
|
|
242
|
+
{ name: 'None / Skip', value: 'none' },
|
|
243
|
+
],
|
|
244
|
+
}]);
|
|
245
|
+
db = dbAnswer;
|
|
246
|
+
}
|
|
247
|
+
mcpChoices = { servers: ['playwright', 'teams', 'azure'], db, stripe: false };
|
|
248
|
+
} else {
|
|
249
|
+
// Database selection
|
|
250
|
+
const { db } = await inquirer.prompt([{
|
|
251
|
+
type: 'list',
|
|
252
|
+
name: 'db',
|
|
253
|
+
message: 'What database does this project use?',
|
|
254
|
+
choices: [
|
|
255
|
+
{ name: 'MongoDB', value: 'mongo' },
|
|
256
|
+
{ name: 'SQL Server (local/VM)', value: 'mssql' },
|
|
257
|
+
{ name: 'Azure SQL', value: 'azuresql' },
|
|
258
|
+
{ name: 'PostgreSQL', value: 'postgres' },
|
|
259
|
+
{ name: 'None / Skip', value: 'none' },
|
|
260
|
+
],
|
|
261
|
+
}]);
|
|
262
|
+
dbType = db;
|
|
263
|
+
|
|
264
|
+
// Other servers
|
|
265
|
+
const { servers } = await inquirer.prompt([{
|
|
266
|
+
type: 'checkbox',
|
|
267
|
+
name: 'servers',
|
|
268
|
+
message: 'Select additional MCP servers:',
|
|
269
|
+
choices: [
|
|
270
|
+
{ name: 'Playwright (browser testing)', value: 'playwright', checked: true },
|
|
271
|
+
{ name: 'Microsoft Teams (notifications, messages)', value: 'teams', checked: true },
|
|
272
|
+
{ name: 'Stripe (payment management)', value: 'stripe', checked: false },
|
|
273
|
+
{ name: 'Azure CLI (App Service, Key Vault, DNS)', value: 'azure', checked: true },
|
|
274
|
+
],
|
|
275
|
+
}]);
|
|
276
|
+
|
|
277
|
+
mcpChoices = { servers, db, stripe: servers.includes('stripe') };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Build MCP config
|
|
281
|
+
const mcpConfig = { mcpServers: {} };
|
|
282
|
+
|
|
283
|
+
if (mcpChoices.servers.includes('playwright')) {
|
|
284
|
+
mcpConfig.mcpServers.playwright = {
|
|
285
|
+
command: 'npx',
|
|
286
|
+
args: ['@playwright/mcp'],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (mcpChoices.db === 'mongo') {
|
|
291
|
+
mcpConfig.mcpServers.mongodb = {
|
|
292
|
+
command: 'npx',
|
|
293
|
+
args: ['-y', 'mongodb-mcp-server'],
|
|
294
|
+
env: { MDB_MCP_CONNECTION_STRING: '${MONGODB_CONNECTION_STRING}' },
|
|
295
|
+
};
|
|
296
|
+
} else if (mcpChoices.db === 'mssql' || mcpChoices.db === 'azuresql') {
|
|
297
|
+
mcpConfig.mcpServers.mssql = {
|
|
298
|
+
command: 'npx',
|
|
299
|
+
args: ['-y', '@anthropic/mcp-mssql-server'],
|
|
300
|
+
env: { MSSQL_CONNECTION_STRING: '${MSSQL_CONNECTION_STRING}' },
|
|
301
|
+
};
|
|
302
|
+
} else if (mcpChoices.db === 'postgres') {
|
|
303
|
+
mcpConfig.mcpServers.postgres = {
|
|
304
|
+
command: 'npx',
|
|
305
|
+
args: ['-y', '@modelcontextprotocol/server-postgres'],
|
|
306
|
+
env: { POSTGRES_CONNECTION_STRING: '${POSTGRES_CONNECTION_STRING}' },
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (mcpChoices.servers.includes('teams')) {
|
|
311
|
+
mcpConfig.mcpServers.teams = {
|
|
312
|
+
command: 'npx',
|
|
313
|
+
args: ['-y', '@anthropic/mcp-teams-server'],
|
|
314
|
+
env: {
|
|
315
|
+
TEAMS_TENANT_ID: '${TEAMS_TENANT_ID}',
|
|
316
|
+
TEAMS_CLIENT_ID: '${TEAMS_CLIENT_ID}',
|
|
317
|
+
TEAMS_CLIENT_SECRET: '${TEAMS_CLIENT_SECRET}',
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (mcpChoices.servers.includes('stripe') || mcpChoices.stripe) {
|
|
323
|
+
mcpConfig.mcpServers.stripe = {
|
|
324
|
+
command: 'npx',
|
|
325
|
+
args: ['-y', '@stripe/mcp'],
|
|
326
|
+
env: { STRIPE_SECRET_KEY: '${STRIPE_SECRET_KEY}' },
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (mcpChoices.servers.includes('azure')) {
|
|
331
|
+
mcpConfig.mcpServers.azure = {
|
|
332
|
+
command: 'npx',
|
|
333
|
+
args: ['-y', '@azure/mcp@latest', 'server', 'start'],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
dbType = mcpChoices.db;
|
|
338
|
+
|
|
339
|
+
const mcpPath = join(targetDir, '.mcp.json');
|
|
340
|
+
if (await fs.pathExists(mcpPath)) {
|
|
341
|
+
// In --all mode, check if content is identical first
|
|
342
|
+
const newContent = JSON.stringify(mcpConfig, null, 2);
|
|
343
|
+
const existingContent = await fs.readFile(mcpPath, 'utf8');
|
|
344
|
+
if (newContent.trim() === existingContent.trim()) {
|
|
345
|
+
console.log(chalk.gray(' = .mcp.json (identical, skipped)'));
|
|
346
|
+
} else {
|
|
347
|
+
const { action } = installAll
|
|
348
|
+
? { action: 'overwrite' }
|
|
349
|
+
: await inquirer.prompt([{
|
|
350
|
+
type: 'list',
|
|
351
|
+
name: 'action',
|
|
352
|
+
message: '.mcp.json already exists:',
|
|
353
|
+
choices: [
|
|
354
|
+
{ name: 'Overwrite with new config', value: 'overwrite' },
|
|
355
|
+
{ name: 'Merge (add missing servers)', value: 'merge' },
|
|
356
|
+
{ name: 'Skip', value: 'skip' },
|
|
357
|
+
],
|
|
358
|
+
}]);
|
|
359
|
+
|
|
360
|
+
if (action === 'merge') {
|
|
361
|
+
const existing = await fs.readJson(mcpPath);
|
|
362
|
+
existing.mcpServers = { ...existing.mcpServers, ...mcpConfig.mcpServers };
|
|
363
|
+
await fs.writeJson(mcpPath, existing, { spaces: 2 });
|
|
364
|
+
console.log(chalk.green(' ✓ .mcp.json (merged)'));
|
|
365
|
+
} else if (action === 'overwrite') {
|
|
366
|
+
await fs.writeJson(mcpPath, mcpConfig, { spaces: 2 });
|
|
367
|
+
console.log(chalk.green(' ✓ .mcp.json (overwritten)'));
|
|
368
|
+
} else {
|
|
369
|
+
console.log(chalk.gray(' ⊘ .mcp.json (skipped)'));
|
|
370
|
+
}
|
|
371
|
+
} // close identical check else
|
|
372
|
+
} else {
|
|
373
|
+
await fs.writeJson(mcpPath, mcpConfig, { spaces: 2 });
|
|
374
|
+
console.log(chalk.green(' ✓ .mcp.json'));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Settings ──────────────────────────────────────────────────────────
|
|
379
|
+
if (components.includes('settings')) {
|
|
380
|
+
console.log(chalk.yellow.bold('\n⚙️ Settings → .claude/settings.json\n'));
|
|
381
|
+
await installFile(
|
|
382
|
+
join(TEMPLATES_DIR, 'infrastructure', 'settings.json'),
|
|
383
|
+
join(targetDir, '.claude', 'settings.json'),
|
|
384
|
+
'settings.json'
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── CLAUDE.md Workflow ────────────────────────────────────────────────
|
|
389
|
+
if (components.includes('workflow')) {
|
|
390
|
+
console.log(chalk.yellow.bold('\n📄 CLAUDE.md Workflow\n'));
|
|
391
|
+
const claudeMdPath = join(targetDir, 'CLAUDE.md');
|
|
392
|
+
const workflowContent = await fs.readFile(
|
|
393
|
+
join(TEMPLATES_DIR, 'infrastructure', 'CLAUDE-WORKFLOW.md'),
|
|
394
|
+
'utf8'
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const sensitiveDataPolicy = `## SENSITIVE DATA — MANDATORY RULE
|
|
398
|
+
|
|
399
|
+
**NEVER query, display, read, grep, or expose sensitive PII fields from the database or codebase — even if the values are encrypted.** Blocked fields: TIN, SSN, EIN, TaxId, BankAccountNumber, RoutingNumber, and any \`Encrypted*\` variants. Always use explicit inclusion projections listing only non-sensitive fields. Direct users to the application UI for sensitive data access.
|
|
400
|
+
`;
|
|
401
|
+
|
|
402
|
+
if (!await fs.pathExists(claudeMdPath)) {
|
|
403
|
+
await fs.writeFile(claudeMdPath, `# ${basename(targetDir)}\n\n${sensitiveDataPolicy}\n${workflowContent}`);
|
|
404
|
+
console.log(chalk.green(' ✓ Created CLAUDE.md with sensitive data policy and workflow'));
|
|
405
|
+
} else {
|
|
406
|
+
let existing = await fs.readFile(claudeMdPath, 'utf8');
|
|
407
|
+
|
|
408
|
+
// Inject sensitive data policy if not present
|
|
409
|
+
if (!existing.includes('SENSITIVE DATA — MANDATORY RULE')) {
|
|
410
|
+
// Insert after the first heading line, or at the top
|
|
411
|
+
const firstHeadingEnd = existing.indexOf('\n');
|
|
412
|
+
if (firstHeadingEnd !== -1 && existing.startsWith('#')) {
|
|
413
|
+
existing = existing.slice(0, firstHeadingEnd + 1) + '\n' + sensitiveDataPolicy + existing.slice(firstHeadingEnd + 1);
|
|
414
|
+
} else {
|
|
415
|
+
existing = sensitiveDataPolicy + '\n' + existing;
|
|
416
|
+
}
|
|
417
|
+
await fs.writeFile(claudeMdPath, existing);
|
|
418
|
+
console.log(chalk.green(' ✓ Injected sensitive data policy into CLAUDE.md'));
|
|
419
|
+
} else {
|
|
420
|
+
console.log(chalk.gray(' = Sensitive data policy already exists'));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Re-read in case we just modified it
|
|
424
|
+
existing = await fs.readFile(claudeMdPath, 'utf8');
|
|
425
|
+
|
|
426
|
+
if (existing.includes('Claude Kit Workflow') || existing.includes('Care Solutions AI Workflow')) {
|
|
427
|
+
console.log(chalk.gray(' = Workflow section already exists'));
|
|
428
|
+
if (!installAll) {
|
|
429
|
+
const { replace } = await inquirer.prompt([{
|
|
430
|
+
type: 'confirm',
|
|
431
|
+
name: 'replace',
|
|
432
|
+
message: 'Replace existing workflow section?',
|
|
433
|
+
default: false,
|
|
434
|
+
}]);
|
|
435
|
+
if (replace) {
|
|
436
|
+
const cleaned = existing.replace(/\n## (?:Claude Kit Workflow|Care Solutions AI Workflow)[\s\S]*$/, '').trimEnd();
|
|
437
|
+
await fs.writeFile(claudeMdPath, `${cleaned}\n\n${workflowContent}`);
|
|
438
|
+
console.log(chalk.green(' ✓ Workflow section replaced'));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
await fs.appendFile(claudeMdPath, `\n\n${workflowContent}`);
|
|
443
|
+
console.log(chalk.green(' ✓ Workflow appended to CLAUDE.md'));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ── .gitignore ────────────────────────────────────────────────────────
|
|
449
|
+
if (components.includes('gitignore')) {
|
|
450
|
+
console.log(chalk.yellow.bold('\n🙈 .gitignore\n'));
|
|
451
|
+
const gitignorePath = join(targetDir, '.gitignore');
|
|
452
|
+
const claudeBlock = `
|
|
453
|
+
# =========================
|
|
454
|
+
# Claude Code
|
|
455
|
+
# =========================
|
|
456
|
+
.claude/*
|
|
457
|
+
!.claude/agents/
|
|
458
|
+
!.claude/hooks/
|
|
459
|
+
!.claude/commands/
|
|
460
|
+
!.claude/settings.json
|
|
461
|
+
.claude/settings.local.json`;
|
|
462
|
+
|
|
463
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
464
|
+
const content = await fs.readFile(gitignorePath, 'utf8');
|
|
465
|
+
if (content.includes('.claude/*')) {
|
|
466
|
+
console.log(chalk.gray(' = Claude entries already exist'));
|
|
467
|
+
} else {
|
|
468
|
+
await fs.appendFile(gitignorePath, claudeBlock);
|
|
469
|
+
console.log(chalk.green(' ✓ Added Claude entries'));
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
await fs.writeFile(gitignorePath, claudeBlock.trim());
|
|
473
|
+
console.log(chalk.green(' ✓ Created .gitignore'));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ── Summary ───────────────────────────────────────────────────────────
|
|
478
|
+
const agentCount = (await fs.pathExists(join(targetDir, '.claude', 'agents')))
|
|
479
|
+
? (await fs.readdir(join(targetDir, '.claude', 'agents'))).filter(f => f.endsWith('.md')).length
|
|
480
|
+
: 0;
|
|
481
|
+
const hookCount = (await fs.pathExists(join(targetDir, '.claude', 'hooks')))
|
|
482
|
+
? (await fs.readdir(join(targetDir, '.claude', 'hooks'))).filter(f => f.endsWith('.sh')).length
|
|
483
|
+
: 0;
|
|
484
|
+
const cmdCount = (await fs.pathExists(join(targetDir, '.claude', 'commands')))
|
|
485
|
+
? (await fs.readdir(join(targetDir, '.claude', 'commands'))).filter(f => f.endsWith('.md')).length
|
|
486
|
+
: 0;
|
|
487
|
+
const globalCount = (await fs.pathExists(join(GLOBAL_CLAUDE_DIR, 'agents')))
|
|
488
|
+
? (await fs.readdir(join(GLOBAL_CLAUDE_DIR, 'agents'))).filter(f => f.endsWith('.md')).length
|
|
489
|
+
: 0;
|
|
490
|
+
|
|
491
|
+
console.log('');
|
|
492
|
+
console.log(chalk.blue('╔══════════════════════════════════════════════════╗'));
|
|
493
|
+
console.log(chalk.blue('║') + chalk.green.bold(' ✅ Installation Complete! ') + chalk.blue('║'));
|
|
494
|
+
console.log(chalk.blue('╚══════════════════════════════════════════════════╝'));
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log(` Target: ${chalk.bold(targetDir)}`);
|
|
497
|
+
console.log(` Project agents: ${chalk.bold(agentCount)}`);
|
|
498
|
+
console.log(` Hooks: ${chalk.bold(hookCount)}`);
|
|
499
|
+
console.log(` Commands: ${chalk.bold(cmdCount)}`);
|
|
500
|
+
console.log(` Global agents: ${chalk.bold(globalCount)}`);
|
|
501
|
+
console.log(` Database: ${chalk.bold(dbType)}`);
|
|
502
|
+
console.log('');
|
|
503
|
+
|
|
504
|
+
// Show required env vars
|
|
505
|
+
console.log(chalk.yellow(' Environment variables to set:'));
|
|
506
|
+
if (dbType === 'mongo') console.log(chalk.gray(' export MONGODB_CONNECTION_STRING="mongodb+srv://..."'));
|
|
507
|
+
if (dbType === 'mssql' || dbType === 'azuresql') console.log(chalk.gray(' export MSSQL_CONNECTION_STRING="Server=...;Database=..."'));
|
|
508
|
+
if (dbType === 'postgres') console.log(chalk.gray(' export POSTGRES_CONNECTION_STRING="postgresql://..."'));
|
|
509
|
+
console.log(chalk.gray(' export TEAMS_TENANT_ID="..." TEAMS_CLIENT_ID="..." TEAMS_CLIENT_SECRET="..."'));
|
|
510
|
+
console.log(chalk.gray(' az login'));
|
|
511
|
+
console.log('');
|
|
512
|
+
console.log(` Then: ${chalk.blue(`cd ${targetDir} && claude`)}`);
|
|
513
|
+
console.log(` Run: ${chalk.blue('/implement AB#1234')} to start working`);
|
|
514
|
+
console.log(` Run: ${chalk.blue('/review 142')} to review a PR`);
|
|
515
|
+
console.log('');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
main().catch(err => {
|
|
519
|
+
console.error(chalk.red(`\n Error: ${err.message}\n`));
|
|
520
|
+
process.exit(1);
|
|
521
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chris1807/claude-kit",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Claude Code starter kit — agents, hooks, MCP servers, slash commands, and workflow automation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-kit": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/cli.js --help",
|
|
11
|
+
"publish:public": "npm publish --access public"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"claude-code",
|
|
15
|
+
"claude",
|
|
16
|
+
"ai-infrastructure",
|
|
17
|
+
"mcp",
|
|
18
|
+
"agents",
|
|
19
|
+
"hooks",
|
|
20
|
+
"starter-kit"
|
|
21
|
+
],
|
|
22
|
+
"author": "Chris Waters <chriswaters@caresolutions.com>",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Christopher-Waters/claude-kit.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/Christopher-Waters/claude-kit#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/Christopher-Waters/claude-kit/issues"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"bin/",
|
|
40
|
+
"templates/",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chalk": "^5.6.2",
|
|
46
|
+
"fs-extra": "^11.3.4",
|
|
47
|
+
"inquirer": "^13.3.2",
|
|
48
|
+
"ora": "^9.3.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-tester
|
|
3
|
+
description: Tests API endpoints — auth flows, CRUD operations, error handling. Use to verify APIs work correctly after changes.
|
|
4
|
+
tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Grep
|
|
8
|
+
- Glob
|
|
9
|
+
model: sonnet
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# API Tester Agent
|
|
13
|
+
|
|
14
|
+
You test REST API endpoints using `curl`. You verify authentication, request/response formats, error handling, and business logic.
|
|
15
|
+
|
|
16
|
+
## Testing Flow
|
|
17
|
+
|
|
18
|
+
### 1. Authenticate First
|
|
19
|
+
```bash
|
|
20
|
+
# Get a JWT token
|
|
21
|
+
TOKEN=$(curl -s -X POST "$API_URL/api/v1/auth/login" \
|
|
22
|
+
-H "Content-Type: application/json" \
|
|
23
|
+
-H "X-Organization-Subdomain: $SUBDOMAIN" \
|
|
24
|
+
-d '{"email":"EMAIL","password":"PASSWORD"}' \
|
|
25
|
+
| python3 -c "import sys,json; print(json.load(sys.stdin)['data']['accessToken'])")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Test Endpoint
|
|
29
|
+
```bash
|
|
30
|
+
curl -s "$API_URL/api/v1/ENDPOINT" \
|
|
31
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
32
|
+
-H "Content-Type: application/json" \
|
|
33
|
+
-H "X-Organization-Subdomain: $SUBDOMAIN"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Report Results
|
|
37
|
+
|
|
38
|
+
For each endpoint tested, report:
|
|
39
|
+
```
|
|
40
|
+
ENDPOINT: METHOD /api/v1/path
|
|
41
|
+
STATUS: 200 | 400 | 401 | 403 | 404 | 500
|
|
42
|
+
RESPONSE: (summarized)
|
|
43
|
+
RESULT: PASS | FAIL
|
|
44
|
+
NOTES: any issues found
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Test Categories
|
|
48
|
+
|
|
49
|
+
### Happy Path
|
|
50
|
+
- Valid request → expected response
|
|
51
|
+
- Correct status code
|
|
52
|
+
- Response body matches expected schema
|
|
53
|
+
|
|
54
|
+
### Authentication
|
|
55
|
+
- No token → 401
|
|
56
|
+
- Invalid token → 401
|
|
57
|
+
- Wrong role → 403
|
|
58
|
+
- Expired token → 401
|
|
59
|
+
|
|
60
|
+
### Validation
|
|
61
|
+
- Missing required fields → 400 with field errors
|
|
62
|
+
- Invalid data types → 400
|
|
63
|
+
- Boundary values (empty strings, very long strings, negative numbers)
|
|
64
|
+
|
|
65
|
+
### Error Handling
|
|
66
|
+
- Non-existent resource → 404
|
|
67
|
+
- Duplicate creation → 400 or 409
|
|
68
|
+
- Server error → 500 (should not happen)
|
|
69
|
+
|
|
70
|
+
## Rules
|
|
71
|
+
- Never use real SSNs, bank accounts, or PII in test data — use placeholders
|
|
72
|
+
- Always clean up test data you create (or note what was created)
|
|
73
|
+
- Test both the success AND failure paths
|
|
74
|
+
- Use `python3 -c "import sys,json; ..."` for JSON parsing (more reliable than jq)
|
|
75
|
+
- Report ALL failures, even intermittent ones
|