@house365/comate 0.2.0 → 0.2.2

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
@@ -1,11 +1,12 @@
1
1
  // The `init` flow: install whatever the current project is missing.
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join, resolve } from 'node:path';
4
4
 
5
5
  import {
6
6
  MIN_OPENSPEC,
7
7
  NPM_TIMEOUT_MS,
8
8
  SKILLS_TIMEOUT_MS,
9
+ capture,
9
10
  npmInvocation,
10
11
  openSpecState,
11
12
  run,
@@ -82,8 +83,12 @@ export async function init(pkgRoot) {
82
83
  if (superpowersInstalled('global') || superpowersInstalled('project')) {
83
84
  results.push(['Superpowers', 'skipped', '已安装']);
84
85
  } else {
85
- console.log('\n→ 安装 Superpowers (skills add obra/superpowers -g)');
86
- const r = run(skillsInvocation(pkgRoot, ['add', 'obra/superpowers', '-g', '-y']), SKILLS_TIMEOUT_MS);
86
+ console.log('\n→ 安装 Superpowers');
87
+ const r = run(
88
+ skillsInvocation(pkgRoot, ['add', 'obra/superpowers', '-g', '-y']),
89
+ SKILLS_TIMEOUT_MS,
90
+ { quiet: true },
91
+ );
87
92
  // It clones from GitHub, so a bare exit code hides the usual cause: no
88
93
  // network. Say so rather than leaving the user with "exit code 1".
89
94
  const note = r.ok
@@ -92,11 +97,18 @@ export async function init(pkgRoot) {
92
97
  results.push(['Superpowers', r.ok ? 'installed' : 'failed', note]);
93
98
  }
94
99
 
95
- // 3. comate itself.
100
+ // 3. comate itself. The skills CLI reports per-agent failures without ever
101
+ // setting a non-zero exit code, and we swallow its output — so a zero status
102
+ // is not evidence the skill landed. Check the file.
96
103
  console.log('\n→ 安装 comate skills');
97
104
  const comateArgs = ['add', pkgRoot, '--skill', '*', '-g', '-y'];
98
- const r = run(skillsInvocation(pkgRoot, comateArgs), SKILLS_TIMEOUT_MS);
99
- results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason]);
105
+ const r = run(skillsInvocation(pkgRoot, comateArgs), SKILLS_TIMEOUT_MS, { quiet: true });
106
+ const landed = existsSync(join(home, '.agents', 'skills', 'comate', 'SKILL.md'));
107
+ if (r.ok && !landed) {
108
+ results.push(['comate', 'failed', `未装到 ${join(home, '.agents', 'skills')},重跑看详情`]);
109
+ } else {
110
+ results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason]);
111
+ }
100
112
 
101
113
  // 4. openspec/ project structure. Not optional: comate's open and archive
102
114
  // phases read it, so an install without it cannot actually run. An existing
@@ -105,8 +117,17 @@ export async function init(pkgRoot) {
105
117
  results.push(['openspec 结构', 'skipped', '已存在']);
106
118
  } else {
107
119
  console.log('\n→ 初始化 openspec/ 项目结构');
120
+ // --tools is not optional in practice: openspec derives the tool list by
121
+ // sniffing existing agent config dirs, so a fresh project (no .claude etc.)
122
+ // aborts with "No tools detected and no --tools flag provided". comate's
123
+ // skills are agent-agnostic and only openspec/ itself matters here, so ask
124
+ // for none rather than guessing which agents the user wants wired up.
108
125
  const r2 = run(
109
- { command: 'openspec', args: ['init', '.'], shell: process.platform === 'win32' },
126
+ {
127
+ command: 'openspec',
128
+ args: ['init', '.', '--tools', 'none'],
129
+ shell: process.platform === 'win32',
130
+ },
110
131
  NPM_TIMEOUT_MS,
111
132
  );
112
133
  results.push(['openspec 结构', r2.ok ? 'installed' : 'failed', r2.ok ? '' : r2.reason]);
@@ -127,6 +148,54 @@ function report(results) {
127
148
  console.error(`\n${failed.length} 项失败,修好后重跑 comate init。`);
128
149
  return 1;
129
150
  }
130
- console.log('\n完成。在 agent 中输入 /comate 开始使用,或跑 comate doctor 自检。');
151
+ console.log(banner(pkgVersion()));
152
+ console.log('在 agent 中输入 /comate 开始使用,或跑 comate doctor 自检。\n');
131
153
  return 0;
132
154
  }
155
+
156
+ // ponytail: read package.json rather than hardcoding — npm version bumps the
157
+ // file, and a stale literal in the banner is worse than no version at all.
158
+ export function pkgVersion(root = resolve(import.meta.dirname, '..')) {
159
+ try {
160
+ return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
161
+ } catch {
162
+ return '';
163
+ }
164
+ }
165
+
166
+ // Box-drawing glyphs need a UTF-8 capable terminal. Windows consoles still
167
+ // default to a legacy codepage where they render as mojibake, so the wordmark
168
+ // only goes out when the codepage says otherwise; everything else gets the
169
+ // plain line. Piped output is skipped too — logos belong on screens, not in
170
+ // logs or CI transcripts.
171
+ export function banner(version, { tty = process.stdout.isTTY, utf8 = unicodeSafe() } = {}) {
172
+ const tag = `comate${version ? ` v${version}` : ''}`;
173
+ const plain = `\n ${tag} — OpenSpec + Superpowers 规约驱动开发编排器\n`;
174
+ if (!tty || !utf8) return plain;
175
+
176
+ const art = [
177
+ '██████╗ ██████╗ ███╗ ███╗ █████╗ ███████╗',
178
+ '██╔═══╝██╔═══██╗████╗ ████║██╔══██╗╚══██╔══╝',
179
+ '██║ ██║ ██║██╔████╔██║███████║ ██║',
180
+ '██║ ██║ ██║██║╚██╔╝██║██╔══██║ ██║',
181
+ '╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║ ██║',
182
+ ' ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝',
183
+ ];
184
+ const cyan = (s) => (process.env.NO_COLOR ? s : `\x1B[36m${s}\x1B[0m`);
185
+ const dim = (s) => (process.env.NO_COLOR ? s : `\x1B[2m${s}\x1B[0m`);
186
+ return [
187
+ '',
188
+ ...art.map((line) => ` ${cyan(line)}`),
189
+ '',
190
+ ` ${dim(`${tag} — OpenSpec + Superpowers 规约驱动开发编排器`)}`,
191
+ '',
192
+ ].join('\n');
193
+ }
194
+
195
+ // POSIX terminals are UTF-8 in practice. Windows consoles are not: the legacy
196
+ // codepage (936, 437, …) renders box-drawing glyphs as mojibake, so ask chcp
197
+ // and only claim UTF-8 on 65001.
198
+ function unicodeSafe() {
199
+ if (process.platform !== 'win32') return true;
200
+ return /65001/.test(capture('chcp', []) ?? '');
201
+ }
package/bin/init.mjs CHANGED
@@ -75,14 +75,24 @@ function parsePackOutput(stdout) {
75
75
  throw new Error(`no usable file list in npm pack output:\n${stdout}`);
76
76
  }
77
77
 
78
- function run({ command, args, shell }, timeout) {
79
- const { status, error } = spawnSync(command, args, {
80
- stdio: 'inherit',
78
+ // quiet buffers the child's output and prints it only on failure. The skills
79
+ // CLI reports a per-agent failure for every agent that has no user-level skills
80
+ // dir (PromptScript et al.) — cosmetic, since it neither affects the exit code
81
+ // nor the agents that did get installed, but it reads like a broken install.
82
+ // init prints its own summary, so on success the child's output adds nothing.
83
+ function run({ command, args, shell }, timeout, { quiet = false } = {}) {
84
+ const { status, error, stdout, stderr } = spawnSync(command, args, {
85
+ stdio: quiet ? ['ignore', 'pipe', 'pipe'] : 'inherit',
86
+ encoding: quiet ? 'utf8' : undefined,
81
87
  timeout,
82
88
  ...(shell ? { shell: true } : {}),
83
89
  });
84
90
  if (error) return { ok: false, reason: error.message };
85
- if (status !== 0) return { ok: false, reason: `exit code ${String(status)}` };
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)}` };
95
+ }
86
96
  return { ok: true };
87
97
  }
88
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@house365/comate",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "comate —— OpenSpec + Superpowers 规约驱动的 AI 开发编排器(Agent Skills)",
5
5
  "type": "module",
6
6
  "license": "MIT",