@shirayner/ace 0.1.1-snapshot.1 → 0.1.1-snapshot.3
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/package.json +1 -1
- package/src/commands/init.js +107 -143
- package/src/core/ui.js +57 -142
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -1,88 +1,55 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { PRESETS, ROLES, COMPONENTS } from '../core/constants.js';
|
|
5
5
|
import { Installer } from '../core/installer.js';
|
|
6
6
|
import {
|
|
7
|
-
printBanner,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
colors, icons, componentIcons, componentLabels,
|
|
7
|
+
printBanner, renderScreen,
|
|
8
|
+
doneMessage, doneWithErrors,
|
|
9
|
+
colors, icons, componentLabels,
|
|
11
10
|
} from '../core/ui.js';
|
|
12
11
|
|
|
13
12
|
const require = createRequire(import.meta.url);
|
|
14
13
|
const pkg = require('../../package.json');
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
function formatStep(label, value) {
|
|
16
|
+
return ` ${colors.success(icons.check)} ${colors.dim(label)} ${colors.dim('·')} ${colors.white(value)}`;
|
|
17
|
+
}
|
|
19
18
|
|
|
19
|
+
export async function initCommand(options) {
|
|
20
|
+
const version = pkg.version;
|
|
20
21
|
let role = 'fullstack';
|
|
21
22
|
let preset = options.preset;
|
|
23
|
+
const completedSteps = [];
|
|
22
24
|
|
|
23
|
-
// ───
|
|
25
|
+
// ─── Step 1: Role ──────────────────────────────────────────
|
|
24
26
|
if (options.interaction !== false) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
choices: [
|
|
43
|
-
{
|
|
44
|
-
name: `${colors.white('Full')} ${colors.dim('—')} ${colors.muted('All components (rules, plugin, hooks, safety guards, memory)')}`,
|
|
45
|
-
value: 'full',
|
|
46
|
-
short: 'Full',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: `${colors.white('Safe')} ${colors.dim('—')} ${colors.muted('Core + rules + plugin + safety guards + memory')}`,
|
|
50
|
-
value: 'safe',
|
|
51
|
-
short: 'Safe',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: `${colors.white('Minimal')} ${colors.dim('—')} ${colors.muted('Core + rules + plugin only')}`,
|
|
55
|
-
value: 'minimal',
|
|
56
|
-
short: 'Minimal',
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
default: 'full',
|
|
60
|
-
prefix: colors.brand('?'),
|
|
61
|
-
},
|
|
62
|
-
]);
|
|
63
|
-
role = answers.role;
|
|
64
|
-
preset = answers.preset;
|
|
27
|
+
renderScreen(version);
|
|
28
|
+
|
|
29
|
+
const result = await inquirer.prompt([{
|
|
30
|
+
type: 'list',
|
|
31
|
+
name: 'role',
|
|
32
|
+
message: 'Role',
|
|
33
|
+
choices: Object.entries(ROLES).map(([key, val]) => ({
|
|
34
|
+
name: `${colors.white(val.label)} ${colors.dim(val.description)}`,
|
|
35
|
+
value: key,
|
|
36
|
+
short: val.label,
|
|
37
|
+
})),
|
|
38
|
+
default: 'fullstack',
|
|
39
|
+
prefix: colors.brand('?'),
|
|
40
|
+
}]);
|
|
41
|
+
|
|
42
|
+
role = result.role;
|
|
43
|
+
completedSteps.push(formatStep('Role', ROLES[role].label));
|
|
65
44
|
}
|
|
66
45
|
|
|
67
46
|
const components = PRESETS[preset];
|
|
68
47
|
if (!components) {
|
|
69
|
-
console.error(colors.error(
|
|
48
|
+
console.error(` ${colors.error(icons.cross)} Unknown preset: ${preset}. Use: ${Object.keys(PRESETS).join(', ')}`);
|
|
70
49
|
process.exit(1);
|
|
71
50
|
}
|
|
72
51
|
|
|
73
|
-
// ───
|
|
74
|
-
console.log();
|
|
75
|
-
console.log(` ${colors.dim('│')} ${colors.dim('Role')} ${colors.white(ROLES[role].label)}`);
|
|
76
|
-
console.log(` ${colors.dim('│')} ${colors.dim('Preset')} ${colors.white(preset)}`);
|
|
77
|
-
console.log(` ${colors.dim('│')} ${colors.dim('Scope')} ${components.map(c => componentLabels[c] || c).join(colors.dim(', '))}`);
|
|
78
|
-
if (options.force) {
|
|
79
|
-
console.log(` ${colors.dim('│')} ${colors.warning(`${icons.warn} Force mode — existing files will be overwritten`)}`);
|
|
80
|
-
}
|
|
81
|
-
if (options.dryRun) {
|
|
82
|
-
console.log(` ${colors.dim('│')} ${colors.accent(`${icons.info} Dry-run mode — no changes will be made`)}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ─── Conflict detection & category confirmation ──────────────────
|
|
52
|
+
// ─── Step 2: Conflicts (conditional) ───────────────────────
|
|
86
53
|
const installer = new Installer({
|
|
87
54
|
force: options.force,
|
|
88
55
|
dryRun: options.dryRun,
|
|
@@ -98,118 +65,115 @@ export async function initCommand(options) {
|
|
|
98
65
|
const conflictKeys = Object.keys(conflicts);
|
|
99
66
|
|
|
100
67
|
if (conflictKeys.length > 0) {
|
|
68
|
+
const totalFiles = conflictKeys.reduce((sum, k) => sum + conflicts[k].files.length, 0);
|
|
69
|
+
|
|
70
|
+
renderScreen(version, completedSteps);
|
|
71
|
+
console.log(` ${colors.warning(icons.warn)} ${totalFiles} existing file(s) found`);
|
|
101
72
|
console.log();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
73
|
+
|
|
74
|
+
const { action } = await inquirer.prompt([{
|
|
75
|
+
type: 'list',
|
|
76
|
+
name: 'action',
|
|
77
|
+
message: 'How to handle?',
|
|
78
|
+
choices: [
|
|
79
|
+
{
|
|
80
|
+
name: `${colors.white('Keep existing')} ${colors.dim('merge compatible, skip rest')}`,
|
|
81
|
+
value: 'skip',
|
|
82
|
+
short: 'Keep',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: `${colors.warning('Overwrite all')} ${colors.dim('replace with latest')}`,
|
|
86
|
+
value: 'overwrite',
|
|
87
|
+
short: 'Overwrite',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
default: 'skip',
|
|
91
|
+
prefix: colors.brand('?'),
|
|
92
|
+
}]);
|
|
93
|
+
|
|
94
|
+
for (const key of conflictKeys) {
|
|
95
|
+
resolutions[key] = action;
|
|
116
96
|
}
|
|
117
97
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
conflictKeys.map((componentName) => {
|
|
121
|
-
const icon = componentIcons[componentName] || icons.file;
|
|
122
|
-
const label = componentLabels[componentName] || componentName;
|
|
123
|
-
const count = conflicts[componentName].files.length;
|
|
124
|
-
return {
|
|
125
|
-
type: 'list',
|
|
126
|
-
name: componentName,
|
|
127
|
-
message: `${icon} ${label} (${count} files)`,
|
|
128
|
-
choices: [
|
|
129
|
-
{
|
|
130
|
-
name: `${colors.muted('Skip')} ${colors.dim('— keep existing files')}`,
|
|
131
|
-
value: 'skip',
|
|
132
|
-
short: 'Skip',
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: `${colors.warning('Overwrite')} ${colors.dim('— replace with ace templates')}`,
|
|
136
|
-
value: 'overwrite',
|
|
137
|
-
short: 'Overwrite',
|
|
138
|
-
},
|
|
139
|
-
],
|
|
140
|
-
default: 'skip',
|
|
141
|
-
prefix: colors.brand('?'),
|
|
142
|
-
};
|
|
143
|
-
})
|
|
98
|
+
completedSteps.push(
|
|
99
|
+
formatStep('Conflicts', action === 'skip' ? 'Keep existing' : 'Overwrite all')
|
|
144
100
|
);
|
|
145
|
-
|
|
146
|
-
resolutions = conflictAnswers;
|
|
147
101
|
}
|
|
148
102
|
}
|
|
149
103
|
|
|
150
|
-
// Apply resolutions to installer
|
|
151
104
|
installer.resolutions = resolutions;
|
|
152
105
|
|
|
153
|
-
// ───
|
|
154
|
-
|
|
155
|
-
|
|
106
|
+
// ─── Step 3: Install ───────────────────────────────────────
|
|
107
|
+
if (options.interaction !== false) {
|
|
108
|
+
renderScreen(version, completedSteps);
|
|
109
|
+
} else {
|
|
110
|
+
printBanner(version);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (options.dryRun) {
|
|
114
|
+
console.log(` ${colors.dim('dry-run — no changes will be made')}`);
|
|
115
|
+
console.log();
|
|
116
|
+
}
|
|
156
117
|
|
|
157
118
|
for (const componentName of components) {
|
|
158
119
|
const component = COMPONENTS[componentName];
|
|
159
120
|
if (!component) continue;
|
|
160
121
|
|
|
161
|
-
const icon = componentIcons[componentName] || icons.file;
|
|
162
122
|
const label = componentLabels[componentName] || componentName;
|
|
163
|
-
|
|
164
123
|
const beforeInstalled = installer.results.installed.length;
|
|
165
124
|
const beforeMerged = installer.results.merged.length;
|
|
166
125
|
const beforeSkipped = installer.results.skipped.length;
|
|
167
126
|
|
|
127
|
+
const spinner = ora({
|
|
128
|
+
text: label,
|
|
129
|
+
indent: 2,
|
|
130
|
+
color: 'magenta',
|
|
131
|
+
}).start();
|
|
132
|
+
|
|
168
133
|
try {
|
|
169
134
|
await installer.installComponent(componentName, component);
|
|
135
|
+
|
|
170
136
|
const newInstalled = installer.results.installed.length - beforeInstalled;
|
|
171
137
|
const newMerged = installer.results.merged.length - beforeMerged;
|
|
172
138
|
const newSkipped = installer.results.skipped.length - beforeSkipped;
|
|
173
139
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
140
|
+
if (newMerged > 0 && newInstalled === 0 && newSkipped === 0) {
|
|
141
|
+
spinner.stopAndPersist({
|
|
142
|
+
symbol: colors.blue(icons.merge),
|
|
143
|
+
text: `${label} ${colors.dim('merged')}`,
|
|
144
|
+
});
|
|
145
|
+
} else if (newSkipped > 0 && newInstalled === 0 && newMerged === 0) {
|
|
146
|
+
spinner.stopAndPersist({
|
|
147
|
+
symbol: colors.muted(icons.skip),
|
|
148
|
+
text: `${colors.muted(label)} ${colors.dim('unchanged')}`,
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
const count = newInstalled + newMerged;
|
|
152
|
+
const detail = count > 0 ? ` ${count} file${count > 1 ? 's' : ''}` : '';
|
|
153
|
+
spinner.stopAndPersist({
|
|
154
|
+
symbol: colors.success(icons.check),
|
|
155
|
+
text: `${label}${colors.dim(detail)}`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
181
158
|
} catch (err) {
|
|
182
|
-
|
|
159
|
+
spinner.stopAndPersist({
|
|
160
|
+
symbol: colors.error(icons.cross),
|
|
161
|
+
text: `${label} ${colors.dim(err.message)}`,
|
|
162
|
+
});
|
|
183
163
|
installer.results.errors.push({ component: componentName, error: err.message });
|
|
184
164
|
}
|
|
185
165
|
}
|
|
186
166
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// ─── Detailed file list ──────────────────────────────────────────
|
|
190
|
-
if (installed.length > 0 || merged.length > 0 || skipped.length > 0) {
|
|
191
|
-
separator();
|
|
192
|
-
sectionHeader(icons.file, 'File details');
|
|
193
|
-
for (const f of installed) fileEntry('install', f.replace(/\\/g, '/'));
|
|
194
|
-
for (const m of merged) {
|
|
195
|
-
const detail = m.added ? ` (${m.added.length} refs)` : '';
|
|
196
|
-
fileEntry('merge', `${m.file.replace(/\\/g, '/')}${detail}`);
|
|
197
|
-
}
|
|
198
|
-
for (const f of skipped) fileEntry('skip', f.replace(/\\/g, '/'));
|
|
199
|
-
for (const e of errors) fileEntry('error', `${(e.file || e.component).replace(/\\/g, '/')}: ${e.error}`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ─── Summary ─────────────────────────────────────────────────────
|
|
203
|
-
summaryBox({
|
|
204
|
-
installed: installed.length,
|
|
205
|
-
merged: merged.length,
|
|
206
|
-
skipped: skipped.length,
|
|
207
|
-
errors: errors.length,
|
|
208
|
-
});
|
|
167
|
+
// ─── Summary ────────────────────────────────────────────────
|
|
168
|
+
const { installed, merged, skipped, errors } = installer.results;
|
|
209
169
|
|
|
210
170
|
if (errors.length === 0) {
|
|
211
|
-
doneMessage(
|
|
171
|
+
doneMessage({
|
|
172
|
+
installed: installed.length,
|
|
173
|
+
merged: merged.length,
|
|
174
|
+
skipped: skipped.length,
|
|
175
|
+
});
|
|
212
176
|
} else {
|
|
213
|
-
doneWithErrors();
|
|
177
|
+
doneWithErrors({ errors: errors.length });
|
|
214
178
|
}
|
|
215
179
|
}
|
package/src/core/ui.js
CHANGED
|
@@ -1,177 +1,92 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
|
|
3
|
-
// ─── Icons
|
|
3
|
+
// ─── Icons (minimal set) ────────────────────────────────────
|
|
4
4
|
export const icons = {
|
|
5
|
-
ace:
|
|
6
|
-
check:
|
|
7
|
-
cross:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
dot:
|
|
11
|
-
|
|
12
|
-
warn: '⚠',
|
|
13
|
-
info: 'ℹ',
|
|
14
|
-
skip: '◇',
|
|
15
|
-
merge: '⇄',
|
|
16
|
-
folder: '▸',
|
|
17
|
-
shield: '🛡',
|
|
18
|
-
gear: '⚙',
|
|
19
|
-
rocket: '🚀',
|
|
20
|
-
sparkles: '✨',
|
|
21
|
-
package: '📦',
|
|
22
|
-
file: '📄',
|
|
23
|
-
brain: '🧠',
|
|
24
|
-
hook: '🪝',
|
|
25
|
-
guard: '🔒',
|
|
26
|
-
memory: '💾',
|
|
27
|
-
plug: '🔌',
|
|
5
|
+
ace: '◆',
|
|
6
|
+
check: '✔',
|
|
7
|
+
cross: '✖',
|
|
8
|
+
warn: '⚠',
|
|
9
|
+
skip: '◇',
|
|
10
|
+
dot: '·',
|
|
11
|
+
merge: '~',
|
|
28
12
|
};
|
|
29
13
|
|
|
30
|
-
// ─── Colors
|
|
14
|
+
// ─── Colors ─────────────────────────────────────────────────
|
|
31
15
|
export const colors = {
|
|
32
|
-
brand:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
blue: chalk.hex('#3B82F6'), // blue
|
|
16
|
+
brand: chalk.hex('#7C3AED'),
|
|
17
|
+
success: chalk.hex('#10B981'),
|
|
18
|
+
warning: chalk.hex('#F59E0B'),
|
|
19
|
+
error: chalk.hex('#EF4444'),
|
|
20
|
+
dim: chalk.hex('#6B7280'),
|
|
21
|
+
muted: chalk.hex('#9CA3AF'),
|
|
22
|
+
white: chalk.hex('#F9FAFB'),
|
|
23
|
+
blue: chalk.hex('#3B82F6'),
|
|
41
24
|
};
|
|
42
25
|
|
|
43
|
-
// ───
|
|
44
|
-
export function
|
|
45
|
-
|
|
46
|
-
|
|
26
|
+
// ─── Screen control ─────────────────────────────────────────
|
|
27
|
+
export function clearScreen() {
|
|
28
|
+
process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
|
|
29
|
+
}
|
|
47
30
|
|
|
31
|
+
// ─── Banner (single line) ───────────────────────────────────
|
|
32
|
+
export function printBanner(version) {
|
|
48
33
|
console.log();
|
|
49
|
-
console.log(
|
|
50
|
-
console.log(purple(' ╠═══╣ ║ ╠═══╝'));
|
|
51
|
-
console.log(purple(' ║ ║ ╚═══╝ ╚═══╗'));
|
|
52
|
-
console.log();
|
|
53
|
-
console.log(` ${purple.bold('ace')} ${dim(`v${version}`)} ${dim('— AI Coding Environment')}`);
|
|
34
|
+
console.log(` ${colors.brand.bold('◆ ace')} ${colors.dim(`v${version}`)}`);
|
|
54
35
|
console.log();
|
|
55
36
|
}
|
|
56
37
|
|
|
57
|
-
// ───
|
|
58
|
-
export function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
process.stdout.write(` ${colors.brand(icons.dot)} ${label}\n`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function stepDone(label) {
|
|
69
|
-
process.stdout.write(` ${colors.success(icons.check)} ${label}\n`);
|
|
38
|
+
// ─── Step-by-step screen (clear + banner + previous answers) ─
|
|
39
|
+
export function renderScreen(version, completedSteps = []) {
|
|
40
|
+
clearScreen();
|
|
41
|
+
printBanner(version);
|
|
42
|
+
for (const step of completedSteps) {
|
|
43
|
+
console.log(step);
|
|
44
|
+
}
|
|
45
|
+
if (completedSteps.length > 0) console.log();
|
|
70
46
|
}
|
|
71
47
|
|
|
72
|
-
|
|
73
|
-
|
|
48
|
+
// ─── Step indicators ────────────────────────────────────────
|
|
49
|
+
export function stepDone(label, detail) {
|
|
50
|
+
const d = detail ? colors.dim(` ${detail}`) : '';
|
|
51
|
+
console.log(` ${colors.success(icons.check)} ${label}${d}`);
|
|
74
52
|
}
|
|
75
53
|
|
|
76
|
-
export function
|
|
77
|
-
|
|
54
|
+
export function stepMerge(label, detail) {
|
|
55
|
+
const d = detail ? colors.dim(` ${detail}`) : '';
|
|
56
|
+
console.log(` ${colors.blue(icons.merge)} ${label}${d}`);
|
|
78
57
|
}
|
|
79
58
|
|
|
80
|
-
export function
|
|
81
|
-
|
|
59
|
+
export function stepSkip(label, detail) {
|
|
60
|
+
const d = detail ? colors.dim(` ${detail}`) : '';
|
|
61
|
+
console.log(` ${colors.muted(icons.skip)} ${colors.muted(label)}${d}`);
|
|
82
62
|
}
|
|
83
63
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
install: ` ${colors.success('+')}`,
|
|
88
|
-
merge: ` ${colors.blue('~')}`,
|
|
89
|
-
skip: ` ${colors.muted('-')}`,
|
|
90
|
-
overwrite:` ${colors.warning('!')}`,
|
|
91
|
-
error: ` ${colors.error('✖')}`,
|
|
92
|
-
};
|
|
93
|
-
const color = {
|
|
94
|
-
install: colors.success,
|
|
95
|
-
merge: colors.blue,
|
|
96
|
-
skip: colors.muted,
|
|
97
|
-
overwrite:colors.warning,
|
|
98
|
-
error: colors.error,
|
|
99
|
-
};
|
|
100
|
-
console.log(`${prefix[action] || ' '} ${(color[action] || colors.dim)(filePath)}`);
|
|
64
|
+
export function stepFail(label, detail) {
|
|
65
|
+
const d = detail ? colors.dim(` ${detail}`) : '';
|
|
66
|
+
console.log(` ${colors.error(icons.cross)} ${label}${d}`);
|
|
101
67
|
}
|
|
102
68
|
|
|
103
|
-
// ───
|
|
104
|
-
export function
|
|
105
|
-
const
|
|
69
|
+
// ─── Done messages ──────────────────────────────────────────
|
|
70
|
+
export function doneMessage(stats) {
|
|
71
|
+
const parts = [];
|
|
72
|
+
if (stats.installed > 0) parts.push(`${stats.installed} installed`);
|
|
73
|
+
if (stats.merged > 0) parts.push(`${stats.merged} merged`);
|
|
74
|
+
if (stats.skipped > 0) parts.push(`${stats.skipped} skipped`);
|
|
106
75
|
|
|
107
76
|
console.log();
|
|
108
|
-
console.log(` ${colors.dim('
|
|
109
|
-
console.log(` ${colors.dim(
|
|
110
|
-
console.log(` ${colors.dim('├─────────────────────────────────────┤')}`);
|
|
111
|
-
|
|
112
|
-
if (installed > 0) {
|
|
113
|
-
console.log(` ${colors.dim('│')} ${colors.success(icons.check)} ${colors.success(`${installed} installed`)}${pad(installed, 'installed')}${colors.dim('│')}`);
|
|
114
|
-
}
|
|
115
|
-
if (merged > 0) {
|
|
116
|
-
console.log(` ${colors.dim('│')} ${colors.blue(icons.merge)} ${colors.blue(`${merged} merged`)}${pad(merged, 'merged')}${colors.dim('│')}`);
|
|
117
|
-
}
|
|
118
|
-
if (skipped > 0) {
|
|
119
|
-
console.log(` ${colors.dim('│')} ${colors.muted(icons.skip)} ${colors.muted(`${skipped} skipped`)}${pad(skipped, 'skipped')}${colors.dim('│')}`);
|
|
120
|
-
}
|
|
121
|
-
if (errors > 0) {
|
|
122
|
-
console.log(` ${colors.dim('│')} ${colors.error(icons.cross)} ${colors.error(`${errors} errors`)}${pad(errors, 'errors')}${colors.dim('│')}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log(` ${colors.dim('╰─────────────────────────────────────╯')}`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function pad(count, label) {
|
|
129
|
-
const text = `${count} ${label}`;
|
|
130
|
-
const total = 33;
|
|
131
|
-
const spaces = total - text.length - 4; // 4 = icon + spaces
|
|
132
|
-
return ' '.repeat(Math.max(1, spaces));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ─── Final message ───────────────────────────────────────────────────
|
|
136
|
-
export function doneMessage() {
|
|
137
|
-
console.log();
|
|
138
|
-
console.log(` ${icons.rocket} ${colors.success.bold('Your AI coding environment is ready.')}`);
|
|
139
|
-
console.log(` ${colors.dim(`Run ${chalk.white('ace doctor')} to verify the installation.`)}`);
|
|
77
|
+
console.log(` ${colors.success(icons.check)} ${colors.success.bold('Done.')} ${colors.dim(parts.join(', '))}`);
|
|
78
|
+
console.log(` ${colors.dim(` Run ${chalk.white('ace doctor')} to verify.`)}`);
|
|
140
79
|
console.log();
|
|
141
80
|
}
|
|
142
81
|
|
|
143
|
-
export function doneWithErrors() {
|
|
144
|
-
console.log();
|
|
145
|
-
console.log(` ${icons.warn} ${colors.warning.bold('Completed with errors.')}`);
|
|
146
|
-
console.log(` ${colors.dim(`Run ${chalk.white('ace doctor')} to diagnose.`)}`);
|
|
82
|
+
export function doneWithErrors(stats) {
|
|
147
83
|
console.log();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ─── Separator ───────────────────────────────────────────────────────
|
|
151
|
-
export function separator() {
|
|
152
|
-
console.log(` ${colors.dim('│')}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ─── Conflict prompt helpers ─────────────────────────────────────────
|
|
156
|
-
export function conflictHeader(componentName, icon, fileCount) {
|
|
84
|
+
console.log(` ${colors.warning(icons.warn)} ${colors.warning.bold('Done with errors.')} ${colors.dim(`${stats.errors} failed`)}`);
|
|
85
|
+
console.log(` ${colors.dim(` Run ${chalk.white('ace doctor')} to diagnose.`)}`);
|
|
157
86
|
console.log();
|
|
158
|
-
console.log(` ${colors.warning(icons.warn)} ${icon} ${colors.white.bold(componentName)} — ${colors.warning(`${fileCount} file(s) already exist`)}`);
|
|
159
87
|
}
|
|
160
88
|
|
|
161
|
-
|
|
162
|
-
console.log(` ${colors.dim(icons.arrowR)} ${colors.muted(filePath)}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ─── Component icons ─────────────────────────────────────────────────
|
|
166
|
-
export const componentIcons = {
|
|
167
|
-
core: icons.gear,
|
|
168
|
-
rules: icons.brain,
|
|
169
|
-
plugin: icons.plug,
|
|
170
|
-
hooks: icons.hook,
|
|
171
|
-
hookify: icons.guard,
|
|
172
|
-
memory: icons.memory,
|
|
173
|
-
};
|
|
174
|
-
|
|
89
|
+
// ─── Component labels ───────────────────────────────────────
|
|
175
90
|
export const componentLabels = {
|
|
176
91
|
core: 'Core Config',
|
|
177
92
|
rules: 'Rules',
|