@house365/comate 0.2.3 → 0.2.5
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/init-run.mjs +48 -44
- package/bin/init.mjs +5 -3
- package/package.json +1 -1
package/bin/init-run.mjs
CHANGED
|
@@ -3,10 +3,8 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
MIN_OPENSPEC,
|
|
7
6
|
NPM_TIMEOUT_MS,
|
|
8
7
|
SKILLS_TIMEOUT_MS,
|
|
9
|
-
capture,
|
|
10
8
|
npmInvocation,
|
|
11
9
|
openSpecState,
|
|
12
10
|
run,
|
|
@@ -56,21 +54,26 @@ export async function init(pkgRoot) {
|
|
|
56
54
|
return 1;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
// ponytail: a plain line, not a spinner. \r-erasing it needs a real terminal,
|
|
58
|
+
// and through npm's .ps1 shim stdout is a pipe where \r leaves the padding
|
|
59
|
+
// visible — the failed cleanup looked worse than just keeping the line.
|
|
60
|
+
console.log('\n安装中…');
|
|
61
61
|
|
|
62
62
|
// 1. OpenSpec CLI — a PATH command, so always global.
|
|
63
63
|
const os = openSpecState();
|
|
64
64
|
if (os.status === 'ready') {
|
|
65
65
|
results.push(['OpenSpec CLI', 'skipped', `已安装 ${os.version}`]);
|
|
66
66
|
} else {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const r = run(
|
|
68
|
+
npmInvocation(['install', '-g', '@fission-ai/openspec@latest']),
|
|
69
|
+
NPM_TIMEOUT_MS,
|
|
70
|
+
{ quiet: true },
|
|
71
|
+
);
|
|
70
72
|
if (!r.ok) {
|
|
71
73
|
// An outdated-but-usable CLI is better than a hard failure.
|
|
72
74
|
const usable = os.status === 'outdated';
|
|
73
|
-
|
|
75
|
+
const note = usable ? `升级失败,沿用 ${os.version}` : r.reason;
|
|
76
|
+
results.push(['OpenSpec CLI', usable ? 'skipped' : 'failed', note, usable ? '' : r.output]);
|
|
74
77
|
} else if (openSpecState().status === 'missing') {
|
|
75
78
|
results.push(['OpenSpec CLI', 'failed', 'npm 安装完成但 PATH 中仍找不到 openspec,请重启终端']);
|
|
76
79
|
} else {
|
|
@@ -83,7 +86,6 @@ export async function init(pkgRoot) {
|
|
|
83
86
|
if (superpowersInstalled('global') || superpowersInstalled('project')) {
|
|
84
87
|
results.push(['Superpowers', 'skipped', '已安装']);
|
|
85
88
|
} else {
|
|
86
|
-
console.log('\n→ 安装 Superpowers');
|
|
87
89
|
const r = run(
|
|
88
90
|
skillsInvocation(pkgRoot, ['add', 'obra/superpowers', '-g', '-y']),
|
|
89
91
|
SKILLS_TIMEOUT_MS,
|
|
@@ -94,20 +96,19 @@ export async function init(pkgRoot) {
|
|
|
94
96
|
const note = r.ok
|
|
95
97
|
? ''
|
|
96
98
|
: `${r.reason} — 需要访问 github.com,检查网络/代理后重试`;
|
|
97
|
-
results.push(['Superpowers', r.ok ? 'installed' : 'failed', note]);
|
|
99
|
+
results.push(['Superpowers', r.ok ? 'installed' : 'failed', note, r.output]);
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
// 3. comate itself. The skills CLI reports per-agent failures without ever
|
|
101
103
|
// setting a non-zero exit code, and we swallow its output — so a zero status
|
|
102
104
|
// is not evidence the skill landed. Check the file.
|
|
103
|
-
console.log('\n→ 安装 comate skills');
|
|
104
105
|
const comateArgs = ['add', pkgRoot, '--skill', '*', '-g', '-y'];
|
|
105
106
|
const r = run(skillsInvocation(pkgRoot, comateArgs), SKILLS_TIMEOUT_MS, { quiet: true });
|
|
106
107
|
const landed = existsSync(join(home, '.agents', 'skills', 'comate', 'SKILL.md'));
|
|
107
108
|
if (r.ok && !landed) {
|
|
108
|
-
results.push(['comate', 'failed', `未装到 ${join(home, '.agents', 'skills')}
|
|
109
|
+
results.push(['comate', 'failed', `未装到 ${join(home, '.agents', 'skills')}`]);
|
|
109
110
|
} else {
|
|
110
|
-
results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason]);
|
|
111
|
+
results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason, r.output]);
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
// 4. openspec/ project structure. Not optional: comate's open and archive
|
|
@@ -116,7 +117,6 @@ export async function init(pkgRoot) {
|
|
|
116
117
|
if (existsSync('openspec')) {
|
|
117
118
|
results.push(['openspec 结构', 'skipped', '已存在']);
|
|
118
119
|
} else {
|
|
119
|
-
console.log('\n→ 初始化 openspec/ 项目结构');
|
|
120
120
|
// --tools is not optional in practice: openspec derives the tool list by
|
|
121
121
|
// sniffing existing agent config dirs, so a fresh project (no .claude etc.)
|
|
122
122
|
// aborts with "No tools detected and no --tools flag provided". comate's
|
|
@@ -129,27 +129,38 @@ export async function init(pkgRoot) {
|
|
|
129
129
|
shell: process.platform === 'win32',
|
|
130
130
|
},
|
|
131
131
|
NPM_TIMEOUT_MS,
|
|
132
|
+
{ quiet: true },
|
|
132
133
|
);
|
|
133
|
-
results.push(['openspec 结构', r2.ok ? 'installed' : 'failed', r2.ok ? '' : r2.reason]);
|
|
134
|
+
results.push(['openspec 结构', r2.ok ? 'installed' : 'failed', r2.ok ? '' : r2.reason, r2.output]);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
return report(results);
|
|
137
|
+
return report(results, { home, cwd });
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
console.log(` ${mark[status]} ${name.padEnd(14)}${note}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
140
|
+
// Success is one screen: the wordmark and where things landed. The per-item
|
|
141
|
+
// breakdown only earns its space when something failed, and then it carries the
|
|
142
|
+
// reason — a bare "失败" would leave the user with nowhere to go.
|
|
143
|
+
function report(results, { home, cwd }) {
|
|
146
144
|
const failed = results.filter(([, s]) => s === 'failed');
|
|
147
145
|
if (failed.length > 0) {
|
|
148
|
-
console.error(
|
|
146
|
+
console.error('\n安装失败\n');
|
|
147
|
+
for (const [name, status, note, output] of results) {
|
|
148
|
+
const mark = { installed: '✓', skipped: '·', failed: '✗' }[status];
|
|
149
|
+
console.error(` ${mark} ${name.padEnd(14)}${note}`);
|
|
150
|
+
// The child's own output, indented under the item it belongs to, so a
|
|
151
|
+
// knock-on failure doesn't look like a second unrelated problem.
|
|
152
|
+
if (status === 'failed' && output) {
|
|
153
|
+
for (const line of output.split('\n')) console.error(` ${line.trim()}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
console.error('\n修好后重跑 comate init。');
|
|
149
157
|
return 1;
|
|
150
158
|
}
|
|
159
|
+
|
|
151
160
|
console.log(banner(pkgVersion()));
|
|
152
|
-
console.log('
|
|
161
|
+
console.log(` skills ${join(home, '.agents', 'skills')}`);
|
|
162
|
+
console.log(` openspec ${join(cwd, 'openspec')}`);
|
|
163
|
+
console.log('\n 输入 /comate 开始使用,comate doctor 可自检\n');
|
|
153
164
|
return 0;
|
|
154
165
|
}
|
|
155
166
|
|
|
@@ -163,20 +174,21 @@ export function pkgVersion(root = resolve(import.meta.dirname, '..')) {
|
|
|
163
174
|
}
|
|
164
175
|
}
|
|
165
176
|
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
// only goes out when the codepage says otherwise; everything else gets the
|
|
169
|
-
// plain line.
|
|
177
|
+
// Only CI gates the wordmark. Two earlier guards were both wrong and are worth
|
|
178
|
+
// naming so they don't come back:
|
|
170
179
|
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
180
|
+
// isTTY — npm's Windows global install resolves `comate` to a comate.ps1
|
|
181
|
+
// shim, and PowerShell calling node through a .ps1 wrapper takes over stdout,
|
|
182
|
+
// so isTTY is false in a real terminal — exactly where the logo belongs.
|
|
183
|
+
//
|
|
184
|
+
// chcp — reports the codepage the child process inherited, which the shim
|
|
185
|
+
// does not propagate (65001 outside, 936 inside). It also answers the wrong
|
|
186
|
+
// question: writing to a real Windows console goes through WriteConsoleW,
|
|
187
|
+
// which converts to UTF-16 and bypasses the codepage entirely.
|
|
188
|
+
export function banner(version, { ci = isCI() } = {}) {
|
|
177
189
|
const tag = `comate${version ? ` v${version}` : ''}`;
|
|
178
190
|
const plain = `\n ${tag} - AI coding almost there!\n`;
|
|
179
|
-
if (
|
|
191
|
+
if (ci) return plain;
|
|
180
192
|
|
|
181
193
|
const art = [
|
|
182
194
|
' ██████╗ ██████╗ ███╗ ███╗ █████╗ ████████╗███████╗',
|
|
@@ -199,14 +211,6 @@ export function banner(version, { utf8 = unicodeSafe(), ci = isCI() } = {}) {
|
|
|
199
211
|
].join('\n');
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
// POSIX terminals are UTF-8 in practice. Windows consoles are not: the legacy
|
|
203
|
-
// codepage (936, 437, …) renders box-drawing glyphs as mojibake, so ask chcp
|
|
204
|
-
// and only claim UTF-8 on 65001.
|
|
205
|
-
function unicodeSafe() {
|
|
206
|
-
if (process.platform !== 'win32') return true;
|
|
207
|
-
return /65001/.test(capture('chcp', []) ?? '');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
214
|
// Every major CI sets CI=true; GITHUB_ACTIONS covers the Actions runner even in
|
|
211
215
|
// the odd config that doesn't.
|
|
212
216
|
function isCI() {
|
package/bin/init.mjs
CHANGED
|
@@ -89,9 +89,11 @@ function run({ command, args, shell }, timeout, { quiet = false } = {}) {
|
|
|
89
89
|
});
|
|
90
90
|
if (error) return { ok: false, reason: error.message };
|
|
91
91
|
if (status !== 0) {
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Hand the buffered output back rather than printing it here: the caller
|
|
93
|
+
// knows whether this failure is the root cause or a knock-on effect, and
|
|
94
|
+
// printing both would bury the one line that matters.
|
|
95
|
+
const out = quiet ? ((stdout ?? '') + (stderr ?? '')).trim() : '';
|
|
96
|
+
return { ok: false, reason: `exit code ${String(status)}`, output: out };
|
|
95
97
|
}
|
|
96
98
|
return { ok: true };
|
|
97
99
|
}
|