@kafka0102/onespec 0.1.2 → 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/README.md +45 -48
- package/assets/skills/onespec/SKILL.md +22 -14
- package/assets/skills/onespec/references/archive.md +214 -0
- package/assets/skills/{onespec-design/SKILL.md → onespec/references/design.md} +55 -51
- package/assets/skills/onespec/references/execute.md +291 -0
- package/assets/skills/onespec/references/fast.md +110 -0
- package/assets/skills/onespec/scripts/onespec-closeout.sh +238 -77
- package/assets/skills/onespec/scripts/onespec-commit.sh +191 -11
- package/assets/skills/onespec/scripts/onespec-handoff.sh +19 -6
- package/assets/skills/onespec/scripts/onespec-state.sh +157 -18
- package/assets/skills/onespec-fast/SKILL.md +22 -0
- package/assets/skills/onespec-fast/agents/openai.yaml +4 -0
- package/assets/skills-en/onespec/SKILL.md +22 -13
- package/assets/skills-en/onespec/references/archive.md +213 -0
- package/assets/skills-en/{onespec-design/SKILL.md → onespec/references/design.md} +58 -43
- package/assets/skills-en/onespec/references/execute.md +291 -0
- package/assets/skills-en/onespec/references/fast.md +110 -0
- package/assets/skills-en/onespec-fast/SKILL.md +22 -0
- package/package.json +10 -3
- package/scripts/postinstall.js +3 -3
- package/src/cli.js +120 -110
- package/src/doctor.js +46 -20
- package/src/init.js +24 -10
- package/src/platforms.js +88 -8
- package/src/setup.js +211 -0
- package/assets/skills/onespec-archive/SKILL.md +0 -202
- package/assets/skills/onespec-execute/SKILL.md +0 -219
- package/assets/skills-en/onespec-archive/SKILL.md +0 -199
- package/assets/skills-en/onespec-execute/SKILL.md +0 -219
package/src/cli.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
1
|
import path from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { checkbox, select, confirm } from '@inquirer/prompts';
|
|
5
4
|
|
|
6
5
|
import { doctorProject } from './doctor.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import { SUPPORTED_LANGUAGES } from './init.js';
|
|
7
|
+
import { getPlatform, PLATFORMS } from './platforms.js';
|
|
8
|
+
import {
|
|
9
|
+
detectExistingOneSpecPlatforms,
|
|
10
|
+
detectPlatforms,
|
|
11
|
+
initWorkspace,
|
|
12
|
+
parsePlatformList,
|
|
13
|
+
SUPPORTED_PLATFORM_IDS,
|
|
14
|
+
} from './setup.js';
|
|
11
15
|
|
|
12
16
|
function parseArgs(argv) {
|
|
13
17
|
const args = [...argv];
|
|
14
18
|
const command = args.shift() ?? 'help';
|
|
15
19
|
const options = {
|
|
16
20
|
targetPath: process.cwd(),
|
|
17
|
-
|
|
21
|
+
platforms: [],
|
|
18
22
|
scope: undefined,
|
|
19
23
|
language: undefined,
|
|
20
24
|
yes: false,
|
|
@@ -43,7 +47,7 @@ function parseArgs(argv) {
|
|
|
43
47
|
options.language = args.shift();
|
|
44
48
|
break;
|
|
45
49
|
case '--platform':
|
|
46
|
-
options.
|
|
50
|
+
options.platforms.push(args.shift() ?? '');
|
|
47
51
|
break;
|
|
48
52
|
default:
|
|
49
53
|
if (arg?.startsWith('-')) {
|
|
@@ -57,135 +61,141 @@ function parseArgs(argv) {
|
|
|
57
61
|
return { command, options };
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
async function selectScope(options) {
|
|
65
|
+
if (options.scope) return options.scope;
|
|
66
|
+
if (options.yes) return 'project';
|
|
67
|
+
|
|
68
|
+
return select({
|
|
69
|
+
message: 'Install scope:',
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: 'Project (current directory)', value: 'project' },
|
|
72
|
+
{ name: 'Global (home directory)', value: 'global' },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
async function
|
|
71
|
-
if (options.yes)
|
|
72
|
-
return {
|
|
73
|
-
...options,
|
|
74
|
-
scope: options.scope ?? 'project',
|
|
75
|
-
language: options.language ?? 'zh',
|
|
76
|
-
installOpenSpecCli: false,
|
|
77
|
-
initOpenSpecProject: false,
|
|
78
|
-
installSuperpowers: false,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
77
|
+
async function selectLanguage(options) {
|
|
78
|
+
if (options.yes) return 'zh';
|
|
81
79
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
const langId = await select({
|
|
81
|
+
message: 'Language for OneSpec skills:',
|
|
82
|
+
choices: Object.entries(SUPPORTED_LANGUAGES).map(([id, lang]) => ({ name: lang.name, value: id })),
|
|
85
83
|
});
|
|
86
|
-
const rl = readline.createInterface({ input, output });
|
|
87
|
-
try {
|
|
88
|
-
const scopeAnswer =
|
|
89
|
-
options.scope ??
|
|
90
|
-
(await rl.question('安装范围?输入 project 或 global(默认 project):'));
|
|
91
|
-
const resolvedScope = scopeAnswer.trim() || 'project';
|
|
92
|
-
const languageAnswer =
|
|
93
|
-
options.language ??
|
|
94
|
-
(await rl.question('Skill 语言?输入 zh 或 en(默认 zh):'));
|
|
95
|
-
const overwriteAnswer = options.overwrite
|
|
96
|
-
? 'yes'
|
|
97
|
-
: await rl.question('如果 OneSpec skill 已存在,是否覆盖?输入 yes 或 no(默认 no):');
|
|
98
|
-
const installOpenSpecCliAnswer =
|
|
99
|
-
preflight.openspecCli.available
|
|
100
|
-
? 'no'
|
|
101
|
-
: await rl.question(
|
|
102
|
-
`未检测到 OpenSpec CLI。是否现在执行 npm install -g ${OPEN_SPEC_CLI_PACKAGE} ?输入 yes 或 no(默认 no):`,
|
|
103
|
-
);
|
|
104
|
-
const initOpenSpecProjectAnswer =
|
|
105
|
-
resolvedScope === 'project' && !preflight.hasOpenSpecProject
|
|
106
|
-
? await rl.question(
|
|
107
|
-
'当前项目未初始化 OpenSpec。是否在安装 OneSpec 后执行 openspec init?输入 yes 或 no(默认 no):',
|
|
108
|
-
)
|
|
109
|
-
: 'no';
|
|
110
|
-
const installSuperpowersAnswer = preflight.superpowers.available
|
|
111
|
-
? 'no'
|
|
112
|
-
: await rl.question(
|
|
113
|
-
`未检测到 Superpowers。是否现在执行 npx skills add ${SUPERPOWERS_PACKAGE} -a codex${resolvedScope === 'global' ? ' -g' : ''} -y ?输入 yes 或 no(默认 no):`,
|
|
114
|
-
);
|
|
115
84
|
|
|
85
|
+
return langId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function selectPlatforms(detected, options) {
|
|
89
|
+
const choices = SUPPORTED_PLATFORM_IDS.map((platformId) => {
|
|
90
|
+
const platform = getPlatform(platformId);
|
|
116
91
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
overwrite: options.overwrite || ['y', 'yes', '是', '覆盖'].includes(overwriteAnswer.trim()),
|
|
121
|
-
installOpenSpecCli: ['y', 'yes', '是'].includes(installOpenSpecCliAnswer.trim()),
|
|
122
|
-
initOpenSpecProject: ['y', 'yes', '是'].includes(initOpenSpecProjectAnswer.trim()),
|
|
123
|
-
installSuperpowers: ['y', 'yes', '是'].includes(installSuperpowersAnswer.trim()),
|
|
92
|
+
name: `${platform.name}${detected.has(platformId) ? ' (detected)' : ''}`,
|
|
93
|
+
value: platformId,
|
|
94
|
+
checked: detected.has(platformId),
|
|
124
95
|
};
|
|
125
|
-
}
|
|
126
|
-
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (options.yes) {
|
|
99
|
+
const selected = [...detected];
|
|
100
|
+
return selected.length > 0 ? selected : ['codex'];
|
|
127
101
|
}
|
|
128
|
-
}
|
|
129
102
|
|
|
130
|
-
|
|
131
|
-
execFileSync(command, args, {
|
|
132
|
-
cwd,
|
|
133
|
-
stdio: 'inherit',
|
|
134
|
-
shell: process.platform === 'win32',
|
|
135
|
-
});
|
|
103
|
+
return checkbox({ message: 'Select platforms to set up:', choices, required: true });
|
|
136
104
|
}
|
|
137
105
|
|
|
138
|
-
function
|
|
139
|
-
|
|
106
|
+
async function askOverwrite(existingPlatforms, options) {
|
|
107
|
+
if (options.overwrite || existingPlatforms.length === 0) {
|
|
108
|
+
return options.overwrite;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return confirm({
|
|
112
|
+
message: `OneSpec skills already exist for ${existingPlatforms.join(', ')}. Overwrite existing items?`,
|
|
113
|
+
default: false,
|
|
114
|
+
});
|
|
140
115
|
}
|
|
141
116
|
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
117
|
+
async function askInitOptions(options) {
|
|
118
|
+
const explicitPlatforms = parsePlatformList(options.platforms);
|
|
119
|
+
const detectedPlatforms = await detectPlatforms(options.targetPath);
|
|
120
|
+
const defaultPlatforms = explicitPlatforms.length > 0 ? explicitPlatforms : detectedPlatforms.length > 0 ? detectedPlatforms : ['codex'];
|
|
146
121
|
|
|
147
|
-
if (options.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
122
|
+
if (options.yes) {
|
|
123
|
+
return {
|
|
124
|
+
...options,
|
|
125
|
+
scope: options.scope ?? 'project',
|
|
126
|
+
language: options.language ?? 'zh',
|
|
127
|
+
platforms: defaultPlatforms,
|
|
128
|
+
};
|
|
153
129
|
}
|
|
154
130
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
131
|
+
const detectedSet = new Set(detectedPlatforms);
|
|
132
|
+
const selectedPlatforms = explicitPlatforms.length > 0 ? explicitPlatforms : await selectPlatforms(detectedSet, options);
|
|
133
|
+
const resolvedScope = await selectScope(options);
|
|
134
|
+
const language = await selectLanguage(options);
|
|
135
|
+
const existingPlatforms = await detectExistingOneSpecPlatforms(
|
|
136
|
+
options.targetPath,
|
|
137
|
+
resolvedScope,
|
|
138
|
+
selectedPlatforms,
|
|
139
|
+
);
|
|
140
|
+
const overwrite = await askOverwrite(existingPlatforms, options);
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...options,
|
|
144
|
+
platforms: selectedPlatforms,
|
|
145
|
+
scope: resolvedScope,
|
|
146
|
+
language,
|
|
147
|
+
overwrite,
|
|
148
|
+
};
|
|
158
149
|
}
|
|
159
150
|
|
|
160
151
|
function printHelp() {
|
|
161
152
|
console.log(`OneSpec Skill Installer
|
|
162
153
|
|
|
163
154
|
用法:
|
|
164
|
-
onespec init [path] [--yes] [--overwrite] [--scope project|global] [--language zh|en]
|
|
165
|
-
onespec doctor [path] [--scope project|global]
|
|
155
|
+
onespec init [path] [--yes] [--overwrite] [--scope project|global] [--language zh|en] [--platform ${SUPPORTED_PLATFORM_IDS.join('|')}[,...]]
|
|
156
|
+
onespec doctor [path] [--scope project|global] [--platform ${SUPPORTED_PLATFORM_IDS.join('|')}]
|
|
166
157
|
|
|
167
158
|
说明:
|
|
168
|
-
当前提供中英文 Skill bundle
|
|
159
|
+
当前提供中英文 Skill bundle,官方支持 ${SUPPORTED_PLATFORM_IDS.join(' / ')}。
|
|
160
|
+
init 会引导选择 agent,并自动安装 OpenSpec / Superpowers / OneSpec。
|
|
169
161
|
`);
|
|
170
162
|
}
|
|
171
163
|
|
|
164
|
+
let cachedVersion;
|
|
165
|
+
|
|
166
|
+
async function getPackageVersion() {
|
|
167
|
+
if (cachedVersion) {
|
|
168
|
+
return cachedVersion;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const packageJsonPath = new URL('../package.json', import.meta.url);
|
|
172
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
|
|
173
|
+
cachedVersion = packageJson.version;
|
|
174
|
+
return cachedVersion;
|
|
175
|
+
}
|
|
176
|
+
|
|
172
177
|
function printSummary(result) {
|
|
173
178
|
console.log('\nOneSpec 初始化完成\n');
|
|
174
|
-
console.log(
|
|
179
|
+
console.log(`目标平台:${result.platformNames.join(', ')}`);
|
|
175
180
|
console.log(`安装范围:${result.scope}`);
|
|
176
181
|
console.log(`Skill 语言:${result.languageName} (${result.language})`);
|
|
177
|
-
console.log(`
|
|
178
|
-
console.log(
|
|
179
|
-
console.log(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
console.log(`OpenSpec CLI:${result.openspecCli.status === 'installed' ? '已自动安装' : '已存在'}`);
|
|
183
|
+
console.log(`OpenSpec Tools:${result.openspec.toolIds.join(', ')}`);
|
|
184
|
+
console.log(`Superpowers Agents:${result.superpowers.agents.join(', ')}`);
|
|
185
|
+
for (const platformResult of result.results) {
|
|
186
|
+
const platformLabel = `${platformResult.platformName} (${platformResult.platform})`;
|
|
187
|
+
const skillStatus = platformResult.installedSkill ? '已安装/已覆盖' : '已存在,已跳过';
|
|
188
|
+
console.log(`- ${platformLabel}:${skillStatus} -> ${platformResult.skillPath}`);
|
|
189
|
+
}
|
|
190
|
+
if (result.scope === 'project') {
|
|
191
|
+
console.log(`工作目录:${path.join(result.projectPath, 'docs', 'superpowers')}`);
|
|
192
|
+
}
|
|
193
|
+
console.log('\n开始使用:重启对应 agent 会话后,直接输入 “使用 onespec:<你的任务描述>”。\n');
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
function printDoctor(report) {
|
|
188
197
|
console.log('\nOneSpec 环境检查\n');
|
|
198
|
+
console.log(`目标平台:${report.platformName} (${report.platform})`);
|
|
189
199
|
console.log(`OneSpec Skill:${report.onespec.installed ? '已安装' : '未安装'}`);
|
|
190
200
|
console.log(`OneSpec 子 Skills:${report.onespec.installedSkills.join(', ') || '无'}`);
|
|
191
201
|
console.log(`缺少 OneSpec 子 Skills:${report.onespec.missingSkills.join(', ') || '无'}`);
|
|
@@ -205,6 +215,10 @@ function printDoctor(report) {
|
|
|
205
215
|
export async function main(argv = process.argv.slice(2)) {
|
|
206
216
|
const { command, options } = parseArgs(argv);
|
|
207
217
|
|
|
218
|
+
if (command === 'version' || command === '--version' || command === '-v') {
|
|
219
|
+
console.log(await getPackageVersion());
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
208
222
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
209
223
|
printHelp();
|
|
210
224
|
return;
|
|
@@ -214,8 +228,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
214
228
|
}
|
|
215
229
|
|
|
216
230
|
if (command === 'doctor') {
|
|
231
|
+
const doctorPlatforms = parsePlatformList(options.platforms);
|
|
217
232
|
const report = await doctorProject(options.targetPath, {
|
|
218
|
-
platform:
|
|
233
|
+
platform: doctorPlatforms[0],
|
|
219
234
|
scope: options.scope ?? 'project',
|
|
220
235
|
});
|
|
221
236
|
if (options.json) {
|
|
@@ -230,12 +245,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
230
245
|
if (!SUPPORTED_LANGUAGES[initOptions.language]) {
|
|
231
246
|
throw new Error(`Unsupported language: ${initOptions.language}`);
|
|
232
247
|
}
|
|
233
|
-
const
|
|
234
|
-
platform: initOptions.platform,
|
|
235
|
-
scope: initOptions.scope ?? 'project',
|
|
236
|
-
});
|
|
237
|
-
await ensureRequestedDependencies(initOptions.targetPath, initOptions, preflight);
|
|
238
|
-
const result = await initProject(initOptions.targetPath, initOptions);
|
|
248
|
+
const result = await initWorkspace(initOptions.targetPath, initOptions);
|
|
239
249
|
if (initOptions.json) {
|
|
240
250
|
console.log(JSON.stringify(result, null, 2));
|
|
241
251
|
} else {
|
package/src/doctor.js
CHANGED
|
@@ -3,8 +3,8 @@ import { access, readFile } from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
6
|
-
import { BUNDLED_ONESPEC_SKILLS } from './init.js';
|
|
7
|
-
import {
|
|
6
|
+
import { BUNDLED_ONESPEC_REFERENCE_FILES, BUNDLED_ONESPEC_SKILLS } from './init.js';
|
|
7
|
+
import { getDiscoveryRoots, getPlatform, getSkillDir } from './platforms.js';
|
|
8
8
|
|
|
9
9
|
const REQUIRED_SUPERPOWERS = [
|
|
10
10
|
'brainstorming',
|
|
@@ -41,8 +41,12 @@ function defaultCommandChecker(command) {
|
|
|
41
41
|
function defaultSkillRoots(projectPath, scope, platform) {
|
|
42
42
|
return [
|
|
43
43
|
getSkillDir(projectPath, scope, platform),
|
|
44
|
-
|
|
44
|
+
...getDiscoveryRoots(projectPath, platform),
|
|
45
|
+
path.join(os.homedir(), '.claude', 'skills'),
|
|
45
46
|
path.join(os.homedir(), '.codex', 'superpowers', 'skills'),
|
|
47
|
+
path.join(os.homedir(), '.cursor', 'skills'),
|
|
48
|
+
path.join(os.homedir(), '.gemini', 'skills'),
|
|
49
|
+
path.join(os.homedir(), '.copilot', 'skills'),
|
|
46
50
|
path.join(os.homedir(), '.agents', 'skills'),
|
|
47
51
|
];
|
|
48
52
|
}
|
|
@@ -60,7 +64,9 @@ async function isChineseOneSpec(projectPath, scope, platform) {
|
|
|
60
64
|
const skillsDir = getSkillDir(projectPath, scope, platform);
|
|
61
65
|
const installedSkills = [];
|
|
62
66
|
const missingSkills = [];
|
|
67
|
+
const missingFiles = [];
|
|
63
68
|
const skillPaths = {};
|
|
69
|
+
const referencePaths = {};
|
|
64
70
|
|
|
65
71
|
for (const skillName of BUNDLED_ONESPEC_SKILLS) {
|
|
66
72
|
const skillPath = path.join(skillsDir, skillName, 'SKILL.md');
|
|
@@ -72,14 +78,24 @@ async function isChineseOneSpec(projectPath, scope, platform) {
|
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
for (const referenceFile of BUNDLED_ONESPEC_REFERENCE_FILES) {
|
|
82
|
+
const referencePath = path.join(skillsDir, 'onespec', referenceFile);
|
|
83
|
+
referencePaths[referenceFile] = referencePath;
|
|
84
|
+
if (!(await exists(referencePath))) {
|
|
85
|
+
missingFiles.push(path.join('onespec', referenceFile));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
75
89
|
const routerPath = skillPaths.onespec;
|
|
76
90
|
if (!(await exists(routerPath))) {
|
|
77
91
|
return {
|
|
78
92
|
installed: false,
|
|
79
93
|
skillPath: routerPath,
|
|
80
94
|
skillPaths,
|
|
95
|
+
referencePaths,
|
|
81
96
|
installedSkills,
|
|
82
97
|
missingSkills,
|
|
98
|
+
missingFiles,
|
|
83
99
|
chinese: false,
|
|
84
100
|
};
|
|
85
101
|
}
|
|
@@ -88,11 +104,13 @@ async function isChineseOneSpec(projectPath, scope, platform) {
|
|
|
88
104
|
const chinese = content.includes('OneSpec 工作流');
|
|
89
105
|
const english = content.includes('# OneSpec Workflow');
|
|
90
106
|
return {
|
|
91
|
-
installed: missingSkills.length === 0 && (chinese || english),
|
|
107
|
+
installed: missingSkills.length === 0 && missingFiles.length === 0 && (chinese || english),
|
|
92
108
|
skillPath: routerPath,
|
|
93
109
|
skillPaths,
|
|
110
|
+
referencePaths,
|
|
94
111
|
installedSkills,
|
|
95
112
|
missingSkills,
|
|
113
|
+
missingFiles,
|
|
96
114
|
chinese,
|
|
97
115
|
english,
|
|
98
116
|
language: chinese ? 'zh' : english ? 'en' : 'unknown',
|
|
@@ -101,20 +119,16 @@ async function isChineseOneSpec(projectPath, scope, platform) {
|
|
|
101
119
|
|
|
102
120
|
export async function doctorProject(projectPath, options = {}) {
|
|
103
121
|
const resolvedProject = path.resolve(projectPath);
|
|
104
|
-
const platform = options.platform ?? 'codex';
|
|
122
|
+
const platform = getPlatform(options.platform ?? 'codex');
|
|
105
123
|
const scope = options.scope ?? 'project';
|
|
106
124
|
const commandChecker = options.commandChecker ?? defaultCommandChecker;
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
throw new Error(`Unsupported platform "${platform}". Currently only "codex" is supported.`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const onespec = await isChineseOneSpec(resolvedProject, scope, platform);
|
|
126
|
+
const onespec = await isChineseOneSpec(resolvedProject, scope, platform.id);
|
|
113
127
|
const skillRoots =
|
|
114
128
|
options.skillRoots ??
|
|
115
129
|
[
|
|
116
130
|
...new Set([
|
|
117
|
-
...defaultSkillRoots(resolvedProject, scope, platform),
|
|
131
|
+
...defaultSkillRoots(resolvedProject, scope, platform.id),
|
|
118
132
|
...(options.extraSkillRoots ?? []),
|
|
119
133
|
]),
|
|
120
134
|
];
|
|
@@ -139,29 +153,41 @@ export async function doctorProject(projectPath, options = {}) {
|
|
|
139
153
|
const nextSteps = [];
|
|
140
154
|
if (onespec.missingSkills.length > 0) {
|
|
141
155
|
nextSteps.push(
|
|
142
|
-
`缺少 OneSpec Skills:${onespec.missingSkills.join(', ')}。运行 \`onespec init --overwrite\` 补齐 OneSpec Skill bundle。`,
|
|
156
|
+
`缺少 OneSpec Skills:${onespec.missingSkills.join(', ')}。运行 \`onespec init --platform ${platform.id} --overwrite\` 补齐 OneSpec Skill bundle。`,
|
|
157
|
+
);
|
|
158
|
+
} else if (onespec.missingFiles.length > 0) {
|
|
159
|
+
nextSteps.push(
|
|
160
|
+
`缺少 OneSpec references:${onespec.missingFiles.join(', ')}。运行 \`onespec init --platform ${platform.id} --overwrite\` 补齐 OneSpec Skill bundle。`,
|
|
143
161
|
);
|
|
144
162
|
} else if (!onespec.installed) {
|
|
145
|
-
nextSteps.push(
|
|
163
|
+
nextSteps.push(`运行 \`onespec init --platform ${platform.id} --yes\` 安装 OneSpec Skill。`);
|
|
146
164
|
} else if (!onespec.chinese && !onespec.english) {
|
|
147
|
-
nextSteps.push(
|
|
165
|
+
nextSteps.push(
|
|
166
|
+
`当前 OneSpec Skill 无法识别语言版本,运行 \`onespec init --platform ${platform.id} --overwrite\` 覆盖安装。`,
|
|
167
|
+
);
|
|
148
168
|
}
|
|
149
169
|
if (!openspecCli.available) {
|
|
150
|
-
nextSteps.push(
|
|
170
|
+
nextSteps.push(
|
|
171
|
+
`未找到 OpenSpec CLI。运行 \`onespec init --platform ${platform.id} --scope ${scope}\` 让 OneSpec 自动安装并初始化 OpenSpec。`,
|
|
172
|
+
);
|
|
151
173
|
} else if (scope === 'project' && !openSpecProjectInstalled) {
|
|
152
|
-
nextSteps.push(
|
|
174
|
+
nextSteps.push(
|
|
175
|
+
`当前项目尚未初始化 OpenSpec。请重新运行 \`onespec init --platform ${platform.id} --scope project\` 让 OneSpec 自动补齐。`,
|
|
176
|
+
);
|
|
153
177
|
}
|
|
154
178
|
if (!superpowers.available) {
|
|
155
|
-
nextSteps.push(
|
|
179
|
+
nextSteps.push(
|
|
180
|
+
`缺少 Superpowers Skills:${missing.join(', ')}。运行 \`onespec init --platform ${platform.id} --scope ${scope}\` 让 OneSpec 自动补齐。`,
|
|
181
|
+
);
|
|
156
182
|
}
|
|
157
183
|
if (nextSteps.length === 0) {
|
|
158
|
-
nextSteps.push(
|
|
184
|
+
nextSteps.push(`环境检查通过。可以在 ${platform.name} 中使用 \`onespec\` 工作流。`);
|
|
159
185
|
}
|
|
160
186
|
|
|
161
187
|
return {
|
|
162
188
|
projectPath: resolvedProject,
|
|
163
|
-
platform,
|
|
164
|
-
platformName:
|
|
189
|
+
platform: platform.id,
|
|
190
|
+
platformName: platform.name,
|
|
165
191
|
scope,
|
|
166
192
|
onespec,
|
|
167
193
|
openspecCli,
|
package/src/init.js
CHANGED
|
@@ -2,18 +2,29 @@ import { access, chmod, cp, mkdir, rm, stat } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { getPlatform, getSkillDir } from './platforms.js';
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
9
9
|
|
|
10
10
|
export const BUNDLED_ONESPEC_SKILLS = [
|
|
11
11
|
'onespec',
|
|
12
|
+
'onespec-fast',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export const LEGACY_ONESPEC_CHILD_SKILLS = [
|
|
12
16
|
'onespec-design',
|
|
13
17
|
'onespec-execute',
|
|
14
18
|
'onespec-archive',
|
|
15
19
|
];
|
|
16
20
|
|
|
21
|
+
export const BUNDLED_ONESPEC_REFERENCE_FILES = [
|
|
22
|
+
'references/design.md',
|
|
23
|
+
'references/execute.md',
|
|
24
|
+
'references/archive.md',
|
|
25
|
+
'references/fast.md',
|
|
26
|
+
];
|
|
27
|
+
|
|
17
28
|
export const SUPPORTED_LANGUAGES = {
|
|
18
29
|
zh: {
|
|
19
30
|
id: 'zh',
|
|
@@ -66,14 +77,11 @@ async function createWorkingDirs(projectPath) {
|
|
|
66
77
|
|
|
67
78
|
export async function initProject(projectPath, options = {}) {
|
|
68
79
|
const resolvedProject = path.resolve(projectPath);
|
|
69
|
-
const platform = options.platform ?? 'codex';
|
|
80
|
+
const platform = getPlatform(options.platform ?? 'codex');
|
|
70
81
|
const scope = options.scope ?? 'project';
|
|
71
82
|
const overwrite = Boolean(options.overwrite);
|
|
72
83
|
const language = options.language ?? 'zh';
|
|
73
84
|
|
|
74
|
-
if (!PLATFORMS[platform]) {
|
|
75
|
-
throw new Error(`Unsupported platform "${platform}". Currently only "codex" is supported.`);
|
|
76
|
-
}
|
|
77
85
|
if (!['project', 'global'].includes(scope)) {
|
|
78
86
|
throw new Error(`Unsupported scope "${scope}". Use "project" or "global".`);
|
|
79
87
|
}
|
|
@@ -85,13 +93,19 @@ export async function initProject(projectPath, options = {}) {
|
|
|
85
93
|
|
|
86
94
|
const sourceRoot = assetsSkillsDir();
|
|
87
95
|
const localizedRoot = localizedSkillsDir(language);
|
|
88
|
-
const skillsDir = getSkillDir(resolvedProject, scope, platform);
|
|
96
|
+
const skillsDir = getSkillDir(resolvedProject, scope, platform.id);
|
|
89
97
|
const destination = path.join(skillsDir, 'onespec');
|
|
90
98
|
const installedSkills = [];
|
|
91
99
|
const skippedSkills = [];
|
|
92
100
|
|
|
93
101
|
await mkdir(skillsDir, { recursive: true });
|
|
94
102
|
|
|
103
|
+
if (overwrite) {
|
|
104
|
+
for (const skillName of LEGACY_ONESPEC_CHILD_SKILLS) {
|
|
105
|
+
await rm(path.join(skillsDir, skillName), { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
95
109
|
for (const skillName of BUNDLED_ONESPEC_SKILLS) {
|
|
96
110
|
const source = path.join(sourceRoot, skillName);
|
|
97
111
|
const target = path.join(skillsDir, skillName);
|
|
@@ -107,9 +121,9 @@ export async function initProject(projectPath, options = {}) {
|
|
|
107
121
|
}
|
|
108
122
|
await cp(source, target, { recursive: true });
|
|
109
123
|
if (language !== 'zh') {
|
|
110
|
-
const localizedSkill = path.join(localizedRoot, skillName
|
|
124
|
+
const localizedSkill = path.join(localizedRoot, skillName);
|
|
111
125
|
if (await exists(localizedSkill)) {
|
|
112
|
-
await cp(localizedSkill,
|
|
126
|
+
await cp(localizedSkill, target, { recursive: true, force: true });
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
await makeBundledScriptsExecutable(target);
|
|
@@ -122,8 +136,8 @@ export async function initProject(projectPath, options = {}) {
|
|
|
122
136
|
|
|
123
137
|
return {
|
|
124
138
|
projectPath: resolvedProject,
|
|
125
|
-
platform,
|
|
126
|
-
platformName:
|
|
139
|
+
platform: platform.id,
|
|
140
|
+
platformName: platform.name,
|
|
127
141
|
scope,
|
|
128
142
|
language,
|
|
129
143
|
languageName: SUPPORTED_LANGUAGES[language].name,
|
package/src/platforms.js
CHANGED
|
@@ -1,23 +1,103 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
const home = os.homedir();
|
|
5
|
+
const codexHome = process.env.CODEX_HOME?.trim() || path.join(home, '.codex');
|
|
6
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || path.join(home, '.claude');
|
|
7
|
+
|
|
4
8
|
export const PLATFORMS = {
|
|
5
9
|
codex: {
|
|
6
10
|
id: 'codex',
|
|
7
11
|
name: 'Codex',
|
|
8
|
-
|
|
12
|
+
projectSkillsDir: path.join('.agents', 'skills'),
|
|
13
|
+
globalSkillsDir: path.join(codexHome, 'skills'),
|
|
14
|
+
legacyProjectSkillDirs: [path.join('.codex', 'skills')],
|
|
15
|
+
discoveryRoots: [path.join(home, '.agents', 'skills'), '/etc/codex/skills'],
|
|
9
16
|
openspecToolId: 'codex',
|
|
10
17
|
},
|
|
18
|
+
'claude-code': {
|
|
19
|
+
id: 'claude-code',
|
|
20
|
+
name: 'Claude Code',
|
|
21
|
+
projectSkillsDir: path.join('.claude', 'skills'),
|
|
22
|
+
globalSkillsDir: path.join(claudeHome, 'skills'),
|
|
23
|
+
discoveryRoots: [],
|
|
24
|
+
openspecToolId: 'claude',
|
|
25
|
+
},
|
|
26
|
+
cursor: {
|
|
27
|
+
id: 'cursor',
|
|
28
|
+
name: 'Cursor',
|
|
29
|
+
projectSkillsDir: path.join('.agents', 'skills'),
|
|
30
|
+
globalSkillsDir: path.join(home, '.cursor', 'skills'),
|
|
31
|
+
discoveryRoots: [path.join(home, '.agents', 'skills')],
|
|
32
|
+
openspecToolId: 'cursor',
|
|
33
|
+
},
|
|
34
|
+
'gemini-cli': {
|
|
35
|
+
id: 'gemini-cli',
|
|
36
|
+
name: 'Gemini CLI',
|
|
37
|
+
projectSkillsDir: path.join('.agents', 'skills'),
|
|
38
|
+
globalSkillsDir: path.join(home, '.gemini', 'skills'),
|
|
39
|
+
discoveryRoots: [path.join(home, '.agents', 'skills')],
|
|
40
|
+
openspecToolId: 'gemini',
|
|
41
|
+
},
|
|
42
|
+
'github-copilot': {
|
|
43
|
+
id: 'github-copilot',
|
|
44
|
+
name: 'GitHub Copilot',
|
|
45
|
+
projectSkillsDir: path.join('.agents', 'skills'),
|
|
46
|
+
globalSkillsDir: path.join(home, '.copilot', 'skills'),
|
|
47
|
+
discoveryRoots: [path.join(home, '.agents', 'skills')],
|
|
48
|
+
openspecToolId: 'github-copilot',
|
|
49
|
+
},
|
|
11
50
|
};
|
|
12
51
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
52
|
+
const PLATFORM_ALIASES = {
|
|
53
|
+
codex: 'codex',
|
|
54
|
+
claude: 'claude-code',
|
|
55
|
+
'claude-code': 'claude-code',
|
|
56
|
+
claude_code: 'claude-code',
|
|
57
|
+
cursor: 'cursor',
|
|
58
|
+
gemini: 'gemini-cli',
|
|
59
|
+
'gemini-cli': 'gemini-cli',
|
|
60
|
+
gemini_cli: 'gemini-cli',
|
|
61
|
+
copilot: 'github-copilot',
|
|
62
|
+
'github-copilot': 'github-copilot',
|
|
63
|
+
github_copilot: 'github-copilot',
|
|
64
|
+
'github-copilot-cli': 'github-copilot',
|
|
65
|
+
};
|
|
16
66
|
|
|
17
|
-
export function
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
67
|
+
export function resolvePlatformId(platformId = 'codex') {
|
|
68
|
+
const resolved = PLATFORM_ALIASES[platformId];
|
|
69
|
+
if (!resolved) {
|
|
20
70
|
throw new Error(`Unsupported platform: ${platformId}`);
|
|
21
71
|
}
|
|
22
|
-
return
|
|
72
|
+
return resolved;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getPlatform(platformId = 'codex') {
|
|
76
|
+
return PLATFORMS[resolvePlatformId(platformId)];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getProjectSkillDir(projectPath, platformId = 'codex') {
|
|
80
|
+
const platform = getPlatform(platformId);
|
|
81
|
+
return path.join(projectPath, platform.projectSkillsDir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getGlobalSkillDir(platformId = 'codex') {
|
|
85
|
+
const platform = getPlatform(platformId);
|
|
86
|
+
return platform.globalSkillsDir;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getSkillDir(projectPath, scope, platformId = 'codex') {
|
|
90
|
+
return scope === 'global'
|
|
91
|
+
? getGlobalSkillDir(platformId)
|
|
92
|
+
: getProjectSkillDir(projectPath, platformId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getDiscoveryRoots(projectPath, platformId = 'codex') {
|
|
96
|
+
const platform = getPlatform(platformId);
|
|
97
|
+
return [
|
|
98
|
+
getProjectSkillDir(projectPath, platformId),
|
|
99
|
+
...(platform.legacyProjectSkillDirs ?? []).map((relativeDir) => path.join(projectPath, relativeDir)),
|
|
100
|
+
getGlobalSkillDir(platformId),
|
|
101
|
+
...(platform.discoveryRoots ?? []),
|
|
102
|
+
];
|
|
23
103
|
}
|