@house365/comate 0.1.4 → 0.2.1

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/README.md CHANGED
@@ -37,9 +37,9 @@ comate init
37
37
 
38
38
  `init` 会装好 comate 及其依赖(OpenSpec CLI、Superpowers),并创建 `openspec/` 项目结构。支持 Claude Code、Codex、Cursor 等 70+ agent。可重复执行,已装的自动跳过。
39
39
 
40
- 装完输入 `/comate` 开始使用,`comate doctor` 可自检。
40
+ skills 装到用户级(`~/.agents/skills`),装一次所有项目共用;`openspec/` 是项目自己的规约,建在当前目录,所以 `init` 需要在项目目录下运行。
41
41
 
42
- 想装到用户全局、所有项目共用,加 `-g`。
42
+ 装完输入 `/comate` 开始使用,`comate doctor` 可自检。
43
43
 
44
44
  ## 用法
45
45
 
package/bin/cli.mjs CHANGED
@@ -25,7 +25,7 @@ if (args[0] === '--help' || args[0] === '-h') {
25
25
  console.log(`comate — OpenSpec + Superpowers 规约驱动的开发编排器
26
26
 
27
27
  用法:
28
- comate init 在当前项目安装 comate、依赖和 openspec/ 结构
28
+ comate init 安装 comate 和依赖到用户级,并在当前项目创建 openspec/
29
29
  comate doctor 健康检查
30
30
 
31
31
  装好后在 agent 中输入 /comate 开始使用。`);
package/bin/doctor.mjs CHANGED
@@ -4,10 +4,11 @@
4
4
  // installed copy so the user doesn't have to remember the path.
5
5
  import { spawnSync } from 'node:child_process';
6
6
  import { existsSync } from 'node:fs';
7
- import { join } from 'node:path';
7
+ import { delimiter, join, resolve } from 'node:path';
8
8
 
9
- // Where the skills CLI puts things. The project copy comes first so it wins
10
- // over a global one left from an earlier `comate -g` install.
9
+ // Where the skills CLI puts things. `init` installs user-level, but a project
10
+ // copy shadows it for the agent, so check cwd first and report on the same one
11
+ // the agent would load.
11
12
  function skillRoots() {
12
13
  const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
13
14
  const dirs = ['.agents/skills', '.claude/skills', '.cursor/skills', '.codex/skills'];
@@ -22,6 +23,26 @@ export function findComateScript(roots = skillRoots()) {
22
23
  return null;
23
24
  }
24
25
 
26
+ // On Windows, bare `bash` usually resolves to System32\bash.exe — the WSL
27
+ // launcher, which cannot open `D:\...` paths (it eats the backslashes and
28
+ // reports "No such file or directory"). Find Git's bash instead, next to the
29
+ // git.exe already on PATH, then the standard install dirs.
30
+ export function findBash(env = process.env, exists = existsSync) {
31
+ if (process.platform !== 'win32') return 'bash';
32
+
33
+ const gitDirs = (env.PATH ?? env.Path ?? '').split(delimiter).filter(Boolean);
34
+ const candidates = [
35
+ // <git>/cmd/git.exe or <git>/mingw64/bin/git.exe -> <git>/bin/bash.exe
36
+ ...gitDirs.flatMap((d) =>
37
+ exists(join(d, 'git.exe')) ? [resolve(d, '..', 'bin', 'bash.exe'), resolve(d, '..', '..', 'bin', 'bash.exe')] : [],
38
+ ),
39
+ join(env.ProgramFiles ?? 'C:\\Program Files', 'Git', 'bin', 'bash.exe'),
40
+ join(env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Git', 'bin', 'bash.exe'),
41
+ join(env.LOCALAPPDATA ?? '', 'Programs', 'Git', 'bin', 'bash.exe'),
42
+ ];
43
+ return candidates.find(exists) ?? null;
44
+ }
45
+
25
46
  export function doctor(argv) {
26
47
  const script = findComateScript();
27
48
  if (!script) {
@@ -29,15 +50,21 @@ export function doctor(argv) {
29
50
  return 1;
30
51
  }
31
52
 
53
+ const bash = findBash();
54
+ if (!bash) {
55
+ console.error('找不到 bash — comate 的脚本需要 bash。请安装 Git for Windows。');
56
+ return 1;
57
+ }
58
+
32
59
  // comate.sh resolves sibling skills relative to the skills root, so run it
33
60
  // from there rather than from wherever the user happens to be.
34
61
  const skillsRoot = join(script, '..', '..', '..');
35
- const { status, error } = spawnSync('bash', [script, 'doctor', ...argv], {
62
+ const { status, error } = spawnSync(bash, [script, 'doctor', ...argv], {
36
63
  cwd: skillsRoot,
37
64
  stdio: 'inherit',
38
65
  });
39
66
  if (error) {
40
- console.error(`无法执行 bash — comate 的脚本需要 bash。${error.message}`);
67
+ console.error(`无法执行 bash (${bash}) — comate 的脚本需要 bash。${error.message}`);
41
68
  return 1;
42
69
  }
43
70
  return status ?? 1;
package/bin/init-run.mjs CHANGED
@@ -13,12 +13,12 @@ import {
13
13
  superpowersInstalled,
14
14
  } from './init.mjs';
15
15
 
16
- // `comate init` initialises the current project — no flags. Global installs are
17
- // still reachable through the bare `comate -g` passthrough to the skills CLI.
16
+ // `comate init` — no flags. Skills go user-level (~/.agents/skills), matching how
17
+ // the CLI itself is installed (npm i -g): one copy, every project sees it.
18
18
  //
19
- // cwd is where the install lands, so refuse the two cases that are almost
20
- // certainly a mistake: the home directory, and a directory with no project
21
- // marker at all.
19
+ // openspec/ is still per-project — it holds the project's own specs — so cwd
20
+ // must be a project. Refuse the two cases that are almost certainly a mistake:
21
+ // the home directory, and a directory with no project marker at all.
22
22
  const PROJECT_MARKERS = [
23
23
  '.git',
24
24
  'package.json',
@@ -55,10 +55,10 @@ export async function init(pkgRoot) {
55
55
  return 1;
56
56
  }
57
57
 
58
- console.log(`\n安装到 ${join(cwd, '.agents', 'skills')}\n`);
58
+ console.log(`\nskills 装到 ${join(home, '.agents', 'skills')}(用户级,所有项目共用)`);
59
+ console.log(`openspec/ 建在 ${cwd}\n`);
59
60
 
60
- // 1. OpenSpec CLI — invoked as a PATH command, so it installs globally even
61
- // though the skills themselves go into this project.
61
+ // 1. OpenSpec CLI — a PATH command, so always global.
62
62
  const os = openSpecState();
63
63
  if (os.status === 'ready') {
64
64
  results.push(['OpenSpec CLI', 'skipped', `已安装 ${os.version}`]);
@@ -77,12 +77,17 @@ export async function init(pkgRoot) {
77
77
  }
78
78
  }
79
79
 
80
- // 2. Superpowers — the skills CLI detects the agent itself.
81
- if (superpowersInstalled('project')) {
80
+ // 2. Superpowers — the skills CLI detects the agent itself. A pre-existing
81
+ // project-level copy also counts: reinstalling globally would just shadow it.
82
+ if (superpowersInstalled('global') || superpowersInstalled('project')) {
82
83
  results.push(['Superpowers', 'skipped', '已安装']);
83
84
  } else {
84
- console.log('\n→ 安装 Superpowers (skills add obra/superpowers)');
85
- const r = run(skillsInvocation(pkgRoot, ['add', 'obra/superpowers', '-y']), SKILLS_TIMEOUT_MS);
85
+ console.log('\n→ 安装 Superpowers');
86
+ const r = run(
87
+ skillsInvocation(pkgRoot, ['add', 'obra/superpowers', '-g', '-y']),
88
+ SKILLS_TIMEOUT_MS,
89
+ { quiet: true },
90
+ );
86
91
  // It clones from GitHub, so a bare exit code hides the usual cause: no
87
92
  // network. Say so rather than leaving the user with "exit code 1".
88
93
  const note = r.ok
@@ -91,11 +96,18 @@ export async function init(pkgRoot) {
91
96
  results.push(['Superpowers', r.ok ? 'installed' : 'failed', note]);
92
97
  }
93
98
 
94
- // 3. comate itself.
99
+ // 3. comate itself. The skills CLI reports per-agent failures without ever
100
+ // setting a non-zero exit code, and we swallow its output — so a zero status
101
+ // is not evidence the skill landed. Check the file.
95
102
  console.log('\n→ 安装 comate skills');
96
- const comateArgs = ['add', pkgRoot, '--skill', '*', '-y'];
97
- const r = run(skillsInvocation(pkgRoot, comateArgs), SKILLS_TIMEOUT_MS);
98
- results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason]);
103
+ const comateArgs = ['add', pkgRoot, '--skill', '*', '-g', '-y'];
104
+ const r = run(skillsInvocation(pkgRoot, comateArgs), SKILLS_TIMEOUT_MS, { quiet: true });
105
+ const landed = existsSync(join(home, '.agents', 'skills', 'comate', 'SKILL.md'));
106
+ if (r.ok && !landed) {
107
+ results.push(['comate', 'failed', `未装到 ${join(home, '.agents', 'skills')},重跑看详情`]);
108
+ } else {
109
+ results.push(['comate', r.ok ? 'installed' : 'failed', r.ok ? '' : r.reason]);
110
+ }
99
111
 
100
112
  // 4. openspec/ project structure. Not optional: comate's open and archive
101
113
  // phases read it, so an install without it cannot actually run. An existing
@@ -104,8 +116,17 @@ export async function init(pkgRoot) {
104
116
  results.push(['openspec 结构', 'skipped', '已存在']);
105
117
  } else {
106
118
  console.log('\n→ 初始化 openspec/ 项目结构');
119
+ // --tools is not optional in practice: openspec derives the tool list by
120
+ // sniffing existing agent config dirs, so a fresh project (no .claude etc.)
121
+ // aborts with "No tools detected and no --tools flag provided". comate's
122
+ // skills are agent-agnostic and only openspec/ itself matters here, so ask
123
+ // for none rather than guessing which agents the user wants wired up.
107
124
  const r2 = run(
108
- { command: 'openspec', args: ['init', '.'], shell: process.platform === 'win32' },
125
+ {
126
+ command: 'openspec',
127
+ args: ['init', '.', '--tools', 'none'],
128
+ shell: process.platform === 'win32',
129
+ },
109
130
  NPM_TIMEOUT_MS,
110
131
  );
111
132
  results.push(['openspec 结构', r2.ok ? 'installed' : 'failed', r2.ok ? '' : r2.reason]);
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.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "comate —— OpenSpec + Superpowers 规约驱动的 AI 开发编排器(Agent Skills)",
5
5
  "type": "module",
6
6
  "license": "MIT",