@shirayner/ace 0.1.1-snapshot.3 → 0.1.1-snapshot.4

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/ace.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { createRequire } from 'module';
4
4
  import { Command } from 'commander';
5
- import chalk from 'chalk';
6
5
  import { initCommand } from '../src/commands/init.js';
7
6
  import { doctorCommand } from '../src/commands/doctor.js';
8
7
  import { listCommand } from '../src/commands/list.js';
@@ -22,10 +21,8 @@ program
22
21
  program
23
22
  .command('init')
24
23
  .description('Initialize AI coding environment')
25
- .option('-p, --preset <name>', 'Installation preset: full, minimal, safe', 'full')
26
24
  .option('-f, --force', 'Overwrite existing files', false)
27
25
  .option('--dry-run', 'Show what would be done without making changes', false)
28
- .option('--no-interaction', 'Skip interactive prompts, use defaults')
29
26
  .action(initCommand);
30
27
 
31
28
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shirayner/ace",
3
- "version": "0.1.1-snapshot.3",
3
+ "version": "0.1.1-snapshot.4",
4
4
  "description": "AI Coding Environment - One command to set up your Claude Code harness",
5
5
  "bin": {
6
6
  "ace": "./bin/ace.js"
@@ -25,11 +25,12 @@
25
25
  },
26
26
  "type": "module",
27
27
  "dependencies": {
28
- "commander": "^12.0.0",
28
+ "@clack/prompts": "^1.2.0",
29
29
  "chalk": "^5.3.0",
30
- "inquirer": "^9.2.0",
31
- "fs-extra": "^11.2.0",
30
+ "commander": "^12.0.0",
32
31
  "deepmerge": "^4.3.1",
32
+ "fs-extra": "^11.2.0",
33
+ "inquirer": "^9.2.0",
33
34
  "js-yaml": "^4.1.0",
34
35
  "ora": "^8.0.0"
35
36
  },
@@ -1,120 +1,84 @@
1
- import inquirer from 'inquirer';
2
- import ora from 'ora';
1
+ import * as p from '@clack/prompts';
3
2
  import { createRequire } from 'module';
4
- import { PRESETS, ROLES, COMPONENTS } from '../core/constants.js';
3
+ import { PRESETS, COMPONENTS } from '../core/constants.js';
5
4
  import { Installer } from '../core/installer.js';
6
- import {
7
- printBanner, renderScreen,
8
- doneMessage, doneWithErrors,
9
- colors, icons, componentLabels,
10
- } from '../core/ui.js';
11
5
 
12
6
  const require = createRequire(import.meta.url);
13
7
  const pkg = require('../../package.json');
14
8
 
15
- function formatStep(label, value) {
16
- return ` ${colors.success(icons.check)} ${colors.dim(label)} ${colors.dim('·')} ${colors.white(value)}`;
17
- }
9
+ const componentLabels = {
10
+ core: 'Core Config',
11
+ rules: 'Rules',
12
+ plugin: 'Plugin',
13
+ hooks: 'Hooks',
14
+ hookify: 'Safety Guards',
15
+ memory: 'Memory',
16
+ };
18
17
 
19
18
  export async function initCommand(options) {
20
19
  const version = pkg.version;
21
- let role = 'fullstack';
22
- let preset = options.preset;
23
- const completedSteps = [];
24
-
25
- // ─── Step 1: Role ──────────────────────────────────────────
26
- if (options.interaction !== false) {
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));
44
- }
20
+ const components = PRESETS['full'];
45
21
 
46
- const components = PRESETS[preset];
47
- if (!components) {
48
- console.error(` ${colors.error(icons.cross)} Unknown preset: ${preset}. Use: ${Object.keys(PRESETS).join(', ')}`);
49
- process.exit(1);
50
- }
22
+ // ─── Intro ──────────────────────────────────────────────
23
+ p.intro(`ace v${version}`);
51
24
 
52
- // ─── Step 2: Conflicts (conditional) ───────────────────────
25
+ // ─── Conflict detection ─────────────────────────────────
53
26
  const installer = new Installer({
54
27
  force: options.force,
55
28
  dryRun: options.dryRun,
56
- role,
29
+ role: 'fullstack',
57
30
  components,
58
31
  quiet: true,
59
32
  });
60
33
 
61
34
  let resolutions = {};
62
35
 
63
- if (!options.force && options.interaction !== false) {
36
+ if (!options.force) {
64
37
  const conflicts = await installer.detectConflicts();
65
38
  const conflictKeys = Object.keys(conflicts);
66
39
 
67
40
  if (conflictKeys.length > 0) {
68
41
  const totalFiles = conflictKeys.reduce((sum, k) => sum + conflicts[k].files.length, 0);
42
+ const mergeComponents = conflictKeys.filter(k => conflicts[k].hasMerge);
43
+
44
+ if (mergeComponents.length > 0) {
45
+ p.log.info('Safe merge: CLAUDE.md, settings.json (preserves your changes)');
46
+ }
47
+ if (totalFiles > 0) {
48
+ p.log.warn(`${totalFiles} existing file(s) found`);
49
+ }
69
50
 
70
- renderScreen(version, completedSteps);
71
- console.log(` ${colors.warning(icons.warn)} ${totalFiles} existing file(s) found`);
72
- console.log();
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
- },
51
+ const action = await p.select({
52
+ message: 'How to handle existing files?',
53
+ options: [
54
+ { value: 'skip', label: 'Keep & merge', hint: 'recommended' },
55
+ { value: 'overwrite', label: 'Overwrite all', hint: 'replace with latest' },
56
+ { value: 'cancel', label: 'Cancel' },
89
57
  ],
90
- default: 'skip',
91
- prefix: colors.brand('?'),
92
- }]);
58
+ initialValue: 'skip',
59
+ });
60
+
61
+ if (p.isCancel(action) || action === 'cancel') {
62
+ p.cancel('Setup cancelled.');
63
+ process.exit(0);
64
+ }
93
65
 
94
66
  for (const key of conflictKeys) {
95
67
  resolutions[key] = action;
96
68
  }
97
-
98
- completedSteps.push(
99
- formatStep('Conflicts', action === 'skip' ? 'Keep existing' : 'Overwrite all')
100
- );
101
69
  }
102
70
  }
103
71
 
104
72
  installer.resolutions = resolutions;
105
73
 
106
- // ─── Step 3: Install ───────────────────────────────────────
107
- if (options.interaction !== false) {
108
- renderScreen(version, completedSteps);
109
- } else {
110
- printBanner(version);
111
- }
112
-
74
+ // ─── Dry-run notice ─────────────────────────────────────
113
75
  if (options.dryRun) {
114
- console.log(` ${colors.dim('dry-run — no changes will be made')}`);
115
- console.log();
76
+ p.log.warn('dry-run — no changes will be made');
116
77
  }
117
78
 
79
+ // ─── Install ────────────────────────────────────────────
80
+ p.log.step('Installing to ~/.claude/');
81
+
118
82
  for (const componentName of components) {
119
83
  const component = COMPONENTS[componentName];
120
84
  if (!component) continue;
@@ -124,56 +88,66 @@ export async function initCommand(options) {
124
88
  const beforeMerged = installer.results.merged.length;
125
89
  const beforeSkipped = installer.results.skipped.length;
126
90
 
127
- const spinner = ora({
128
- text: label,
129
- indent: 2,
130
- color: 'magenta',
131
- }).start();
91
+ const s = p.spinner();
92
+ s.start(label);
132
93
 
133
94
  try {
134
95
  await installer.installComponent(componentName, component);
96
+ s.stop(label);
135
97
 
136
98
  const newInstalled = installer.results.installed.length - beforeInstalled;
137
99
  const newMerged = installer.results.merged.length - beforeMerged;
138
100
  const newSkipped = installer.results.skipped.length - beforeSkipped;
139
101
 
140
102
  if (newMerged > 0 && newInstalled === 0 && newSkipped === 0) {
141
- spinner.stopAndPersist({
142
- symbol: colors.blue(icons.merge),
143
- text: `${label} ${colors.dim('merged')}`,
144
- });
103
+ p.log.info(`${label} — merged`);
145
104
  } 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
- });
105
+ p.log.message(`${label} — unchanged`);
150
106
  } else {
151
107
  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
- });
108
+ const detail = count > 0 ? `${count} file${count > 1 ? 's' : ''}` : '';
109
+ p.log.success(`${label} — ${detail}`);
157
110
  }
158
111
  } catch (err) {
159
- spinner.stopAndPersist({
160
- symbol: colors.error(icons.cross),
161
- text: `${label} ${colors.dim(err.message)}`,
162
- });
112
+ s.stop(label);
113
+ p.log.error(`${label} — ${err.message}`);
163
114
  installer.results.errors.push({ component: componentName, error: err.message });
164
115
  }
165
116
  }
166
117
 
167
- // ─── Summary ────────────────────────────────────────────────
118
+ // ─── Summary ────────────────────────────────────────────
168
119
  const { installed, merged, skipped, errors } = installer.results;
120
+ const parts = [];
121
+ if (installed.length > 0) parts.push(`${installed.length} installed`);
122
+ if (merged.length > 0) parts.push(`${merged.length} merged`);
123
+ if (skipped.length > 0) parts.push(`${skipped.length} skipped`);
124
+
125
+ if (errors.length === 0) {
126
+ p.log.success(parts.join(', '));
127
+ } else {
128
+ p.log.warn(`${parts.join(', ')}, ${errors.length} failed`);
129
+ }
169
130
 
131
+ // ─── Next Steps ─────────────────────────────────────────
132
+ p.note(
133
+ [
134
+ 'Get started',
135
+ ' 1. cd <your-project> && ace spec init',
136
+ ' 2. Open Claude Code, type: /opsx:propose 创建需求提案',
137
+ '',
138
+ 'Customize',
139
+ ' Change role edit ~/.claude/memory/user_profile.md',
140
+ ' Adjust rules edit ~/.claude/rules/ace/',
141
+ ' Safety guards edit ~/.claude/hookify.ace.*.local.md',
142
+ ' Verify setup ace doctor',
143
+ ].join('\n'),
144
+ 'Next steps'
145
+ );
146
+
147
+ // ─── Outro ──────────────────────────────────────────────
170
148
  if (errors.length === 0) {
171
- doneMessage({
172
- installed: installed.length,
173
- merged: merged.length,
174
- skipped: skipped.length,
175
- });
149
+ p.outro('Done. Go to your project and run ace spec init.');
176
150
  } else {
177
- doneWithErrors({ errors: errors.length });
151
+ p.outro('Done with errors. Run ace doctor to diagnose.');
178
152
  }
179
153
  }