ccraft 1.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.
Files changed (40) hide show
  1. package/bin/claude-craft.js +85 -0
  2. package/package.json +39 -0
  3. package/src/commands/auth.js +43 -0
  4. package/src/commands/create.js +543 -0
  5. package/src/commands/install.js +480 -0
  6. package/src/commands/logout.js +24 -0
  7. package/src/commands/update.js +339 -0
  8. package/src/constants.js +299 -0
  9. package/src/generators/directories.js +30 -0
  10. package/src/generators/metadata.js +57 -0
  11. package/src/generators/security.js +39 -0
  12. package/src/prompts/gather.js +308 -0
  13. package/src/ui/brand.js +62 -0
  14. package/src/ui/cards.js +179 -0
  15. package/src/ui/format.js +55 -0
  16. package/src/ui/phase-header.js +20 -0
  17. package/src/ui/prompts.js +56 -0
  18. package/src/ui/tables.js +89 -0
  19. package/src/ui/tasks.js +258 -0
  20. package/src/ui/theme.js +83 -0
  21. package/src/utils/analysis-cache.js +519 -0
  22. package/src/utils/api-client.js +253 -0
  23. package/src/utils/api-file-writer.js +197 -0
  24. package/src/utils/bootstrap-runner.js +148 -0
  25. package/src/utils/claude-analyzer.js +255 -0
  26. package/src/utils/claude-optimizer.js +341 -0
  27. package/src/utils/claude-rewriter.js +553 -0
  28. package/src/utils/claude-scorer.js +101 -0
  29. package/src/utils/description-analyzer.js +116 -0
  30. package/src/utils/detect-project.js +1276 -0
  31. package/src/utils/existing-setup.js +341 -0
  32. package/src/utils/file-writer.js +64 -0
  33. package/src/utils/json-extract.js +56 -0
  34. package/src/utils/logger.js +27 -0
  35. package/src/utils/mcp-setup.js +461 -0
  36. package/src/utils/preflight.js +112 -0
  37. package/src/utils/prompt-api-key.js +59 -0
  38. package/src/utils/run-claude.js +152 -0
  39. package/src/utils/security.js +82 -0
  40. package/src/utils/toolkit-rule-generator.js +364 -0
@@ -0,0 +1,480 @@
1
+ import { resolve } from 'path';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { detectProject } from '../utils/detect-project.js';
5
+ import { analyzeWithClaude } from '../utils/claude-analyzer.js';
6
+ import { rewriteClaudeMd } from '../utils/claude-rewriter.js';
7
+ import {
8
+ detectExistingSetup,
9
+ extractExistingContext,
10
+ removeExistingSetup,
11
+ } from '../utils/existing-setup.js';
12
+ import {
13
+ gatherProjectPath,
14
+ gatherUserProfile,
15
+ gatherMcpConfig,
16
+ gatherSecurityConfig,
17
+ confirmInstallation,
18
+ } from '../prompts/gather.js';
19
+ import { callGenerate, ApiError } from '../utils/api-client.js';
20
+ import { writeApiFiles, buildFileList } from '../utils/api-file-writer.js';
21
+ import { setupMcps } from '../utils/mcp-setup.js';
22
+ import { scoreWithClaude } from '../utils/claude-scorer.js';
23
+ import { optimizeSettings } from '../utils/claude-optimizer.js';
24
+ import { runPreflight } from '../utils/preflight.js';
25
+ import {
26
+ writeAnalysisCache,
27
+ writeUserProfile,
28
+ updateManifest,
29
+ readAnalysisCache,
30
+ promoteCache,
31
+ cleanupAnalysisCache,
32
+ } from '../utils/analysis-cache.js';
33
+ import { PRESET_ALIASES, VERSION } from '../constants.js';
34
+ import * as logger from '../utils/logger.js';
35
+
36
+ // UI modules
37
+ import { renderBanner } from '../ui/brand.js';
38
+ import { renderPhaseHeader } from '../ui/phase-header.js';
39
+ import { renderProjectCard, renderSuccessCard } from '../ui/cards.js';
40
+ import { renderComponentBreakdown, renderMcpStatus, renderFileResults } from '../ui/tables.js';
41
+ import { runExistingSetupTasks, runAnalysisTasks, runInstallTasks, runVerifyTasks, runFinalizeTasks } from '../ui/tasks.js';
42
+ import { colors } from '../ui/theme.js';
43
+ import { dotPad } from '../ui/format.js';
44
+
45
+ /**
46
+ * Main install command — 5-phase orchestrator.
47
+ *
48
+ * Phase 1: Welcome & Setup (env check + user profile)
49
+ * Phase 2: Project Discovery (Claude analysis + filesystem scan)
50
+ * Phase 3: Configuration (server API + MCP selection + security + confirmation)
51
+ * Phase 4: Installation (Claude scoring + file writing + MCP verification)
52
+ * Phase 5: Finalization (settings optimization + CLAUDE.md rewrite + success)
53
+ */
54
+ export async function runInstall(options = {}) {
55
+ let targetDir;
56
+ try {
57
+ // ================================================================
58
+ // PHASE 1: Welcome & Setup
59
+ // ================================================================
60
+ renderBanner(VERSION);
61
+
62
+ // ── Pre-flight checks (Claude Code + API key + server) ──────
63
+ const { apiConfig } = await runPreflight({
64
+ interactive: !options.yes,
65
+ requireClaude: true,
66
+ });
67
+
68
+ // ── Project path ────────────────────────────────────────────────
69
+ if (!options.yes && !options.dir) {
70
+ targetDir = await gatherProjectPath();
71
+ } else {
72
+ targetDir = resolve(options.dir || process.cwd());
73
+ }
74
+
75
+ // ── User profile ───────────────────────────────────────────────
76
+ renderPhaseHeader(1);
77
+
78
+ let userProfile;
79
+ if (options.yes) {
80
+ userProfile = { role: 'web', intents: ['implementing', 'debugging', 'refactoring', 'testing', 'reviewing'], sourceControl: 'github', documentTools: [] };
81
+ logger.info('Non-interactive mode — using default profile (web, all intents, github).');
82
+ } else {
83
+ userProfile = await gatherUserProfile();
84
+ }
85
+
86
+ // ================================================================
87
+ // PHASE 2: Project Discovery
88
+ // ================================================================
89
+ renderPhaseHeader(2);
90
+
91
+ // ── Check for existing Claude setup ─────────────────────────────
92
+ let existingContext = null;
93
+ try {
94
+ const setupCtx = await runExistingSetupTasks(targetDir, {
95
+ detectExistingSetup,
96
+ extractExistingContext,
97
+ removeExistingSetup,
98
+ });
99
+ existingContext = setupCtx.existingContext || null;
100
+ } catch (setupErr) {
101
+ logger.debug(`Existing setup check failed: ${setupErr.message}`);
102
+ }
103
+
104
+ // ── Project analysis ────────────────────────────────────────────
105
+ let projectInfo;
106
+ let detected;
107
+
108
+ try {
109
+ const ctx = await runAnalysisTasks(targetDir, { analyzeWithClaude, detectProject, existingContext });
110
+
111
+ const { claudeAnalysis, claudeFailReason, fsDetected } = ctx;
112
+
113
+ if (claudeAnalysis) {
114
+ projectInfo = {
115
+ name: claudeAnalysis.name || fsDetected.name || 'my-project',
116
+ description: claudeAnalysis.description || fsDetected.description || '',
117
+ projectType: claudeAnalysis.projectType || fsDetected.projectType || 'monolith',
118
+ languages: claudeAnalysis.languages.length ? claudeAnalysis.languages : (fsDetected.languages.length ? fsDetected.languages : ['JavaScript']),
119
+ frameworks: claudeAnalysis.frameworks.length ? claudeAnalysis.frameworks : fsDetected.frameworks,
120
+ codeStyle: claudeAnalysis.codeStyle.length ? claudeAnalysis.codeStyle : fsDetected.codeStyle,
121
+ cicd: claudeAnalysis.cicd.length ? claudeAnalysis.cicd : fsDetected.cicd,
122
+ subprojects: claudeAnalysis.subprojects.length ? claudeAnalysis.subprojects : fsDetected.subprojects,
123
+ architecture: claudeAnalysis.architecture || '',
124
+ buildCommands: claudeAnalysis.buildCommands || {},
125
+ complexity: claudeAnalysis.complexity ?? 0.5,
126
+ metrics: claudeAnalysis.metrics || null,
127
+ entryPoints: claudeAnalysis.entryPoints || [],
128
+ coreModules: claudeAnalysis.coreModules || [],
129
+ testFramework: claudeAnalysis.testFramework || '',
130
+ packageManager: claudeAnalysis.packageManager || fsDetected.packageManager || '',
131
+ languageDistribution: claudeAnalysis.languageDistribution || fsDetected.languageDistribution || null,
132
+ };
133
+ } else {
134
+ projectInfo = {
135
+ name: fsDetected.name || targetDir.split(/[/\\]/).filter(Boolean).pop() || 'my-project',
136
+ description: fsDetected.description || '',
137
+ projectType: fsDetected.projectType || 'monolith',
138
+ languages: fsDetected.languages.length ? fsDetected.languages : ['JavaScript'],
139
+ frameworks: fsDetected.frameworks,
140
+ codeStyle: fsDetected.codeStyle,
141
+ cicd: fsDetected.cicd,
142
+ subprojects: fsDetected.subprojects,
143
+ architecture: '',
144
+ buildCommands: {},
145
+ complexity: 0.5,
146
+ metrics: null,
147
+ entryPoints: [],
148
+ coreModules: [],
149
+ testFramework: '',
150
+ packageManager: fsDetected.packageManager || '',
151
+ languageDistribution: fsDetected.languageDistribution || null,
152
+ };
153
+ }
154
+
155
+ detected = { ...fsDetected, ...projectInfo };
156
+ } catch (analysisErr) {
157
+ // Fallback if task runner fails — run sequentially with spinner
158
+ const spinner = ora('Analyzing project...').start();
159
+ const fsDetected = await detectProject(targetDir);
160
+ spinner.succeed('Project scanned.');
161
+ projectInfo = {
162
+ name: fsDetected.name || targetDir.split(/[/\\]/).filter(Boolean).pop() || 'my-project',
163
+ description: fsDetected.description || '',
164
+ projectType: fsDetected.projectType || 'monolith',
165
+ languages: fsDetected.languages.length ? fsDetected.languages : ['JavaScript'],
166
+ frameworks: fsDetected.frameworks,
167
+ codeStyle: fsDetected.codeStyle,
168
+ cicd: fsDetected.cicd,
169
+ subprojects: fsDetected.subprojects,
170
+ architecture: '',
171
+ buildCommands: {},
172
+ complexity: 0.5,
173
+ metrics: null,
174
+ entryPoints: [],
175
+ coreModules: [],
176
+ testFramework: '',
177
+ packageManager: fsDetected.packageManager || '',
178
+ languageDistribution: fsDetected.languageDistribution || null,
179
+ };
180
+ detected = { ...fsDetected, ...projectInfo };
181
+ }
182
+
183
+ // Cache analysis and user profile for later phases and future update runs
184
+ try {
185
+ writeAnalysisCache(targetDir, projectInfo, detected, existingContext);
186
+ writeUserProfile(targetDir, userProfile);
187
+ } catch (cacheErr) {
188
+ logger.debug(`Analysis cache write failed: ${cacheErr.message}`);
189
+ }
190
+
191
+ // Display results
192
+ console.log();
193
+ renderProjectCard(projectInfo);
194
+
195
+ // Preset alias injection
196
+ if (options.preset) {
197
+ const alias = PRESET_ALIASES[options.preset];
198
+ if (!alias) {
199
+ logger.error(
200
+ `Unknown preset ${chalk.bold(options.preset)}. ` +
201
+ `Available presets: ${Object.keys(PRESET_ALIASES).map((p) => colors.success(p)).join(', ')}`
202
+ );
203
+ process.exit(1);
204
+ }
205
+ for (const fw of alias.frameworks) {
206
+ if (!projectInfo.frameworks.includes(fw)) projectInfo.frameworks.push(fw);
207
+ }
208
+ for (const lang of alias.languages) {
209
+ if (!projectInfo.languages.includes(lang)) projectInfo.languages.push(lang);
210
+ }
211
+ }
212
+
213
+ // ================================================================
214
+ // PHASE 3: Configuration
215
+ // ================================================================
216
+ renderPhaseHeader(3);
217
+
218
+ const spinner3 = ora('Calling claude-craft server...').start();
219
+
220
+ let apiResponse;
221
+ try {
222
+ apiResponse = await callGenerate(
223
+ userProfile,
224
+ {
225
+ name: projectInfo.name,
226
+ projectType: projectInfo.projectType,
227
+ languages: projectInfo.languages,
228
+ languageDistribution: projectInfo.languageDistribution,
229
+ frameworks: projectInfo.frameworks,
230
+ complexity: projectInfo.complexity,
231
+ architecture: projectInfo.architecture,
232
+ buildCommands: projectInfo.buildCommands,
233
+ codeStyle: projectInfo.codeStyle,
234
+ cicd: projectInfo.cicd,
235
+ subprojects: projectInfo.subprojects,
236
+ metrics: projectInfo.metrics,
237
+ entryPoints: projectInfo.entryPoints,
238
+ coreModules: projectInfo.coreModules,
239
+ testFramework: projectInfo.testFramework,
240
+ packageManager: projectInfo.packageManager,
241
+ detectedFiles: detected._rootFiles || [],
242
+ databases: projectInfo.databases || detected.databases || [],
243
+ },
244
+ { preset: options.preset },
245
+ );
246
+ spinner3.succeed('Server returned configuration.');
247
+ } catch (err) {
248
+ spinner3.fail('Server request failed.');
249
+ if (err instanceof ApiError) {
250
+ logger.error(err.message);
251
+ process.exit(1);
252
+ }
253
+ throw err;
254
+ }
255
+
256
+ const { summary, mcpConfigs } = apiResponse;
257
+
258
+ // Display scoring summary
259
+ console.log();
260
+ renderComponentBreakdown(summary);
261
+
262
+ // MCP selection + credential setup + security + confirmation
263
+ let selectedMcps;
264
+ let mcpKeys = {};
265
+ let securityConfig;
266
+
267
+ if (options.yes) {
268
+ selectedMcps = mcpConfigs.filter(
269
+ (m) => m.tier === 'core' || m.tier === 'role' || m.tier === 'stack' || m.tier === 'auto' || m.recommended
270
+ );
271
+ securityConfig = { addSecurityGitignore: true };
272
+ } else {
273
+ const mcpResult = await gatherMcpConfig(mcpConfigs);
274
+ selectedMcps = mcpResult.selectedMcps;
275
+ mcpKeys = mcpResult.mcpKeys;
276
+
277
+ securityConfig = gatherSecurityConfig(detected);
278
+
279
+ const finalSummary = { ...summary, mcps: selectedMcps.map((m) => ({ id: m.id, tier: m.tier })) };
280
+ const proceed = await confirmInstallation(finalSummary);
281
+ if (!proceed) {
282
+ console.log();
283
+ logger.info('Cancelled.');
284
+ process.exit(0);
285
+ }
286
+ }
287
+
288
+ // ================================================================
289
+ // PHASE 4: Installation
290
+ // ================================================================
291
+ renderPhaseHeader(4);
292
+
293
+ let results;
294
+ let filesToWrite;
295
+ let selectedCandidateIds = null;
296
+
297
+ try {
298
+ const installCtx = await runInstallTasks({
299
+ apiResponse,
300
+ selectedCandidateIds: null,
301
+ targetDir,
302
+ selectedMcps,
303
+ mcpKeys,
304
+ securityConfig,
305
+ detected,
306
+ buildFileList,
307
+ writeApiFiles,
308
+ scoreWithClaude,
309
+ });
310
+
311
+ results = installCtx.results;
312
+ filesToWrite = installCtx.filesToWrite;
313
+ selectedCandidateIds = installCtx.selectedCandidateIds ?? null;
314
+ } catch (installErr) {
315
+ // Fallback to sequential if task runner fails
316
+ const candidateCount = apiResponse.candidates?.items?.length || 0;
317
+
318
+ if (apiResponse.prompts?.scoring && candidateCount > 0) {
319
+ const spinner4 = ora(`Evaluating ${candidateCount} optional candidates...`).start();
320
+ const scoreResult = await scoreWithClaude(apiResponse.prompts.scoring, targetDir);
321
+ if (scoreResult.selected) {
322
+ selectedCandidateIds = scoreResult.selected;
323
+ spinner4.succeed(`Selected ${selectedCandidateIds.length}/${candidateCount} candidates.`);
324
+ } else {
325
+ spinner4.warn(`Scoring unavailable — including all ${candidateCount} candidates.`);
326
+ }
327
+ }
328
+
329
+ const spinnerWrite = ora('Writing configuration files...').start();
330
+ filesToWrite = buildFileList(apiResponse, selectedCandidateIds);
331
+ results = await writeApiFiles(filesToWrite, targetDir, {
332
+ force: true,
333
+ selectedMcpIds: selectedMcps.map((m) => m.id),
334
+ mcpKeys,
335
+ securityConfig,
336
+ detected,
337
+ });
338
+ spinnerWrite.succeed('Configuration generated.');
339
+ }
340
+
341
+ // Update cache with installed file manifest
342
+ try {
343
+ if (filesToWrite) {
344
+ updateManifest(targetDir, results, filesToWrite);
345
+ }
346
+ } catch (cacheErr) {
347
+ logger.debug(`Manifest update failed: ${cacheErr.message}`);
348
+ }
349
+
350
+ // Display file results
351
+ renderFileResults(results);
352
+
353
+ // MCP verification
354
+ let mcpResults = [];
355
+ if (selectedMcps.length > 0) {
356
+ console.log();
357
+ try {
358
+ const verifyCtx = await runVerifyTasks(selectedMcps, mcpKeys, { setupMcps, targetDir });
359
+ mcpResults = verifyCtx.mcpResults;
360
+ } catch {
361
+ const spinnerMcp = ora('Verifying MCP servers...').start();
362
+ mcpResults = await setupMcps(selectedMcps, mcpKeys, {
363
+ healthCheck: true,
364
+ targetDir,
365
+ onStatus: (id, status) => {
366
+ if (status === 'verifying') spinnerMcp.text = `Verifying ${id}...`;
367
+ else if (status === 'testing') spinnerMcp.text = `Health-checking ${id}...`;
368
+ },
369
+ });
370
+ spinnerMcp.succeed('MCP verification complete.');
371
+ }
372
+
373
+ renderMcpStatus(mcpResults);
374
+ }
375
+
376
+ // ================================================================
377
+ // PHASE 5: Finalization
378
+ // ================================================================
379
+ renderPhaseHeader(5);
380
+
381
+ try {
382
+ const finCtx = await runFinalizeTasks({
383
+ targetDir,
384
+ readAnalysisCache,
385
+ optimizeSettings,
386
+ rewriteClaudeMd,
387
+ });
388
+
389
+ // Show what was replaced
390
+ const opt = finCtx.optimizationResult;
391
+ if (opt?.status === 'ok' && opt.applied > 0 && opt.replacements?.length > 0) {
392
+ for (const label of opt.replacements) {
393
+ console.log(chalk.dim(` • ${label}`));
394
+ }
395
+ }
396
+ } catch {
397
+ // Fallback to sequential
398
+ // toolkit-usage.md no longer generated — capability-map.md covers routing
399
+
400
+ const spinner7 = ora('Optimizing settings...').start();
401
+ const optResult = optimizeSettings(targetDir);
402
+
403
+ if (optResult.status === 'ok' && optResult.applied > 0) {
404
+ spinner7.succeed(`Optimized ${optResult.applied} setting(s).`);
405
+ } else {
406
+ spinner7.succeed('Settings reviewed — no changes needed.');
407
+ }
408
+
409
+ const spinner8 = ora('Rewriting CLAUDE.md...').start();
410
+ const cache8 = readAnalysisCache(targetDir);
411
+ const rewritten = await rewriteClaudeMd(targetDir, cache8);
412
+ if (rewritten) {
413
+ spinner8.succeed('CLAUDE.md rewritten.');
414
+ } else {
415
+ spinner8.warn('CLAUDE.md rewrite skipped — using template version.');
416
+ }
417
+ }
418
+
419
+ // ── Success ──────────────────────────────────────────────────────
420
+ const totalItems = countTotalItems(summary, selectedCandidateIds);
421
+ const mcpsNeedingKeys = mcpResults.filter((r) => r.status === 'needs-key');
422
+
423
+ renderSuccessCard({
424
+ totalItems,
425
+ mcpCount: selectedMcps.length,
426
+ mcpsNeedingKeys,
427
+ });
428
+
429
+ console.log();
430
+ logger.success('Done! Claude Code is ready.');
431
+ console.log();
432
+ } catch (err) {
433
+ if (
434
+ err &&
435
+ (err.name === 'ExitPromptError' ||
436
+ err.constructor?.name === 'ExitPromptError' ||
437
+ err.message?.includes('User force closed'))
438
+ ) {
439
+ console.log();
440
+ logger.info('Cancelled.');
441
+ process.exit(0);
442
+ }
443
+
444
+ console.log();
445
+ logger.error(err.message || String(err));
446
+ process.exit(1);
447
+ } finally {
448
+ if (targetDir) {
449
+ try {
450
+ promoteCache(targetDir);
451
+ cleanupAnalysisCache(targetDir);
452
+ } catch {
453
+ // Ignore cleanup errors
454
+ }
455
+ }
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Count total installed items from guaranteed + selected candidates.
461
+ */
462
+ function countTotalItems(summary, selectedCandidateIds) {
463
+ const countBucket = (bucket) => {
464
+ if (!bucket) return 0;
465
+ return Object.values(bucket).reduce(
466
+ (sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0),
467
+ 0,
468
+ );
469
+ };
470
+
471
+ let total = countBucket(summary.guaranteed);
472
+
473
+ if (selectedCandidateIds === null) {
474
+ total += countBucket(summary.candidates);
475
+ } else {
476
+ total += selectedCandidateIds.length;
477
+ }
478
+
479
+ return total;
480
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Logout command — clear stored API key.
3
+ *
4
+ * Usage: claude-craft logout
5
+ */
6
+ import chalk from 'chalk';
7
+ import { loadConfig, saveConfig } from '../utils/api-client.js';
8
+ import * as logger from '../utils/logger.js';
9
+
10
+ export async function runLogout() {
11
+ const config = loadConfig();
12
+
13
+ if (!config?.apiKey) {
14
+ logger.warn('No API key is currently stored.');
15
+ return;
16
+ }
17
+
18
+ const { apiKey, ...rest } = config;
19
+ saveConfig(rest);
20
+
21
+ console.log();
22
+ logger.success('API key removed from ~/.claude-craft/config.json');
23
+ console.log();
24
+ }