@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 +0 -3
- package/package.json +5 -4
- package/src/commands/init.js +84 -77
- package/src/core/ui.js +15 -0
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,90 +1,67 @@
|
|
|
1
|
-
import
|
|
2
|
-
import inquirer from 'inquirer';
|
|
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, 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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
// ───
|
|
74
|
+
// ─── Dry-run notice ─────────────────────────────────────
|
|
98
75
|
if (options.dryRun) {
|
|
99
|
-
|
|
76
|
+
p.log.warn('dry-run — no changes will be made');
|
|
100
77
|
}
|
|
101
|
-
|
|
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
|
-
|
|
103
|
+
p.log.info(`${label} — merged`);
|
|
121
104
|
} else if (newSkipped > 0 && newInstalled === 0 && newMerged === 0) {
|
|
122
|
-
|
|
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
|
-
|
|
109
|
+
p.log.success(`${label} — ${detail}`);
|
|
127
110
|
}
|
|
128
111
|
} catch (err) {
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}`) : '';
|