@shirayner/ace 0.1.1-snapshot.1 → 0.1.1-snapshot.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shirayner/ace",
3
- "version": "0.1.1-snapshot.1",
3
+ "version": "0.1.1-snapshot.2",
4
4
  "description": "AI Coding Environment - One command to set up your Claude Code harness",
5
5
  "bin": {
6
6
  "ace": "./bin/ace.js"
@@ -4,85 +4,46 @@ 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, sectionHeader, stepDone, stepSkip, stepFail,
8
- fileEntry, summaryBox, doneMessage, doneWithErrors, separator,
9
- conflictHeader, conflictFile,
10
- colors, icons, componentIcons, componentLabels,
7
+ printBanner, stepDone, stepMerge, stepSkip, stepFail,
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
15
  export async function initCommand(options) {
17
- // ─── Banner ──────────────────────────────────────────────────────
18
16
  printBanner(pkg.version);
19
17
 
20
18
  let role = 'fullstack';
21
19
  let preset = options.preset;
22
20
 
23
- // ─── Interactive prompts ─────────────────────────────────────────
21
+ // ─── Interactive: ask role only ─────────────────────────────
24
22
  if (options.interaction !== false) {
25
23
  const answers = await inquirer.prompt([
26
24
  {
27
25
  type: 'list',
28
26
  name: 'role',
29
- message: `${icons.gear} Your primary role?`,
27
+ message: 'Role',
30
28
  choices: Object.entries(ROLES).map(([key, val]) => ({
31
- name: `${colors.white(val.label)} ${colors.dim('—')} ${colors.muted(val.description)}`,
29
+ name: `${colors.white(val.label)} ${colors.dim(val.description)}`,
32
30
  value: key,
33
31
  short: val.label,
34
32
  })),
35
33
  default: 'fullstack',
36
34
  prefix: colors.brand('?'),
37
35
  },
38
- {
39
- type: 'list',
40
- name: 'preset',
41
- message: `${icons.package} Installation scope?`,
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
36
  ]);
63
37
  role = answers.role;
64
- preset = answers.preset;
65
38
  }
66
39
 
67
40
  const components = PRESETS[preset];
68
41
  if (!components) {
69
- console.error(colors.error(` ${icons.cross} Unknown preset: ${preset}. Available: ${Object.keys(PRESETS).join(', ')}`));
42
+ console.error(` ${colors.error(icons.cross)} Unknown preset: ${preset}. Use: ${Object.keys(PRESETS).join(', ')}`);
70
43
  process.exit(1);
71
44
  }
72
45
 
73
- // ─── Config summary ──────────────────────────────────────────────
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 ──────────────────
46
+ // ─── Conflict detection ─────────────────────────────────────
86
47
  const installer = new Installer({
87
48
  force: options.force,
88
49
  dryRun: options.dryRun,
@@ -98,118 +59,88 @@ export async function initCommand(options) {
98
59
  const conflictKeys = Object.keys(conflicts);
99
60
 
100
61
  if (conflictKeys.length > 0) {
101
- console.log();
102
- sectionHeader(icons.warn, 'Existing files detected');
103
- console.log(` ${colors.dim('│')} ${colors.muted('Some files already exist. Choose how to handle each category:')}`);
104
- console.log(` ${colors.dim('│')} ${colors.muted(`CLAUDE.md ${icons.arrowR} always smart-merge (append new refs only)`)}`);
105
- console.log(` ${colors.dim('│')} ${colors.muted(`settings.json ${icons.arrowR} always deep-merge (preserve your settings)`)}`);
106
-
107
- for (const componentName of conflictKeys) {
108
- const { files } = conflicts[componentName];
109
- const icon = componentIcons[componentName] || icons.file;
110
- const label = componentLabels[componentName] || componentName;
111
-
112
- conflictHeader(label, icon, files.length);
113
- for (const f of files) {
114
- conflictFile(f.replace(/\\/g, '/'));
115
- }
116
- }
62
+ const totalFiles = conflictKeys.reduce((sum, k) => sum + conflicts[k].files.length, 0);
117
63
 
64
+ console.log(` ${colors.warning(icons.warn)} ${totalFiles} existing file(s) found.`);
118
65
  console.log();
119
- const conflictAnswers = await inquirer.prompt(
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
- })
144
- );
145
-
146
- resolutions = conflictAnswers;
66
+
67
+ const { action } = await inquirer.prompt([
68
+ {
69
+ type: 'list',
70
+ name: 'action',
71
+ message: 'How to handle?',
72
+ choices: [
73
+ {
74
+ name: `${colors.white('Keep existing')} ${colors.dim('merge compatible, skip rest')}`,
75
+ value: 'skip',
76
+ short: 'Keep',
77
+ },
78
+ {
79
+ name: `${colors.warning('Overwrite all')} ${colors.dim('replace with latest')}`,
80
+ value: 'overwrite',
81
+ short: 'Overwrite',
82
+ },
83
+ ],
84
+ default: 'skip',
85
+ prefix: colors.brand('?'),
86
+ },
87
+ ]);
88
+
89
+ for (const key of conflictKeys) {
90
+ resolutions[key] = action;
91
+ }
147
92
  }
148
93
  }
149
94
 
150
- // Apply resolutions to installer
151
95
  installer.resolutions = resolutions;
152
96
 
153
- // ─── Installation ────────────────────────────────────────────────
97
+ // ─── Install ────────────────────────────────────────────────
98
+ if (options.dryRun) {
99
+ console.log(` ${colors.dim('dry-run — no changes will be made')}`);
100
+ }
154
101
  console.log();
155
- sectionHeader(icons.rocket, 'Installing components');
156
102
 
157
103
  for (const componentName of components) {
158
104
  const component = COMPONENTS[componentName];
159
105
  if (!component) continue;
160
106
 
161
- const icon = componentIcons[componentName] || icons.file;
162
107
  const label = componentLabels[componentName] || componentName;
163
-
164
108
  const beforeInstalled = installer.results.installed.length;
165
109
  const beforeMerged = installer.results.merged.length;
166
110
  const beforeSkipped = installer.results.skipped.length;
167
111
 
168
112
  try {
169
113
  await installer.installComponent(componentName, component);
114
+
170
115
  const newInstalled = installer.results.installed.length - beforeInstalled;
171
116
  const newMerged = installer.results.merged.length - beforeMerged;
172
117
  const newSkipped = installer.results.skipped.length - beforeSkipped;
173
118
 
174
- const parts = [];
175
- if (newInstalled > 0) parts.push(colors.success(`${newInstalled} installed`));
176
- if (newMerged > 0) parts.push(colors.blue(`${newMerged} merged`));
177
- if (newSkipped > 0) parts.push(colors.muted(`${newSkipped} skipped`));
178
- const detail = parts.length > 0 ? ` ${colors.dim('—')} ${parts.join(colors.dim(', '))}` : '';
179
-
180
- stepDone(`${icon} ${label}${detail}`);
119
+ if (newMerged > 0 && newInstalled === 0 && newSkipped === 0) {
120
+ stepMerge(label, 'merged');
121
+ } else if (newSkipped > 0 && newInstalled === 0 && newMerged === 0) {
122
+ stepSkip(label, 'unchanged');
123
+ } else {
124
+ const count = newInstalled + newMerged;
125
+ const detail = count > 0 ? `${count} file${count > 1 ? 's' : ''}` : '';
126
+ stepDone(label, detail);
127
+ }
181
128
  } catch (err) {
182
- stepFail(`${icon} ${label} ${colors.dim('—')} ${colors.error(err.message)}`);
129
+ stepFail(label, err.message);
183
130
  installer.results.errors.push({ component: componentName, error: err.message });
184
131
  }
185
132
  }
186
133
 
187
- const { installed, skipped, merged, errors } = installer.results;
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
- });
134
+ // ─── Summary ────────────────────────────────────────────────
135
+ const { installed, merged, skipped, errors } = installer.results;
209
136
 
210
137
  if (errors.length === 0) {
211
- doneMessage();
138
+ doneMessage({
139
+ installed: installed.length,
140
+ merged: merged.length,
141
+ skipped: skipped.length,
142
+ });
212
143
  } else {
213
- doneWithErrors();
144
+ doneWithErrors({ errors: errors.length });
214
145
  }
215
146
  }
package/src/core/ui.js CHANGED
@@ -1,177 +1,77 @@
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
- arrow: '',
9
- arrowR: '',
10
- dot: '',
11
- circle: '',
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: chalk.hex('#7C3AED'), // vibrant purple
33
- accent: chalk.hex('#06B6D4'), // cyan
34
- success: chalk.hex('#10B981'), // emerald
35
- warning: chalk.hex('#F59E0B'), // amber
36
- error: chalk.hex('#EF4444'), // red
37
- dim: chalk.hex('#6B7280'), // gray
38
- muted: chalk.hex('#9CA3AF'), // light gray
39
- white: chalk.hex('#F9FAFB'), // near white
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
- // ─── ASCII Banner ────────────────────────────────────────────────────
26
+ // ─── Banner (single line) ───────────────────────────────────
44
27
  export function printBanner(version) {
45
- const purple = colors.brand;
46
- const dim = colors.dim;
47
-
48
- console.log();
49
- console.log(purple(' ╔═══╗ ╔═══╗ ╔═══╗'));
50
- console.log(purple(' ╠═══╣ ║ ╠═══╝'));
51
- console.log(purple(' ║ ║ ╚═══╝ ╚═══╗'));
52
28
  console.log();
53
- console.log(` ${purple.bold('ace')} ${dim(`v${version}`)} ${dim('— AI Coding Environment')}`);
29
+ console.log(` ${colors.brand.bold('ace')} ${colors.dim(`v${version}`)}`);
54
30
  console.log();
55
31
  }
56
32
 
57
- // ─── Section Header ──────────────────────────────────────────────────
58
- export function sectionHeader(icon, title) {
59
- console.log(` ${icon} ${colors.white.bold(title)}`);
60
- }
61
-
62
- // ─── Step indicator ──────────────────────────────────────────────────
63
- export function stepStart(label) {
64
- process.stdout.write(` ${colors.dim('│')}\n`);
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`);
33
+ // ─── Step indicators ────────────────────────────────────────
34
+ export function stepDone(label, detail) {
35
+ const d = detail ? colors.dim(` ${detail}`) : '';
36
+ console.log(` ${colors.success(icons.check)} ${label}${d}`);
70
37
  }
71
38
 
72
- export function stepSkip(label) {
73
- process.stdout.write(` ${colors.muted(icons.skip)} ${colors.muted(label)}\n`);
39
+ export function stepMerge(label, detail) {
40
+ const d = detail ? colors.dim(` ${detail}`) : '';
41
+ console.log(` ${colors.blue(icons.merge)} ${label}${d}`);
74
42
  }
75
43
 
76
- export function stepWarn(label) {
77
- process.stdout.write(` ${colors.warning(icons.warn)} ${label}\n`);
44
+ export function stepSkip(label, detail) {
45
+ const d = detail ? colors.dim(` ${detail}`) : '';
46
+ console.log(` ${colors.muted(icons.skip)} ${colors.muted(label)}${d}`);
78
47
  }
79
48
 
80
- export function stepFail(label) {
81
- process.stdout.write(` ${colors.error(icons.cross)} ${label}\n`);
82
- }
83
-
84
- // ─── File list (compact) ─────────────────────────────────────────────
85
- export function fileEntry(action, filePath) {
86
- const prefix = {
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)}`);
101
- }
102
-
103
- // ─── Summary Box ─────────────────────────────────────────────────────
104
- export function summaryBox(stats) {
105
- const { installed, merged, skipped, errors } = stats;
106
-
107
- console.log();
108
- console.log(` ${colors.dim('╭─────────────────────────────────────╮')}`);
109
- console.log(` ${colors.dim('│')} ${colors.white.bold('Installation Summary')} ${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('╰─────────────────────────────────────╯')}`);
49
+ export function stepFail(label, detail) {
50
+ const d = detail ? colors.dim(` ${detail}`) : '';
51
+ console.log(` ${colors.error(icons.cross)} ${label}${d}`);
126
52
  }
127
53
 
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
- }
54
+ // ─── Done messages ──────────────────────────────────────────
55
+ export function doneMessage(stats) {
56
+ const parts = [];
57
+ if (stats.installed > 0) parts.push(`${stats.installed} installed`);
58
+ if (stats.merged > 0) parts.push(`${stats.merged} merged`);
59
+ if (stats.skipped > 0) parts.push(`${stats.skipped} skipped`);
134
60
 
135
- // ─── Final message ───────────────────────────────────────────────────
136
- export function doneMessage() {
137
61
  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.`)}`);
62
+ console.log(` ${colors.success(icons.check)} ${colors.success.bold('Done.')} ${colors.dim(parts.join(', '))}`);
63
+ console.log(` ${colors.dim(` Run ${chalk.white('ace doctor')} to verify.`)}`);
140
64
  console.log();
141
65
  }
142
66
 
143
- export function doneWithErrors() {
67
+ export function doneWithErrors(stats) {
144
68
  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.`)}`);
147
- 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) {
69
+ console.log(` ${colors.warning(icons.warn)} ${colors.warning.bold('Done with errors.')} ${colors.dim(`${stats.errors} failed`)}`);
70
+ console.log(` ${colors.dim(` Run ${chalk.white('ace doctor')} to diagnose.`)}`);
157
71
  console.log();
158
- console.log(` ${colors.warning(icons.warn)} ${icon} ${colors.white.bold(componentName)} — ${colors.warning(`${fileCount} file(s) already exist`)}`);
159
- }
160
-
161
- export function conflictFile(filePath) {
162
- console.log(` ${colors.dim(icons.arrowR)} ${colors.muted(filePath)}`);
163
72
  }
164
73
 
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
-
74
+ // ─── Component labels ───────────────────────────────────────
175
75
  export const componentLabels = {
176
76
  core: 'Core Config',
177
77
  rules: 'Rules',