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.
- package/bin/claude-craft.js +85 -0
- package/package.json +39 -0
- package/src/commands/auth.js +43 -0
- package/src/commands/create.js +543 -0
- package/src/commands/install.js +480 -0
- package/src/commands/logout.js +24 -0
- package/src/commands/update.js +339 -0
- package/src/constants.js +299 -0
- package/src/generators/directories.js +30 -0
- package/src/generators/metadata.js +57 -0
- package/src/generators/security.js +39 -0
- package/src/prompts/gather.js +308 -0
- package/src/ui/brand.js +62 -0
- package/src/ui/cards.js +179 -0
- package/src/ui/format.js +55 -0
- package/src/ui/phase-header.js +20 -0
- package/src/ui/prompts.js +56 -0
- package/src/ui/tables.js +89 -0
- package/src/ui/tasks.js +258 -0
- package/src/ui/theme.js +83 -0
- package/src/utils/analysis-cache.js +519 -0
- package/src/utils/api-client.js +253 -0
- package/src/utils/api-file-writer.js +197 -0
- package/src/utils/bootstrap-runner.js +148 -0
- package/src/utils/claude-analyzer.js +255 -0
- package/src/utils/claude-optimizer.js +341 -0
- package/src/utils/claude-rewriter.js +553 -0
- package/src/utils/claude-scorer.js +101 -0
- package/src/utils/description-analyzer.js +116 -0
- package/src/utils/detect-project.js +1276 -0
- package/src/utils/existing-setup.js +341 -0
- package/src/utils/file-writer.js +64 -0
- package/src/utils/json-extract.js +56 -0
- package/src/utils/logger.js +27 -0
- package/src/utils/mcp-setup.js +461 -0
- package/src/utils/preflight.js +112 -0
- package/src/utils/prompt-api-key.js +59 -0
- package/src/utils/run-claude.js +152 -0
- package/src/utils/security.js +82 -0
- 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
|
+
}
|