godpowers 2.0.0 → 2.1.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +126 -0
- package/README.md +45 -5
- package/RELEASE.md +45 -39
- package/SKILL.md +9 -1
- package/agents/god-design-reviewer.md +6 -6
- package/agents/god-designer.md +1 -1
- package/agents/god-executor.md +23 -0
- package/agents/god-quality-reviewer.md +12 -1
- package/agents/god-spec-reviewer.md +10 -0
- package/bin/install.js +119 -655
- package/extensions/launch-pack/README.md +1 -1
- package/lib/agent-browser-driver.js +13 -13
- package/lib/agent-cache.js +8 -1
- package/lib/agent-refs.js +161 -0
- package/lib/budget.js +25 -11
- package/lib/events.js +11 -4
- package/lib/extension-authoring.js +27 -0
- package/lib/feature-awareness.js +18 -0
- package/lib/fs-async.js +28 -0
- package/lib/installer-args.js +99 -0
- package/lib/installer-core.js +345 -0
- package/lib/installer-files.js +80 -0
- package/lib/installer-runtimes.js +112 -0
- package/lib/intent.js +111 -16
- package/lib/release-surface-sync.js +8 -1
- package/lib/repo-surface-sync.js +9 -2
- package/lib/review-required.js +2 -1
- package/lib/router.js +23 -3
- package/lib/skill-surface.js +42 -0
- package/lib/state-lock.js +10 -0
- package/lib/state.js +101 -8
- package/lib/workflow-runner.js +42 -5
- package/package.json +4 -3
- package/references/HAVE-NOTS.md +4 -3
- package/references/orchestration/GOD-MODE-RUNBOOK.md +273 -0
- package/routing/god-arch.yaml +1 -1
- package/routing/god-build.yaml +1 -1
- package/skills/god-add-backlog.md +1 -1
- package/skills/god-agent-audit.md +2 -2
- package/skills/god-build.md +5 -3
- package/skills/god-context-scan.md +2 -3
- package/skills/god-design.md +2 -2
- package/skills/god-doctor.md +2 -2
- package/skills/god-help.md +4 -3
- package/skills/god-mode.md +10 -266
- package/skills/god-org-context.md +1 -1
- package/skills/god-repair.md +3 -3
- package/skills/god-review.md +9 -0
- package/skills/god-stories.md +1 -1
- package/skills/god-version.md +2 -2
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const { ensureDir, copyRecursive, copyRuntimeBundle } = require('./installer-files');
|
|
5
|
+
const { resolveRuntime } = require('./installer-runtimes');
|
|
6
|
+
|
|
7
|
+
const VERSION = require('../package.json').version;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} InstallOptions
|
|
11
|
+
* @property {boolean} [local] Install relative to the current working directory.
|
|
12
|
+
* @property {boolean} [global] Install to the host runtime config directory.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} InstallSurface
|
|
17
|
+
* @property {number} skills Number of slash-command skill files.
|
|
18
|
+
* @property {number} agents Number of specialist agent files.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function log(msg) {
|
|
22
|
+
console.log(` ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function success(msg) {
|
|
26
|
+
console.log(` \x1b[32m+\x1b[0m ${msg}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function error(msg) {
|
|
30
|
+
console.error(` \x1b[31mx\x1b[0m ${msg}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function installSkillFile(srcFile, skillsDest, runtimeKey, targetName = null) {
|
|
34
|
+
const baseName = targetName || path.basename(srcFile, '.md');
|
|
35
|
+
if (runtimeKey === 'codex') {
|
|
36
|
+
const skillDir = path.join(skillsDest, baseName);
|
|
37
|
+
if (fs.existsSync(skillDir)) {
|
|
38
|
+
fs.rmSync(skillDir, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
ensureDir(skillDir);
|
|
41
|
+
fs.copyFileSync(srcFile, path.join(skillDir, 'SKILL.md'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
fs.copyFileSync(srcFile, path.join(skillsDest, `${baseName}.md`));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseAgentFrontmatter(content) {
|
|
48
|
+
const fallback = { name: null, description: null };
|
|
49
|
+
if (!content.startsWith('---\n')) return fallback;
|
|
50
|
+
|
|
51
|
+
const end = content.indexOf('\n---', 4);
|
|
52
|
+
if (end === -1) return fallback;
|
|
53
|
+
|
|
54
|
+
const lines = content.slice(4, end).split('\n');
|
|
55
|
+
const parsed = { ...fallback };
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i];
|
|
59
|
+
const nameMatch = line.match(/^name:\s*(.+)\s*$/);
|
|
60
|
+
if (nameMatch) {
|
|
61
|
+
parsed.name = nameMatch[1].replace(/^["']|["']$/g, '');
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (line === 'description: |') {
|
|
66
|
+
const desc = [];
|
|
67
|
+
i++;
|
|
68
|
+
while (i < lines.length && /^ {2}/.test(lines[i])) {
|
|
69
|
+
desc.push(lines[i].slice(2));
|
|
70
|
+
i++;
|
|
71
|
+
}
|
|
72
|
+
i--;
|
|
73
|
+
parsed.description = desc.join('\n').trim();
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const descMatch = line.match(/^description:\s*(.+)\s*$/);
|
|
78
|
+
if (descMatch) {
|
|
79
|
+
parsed.description = descMatch[1].replace(/^["']|["']$/g, '');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return parsed;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function stripFrontmatter(content) {
|
|
87
|
+
if (!content.startsWith('---\n')) return content.trim();
|
|
88
|
+
const end = content.indexOf('\n---', 4);
|
|
89
|
+
if (end === -1) return content.trim();
|
|
90
|
+
return content.slice(end + 4).trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function tomlString(value) {
|
|
94
|
+
return JSON.stringify(value || '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function tomlLiteral(value) {
|
|
98
|
+
return `'''\n${(value || '').replace(/'''/g, "'''\\'''")}\n'''`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeCodexAgentToml(srcFile, agentsDest) {
|
|
102
|
+
const content = fs.readFileSync(srcFile, 'utf8');
|
|
103
|
+
const frontmatter = parseAgentFrontmatter(content);
|
|
104
|
+
const name = frontmatter.name || path.basename(srcFile, '.md');
|
|
105
|
+
const description = frontmatter.description || `Godpowers specialist agent: ${name}.`;
|
|
106
|
+
const instructions = stripFrontmatter(content);
|
|
107
|
+
const toml = [
|
|
108
|
+
`name = ${tomlString(name)}`,
|
|
109
|
+
`description = ${tomlString(description)}`,
|
|
110
|
+
'sandbox_mode = "workspace-write"',
|
|
111
|
+
`developer_instructions = ${tomlLiteral(instructions)}`,
|
|
112
|
+
''
|
|
113
|
+
].join('\n');
|
|
114
|
+
|
|
115
|
+
fs.writeFileSync(path.join(agentsDest, `${name}.toml`), toml);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function installAgentFile(srcFile, agentsDest, runtime) {
|
|
119
|
+
fs.copyFileSync(srcFile, path.join(agentsDest, path.basename(srcFile)));
|
|
120
|
+
if (runtime.agentMetadata === 'toml') {
|
|
121
|
+
writeCodexAgentToml(srcFile, agentsDest);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function removeSkillEntry(skillsDir, entry) {
|
|
126
|
+
const entryPath = path.join(skillsDir, entry.name);
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
const skillFile = path.join(entryPath, 'SKILL.md');
|
|
129
|
+
if (entry.name.startsWith('god-') || entry.name === 'god' || entry.name === 'godpowers') {
|
|
130
|
+
if (fs.existsSync(skillFile)) {
|
|
131
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (entry.name.startsWith('god-') || entry.name === 'god.md' || entry.name === 'godpowers.md') {
|
|
138
|
+
fs.unlinkSync(entryPath);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function installForRuntime(runtimeKey, srcDir, opts = {}) {
|
|
145
|
+
const runtime = resolveRuntime(runtimeKey, opts);
|
|
146
|
+
if (!runtime) {
|
|
147
|
+
error(`Unknown runtime: ${runtimeKey}`);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
log(`\n Installing for \x1b[36m${runtime.name}\x1b[0m to \x1b[36m${runtime.configDir}\x1b[0m\n`);
|
|
152
|
+
ensureDir(runtime.configDir);
|
|
153
|
+
|
|
154
|
+
installSkills(srcDir, runtimeKey, runtime);
|
|
155
|
+
installAgents(srcDir, runtime);
|
|
156
|
+
installMasterSkill(srcDir, runtimeKey, runtime);
|
|
157
|
+
installDataDirs(srcDir, runtime);
|
|
158
|
+
installHooks(srcDir, runtimeKey, runtime);
|
|
159
|
+
|
|
160
|
+
fs.writeFileSync(path.join(runtime.configDir, 'GODPOWERS_VERSION'), VERSION);
|
|
161
|
+
success(`Wrote GODPOWERS_VERSION (${VERSION})`);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function installSkills(srcDir, runtimeKey, runtime) {
|
|
166
|
+
const skillsSrc = path.join(srcDir, 'skills');
|
|
167
|
+
const skillsDest = path.join(runtime.configDir, runtime.skillsDir);
|
|
168
|
+
if (!fs.existsSync(skillsSrc)) return;
|
|
169
|
+
|
|
170
|
+
ensureDir(skillsDest);
|
|
171
|
+
let count = 0;
|
|
172
|
+
for (const file of fs.readdirSync(skillsSrc)) {
|
|
173
|
+
if (file.endsWith('.md')) {
|
|
174
|
+
installSkillFile(path.join(skillsSrc, file), skillsDest, runtimeKey);
|
|
175
|
+
count++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const shape = runtimeKey === 'codex' ? 'Codex skill directories' : 'skills/';
|
|
179
|
+
success(`Installed ${count} slash commands to ${shape}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function installAgents(srcDir, runtime) {
|
|
183
|
+
const agentsSrc = path.join(srcDir, 'agents');
|
|
184
|
+
const agentsDest = path.join(runtime.configDir, 'agents');
|
|
185
|
+
if (!fs.existsSync(agentsSrc)) return;
|
|
186
|
+
|
|
187
|
+
ensureDir(agentsDest);
|
|
188
|
+
let count = 0;
|
|
189
|
+
for (const file of fs.readdirSync(agentsSrc)) {
|
|
190
|
+
if (/^god-.*\.md$/.test(file)) {
|
|
191
|
+
installAgentFile(path.join(agentsSrc, file), agentsDest, runtime);
|
|
192
|
+
count++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const shape = runtime.agentMetadata === 'toml' ? 'agents/ with Codex metadata' : 'agents/';
|
|
196
|
+
success(`Installed ${count} specialist agents to ${shape}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function installMasterSkill(srcDir, runtimeKey, runtime) {
|
|
200
|
+
const masterSkill = path.join(srcDir, 'SKILL.md');
|
|
201
|
+
if (!fs.existsSync(masterSkill)) return;
|
|
202
|
+
const skillsDest = path.join(runtime.configDir, runtime.skillsDir);
|
|
203
|
+
installSkillFile(masterSkill, skillsDest, runtimeKey, 'godpowers');
|
|
204
|
+
success('Installed master SKILL.md as godpowers');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function installDataDirs(srcDir, runtime) {
|
|
208
|
+
const dataDirs = [
|
|
209
|
+
['templates', 'godpowers-templates', 'Installed templates/'],
|
|
210
|
+
['references', 'godpowers-references', 'Installed references/'],
|
|
211
|
+
['workflows', 'godpowers-workflows', 'Installed workflows/'],
|
|
212
|
+
['schema', 'godpowers-schema', 'Installed schema/'],
|
|
213
|
+
['routing', 'godpowers-routing', 'Installed routing/']
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
for (const [sourceName, destName, message] of dataDirs) {
|
|
217
|
+
const src = path.join(srcDir, sourceName);
|
|
218
|
+
if (fs.existsSync(src)) {
|
|
219
|
+
const dest = path.join(runtime.configDir, destName);
|
|
220
|
+
// Clean replace. These destinations are entirely Godpowers-owned (the
|
|
221
|
+
// uninstaller removes each one wholesale), so clearing first guarantees
|
|
222
|
+
// a version upgrade never leaves behind files that no longer ship.
|
|
223
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
224
|
+
copyRecursive(src, dest);
|
|
225
|
+
success(message);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const bundleDest = path.join(runtime.configDir, 'godpowers-runtime');
|
|
230
|
+
fs.rmSync(bundleDest, { recursive: true, force: true });
|
|
231
|
+
copyRuntimeBundle(srcDir, bundleDest);
|
|
232
|
+
success('Installed runtime bundle/');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function installHooks(srcDir, runtimeKey, runtime) {
|
|
236
|
+
if (runtimeKey !== 'claude') return;
|
|
237
|
+
const hooksSrc = path.join(srcDir, 'hooks');
|
|
238
|
+
const hooksDest = path.join(runtime.configDir, 'hooks');
|
|
239
|
+
if (!fs.existsSync(hooksSrc)) return;
|
|
240
|
+
|
|
241
|
+
ensureDir(hooksDest);
|
|
242
|
+
for (const file of fs.readdirSync(hooksSrc)) {
|
|
243
|
+
const src = path.join(hooksSrc, file);
|
|
244
|
+
const dest = path.join(hooksDest, file);
|
|
245
|
+
fs.copyFileSync(src, dest);
|
|
246
|
+
try {
|
|
247
|
+
fs.chmodSync(dest, 0o755);
|
|
248
|
+
} catch (_) {
|
|
249
|
+
// Preserve install success when chmod is unavailable on the host.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
success('Installed hooks/');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function uninstallForRuntime(runtimeKey, opts = {}) {
|
|
256
|
+
const runtime = resolveRuntime(runtimeKey, opts);
|
|
257
|
+
if (!runtime) {
|
|
258
|
+
error(`Unknown runtime: ${runtimeKey}`);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
log(`\n Uninstalling from \x1b[36m${runtime.name}\x1b[0m at \x1b[36m${runtime.configDir}\x1b[0m\n`);
|
|
263
|
+
uninstallSkills(runtime);
|
|
264
|
+
uninstallAgents(runtime);
|
|
265
|
+
uninstallDataDirs(runtime);
|
|
266
|
+
uninstallHooks(runtimeKey, runtime);
|
|
267
|
+
|
|
268
|
+
const versionFile = path.join(runtime.configDir, 'GODPOWERS_VERSION');
|
|
269
|
+
if (fs.existsSync(versionFile)) {
|
|
270
|
+
fs.unlinkSync(versionFile);
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function uninstallSkills(runtime) {
|
|
276
|
+
const skillsDir = path.join(runtime.configDir, runtime.skillsDir);
|
|
277
|
+
let removed = 0;
|
|
278
|
+
if (!fs.existsSync(skillsDir)) return;
|
|
279
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
280
|
+
if (removeSkillEntry(skillsDir, entry)) {
|
|
281
|
+
removed++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
success(`Removed ${removed} god-* skill(s)`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function uninstallAgents(runtime) {
|
|
288
|
+
const agentsDir = path.join(runtime.configDir, 'agents');
|
|
289
|
+
let removed = 0;
|
|
290
|
+
if (!fs.existsSync(agentsDir)) return;
|
|
291
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
292
|
+
if (file.startsWith('god-')) {
|
|
293
|
+
fs.unlinkSync(path.join(agentsDir, file));
|
|
294
|
+
removed++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
success(`Removed ${removed} god-* agent(s)`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function uninstallDataDirs(runtime) {
|
|
301
|
+
for (const dir of [
|
|
302
|
+
'godpowers-templates',
|
|
303
|
+
'godpowers-references',
|
|
304
|
+
'godpowers-workflows',
|
|
305
|
+
'godpowers-schema',
|
|
306
|
+
'godpowers-routing',
|
|
307
|
+
'godpowers-runtime'
|
|
308
|
+
]) {
|
|
309
|
+
const full = path.join(runtime.configDir, dir);
|
|
310
|
+
if (fs.existsSync(full)) {
|
|
311
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
312
|
+
success(`Removed ${dir}/`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function uninstallHooks(runtimeKey, runtime) {
|
|
318
|
+
if (runtimeKey !== 'claude') return;
|
|
319
|
+
const hooksDir = path.join(runtime.configDir, 'hooks');
|
|
320
|
+
for (const hook of ['session-start.sh', 'pre-tool-use.sh']) {
|
|
321
|
+
const hookPath = path.join(hooksDir, hook);
|
|
322
|
+
if (fs.existsSync(hookPath)) {
|
|
323
|
+
fs.unlinkSync(hookPath);
|
|
324
|
+
success(`Removed hooks/${hook}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function countInstalledSurface(srcDir) {
|
|
330
|
+
return {
|
|
331
|
+
skills: fs.readdirSync(path.join(srcDir, 'skills')).filter(f => f.endsWith('.md')).length,
|
|
332
|
+
agents: fs.readdirSync(path.join(srcDir, 'agents')).filter(f => /^god-.*\.md$/.test(f)).length
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = {
|
|
337
|
+
installForRuntime,
|
|
338
|
+
uninstallForRuntime,
|
|
339
|
+
countInstalledSurface,
|
|
340
|
+
installSkillFile,
|
|
341
|
+
parseAgentFrontmatter,
|
|
342
|
+
stripFrontmatter,
|
|
343
|
+
writeCodexAgentToml,
|
|
344
|
+
removeSkillEntry
|
|
345
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File helpers shared by the installer and installer tests.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
function ensureDir(dir) {
|
|
9
|
+
if (!fs.existsSync(dir)) {
|
|
10
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function replaceExisting(dest) {
|
|
15
|
+
try {
|
|
16
|
+
const stat = fs.lstatSync(dest);
|
|
17
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
18
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
19
|
+
} else {
|
|
20
|
+
fs.unlinkSync(dest);
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if (e.code !== 'ENOENT') throw e;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function copyRecursive(src, dest, root) {
|
|
28
|
+
// `root` bounds where a reproduced symlink is allowed to point. It defaults
|
|
29
|
+
// to the top-level source on the first call and is threaded through recursion.
|
|
30
|
+
if (root === undefined) root = path.resolve(src);
|
|
31
|
+
const rootResolved = path.resolve(root);
|
|
32
|
+
const stat = fs.lstatSync(src);
|
|
33
|
+
|
|
34
|
+
if (stat.isSymbolicLink()) {
|
|
35
|
+
const linkTarget = fs.readlinkSync(src);
|
|
36
|
+
const resolvedTarget = path.resolve(path.dirname(src), linkTarget);
|
|
37
|
+
// Only reproduce symlinks that stay within the source tree. A symlink
|
|
38
|
+
// pointing outside it (absolute path or `../` escape) would otherwise be
|
|
39
|
+
// planted into the user's runtime config pointing anywhere on disk, so we
|
|
40
|
+
// skip it rather than copy it verbatim.
|
|
41
|
+
const inRoot = resolvedTarget === rootResolved ||
|
|
42
|
+
resolvedTarget.startsWith(rootResolved + path.sep);
|
|
43
|
+
if (!inRoot) return;
|
|
44
|
+
ensureDir(path.dirname(dest));
|
|
45
|
+
replaceExisting(dest);
|
|
46
|
+
fs.symlinkSync(linkTarget, dest);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (stat.isDirectory()) {
|
|
51
|
+
ensureDir(dest);
|
|
52
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
copyRecursive(path.join(src, entry.name), path.join(dest, entry.name), root);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (stat.isFile()) {
|
|
60
|
+
ensureDir(path.dirname(dest));
|
|
61
|
+
fs.copyFileSync(src, dest);
|
|
62
|
+
fs.chmodSync(dest, stat.mode);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function copyRuntimeBundle(srcDir, destDir) {
|
|
67
|
+
ensureDir(destDir);
|
|
68
|
+
for (const dir of ['lib', 'routing', 'workflows', 'schema', 'templates', 'references']) {
|
|
69
|
+
const src = path.join(srcDir, dir);
|
|
70
|
+
if (fs.existsSync(src)) {
|
|
71
|
+
copyRecursive(src, path.join(destDir, dir));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const packageJson = path.join(srcDir, 'package.json');
|
|
75
|
+
if (fs.existsSync(packageJson)) {
|
|
76
|
+
fs.copyFileSync(packageJson, path.join(destDir, 'package.json'));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { ensureDir, copyRecursive, copyRuntimeBundle };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
const RUNTIMES = {
|
|
5
|
+
claude: {
|
|
6
|
+
name: 'Claude Code',
|
|
7
|
+
configDir: path.join(os.homedir(), '.claude'),
|
|
8
|
+
skillsDir: 'skills',
|
|
9
|
+
configFile: null,
|
|
10
|
+
},
|
|
11
|
+
codex: {
|
|
12
|
+
name: 'Codex',
|
|
13
|
+
configDir: path.join(os.homedir(), '.codex'),
|
|
14
|
+
skillsDir: 'skills',
|
|
15
|
+
configFile: 'config.toml',
|
|
16
|
+
agentMetadata: 'toml',
|
|
17
|
+
},
|
|
18
|
+
cursor: {
|
|
19
|
+
name: 'Cursor',
|
|
20
|
+
configDir: path.join(os.homedir(), '.cursor'),
|
|
21
|
+
skillsDir: 'rules',
|
|
22
|
+
configFile: null,
|
|
23
|
+
},
|
|
24
|
+
windsurf: {
|
|
25
|
+
name: 'Windsurf',
|
|
26
|
+
configDir: path.join(os.homedir(), '.windsurf'),
|
|
27
|
+
skillsDir: 'rules',
|
|
28
|
+
configFile: null,
|
|
29
|
+
},
|
|
30
|
+
opencode: {
|
|
31
|
+
name: 'OpenCode',
|
|
32
|
+
configDir: path.join(os.homedir(), '.opencode'),
|
|
33
|
+
skillsDir: 'skills',
|
|
34
|
+
configFile: null,
|
|
35
|
+
},
|
|
36
|
+
gemini: {
|
|
37
|
+
name: 'Gemini CLI',
|
|
38
|
+
configDir: path.join(os.homedir(), '.gemini'),
|
|
39
|
+
skillsDir: 'skills',
|
|
40
|
+
configFile: null,
|
|
41
|
+
},
|
|
42
|
+
copilot: {
|
|
43
|
+
name: 'GitHub Copilot',
|
|
44
|
+
configDir: path.join(os.homedir(), '.copilot'),
|
|
45
|
+
skillsDir: 'skills',
|
|
46
|
+
configFile: null,
|
|
47
|
+
},
|
|
48
|
+
augment: {
|
|
49
|
+
name: 'Augment',
|
|
50
|
+
configDir: path.join(os.homedir(), '.augment'),
|
|
51
|
+
skillsDir: 'skills',
|
|
52
|
+
configFile: null,
|
|
53
|
+
},
|
|
54
|
+
trae: {
|
|
55
|
+
name: 'Trae',
|
|
56
|
+
configDir: path.join(os.homedir(), '.trae'),
|
|
57
|
+
skillsDir: 'skills',
|
|
58
|
+
configFile: null,
|
|
59
|
+
},
|
|
60
|
+
cline: {
|
|
61
|
+
name: 'Cline',
|
|
62
|
+
configDir: path.join(os.homedir(), '.cline'),
|
|
63
|
+
skillsDir: 'skills',
|
|
64
|
+
configFile: null,
|
|
65
|
+
},
|
|
66
|
+
kilo: {
|
|
67
|
+
name: 'Kilo',
|
|
68
|
+
configDir: path.join(os.homedir(), '.kilo'),
|
|
69
|
+
skillsDir: 'skills',
|
|
70
|
+
configFile: null,
|
|
71
|
+
},
|
|
72
|
+
antigravity: {
|
|
73
|
+
name: 'Antigravity',
|
|
74
|
+
configDir: path.join(os.homedir(), '.antigravity'),
|
|
75
|
+
skillsDir: 'skills',
|
|
76
|
+
configFile: null,
|
|
77
|
+
},
|
|
78
|
+
qwen: {
|
|
79
|
+
name: 'Qwen Code',
|
|
80
|
+
configDir: path.join(os.homedir(), '.qwen'),
|
|
81
|
+
skillsDir: 'skills',
|
|
82
|
+
configFile: null,
|
|
83
|
+
},
|
|
84
|
+
codebuddy: {
|
|
85
|
+
name: 'CodeBuddy',
|
|
86
|
+
configDir: path.join(os.homedir(), '.codebuddy'),
|
|
87
|
+
skillsDir: 'skills',
|
|
88
|
+
configFile: null,
|
|
89
|
+
},
|
|
90
|
+
pi: {
|
|
91
|
+
name: 'Pi',
|
|
92
|
+
configDir: path.join(os.homedir(), '.pi'),
|
|
93
|
+
skillsDir: 'skills',
|
|
94
|
+
configFile: null,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function resolveRuntime(runtimeKey, opts = {}) {
|
|
99
|
+
const runtime = RUNTIMES[runtimeKey];
|
|
100
|
+
if (!runtime) return null;
|
|
101
|
+
const resolved = { ...runtime };
|
|
102
|
+
if (opts.local && !opts.global) {
|
|
103
|
+
resolved.configDir = path.join(process.cwd(), path.basename(runtime.configDir));
|
|
104
|
+
}
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
RUNTIMES,
|
|
110
|
+
resolveRuntime,
|
|
111
|
+
runtimeKeys: () => Object.keys(RUNTIMES)
|
|
112
|
+
};
|