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,20 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { PHASES, TOTAL_PHASES, getTerminalWidth } from './theme.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render a phase header like:
|
|
6
|
+
* ── Phase 2 of 5 ── Project Discovery ──────────────────
|
|
7
|
+
*/
|
|
8
|
+
export function renderPhaseHeader(phaseNumber) {
|
|
9
|
+
const phase = PHASES.find((p) => p.number === phaseNumber);
|
|
10
|
+
const name = phase ? phase.name : `Phase ${phaseNumber}`;
|
|
11
|
+
const label = `Phase ${phaseNumber} of ${TOTAL_PHASES}`;
|
|
12
|
+
|
|
13
|
+
const w = Math.min(getTerminalWidth() - 4, 56);
|
|
14
|
+
const inner = ` ${label} ── ${name} `;
|
|
15
|
+
const tailLen = Math.max(2, w - inner.length - 2);
|
|
16
|
+
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(chalk.bold.cyan(` ──${inner}${'─'.repeat(tailLen)}`));
|
|
19
|
+
console.log();
|
|
20
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { select, checkbox, confirm, password, input } from '@inquirer/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { colors } from './theme.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Themed select prompt with description hint.
|
|
7
|
+
*/
|
|
8
|
+
export async function themedSelect({ message, hint, choices, ...rest }) {
|
|
9
|
+
if (hint) {
|
|
10
|
+
console.log(colors.muted(` ${hint}`));
|
|
11
|
+
console.log();
|
|
12
|
+
}
|
|
13
|
+
return select({ message, choices, loop: false, ...rest });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Themed checkbox prompt with description hint.
|
|
18
|
+
*/
|
|
19
|
+
export async function themedCheckbox({ message, hint, choices, ...rest }) {
|
|
20
|
+
if (hint) {
|
|
21
|
+
console.log(colors.muted(` ${hint}`));
|
|
22
|
+
console.log();
|
|
23
|
+
}
|
|
24
|
+
return checkbox({ message, choices, loop: false, ...rest });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Themed confirm prompt with description hint.
|
|
29
|
+
*/
|
|
30
|
+
export async function themedConfirm({ message, hint, ...rest }) {
|
|
31
|
+
if (hint) {
|
|
32
|
+
console.log(colors.muted(` ${hint}`));
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
return confirm({ message, ...rest });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Themed password prompt with description hint.
|
|
40
|
+
*/
|
|
41
|
+
export async function themedPassword({ message, hint, ...rest }) {
|
|
42
|
+
if (hint) {
|
|
43
|
+
console.log(colors.muted(` ${hint}`));
|
|
44
|
+
}
|
|
45
|
+
return password({ message, ...rest });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Themed input prompt with description hint.
|
|
50
|
+
*/
|
|
51
|
+
export async function themedInput({ message, hint, ...rest }) {
|
|
52
|
+
if (hint) {
|
|
53
|
+
console.log(colors.muted(` ${hint}`));
|
|
54
|
+
}
|
|
55
|
+
return input({ message, ...rest });
|
|
56
|
+
}
|
package/src/ui/tables.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { colors } from './theme.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render component selection breakdown.
|
|
6
|
+
*/
|
|
7
|
+
export function renderComponentBreakdown(summary) {
|
|
8
|
+
const countItems = (tier) => {
|
|
9
|
+
if (!tier) return 0;
|
|
10
|
+
return Object.values(tier).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const guaranteedCount = countItems(summary.guaranteed);
|
|
14
|
+
const candidateCount = countItems(summary.candidates);
|
|
15
|
+
|
|
16
|
+
const cplx = summary.complexity ?? 0.5;
|
|
17
|
+
const cplxLabel = cplx >= 0.7 ? 'complex' : cplx >= 0.4 ? 'moderate' : 'simple';
|
|
18
|
+
const cplxNote = cplx >= 0.6 ? ' — expanded selection' : cplx < 0.3 ? ' — minimal selection' : '';
|
|
19
|
+
|
|
20
|
+
console.log(chalk.dim(` Complexity: ${colors.success(cplx.toFixed(1))} (${cplxLabel})${cplxNote}`));
|
|
21
|
+
console.log(chalk.dim(' Component selection:'));
|
|
22
|
+
console.log(chalk.dim(` Guaranteed: ${colors.success(String(guaranteedCount))} (core + role + stack)`));
|
|
23
|
+
if (candidateCount > 0) {
|
|
24
|
+
console.log(chalk.dim(` Candidates: ${colors.primary(String(candidateCount))} for Claude scoring`));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (summary.stacks?.length > 0) {
|
|
28
|
+
console.log(chalk.dim(` Tech stacks: ${summary.stacks.map(
|
|
29
|
+
(ts) => `${colors.success(ts.name)} (${ts.impactScore.toFixed(2)})`
|
|
30
|
+
).join(', ')}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Render MCP server verification results.
|
|
36
|
+
*/
|
|
37
|
+
export function renderMcpStatus(mcpResults) {
|
|
38
|
+
console.log();
|
|
39
|
+
for (const r of mcpResults) {
|
|
40
|
+
const versionTag = r.package.version ? chalk.dim(` v${r.package.version}`) : '';
|
|
41
|
+
switch (r.status) {
|
|
42
|
+
case 'ready':
|
|
43
|
+
console.log(colors.success(` ✔ ${r.id}${versionTag} — ready`));
|
|
44
|
+
break;
|
|
45
|
+
case 'verified':
|
|
46
|
+
console.log(colors.success(` ✔ ${r.id}${versionTag} — package verified`));
|
|
47
|
+
break;
|
|
48
|
+
case 'needs-key':
|
|
49
|
+
console.log(colors.warning(` ⚠ ${r.id}${versionTag} — set ${r.apiKey.keyName} env var`));
|
|
50
|
+
break;
|
|
51
|
+
case 'package-error':
|
|
52
|
+
console.log(colors.error(` ✖ ${r.id} — ${r.package.error}`));
|
|
53
|
+
break;
|
|
54
|
+
case 'verified-with-warning':
|
|
55
|
+
console.log(colors.success(` ✔ ${r.id}${versionTag} — package verified`) + chalk.dim(` (startup test: ${r.healthCheck.error || 'failed'})`));
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
console.log(chalk.dim(` ? ${r.id} — ${r.status}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render file write results grouped by status.
|
|
65
|
+
*/
|
|
66
|
+
export function renderFileResults(results) {
|
|
67
|
+
console.log();
|
|
68
|
+
const grouped = { created: [], updated: [], skipped: [] };
|
|
69
|
+
for (const result of results) {
|
|
70
|
+
const status = result.status || 'created';
|
|
71
|
+
if (!grouped[status]) grouped[status] = [];
|
|
72
|
+
grouped[status].push(result);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const statusStyles = {
|
|
76
|
+
created: { icon: '+', color: colors.success },
|
|
77
|
+
updated: { icon: '~', color: colors.warning },
|
|
78
|
+
skipped: { icon: '-', color: chalk.dim },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for (const [status, items] of Object.entries(grouped)) {
|
|
82
|
+
if (items.length === 0) continue;
|
|
83
|
+
const style = statusStyles[status] || { icon: '?', color: chalk.white };
|
|
84
|
+
console.log(style.color(` ${style.icon} ${status} (${items.length}):`));
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
console.log(style.color(` ${item.path}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/ui/tasks.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { Listr } from 'listr2';
|
|
2
|
+
import { isTTY } from './theme.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a listr2 task runner with appropriate renderer.
|
|
6
|
+
*/
|
|
7
|
+
function createTaskRunner(tasks, options = {}) {
|
|
8
|
+
return new Listr(tasks, {
|
|
9
|
+
concurrent: false,
|
|
10
|
+
exitOnError: true,
|
|
11
|
+
rendererOptions: {
|
|
12
|
+
collapseSubtasks: false,
|
|
13
|
+
showTimer: true,
|
|
14
|
+
...options.rendererOptions,
|
|
15
|
+
},
|
|
16
|
+
renderer: isTTY() ? 'default' : 'simple',
|
|
17
|
+
...options,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run existing setup detection, context extraction, and removal.
|
|
23
|
+
* Returns { existingSetup, existingContext, skip }.
|
|
24
|
+
*/
|
|
25
|
+
export async function runExistingSetupTasks(targetDir, { detectExistingSetup, extractExistingContext, removeExistingSetup }) {
|
|
26
|
+
const ctx = {};
|
|
27
|
+
|
|
28
|
+
const tasks = createTaskRunner([
|
|
29
|
+
{
|
|
30
|
+
title: 'Checking for existing Claude configuration',
|
|
31
|
+
task: async (ctx, task) => {
|
|
32
|
+
const setup = detectExistingSetup(targetDir);
|
|
33
|
+
ctx.existingSetup = setup;
|
|
34
|
+
|
|
35
|
+
if (!setup) {
|
|
36
|
+
task.title = 'No existing Claude configuration found';
|
|
37
|
+
ctx.skip = true;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const parts = [];
|
|
42
|
+
if (setup.claudeMd) parts.push('CLAUDE.md');
|
|
43
|
+
if (setup.settings) parts.push('settings.json');
|
|
44
|
+
if (setup.agents > 0) parts.push(`${setup.agents} agents`);
|
|
45
|
+
if (setup.rules > 0) parts.push(`${setup.rules} rules`);
|
|
46
|
+
if (setup.skills > 0) parts.push(`${setup.skills} skills`);
|
|
47
|
+
if (setup.commands > 0) parts.push(`${setup.commands} commands`);
|
|
48
|
+
task.title = `Existing setup detected: ${parts.join(', ')}`;
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: 'Wrapping up current configuration with Claude',
|
|
53
|
+
skip: (ctx) => ctx.skip,
|
|
54
|
+
task: async (ctx, task) => {
|
|
55
|
+
ctx.existingContext = await extractExistingContext(targetDir);
|
|
56
|
+
task.title = 'Context extracted from previous installation';
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: 'Removing previous configuration',
|
|
61
|
+
skip: (ctx) => ctx.skip,
|
|
62
|
+
task: async (ctx, task) => {
|
|
63
|
+
removeExistingSetup(targetDir);
|
|
64
|
+
task.title = 'Previous .claude/ configuration removed';
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
await tasks.run(ctx);
|
|
70
|
+
return ctx;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run project analysis as a multi-step task list.
|
|
75
|
+
* Returns { claudeAnalysis, fsDetected }.
|
|
76
|
+
*/
|
|
77
|
+
export async function runAnalysisTasks(targetDir, { analyzeWithClaude, detectProject, existingContext }) {
|
|
78
|
+
const ctx = {};
|
|
79
|
+
|
|
80
|
+
const tasks = createTaskRunner([
|
|
81
|
+
{
|
|
82
|
+
title: 'Scanning project structure',
|
|
83
|
+
task: async (ctx) => {
|
|
84
|
+
ctx.fsDetected = await detectProject(targetDir);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: 'Analyzing project with Claude',
|
|
89
|
+
task: async (ctx) => {
|
|
90
|
+
const { analysis, failReason } = await analyzeWithClaude(targetDir, existingContext);
|
|
91
|
+
ctx.claudeAnalysis = analysis;
|
|
92
|
+
ctx.claudeFailReason = failReason;
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
await tasks.run(ctx);
|
|
98
|
+
return ctx;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Run installation as a multi-step task list.
|
|
103
|
+
* Returns { results, filesToWrite }.
|
|
104
|
+
*/
|
|
105
|
+
export async function runInstallTasks({
|
|
106
|
+
apiResponse,
|
|
107
|
+
selectedCandidateIds,
|
|
108
|
+
targetDir,
|
|
109
|
+
selectedMcps,
|
|
110
|
+
mcpKeys,
|
|
111
|
+
securityConfig,
|
|
112
|
+
detected,
|
|
113
|
+
buildFileList,
|
|
114
|
+
writeApiFiles,
|
|
115
|
+
scoreWithClaude,
|
|
116
|
+
}) {
|
|
117
|
+
const ctx = {};
|
|
118
|
+
|
|
119
|
+
const candidateCount = apiResponse.candidates?.items?.length || 0;
|
|
120
|
+
const hasScoring = apiResponse.prompts?.scoring && candidateCount > 0;
|
|
121
|
+
|
|
122
|
+
const taskList = [];
|
|
123
|
+
|
|
124
|
+
if (hasScoring) {
|
|
125
|
+
taskList.push({
|
|
126
|
+
title: `Evaluating ${candidateCount} optional candidates with Claude`,
|
|
127
|
+
task: async (ctx) => {
|
|
128
|
+
const scoreResult = await scoreWithClaude(apiResponse.prompts.scoring, targetDir);
|
|
129
|
+
if (scoreResult.selected) {
|
|
130
|
+
ctx.selectedCandidateIds = scoreResult.selected;
|
|
131
|
+
} else {
|
|
132
|
+
ctx.selectedCandidateIds = null;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
taskList.push({
|
|
139
|
+
title: 'Writing configuration files',
|
|
140
|
+
task: async (ctx) => {
|
|
141
|
+
const ids = ctx.selectedCandidateIds ?? selectedCandidateIds;
|
|
142
|
+
ctx.filesToWrite = buildFileList(apiResponse, ids);
|
|
143
|
+
ctx.results = await writeApiFiles(ctx.filesToWrite, targetDir, {
|
|
144
|
+
force: true,
|
|
145
|
+
selectedMcpIds: selectedMcps.map((m) => m.id),
|
|
146
|
+
mcpKeys,
|
|
147
|
+
securityConfig,
|
|
148
|
+
detected,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const tasks = createTaskRunner(taskList);
|
|
154
|
+
await tasks.run(ctx);
|
|
155
|
+
return ctx;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Run MCP verification as a task list.
|
|
160
|
+
*/
|
|
161
|
+
export async function runVerifyTasks(selectedMcps, mcpKeys, { setupMcps, targetDir }) {
|
|
162
|
+
const ctx = {};
|
|
163
|
+
|
|
164
|
+
const tasks = createTaskRunner([
|
|
165
|
+
{
|
|
166
|
+
title: `Verifying ${selectedMcps.length} MCP servers`,
|
|
167
|
+
task: async (ctx, task) => {
|
|
168
|
+
ctx.mcpResults = await setupMcps(selectedMcps, mcpKeys, {
|
|
169
|
+
healthCheck: true,
|
|
170
|
+
targetDir,
|
|
171
|
+
onStatus: (id, status) => {
|
|
172
|
+
if (status === 'verifying') {
|
|
173
|
+
task.title = `Verifying ${id}...`;
|
|
174
|
+
} else if (status === 'testing') {
|
|
175
|
+
task.title = `Health-checking ${id}...`;
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const ready = ctx.mcpResults.filter((r) => r.status === 'ready' || r.status === 'verified' || r.status === 'verified-with-warning');
|
|
181
|
+
const needsKey = ctx.mcpResults.filter((r) => r.status === 'needs-key');
|
|
182
|
+
const failed = ctx.mcpResults.filter((r) => r.status === 'package-error');
|
|
183
|
+
|
|
184
|
+
if (failed.length === 0 && needsKey.length === 0) {
|
|
185
|
+
task.title = `All ${ctx.mcpResults.length} MCP servers verified`;
|
|
186
|
+
} else if (failed.length === 0) {
|
|
187
|
+
task.title = `${ready.length}/${ctx.mcpResults.length} MCP servers verified (${needsKey.length} need API keys)`;
|
|
188
|
+
} else {
|
|
189
|
+
task.title = `${ready.length}/${ctx.mcpResults.length} MCP servers ready, ${failed.length} need attention`;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
await tasks.run(ctx);
|
|
196
|
+
return ctx;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Run finalization tasks (toolkit rule + optimization + CLAUDE.md rewrite).
|
|
201
|
+
*/
|
|
202
|
+
export async function runFinalizeTasks({
|
|
203
|
+
targetDir,
|
|
204
|
+
readAnalysisCache,
|
|
205
|
+
optimizeSettings,
|
|
206
|
+
rewriteClaudeMd,
|
|
207
|
+
generateToolkitRule,
|
|
208
|
+
}) {
|
|
209
|
+
const ctx = {};
|
|
210
|
+
|
|
211
|
+
const taskList = [];
|
|
212
|
+
|
|
213
|
+
if (generateToolkitRule) {
|
|
214
|
+
taskList.push({
|
|
215
|
+
title: 'Generating toolkit usage guide',
|
|
216
|
+
task: async (ctx, task) => {
|
|
217
|
+
const toolkit = generateToolkitRule(targetDir);
|
|
218
|
+
const total = toolkit.agentCount + toolkit.skillCount + toolkit.commandCount;
|
|
219
|
+
ctx.toolkitResult = toolkit;
|
|
220
|
+
task.title = total > 0
|
|
221
|
+
? `Toolkit guide: ${toolkit.agentCount} agents, ${toolkit.skillCount} skills, ${toolkit.commandCount} commands`
|
|
222
|
+
: 'Toolkit guide skipped — no components found';
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
taskList.push({
|
|
228
|
+
title: 'Optimizing settings for this project',
|
|
229
|
+
task: async (ctx, task) => {
|
|
230
|
+
const result = optimizeSettings(targetDir);
|
|
231
|
+
ctx.optimizationResult = result;
|
|
232
|
+
|
|
233
|
+
if (result.status === 'ok' && result.applied > 0) {
|
|
234
|
+
task.title = `Optimized ${result.applied} setting(s)`;
|
|
235
|
+
} else {
|
|
236
|
+
task.title = 'Settings reviewed — no changes needed';
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
taskList.push({
|
|
242
|
+
title: 'Claude is rewriting CLAUDE.md',
|
|
243
|
+
task: async (ctx, task) => {
|
|
244
|
+
const cache = readAnalysisCache(targetDir);
|
|
245
|
+
const rewritten = await rewriteClaudeMd(targetDir, cache);
|
|
246
|
+
ctx.rewritten = rewritten;
|
|
247
|
+
task.title = rewritten
|
|
248
|
+
? 'CLAUDE.md rewritten with project-specific context'
|
|
249
|
+
: 'CLAUDE.md rewrite skipped — using template version';
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (taskList.length === 0) return ctx;
|
|
254
|
+
|
|
255
|
+
const tasks = createTaskRunner(taskList);
|
|
256
|
+
await tasks.run(ctx);
|
|
257
|
+
return ctx;
|
|
258
|
+
}
|
package/src/ui/theme.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
// ── Color palette ────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export const colors = {
|
|
6
|
+
primary: chalk.cyan,
|
|
7
|
+
primaryBold: chalk.bold.cyan,
|
|
8
|
+
success: chalk.green,
|
|
9
|
+
successBold: chalk.bold.green,
|
|
10
|
+
warning: chalk.yellow,
|
|
11
|
+
warningBold: chalk.bold.yellow,
|
|
12
|
+
error: chalk.red,
|
|
13
|
+
errorBold: chalk.bold.red,
|
|
14
|
+
muted: chalk.dim,
|
|
15
|
+
info: chalk.blue,
|
|
16
|
+
bold: chalk.bold,
|
|
17
|
+
underline: chalk.underline,
|
|
18
|
+
highlight: chalk.bold.white,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ── Icons ────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export const icons = {
|
|
24
|
+
check: chalk.green('✔'),
|
|
25
|
+
cross: chalk.red('✖'),
|
|
26
|
+
warning: chalk.yellow('⚠'),
|
|
27
|
+
info: chalk.blue('ℹ'),
|
|
28
|
+
bullet: chalk.dim('•'),
|
|
29
|
+
arrow: chalk.cyan('›'),
|
|
30
|
+
plus: chalk.green('+'),
|
|
31
|
+
tilde: chalk.yellow('~'),
|
|
32
|
+
dash: chalk.dim('─'),
|
|
33
|
+
dot: chalk.dim('·'),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ── Phase definitions ────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
export const PHASES = [
|
|
39
|
+
{ number: 1, name: 'Welcome & Setup' },
|
|
40
|
+
{ number: 2, name: 'Project Discovery' },
|
|
41
|
+
{ number: 3, name: 'Configuration' },
|
|
42
|
+
{ number: 4, name: 'Installation' },
|
|
43
|
+
{ number: 5, name: 'Finalization' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
export const TOTAL_PHASES = PHASES.length;
|
|
47
|
+
|
|
48
|
+
// ── Boxen styles ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export const boxStyles = {
|
|
51
|
+
info: {
|
|
52
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
53
|
+
margin: { top: 0, bottom: 0, left: 2, right: 0 },
|
|
54
|
+
borderStyle: 'round',
|
|
55
|
+
borderColor: 'cyan',
|
|
56
|
+
},
|
|
57
|
+
success: {
|
|
58
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
59
|
+
margin: { top: 1, bottom: 0, left: 2, right: 0 },
|
|
60
|
+
borderStyle: 'double',
|
|
61
|
+
borderColor: 'green',
|
|
62
|
+
},
|
|
63
|
+
warning: {
|
|
64
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
65
|
+
margin: { top: 0, bottom: 0, left: 2, right: 0 },
|
|
66
|
+
borderStyle: 'round',
|
|
67
|
+
borderColor: 'yellow',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ── Terminal detection ───────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
export function getTerminalWidth() {
|
|
74
|
+
return process.stdout.columns || 80;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isTTY() {
|
|
78
|
+
return !!process.stdout.isTTY;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isNarrow() {
|
|
82
|
+
return getTerminalWidth() < 60;
|
|
83
|
+
}
|