@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 +2 -2
- package/bin/cli.mjs +1 -1
- package/bin/doctor.mjs +32 -5
- package/bin/init-run.mjs +38 -17
- package/bin/init.mjs +14 -4
- package/package.json +1 -1
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
|
-
|
|
40
|
+
skills 装到用户级(`~/.agents/skills`),装一次所有项目共用;`openspec/` 是项目自己的规约,建在当前目录,所以 `init` 需要在项目目录下运行。
|
|
41
41
|
|
|
42
|
-
|
|
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
|
|
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.
|
|
10
|
-
//
|
|
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(
|
|
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`
|
|
17
|
-
//
|
|
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
|
-
//
|
|
20
|
-
//
|
|
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(`\
|
|
58
|
+
console.log(`\nskills 装到 ${join(home, '.agents', 'skills')}(用户级,所有项目共用)`);
|
|
59
|
+
console.log(`openspec/ 建在 ${cwd}\n`);
|
|
59
60
|
|
|
60
|
-
// 1. OpenSpec CLI —
|
|
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
|
-
|
|
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
|
|
85
|
-
const r = run(
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)
|
|
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
|
|