@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 +0 -3
- package/package.json +5 -4
- package/src/commands/init.js +81 -107
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
|
+
"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
|
-
"
|
|
28
|
+
"@clack/prompts": "^1.2.0",
|
|
29
29
|
"chalk": "^5.3.0",
|
|
30
|
-
"
|
|
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
|
},
|
package/src/commands/init.js
CHANGED
|
@@ -1,120 +1,84 @@
|
|
|
1
|
-
import
|
|
2
|
-
import ora from 'ora';
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
3
2
|
import { createRequire } from 'module';
|
|
4
|
-
import { PRESETS,
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
// ───
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
// ───
|
|
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
|
-
|
|
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
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
153
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
p.outro('Done with errors. Run ace doctor to diagnose.');
|
|
178
152
|
}
|
|
179
153
|
}
|