@house365/comate 0.2.4 → 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 CHANGED
@@ -3,7 +3,6 @@ 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
8
  npmInvocation,
@@ -55,21 +54,26 @@ export async function init(pkgRoot) {
55
54
  return 1;
56
55
  }
57
56
 
58
- console.log(`\nskills 装到 ${join(home, '.agents', 'skills')}(用户级,所有项目共用)`);
59
- console.log(`openspec/ 建在 ${cwd}\n`);
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安装中…');
60
61
 
61
62
  // 1. OpenSpec CLI — a PATH command, so always global.
62
63
  const os = openSpecState();
63
64
  if (os.status === 'ready') {
64
65
  results.push(['OpenSpec CLI', 'skipped', `已安装 ${os.version}`]);
65
66
  } else {
66
- const label = os.status === 'outdated' ? `升级 (${os.version} < ${MIN_OPENSPEC})` : '安装';
67
- console.log(`→ ${label} OpenSpec CLI (npm install -g)`);
68
- const r = run(npmInvocation(['install', '-g', '@fission-ai/openspec@latest']), NPM_TIMEOUT_MS);
67
+ const r = run(
68
+ npmInvocation(['install', '-g', '@fission-ai/openspec@latest']),
69
+ NPM_TIMEOUT_MS,
70
+ { quiet: true },
71
+ );
69
72
  if (!r.ok) {
70
73
  // An outdated-but-usable CLI is better than a hard failure.
71
74
  const usable = os.status === 'outdated';
72
- results.push(['OpenSpec CLI', usable ? 'skipped' : 'failed', usable ? `升级失败,沿用 ${os.version}` : r.reason]);
75
+ const note = usable ? `升级失败,沿用 ${os.version}` : r.reason;
76
+ results.push(['OpenSpec CLI', usable ? 'skipped' : 'failed', note, usable ? '' : r.output]);
73
77
  } else if (openSpecState().status === 'missing') {
74
78
  results.push(['OpenSpec CLI', 'failed', 'npm 安装完成但 PATH 中仍找不到 openspec,请重启终端']);
75
79
  } else {
@@ -82,7 +86,6 @@ export async function init(pkgRoot) {
82
86
  if (superpowersInstalled('global') || superpowersInstalled('project')) {
83
87
  results.push(['Superpowers', 'skipped', '已安装']);
84
88
  } else {
85
- console.log('\n→ 安装 Superpowers');
86
89
  const r = run(
87
90
  skillsInvocation(pkgRoot, ['add', 'obra/superpowers', '-g', '-y']),
88
91
  SKILLS_TIMEOUT_MS,
@@ -93,20 +96,19 @@ export async function init(pkgRoot) {
93
96
  const note = r.ok
94
97
  ? ''
95
98
  : `${r.reason} — 需要访问 github.com,检查网络/代理后重试`;
96
- results.push(['Superpowers', r.ok ? 'installed' : 'failed', note]);
99
+ results.push(['Superpowers', r.ok ? 'installed' : 'failed', note, r.output]);
97
100
  }
98
101
 
99
102
  // 3. comate itself. The skills CLI reports per-agent failures without ever
100
103
  // setting a non-zero exit code, and we swallow its output — so a zero status
101
104
  // is not evidence the skill landed. Check the file.
102
- console.log('\n→ 安装 comate skills');
103
105
  const comateArgs = ['add', pkgRoot, '--skill', '*', '-g', '-y'];
104
106
  const r = run(skillsInvocation(pkgRoot, comateArgs), SKILLS_TIMEOUT_MS, { quiet: true });
105
107
  const landed = existsSync(join(home, '.agents', 'skills', 'comate', 'SKILL.md'));
106
108
  if (r.ok && !landed) {
107
- results.push(['comate', 'failed', `未装到 ${join(home, '.agents', 'skills')},重跑看详情`]);
109
+ results.push(['comate', 'failed', `未装到 ${join(home, '.agents', 'skills')}`]);
108
110
  } else {
109
- 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]);
110
112
  }
111
113
 
112
114
  // 4. openspec/ project structure. Not optional: comate's open and archive
@@ -115,7 +117,6 @@ export async function init(pkgRoot) {
115
117
  if (existsSync('openspec')) {
116
118
  results.push(['openspec 结构', 'skipped', '已存在']);
117
119
  } else {
118
- console.log('\n→ 初始化 openspec/ 项目结构');
119
120
  // --tools is not optional in practice: openspec derives the tool list by
120
121
  // sniffing existing agent config dirs, so a fresh project (no .claude etc.)
121
122
  // aborts with "No tools detected and no --tools flag provided". comate's
@@ -128,27 +129,38 @@ export async function init(pkgRoot) {
128
129
  shell: process.platform === 'win32',
129
130
  },
130
131
  NPM_TIMEOUT_MS,
132
+ { quiet: true },
131
133
  );
132
- 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]);
133
135
  }
134
136
 
135
- return report(results);
137
+ return report(results, { home, cwd });
136
138
  }
137
139
 
138
- function report(results) {
139
- const mark = { installed: '✓', skipped: '·', failed: '✗' };
140
- console.log('');
141
- for (const [name, status, note] of results) {
142
- console.log(` ${mark[status]} ${name.padEnd(14)}${note}`);
143
- }
144
-
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 }) {
145
144
  const failed = results.filter(([, s]) => s === 'failed');
146
145
  if (failed.length > 0) {
147
- console.error(`\n${failed.length} 项失败,修好后重跑 comate init。`);
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。');
148
157
  return 1;
149
158
  }
159
+
150
160
  console.log(banner(pkgVersion()));
151
- console.log('在 agent 中输入 /comate 开始使用,或跑 comate doctor 自检。\n');
161
+ console.log(` skills ${join(home, '.agents', 'skills')}`);
162
+ console.log(` openspec ${join(cwd, 'openspec')}`);
163
+ console.log('\n 输入 /comate 开始使用,comate doctor 可自检\n');
152
164
  return 0;
153
165
  }
154
166
 
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
- // Without this the user gets a bare "exit code 1" and no way to diagnose.
93
- if (quiet) process.stderr.write((stdout ?? '') + (stderr ?? ''));
94
- return { ok: false, reason: `exit code ${String(status)}` };
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@house365/comate",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "comate —— OpenSpec + Superpowers 规约驱动的 AI 开发编排器(Agent Skills)",
5
5
  "type": "module",
6
6
  "license": "MIT",