@shirayner/ace 0.1.1-snapshot.2 → 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.2",
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,90 +1,67 @@
1
- import chalk from 'chalk';
2
- import inquirer from 'inquirer';
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, stepDone, stepMerge, stepSkip, stepFail,
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
 
9
+ const componentLabels = {
10
+ core: 'Core Config',
11
+ rules: 'Rules',
12
+ plugin: 'Plugin',
13
+ hooks: 'Hooks',
14
+ hookify: 'Safety Guards',
15
+ memory: 'Memory',
16
+ };
17
+
15
18
  export async function initCommand(options) {
16
- printBanner(pkg.version);
17
-
18
- let role = 'fullstack';
19
- let preset = options.preset;
20
-
21
- // ─── Interactive: ask role only ─────────────────────────────
22
- if (options.interaction !== false) {
23
- const answers = await inquirer.prompt([
24
- {
25
- type: 'list',
26
- name: 'role',
27
- message: 'Role',
28
- choices: Object.entries(ROLES).map(([key, val]) => ({
29
- name: `${colors.white(val.label)} ${colors.dim(val.description)}`,
30
- value: key,
31
- short: val.label,
32
- })),
33
- default: 'fullstack',
34
- prefix: colors.brand('?'),
35
- },
36
- ]);
37
- role = answers.role;
38
- }
19
+ const version = pkg.version;
20
+ const components = PRESETS['full'];
39
21
 
40
- const components = PRESETS[preset];
41
- if (!components) {
42
- console.error(` ${colors.error(icons.cross)} Unknown preset: ${preset}. Use: ${Object.keys(PRESETS).join(', ')}`);
43
- process.exit(1);
44
- }
22
+ // ─── Intro ──────────────────────────────────────────────
23
+ p.intro(`ace v${version}`);
45
24
 
46
- // ─── Conflict detection ─────────────────────────────────────
25
+ // ─── Conflict detection ─────────────────────────────────
47
26
  const installer = new Installer({
48
27
  force: options.force,
49
28
  dryRun: options.dryRun,
50
- role,
29
+ role: 'fullstack',
51
30
  components,
52
31
  quiet: true,
53
32
  });
54
33
 
55
34
  let resolutions = {};
56
35
 
57
- if (!options.force && options.interaction !== false) {
36
+ if (!options.force) {
58
37
  const conflicts = await installer.detectConflicts();
59
38
  const conflictKeys = Object.keys(conflicts);
60
39
 
61
40
  if (conflictKeys.length > 0) {
62
41
  const totalFiles = conflictKeys.reduce((sum, k) => sum + conflicts[k].files.length, 0);
42
+ const mergeComponents = conflictKeys.filter(k => conflicts[k].hasMerge);
63
43
 
64
- console.log(` ${colors.warning(icons.warn)} ${totalFiles} existing file(s) found.`);
65
- console.log();
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
- ]);
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
+ }
50
+
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' },
57
+ ],
58
+ initialValue: 'skip',
59
+ });
60
+
61
+ if (p.isCancel(action) || action === 'cancel') {
62
+ p.cancel('Setup cancelled.');
63
+ process.exit(0);
64
+ }
88
65
 
89
66
  for (const key of conflictKeys) {
90
67
  resolutions[key] = action;
@@ -94,11 +71,13 @@ export async function initCommand(options) {
94
71
 
95
72
  installer.resolutions = resolutions;
96
73
 
97
- // ─── Install ────────────────────────────────────────────────
74
+ // ─── Dry-run notice ─────────────────────────────────────
98
75
  if (options.dryRun) {
99
- console.log(` ${colors.dim('dry-run — no changes will be made')}`);
76
+ p.log.warn('dry-run — no changes will be made');
100
77
  }
101
- console.log();
78
+
79
+ // ─── Install ────────────────────────────────────────────
80
+ p.log.step('Installing to ~/.claude/');
102
81
 
103
82
  for (const componentName of components) {
104
83
  const component = COMPONENTS[componentName];
@@ -109,38 +88,66 @@ export async function initCommand(options) {
109
88
  const beforeMerged = installer.results.merged.length;
110
89
  const beforeSkipped = installer.results.skipped.length;
111
90
 
91
+ const s = p.spinner();
92
+ s.start(label);
93
+
112
94
  try {
113
95
  await installer.installComponent(componentName, component);
96
+ s.stop(label);
114
97
 
115
98
  const newInstalled = installer.results.installed.length - beforeInstalled;
116
99
  const newMerged = installer.results.merged.length - beforeMerged;
117
100
  const newSkipped = installer.results.skipped.length - beforeSkipped;
118
101
 
119
102
  if (newMerged > 0 && newInstalled === 0 && newSkipped === 0) {
120
- stepMerge(label, 'merged');
103
+ p.log.info(`${label} merged`);
121
104
  } else if (newSkipped > 0 && newInstalled === 0 && newMerged === 0) {
122
- stepSkip(label, 'unchanged');
105
+ p.log.message(`${label} unchanged`);
123
106
  } else {
124
107
  const count = newInstalled + newMerged;
125
108
  const detail = count > 0 ? `${count} file${count > 1 ? 's' : ''}` : '';
126
- stepDone(label, detail);
109
+ p.log.success(`${label} — ${detail}`);
127
110
  }
128
111
  } catch (err) {
129
- stepFail(label, err.message);
112
+ s.stop(label);
113
+ p.log.error(`${label} — ${err.message}`);
130
114
  installer.results.errors.push({ component: componentName, error: err.message });
131
115
  }
132
116
  }
133
117
 
134
- // ─── Summary ────────────────────────────────────────────────
118
+ // ─── Summary ────────────────────────────────────────────
135
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
+ }
136
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 ──────────────────────────────────────────────
137
148
  if (errors.length === 0) {
138
- doneMessage({
139
- installed: installed.length,
140
- merged: merged.length,
141
- skipped: skipped.length,
142
- });
149
+ p.outro('Done. Go to your project and run ace spec init.');
143
150
  } else {
144
- doneWithErrors({ errors: errors.length });
151
+ p.outro('Done with errors. Run ace doctor to diagnose.');
145
152
  }
146
153
  }
package/src/core/ui.js CHANGED
@@ -23,6 +23,11 @@ export const colors = {
23
23
  blue: chalk.hex('#3B82F6'),
24
24
  };
25
25
 
26
+ // ─── Screen control ─────────────────────────────────────────
27
+ export function clearScreen() {
28
+ process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
29
+ }
30
+
26
31
  // ─── Banner (single line) ───────────────────────────────────
27
32
  export function printBanner(version) {
28
33
  console.log();
@@ -30,6 +35,16 @@ export function printBanner(version) {
30
35
  console.log();
31
36
  }
32
37
 
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();
46
+ }
47
+
33
48
  // ─── Step indicators ────────────────────────────────────────
34
49
  export function stepDone(label, detail) {
35
50
  const d = detail ? colors.dim(` ${detail}`) : '';