code-abyss 2.0.6 → 2.0.8
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 +129 -58
- package/bin/adapters/claude.js +16 -12
- package/bin/adapters/codex.js +110 -37
- package/bin/adapters/gemini.js +92 -0
- package/bin/install.js +521 -130
- package/bin/lib/ccline.js +18 -8
- package/bin/lib/gstack-claude.js +164 -0
- package/bin/lib/gstack-codex.js +347 -0
- package/bin/lib/gstack-gemini.js +140 -0
- package/bin/lib/pack-bootstrap.js +92 -0
- package/bin/lib/pack-docs.js +61 -0
- package/bin/lib/pack-registry.js +400 -0
- package/bin/lib/pack-reports.js +87 -0
- package/bin/lib/pack-vendor.js +82 -0
- package/bin/lib/style-registry.js +29 -7
- package/bin/lib/target-registry.js +74 -0
- package/bin/lib/utils.js +69 -6
- package/bin/lib/vendor-providers/archive.js +56 -0
- package/bin/lib/vendor-providers/git.js +59 -0
- package/bin/lib/vendor-providers/index.js +91 -0
- package/bin/lib/vendor-providers/local-dir.js +55 -0
- package/bin/lib/vendor-providers/shared.js +86 -0
- package/bin/packs.js +581 -0
- package/config/AGENTS.md +0 -2
- package/config/CLAUDE.md +43 -326
- package/config/codex-config.example.toml +26 -20
- package/config/gemini-settings.example.json +6 -0
- package/config/instruction.md +87 -0
- package/output-styles/abyss-command.md +7 -30
- package/output-styles/abyss-concise.md +11 -52
- package/output-styles/abyss-cultivator.md +20 -249
- package/output-styles/abyss-ritual.md +11 -45
- package/output-styles/index.json +20 -4
- package/package.json +26 -14
- package/packs/abyss/manifest.json +25 -0
- package/packs/gstack/manifest.json +151 -0
- package/skills/SKILL.md +33 -237
- package/skills/domains/frontend-design/agents/openai.yaml +1 -1
- package/skills/run_skill.js +25 -3
- package/skills/tools/gen-docs/agents/openai.yaml +1 -1
- package/skills/tools/verify-change/agents/openai.yaml +1 -1
- package/skills/tools/verify-module/agents/openai.yaml +1 -1
- package/skills/tools/verify-quality/agents/openai.yaml +1 -1
- package/skills/tools/verify-security/agents/openai.yaml +1 -1
- package/skills/tools/override-refusal/SKILL.md +0 -81
- package/skills/tools/override-refusal/agents/openai.yaml +0 -4
- package/skills/tools/override-refusal/scripts/refusal_rewriter.js +0 -340
package/bin/install.js
CHANGED
|
@@ -15,18 +15,36 @@ if (parseInt(process.versions.node) < parseInt(MIN_NODE)) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
const PKG_ROOT = fs.realpathSync(path.join(__dirname, '..'));
|
|
18
|
-
const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog } =
|
|
18
|
+
const { shouldSkip, copyRecursive, rmSafe, deepMergeNew, printMergeLog, formatActionableError } =
|
|
19
19
|
require(path.join(__dirname, 'lib', 'utils.js'));
|
|
20
20
|
const {
|
|
21
21
|
collectInvocableSkills,
|
|
22
22
|
} = require(path.join(__dirname, 'lib', 'skill-registry.js'));
|
|
23
|
+
const {
|
|
24
|
+
resolveProjectPacks,
|
|
25
|
+
selectProjectPacksForInstall,
|
|
26
|
+
readProjectPackLock,
|
|
27
|
+
} = require(path.join(__dirname, 'lib', 'pack-registry.js'));
|
|
28
|
+
const { syncProjectBootstrapArtifacts } = require(path.join(__dirname, 'lib', 'pack-bootstrap.js'));
|
|
29
|
+
const { writeReportArtifact } = require(path.join(__dirname, 'lib', 'pack-reports.js'));
|
|
30
|
+
const {
|
|
31
|
+
listInstallTargets,
|
|
32
|
+
listTargetNames,
|
|
33
|
+
isSupportedTarget,
|
|
34
|
+
getManagedRootRelativeDir,
|
|
35
|
+
formatTargetList,
|
|
36
|
+
} = require(path.join(__dirname, 'lib', 'target-registry.js'));
|
|
23
37
|
const {
|
|
24
38
|
listStyles,
|
|
25
39
|
getDefaultStyle,
|
|
26
40
|
resolveStyle,
|
|
27
|
-
|
|
41
|
+
renderGeminiContext,
|
|
28
42
|
} = require(path.join(__dirname, 'lib', 'style-registry.js'));
|
|
29
43
|
const { detectCclineBin, installCcline: _installCcline } = require(path.join(__dirname, 'lib', 'ccline.js'));
|
|
44
|
+
const { installGstackClaudePack } = require(path.join(__dirname, 'lib', 'gstack-claude.js'));
|
|
45
|
+
const { installGstackGeminiPack } = require(path.join(__dirname, 'lib', 'gstack-gemini.js'));
|
|
46
|
+
|
|
47
|
+
const { installGstackCodexPack } = require(path.join(__dirname, 'lib', 'gstack-codex.js'));
|
|
30
48
|
const {
|
|
31
49
|
detectCodexAuth: detectCodexAuthImpl,
|
|
32
50
|
getCodexCoreFiles,
|
|
@@ -38,6 +56,12 @@ const {
|
|
|
38
56
|
detectClaudeAuth: detectClaudeAuthImpl,
|
|
39
57
|
postClaude: postClaudeFlow,
|
|
40
58
|
} = require(path.join(__dirname, 'adapters', 'claude.js'));
|
|
59
|
+
const {
|
|
60
|
+
GEMINI_SETTINGS_TEMPLATE,
|
|
61
|
+
getGeminiCoreFiles,
|
|
62
|
+
detectGeminiAuth: detectGeminiAuthImpl,
|
|
63
|
+
postGemini: postGeminiFlow,
|
|
64
|
+
} = require(path.join(__dirname, 'adapters', 'gemini.js'));
|
|
41
65
|
|
|
42
66
|
// ── ANSI ──
|
|
43
67
|
|
|
@@ -93,7 +117,41 @@ function detectCodexAuth() {
|
|
|
93
117
|
return detectCodexAuthImpl({ HOME, warn });
|
|
94
118
|
}
|
|
95
119
|
|
|
96
|
-
|
|
120
|
+
function detectGeminiAuth(settings) {
|
|
121
|
+
return detectGeminiAuthImpl({ settings, HOME, warn });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveManagedRootDir(tgt, rootName = tgt) {
|
|
125
|
+
return path.join(HOME, getManagedRootRelativeDir(rootName));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizeManifestEntry(entry, defaultRoot) {
|
|
129
|
+
if (typeof entry === 'string') return { root: defaultRoot, path: entry };
|
|
130
|
+
if (entry && typeof entry === 'object' && typeof entry.path === 'string') {
|
|
131
|
+
return { root: entry.root || defaultRoot, path: entry.path };
|
|
132
|
+
}
|
|
133
|
+
throw new Error('manifest 条目格式无效');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pushManifestEntry(list, rootName, relPath) {
|
|
137
|
+
list.push({ root: rootName, path: relPath });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function pushPackReport(manifest, report) {
|
|
141
|
+
if (!manifest.pack_reports) manifest.pack_reports = [];
|
|
142
|
+
manifest.pack_reports.push(report);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveEffectivePackSource(sourceMode, installResult) {
|
|
146
|
+
return (installResult && installResult.sourceMode) || sourceMode || 'pinned';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function manifestLabel(entry, defaultRoot) {
|
|
150
|
+
const normalized = normalizeManifestEntry(entry, defaultRoot);
|
|
151
|
+
return normalized.root === defaultRoot
|
|
152
|
+
? normalized.path
|
|
153
|
+
: `${normalized.root}/${normalized.path}`;
|
|
154
|
+
}
|
|
97
155
|
|
|
98
156
|
// ── CLI 参数 ──
|
|
99
157
|
|
|
@@ -115,8 +173,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
115
173
|
console.log(`${c.b('用法:')} npx code-abyss [选项]
|
|
116
174
|
|
|
117
175
|
${c.b('选项:')}
|
|
118
|
-
--target ${c.cyn('
|
|
119
|
-
--uninstall ${c.cyn('
|
|
176
|
+
--target ${c.cyn(`<${formatTargetList('|')}>`)} 安装目标
|
|
177
|
+
--uninstall ${c.cyn(`<${formatTargetList('|')}>`)} 卸载目标
|
|
120
178
|
--style ${c.cyn('<slug>')} 指定输出风格
|
|
121
179
|
--list-styles 列出可用输出风格
|
|
122
180
|
--yes, -y 全自动模式
|
|
@@ -137,27 +195,51 @@ ${c.b('示例:')}
|
|
|
137
195
|
// ── 卸载 ──
|
|
138
196
|
|
|
139
197
|
function runUninstall(tgt) {
|
|
140
|
-
if (!
|
|
141
|
-
|
|
198
|
+
if (!isSupportedTarget(tgt)) {
|
|
199
|
+
fail(formatActionableError(`--uninstall 必须是 ${listTargetNames().join('、')}`, 'Try: npx code-abyss --uninstall claude'));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const targetDir = resolveManagedRootDir(tgt);
|
|
142
203
|
const backupDir = path.join(targetDir, '.sage-backup');
|
|
143
204
|
const manifestPath = path.join(backupDir, 'manifest.json');
|
|
144
205
|
if (!fs.existsSync(manifestPath)) { fail(`未找到安装记录: ${manifestPath}`); process.exit(1); }
|
|
145
206
|
|
|
146
207
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
147
|
-
if (manifest.manifest_version && manifest.manifest_version >
|
|
208
|
+
if (manifest.manifest_version && manifest.manifest_version > 2) {
|
|
148
209
|
fail(`manifest 版本 ${manifest.manifest_version} 不兼容,请升级 code-abyss 后再卸载`);
|
|
149
210
|
process.exit(1);
|
|
150
211
|
}
|
|
151
212
|
divider(`卸载 Code Abyss v${manifest.version}`);
|
|
213
|
+
if (Array.isArray(manifest.pack_reports) && manifest.pack_reports.length > 0) {
|
|
214
|
+
console.log(` ${c.b('Packs:')}`);
|
|
215
|
+
manifest.pack_reports.forEach((report) => {
|
|
216
|
+
const source = report.source ? ` source=${report.source}` : '';
|
|
217
|
+
const reason = report.reason ? ` reason=${report.reason}` : '';
|
|
218
|
+
console.log(` ${report.pack}@${report.host} ${report.status || 'installed'}${source}${reason}`);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
152
221
|
|
|
153
|
-
(manifest.installed || []).forEach(
|
|
154
|
-
const
|
|
155
|
-
|
|
222
|
+
(manifest.installed || []).forEach((entry) => {
|
|
223
|
+
const normalized = normalizeManifestEntry(entry, tgt);
|
|
224
|
+
const installRoot = resolveManagedRootDir(tgt, normalized.root);
|
|
225
|
+
const targetPath = path.join(installRoot, normalized.path);
|
|
226
|
+
if (fs.existsSync(targetPath)) {
|
|
227
|
+
rmSafe(targetPath);
|
|
228
|
+
console.log(` ${c.red('✘')} ${manifestLabel(entry, tgt)}`);
|
|
229
|
+
}
|
|
156
230
|
});
|
|
157
|
-
(manifest.backups || []).forEach(
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
231
|
+
(manifest.backups || []).forEach((entry) => {
|
|
232
|
+
const normalized = normalizeManifestEntry(entry, tgt);
|
|
233
|
+
const backupPath = path.join(backupDir, normalized.root, normalized.path);
|
|
234
|
+
const legacyBackupPath = path.join(backupDir, normalized.path);
|
|
235
|
+
const sourcePath = fs.existsSync(backupPath) ? backupPath : legacyBackupPath;
|
|
236
|
+
const restoreRoot = resolveManagedRootDir(tgt, normalized.root);
|
|
237
|
+
const restorePath = path.join(restoreRoot, normalized.path);
|
|
238
|
+
if (fs.existsSync(sourcePath)) {
|
|
239
|
+
fs.mkdirSync(path.dirname(restorePath), { recursive: true });
|
|
240
|
+
fs.renameSync(sourcePath, restorePath);
|
|
241
|
+
ok(`恢复: ${manifestLabel(entry, tgt)}`);
|
|
242
|
+
}
|
|
161
243
|
});
|
|
162
244
|
|
|
163
245
|
rmSafe(backupDir);
|
|
@@ -173,24 +255,17 @@ function scanInvocableSkills(skillsDir) {
|
|
|
173
255
|
return collectInvocableSkills(skillsDir);
|
|
174
256
|
}
|
|
175
257
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
skillRoot: '~/.claude/skills',
|
|
181
|
-
},
|
|
182
|
-
codex: {
|
|
183
|
-
dir: 'prompts',
|
|
184
|
-
label: 'custom prompts',
|
|
185
|
-
skillRoot: '~/.codex/skills',
|
|
186
|
-
},
|
|
258
|
+
const CLAUDE_COMMAND_TARGET = {
|
|
259
|
+
dir: 'commands',
|
|
260
|
+
label: '斜杠命令',
|
|
261
|
+
skillRoot: '~/.claude/skills',
|
|
187
262
|
};
|
|
188
263
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
264
|
+
const GEMINI_COMMAND_TARGET = {
|
|
265
|
+
dir: 'commands',
|
|
266
|
+
label: 'Gemini commands',
|
|
267
|
+
skillRoot: '~/.gemini/skills',
|
|
268
|
+
};
|
|
194
269
|
|
|
195
270
|
function getSkillPath(skillRoot, skillRelPath) {
|
|
196
271
|
return skillRelPath
|
|
@@ -212,23 +287,20 @@ function buildCommandFrontmatter(skill) {
|
|
|
212
287
|
return lines;
|
|
213
288
|
}
|
|
214
289
|
|
|
215
|
-
function
|
|
216
|
-
const targetCfg = getInvocableTarget(targetName);
|
|
290
|
+
function buildClaudeCommandSpec(skill) {
|
|
217
291
|
const runtimeType = skill.runtimeType || 'knowledge';
|
|
218
292
|
const allowedTools = Array.isArray(skill.allowedTools)
|
|
219
293
|
? skill.allowedTools.join(', ')
|
|
220
294
|
: (skill.allowedTools || 'Read');
|
|
221
295
|
return {
|
|
222
|
-
targetName,
|
|
223
|
-
targetCfg,
|
|
224
296
|
name: skill.name,
|
|
225
297
|
description: skill.description,
|
|
226
298
|
argumentHint: skill.argumentHint || '',
|
|
227
299
|
allowedTools,
|
|
228
300
|
relPath: skill.relPath,
|
|
229
301
|
runtimeType,
|
|
230
|
-
scriptRunner: `node ${
|
|
231
|
-
skillPath: getSkillPath(
|
|
302
|
+
scriptRunner: `node ${CLAUDE_COMMAND_TARGET.skillRoot}/run_skill.js ${skill.name} $ARGUMENTS`,
|
|
303
|
+
skillPath: getSkillPath(CLAUDE_COMMAND_TARGET.skillRoot, skill.relPath),
|
|
232
304
|
};
|
|
233
305
|
}
|
|
234
306
|
|
|
@@ -248,31 +320,6 @@ function buildClaudeBody(spec) {
|
|
|
248
320
|
return lines;
|
|
249
321
|
}
|
|
250
322
|
|
|
251
|
-
function buildCodexPromptBody(spec) {
|
|
252
|
-
const lines = [];
|
|
253
|
-
if (spec.argumentHint) lines.push(`Arguments: ${spec.argumentHint}`, '');
|
|
254
|
-
lines.push(`Read \`${spec.skillPath}\` before acting.`, '');
|
|
255
|
-
if (spec.runtimeType === 'scripted') {
|
|
256
|
-
lines.push(`Then run \`${spec.scriptRunner}\`.`);
|
|
257
|
-
lines.push('Do not stop between steps unless blocked by permissions or missing required inputs.');
|
|
258
|
-
lines.push('Use the skill guidance plus script output to complete the task end-to-end.');
|
|
259
|
-
return lines;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
lines.push('Use that skill as the authoritative playbook for the task.');
|
|
263
|
-
lines.push('Respond with concrete actions instead of generic advice.');
|
|
264
|
-
return lines;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function generateInvocableContent(skill, targetName) {
|
|
268
|
-
const spec = buildSkillArtifactSpec(skill, targetName);
|
|
269
|
-
const lines = targetName === 'claude' ? buildCommandFrontmatter(spec) : [];
|
|
270
|
-
const body = targetName === 'claude'
|
|
271
|
-
? buildClaudeBody(spec)
|
|
272
|
-
: buildCodexPromptBody(spec);
|
|
273
|
-
return [...lines, ...body, ''].join('\n');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
323
|
function normalizeGeneratedSkill(meta, skillRelPath, runtimeType) {
|
|
277
324
|
return {
|
|
278
325
|
...meta,
|
|
@@ -285,19 +332,67 @@ function normalizeGeneratedSkill(meta, skillRelPath, runtimeType) {
|
|
|
285
332
|
}
|
|
286
333
|
|
|
287
334
|
function generateCommandContent(meta, skillRelPath, runtimeType = 'knowledge') {
|
|
288
|
-
|
|
335
|
+
const skill = normalizeGeneratedSkill(meta, skillRelPath, runtimeType);
|
|
336
|
+
const spec = buildClaudeCommandSpec(skill);
|
|
337
|
+
return [...buildCommandFrontmatter(spec), ...buildClaudeBody(spec), ''].join('\n');
|
|
289
338
|
}
|
|
290
339
|
|
|
291
|
-
function
|
|
292
|
-
|
|
340
|
+
function buildGeminiCommandSpec(skill) {
|
|
341
|
+
const runtimeType = skill.runtimeType || 'knowledge';
|
|
342
|
+
return {
|
|
343
|
+
name: skill.name,
|
|
344
|
+
description: skill.description || '',
|
|
345
|
+
relPath: skill.relPath,
|
|
346
|
+
runtimeType,
|
|
347
|
+
scriptRunner: `node ${GEMINI_COMMAND_TARGET.skillRoot}/run_skill.js ${skill.name}`,
|
|
348
|
+
skillPath: getSkillPath(GEMINI_COMMAND_TARGET.skillRoot, skill.relPath),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function escapeTomlMultiline(value) {
|
|
353
|
+
return String(value || '').replace(/"""/g, '\"\"\"').trim();
|
|
293
354
|
}
|
|
294
355
|
|
|
295
|
-
function
|
|
356
|
+
function buildGeminiPromptBody(spec) {
|
|
357
|
+
const lines = [
|
|
358
|
+
`Read \`${spec.skillPath}\` before acting.`,
|
|
359
|
+
'',
|
|
360
|
+
'If Gemini CLI appended the raw command invocation after these instructions, parse any extra arguments from that appended invocation before acting.',
|
|
361
|
+
'',
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
if (spec.runtimeType === 'scripted') {
|
|
365
|
+
lines.push(`Then run \`${spec.scriptRunner} <parsed-arguments>\` and complete the task end-to-end.`);
|
|
366
|
+
lines.push('Do not stop between steps unless blocked by permissions or missing required input.');
|
|
367
|
+
} else {
|
|
368
|
+
lines.push('Use that skill as the authoritative playbook for the task.');
|
|
369
|
+
lines.push('Respond with concrete actions instead of generic advice.');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return lines.join('\n').trim();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function generateGeminiCommandContent(meta, skillRelPath, runtimeType = 'knowledge') {
|
|
376
|
+
const skill = normalizeGeneratedSkill(meta, skillRelPath, runtimeType);
|
|
377
|
+
const spec = buildGeminiCommandSpec(skill);
|
|
378
|
+
const description = escapeTomlMultiline(spec.description).replace(/"/g, '\\"');
|
|
379
|
+
const prompt = escapeTomlMultiline(buildGeminiPromptBody(spec));
|
|
380
|
+
return [
|
|
381
|
+
`description = "${description}"`,
|
|
382
|
+
'prompt = """',
|
|
383
|
+
prompt,
|
|
384
|
+
'"""',
|
|
385
|
+
'',
|
|
386
|
+
].join('\n');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest) {
|
|
296
391
|
const skills = collectInvocableSkills(skillsSrcDir);
|
|
297
392
|
if (skills.length === 0) return 0;
|
|
393
|
+
const rootName = manifest.target || 'claude';
|
|
298
394
|
|
|
299
|
-
const
|
|
300
|
-
const installDir = path.join(targetDir, targetCfg.dir);
|
|
395
|
+
const installDir = path.join(targetDir, CLAUDE_COMMAND_TARGET.dir);
|
|
301
396
|
fs.mkdirSync(installDir, { recursive: true });
|
|
302
397
|
|
|
303
398
|
let totalFiles = 0;
|
|
@@ -309,53 +404,95 @@ function installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest,
|
|
|
309
404
|
names.forEach((cmdName) => {
|
|
310
405
|
const fileName = `${cmdName}.md`;
|
|
311
406
|
const destFile = path.join(installDir, fileName);
|
|
312
|
-
const relFile = path.posix.join(
|
|
407
|
+
const relFile = path.posix.join(CLAUDE_COMMAND_TARGET.dir, fileName);
|
|
313
408
|
|
|
314
409
|
if (fs.existsSync(destFile)) {
|
|
315
|
-
const backupSubdir = path.join(backupDir,
|
|
410
|
+
const backupSubdir = path.join(backupDir, rootName, CLAUDE_COMMAND_TARGET.dir);
|
|
316
411
|
fs.mkdirSync(backupSubdir, { recursive: true });
|
|
317
412
|
fs.copyFileSync(destFile, path.join(backupSubdir, fileName));
|
|
318
|
-
manifest.backups
|
|
413
|
+
pushManifestEntry(manifest.backups, rootName, relFile);
|
|
319
414
|
info(`备份: ${c.d(relFile)}`);
|
|
320
415
|
}
|
|
321
416
|
|
|
322
|
-
const content =
|
|
417
|
+
const content = generateCommandContent(skill, skill.relPath, skill.runtimeType);
|
|
323
418
|
fs.writeFileSync(destFile, content);
|
|
324
|
-
manifest.installed
|
|
419
|
+
pushManifestEntry(manifest.installed, rootName, relFile);
|
|
325
420
|
totalFiles++;
|
|
326
421
|
});
|
|
327
422
|
});
|
|
328
423
|
|
|
329
|
-
ok(`${
|
|
424
|
+
ok(`${CLAUDE_COMMAND_TARGET.dir}/ ${c.d(`(自动生成 ${totalFiles} 个 ${CLAUDE_COMMAND_TARGET.label})`)}`);
|
|
330
425
|
return skills.length;
|
|
331
426
|
}
|
|
332
427
|
|
|
333
428
|
function installGeneratedCommands(skillsSrcDir, targetDir, backupDir, manifest) {
|
|
334
|
-
return installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest
|
|
429
|
+
return installGeneratedArtifacts(skillsSrcDir, targetDir, backupDir, manifest);
|
|
335
430
|
}
|
|
336
431
|
|
|
337
|
-
function
|
|
338
|
-
|
|
432
|
+
function installGeneratedGeminiCommands(skillsSrcDir, targetDir, backupDir, manifest) {
|
|
433
|
+
const skills = collectInvocableSkills(skillsSrcDir);
|
|
434
|
+
if (skills.length === 0) return 0;
|
|
435
|
+
|
|
436
|
+
const installDir = path.join(targetDir, GEMINI_COMMAND_TARGET.dir);
|
|
437
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
438
|
+
let totalFiles = 0;
|
|
439
|
+
|
|
440
|
+
skills.forEach((skill) => {
|
|
441
|
+
const names = [skill.name, ...(skill.aliases || [])];
|
|
442
|
+
names.forEach((cmdName) => {
|
|
443
|
+
const fileName = `${cmdName}.toml`;
|
|
444
|
+
const destFile = path.join(installDir, fileName);
|
|
445
|
+
const relFile = path.posix.join(GEMINI_COMMAND_TARGET.dir, fileName);
|
|
446
|
+
|
|
447
|
+
if (fs.existsSync(destFile)) {
|
|
448
|
+
const backupSubdir = path.join(backupDir, 'gemini', GEMINI_COMMAND_TARGET.dir);
|
|
449
|
+
fs.mkdirSync(backupSubdir, { recursive: true });
|
|
450
|
+
fs.copyFileSync(destFile, path.join(backupSubdir, fileName));
|
|
451
|
+
pushManifestEntry(manifest.backups, 'gemini', relFile);
|
|
452
|
+
info(`备份: ${c.d(relFile)}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const content = generateGeminiCommandContent(skill, skill.relPath, skill.runtimeType);
|
|
456
|
+
fs.writeFileSync(destFile, content);
|
|
457
|
+
pushManifestEntry(manifest.installed, 'gemini', relFile);
|
|
458
|
+
totalFiles++;
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
ok(`${GEMINI_COMMAND_TARGET.dir}/ ${c.d(`(自动生成 ${totalFiles} 个 ${GEMINI_COMMAND_TARGET.label})`)}`);
|
|
463
|
+
return totalFiles;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function installGeminiContext(targetDir, backupDir, manifest, selectedStyle) {
|
|
467
|
+
const relPath = 'GEMINI.md';
|
|
468
|
+
backupManagedPathIfExists('gemini', 'gemini', backupDir, relPath, manifest);
|
|
469
|
+
const destPath = path.join(targetDir, relPath);
|
|
470
|
+
const content = renderGeminiContext(PKG_ROOT, selectedStyle.slug);
|
|
471
|
+
fs.writeFileSync(destPath, content);
|
|
472
|
+
pushManifestEntry(manifest.installed, 'gemini', relPath);
|
|
473
|
+
ok(`${relPath} ${c.d(`(动态生成: ${selectedStyle.slug})`)}`);
|
|
339
474
|
}
|
|
340
475
|
|
|
341
|
-
|
|
342
|
-
|
|
476
|
+
|
|
477
|
+
function backupManagedPathIfExists(tgt, rootName, backupDir, relPath, manifest) {
|
|
478
|
+
const targetRoot = resolveManagedRootDir(tgt, rootName);
|
|
479
|
+
const targetPath = path.join(targetRoot, relPath);
|
|
343
480
|
if (!fs.existsSync(targetPath)) return false;
|
|
344
481
|
|
|
345
|
-
const backupPath = path.join(backupDir, relPath);
|
|
482
|
+
const backupPath = path.join(backupDir, rootName, relPath);
|
|
346
483
|
rmSafe(backupPath);
|
|
347
484
|
copyRecursive(targetPath, backupPath);
|
|
348
|
-
manifest.backups
|
|
349
|
-
info(`备份: ${c.d(relPath)}`);
|
|
485
|
+
pushManifestEntry(manifest.backups, rootName, relPath);
|
|
486
|
+
info(`备份: ${c.d(rootName === tgt ? relPath : `${rootName}/${relPath}`)}`);
|
|
350
487
|
return true;
|
|
351
488
|
}
|
|
352
489
|
|
|
353
|
-
function pruneLegacyCodexSettings(
|
|
490
|
+
function pruneLegacyCodexSettings(tgt, backupDir, manifest) {
|
|
354
491
|
const relPath = 'settings.json';
|
|
355
|
-
const settingsPath = path.join(
|
|
492
|
+
const settingsPath = path.join(resolveManagedRootDir(tgt, 'codex'), relPath);
|
|
356
493
|
if (!fs.existsSync(settingsPath)) return null;
|
|
357
494
|
|
|
358
|
-
|
|
495
|
+
backupManagedPathIfExists(tgt, 'codex', backupDir, relPath, manifest);
|
|
359
496
|
rmSafe(settingsPath);
|
|
360
497
|
warn('移除 legacy settings.json(Codex 已改用 config.toml)');
|
|
361
498
|
return settingsPath;
|
|
@@ -374,7 +511,56 @@ function printStyleCatalog() {
|
|
|
374
511
|
console.log('');
|
|
375
512
|
}
|
|
376
513
|
|
|
514
|
+
async function resolveProjectPackPlan(targetName) {
|
|
515
|
+
const projectPacks = resolveProjectPacks(process.cwd(), targetName);
|
|
516
|
+
if (!projectPacks.path) {
|
|
517
|
+
return {
|
|
518
|
+
...projectPacks,
|
|
519
|
+
selected: [],
|
|
520
|
+
optionalSelected: [],
|
|
521
|
+
sources: {},
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
let confirmOptional = null;
|
|
526
|
+
if (projectPacks.optionalPolicy === 'prompt' && projectPacks.optional.length > 0 && !autoYes) {
|
|
527
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
528
|
+
confirmOptional = async (optionalPacks) => confirm({
|
|
529
|
+
message: `当前仓库声明了 optional packs: ${optionalPacks.join(', ')},是否一并安装?`,
|
|
530
|
+
default: true,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const selection = await selectProjectPacksForInstall(projectPacks, {
|
|
535
|
+
autoYes,
|
|
536
|
+
confirm: confirmOptional,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return {
|
|
540
|
+
...projectPacks,
|
|
541
|
+
...selection,
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
377
545
|
async function resolveInstallStyle(targetName) {
|
|
546
|
+
if (targetName === 'codex') {
|
|
547
|
+
if (requestedStyleSlug) {
|
|
548
|
+
warn('Codex 已改为 skills-only,忽略 --style(不再生成 ~/.codex/AGENTS.md)');
|
|
549
|
+
}
|
|
550
|
+
return getDefaultStyle(PKG_ROOT, 'claude');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (targetName === 'gemini') {
|
|
554
|
+
if (requestedStyleSlug) {
|
|
555
|
+
const requested = resolveStyle(PKG_ROOT, requestedStyleSlug, 'gemini') || resolveStyle(PKG_ROOT, requestedStyleSlug, 'claude');
|
|
556
|
+
if (!requested) {
|
|
557
|
+
throw new Error(`未知输出风格: ${requestedStyleSlug}`);
|
|
558
|
+
}
|
|
559
|
+
return requested;
|
|
560
|
+
}
|
|
561
|
+
return getDefaultStyle(PKG_ROOT, 'claude');
|
|
562
|
+
}
|
|
563
|
+
|
|
378
564
|
if (requestedStyleSlug) {
|
|
379
565
|
const style = resolveStyle(PKG_ROOT, requestedStyleSlug, targetName);
|
|
380
566
|
if (!style) {
|
|
@@ -401,36 +587,36 @@ async function resolveInstallStyle(targetName) {
|
|
|
401
587
|
return resolveStyle(PKG_ROOT, slug, targetName);
|
|
402
588
|
}
|
|
403
589
|
|
|
404
|
-
function
|
|
405
|
-
const
|
|
406
|
-
backupPathIfExists(targetDir, backupDir, relPath, manifest);
|
|
407
|
-
const destPath = path.join(targetDir, relPath);
|
|
408
|
-
const content = renderCodexAgents(PKG_ROOT, selectedStyle.slug);
|
|
409
|
-
fs.writeFileSync(destPath, content);
|
|
410
|
-
manifest.installed.push(relPath);
|
|
411
|
-
ok(`${relPath} ${c.d(`(动态生成: ${selectedStyle.slug})`)}`);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function installCore(tgt, selectedStyle) {
|
|
415
|
-
const targetDir = path.join(HOME, `.${tgt}`);
|
|
590
|
+
function installCore(tgt, selectedStyle, packPlan) {
|
|
591
|
+
const targetDir = resolveManagedRootDir(tgt);
|
|
416
592
|
const backupDir = path.join(targetDir, '.sage-backup');
|
|
417
593
|
const manifestPath = path.join(backupDir, 'manifest.json');
|
|
418
594
|
|
|
419
|
-
|
|
595
|
+
const installSummary = tgt === 'codex'
|
|
596
|
+
? `${c.cyn(resolveManagedRootDir(tgt, 'codex'))} + ${c.cyn(resolveManagedRootDir(tgt, 'agents'))}`
|
|
597
|
+
: c.cyn(targetDir);
|
|
598
|
+
step(1, 3, `安装核心文件 → ${installSummary}`);
|
|
420
599
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
421
600
|
|
|
422
601
|
const filesToInstall = tgt === 'codex'
|
|
423
602
|
? getCodexCoreFiles()
|
|
424
|
-
:
|
|
603
|
+
: tgt === 'gemini'
|
|
604
|
+
? getGeminiCoreFiles()
|
|
605
|
+
: getClaudeCoreFiles();
|
|
425
606
|
|
|
426
607
|
const manifest = {
|
|
427
|
-
manifest_version:
|
|
428
|
-
timestamp: new Date().toISOString(), style: selectedStyle.slug, installed: [], backups: []
|
|
608
|
+
manifest_version: 2, version: VERSION, target: tgt,
|
|
609
|
+
timestamp: new Date().toISOString(), style: selectedStyle.slug, installed: [], backups: [],
|
|
610
|
+
project_packs: packPlan.selected,
|
|
611
|
+
optional_policy: packPlan.optionalPolicy || 'auto',
|
|
612
|
+
pack_reports: [],
|
|
429
613
|
};
|
|
430
614
|
|
|
431
|
-
filesToInstall.forEach(({ src, dest }) => {
|
|
615
|
+
filesToInstall.forEach(({ src, dest, root }) => {
|
|
616
|
+
const rootName = root || tgt;
|
|
432
617
|
const srcPath = path.join(PKG_ROOT, src);
|
|
433
|
-
const
|
|
618
|
+
const destRoot = resolveManagedRootDir(tgt, rootName);
|
|
619
|
+
const destPath = path.join(destRoot, dest);
|
|
434
620
|
if (!fs.existsSync(srcPath)) {
|
|
435
621
|
if (src === 'skills') {
|
|
436
622
|
fail(`核心文件缺失: ${srcPath}\n 请尝试: npm cache clean --force && npx code-abyss`);
|
|
@@ -440,22 +626,125 @@ function installCore(tgt, selectedStyle) {
|
|
|
440
626
|
}
|
|
441
627
|
|
|
442
628
|
if (fs.existsSync(destPath)) {
|
|
443
|
-
const
|
|
444
|
-
rmSafe(
|
|
445
|
-
|
|
629
|
+
const backupPath = path.join(backupDir, rootName, dest);
|
|
630
|
+
rmSafe(backupPath);
|
|
631
|
+
copyRecursive(destPath, backupPath);
|
|
632
|
+
pushManifestEntry(manifest.backups, rootName, dest);
|
|
633
|
+
info(`备份: ${c.d(rootName === tgt ? dest : `${rootName}/${dest}`)}`);
|
|
446
634
|
}
|
|
447
|
-
ok(dest);
|
|
448
|
-
rmSafe(destPath);
|
|
635
|
+
ok(rootName === tgt ? dest : `${rootName}/${dest}`);
|
|
636
|
+
rmSafe(destPath);
|
|
637
|
+
copyRecursive(srcPath, destPath);
|
|
638
|
+
pushManifestEntry(manifest.installed, rootName, dest);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
pushPackReport(manifest, {
|
|
642
|
+
pack: 'abyss',
|
|
643
|
+
host: tgt,
|
|
644
|
+
status: 'installed',
|
|
645
|
+
source: 'bundled',
|
|
449
646
|
});
|
|
450
647
|
|
|
451
648
|
// 为目标 CLI 自动生成 user-invocable artifacts
|
|
452
649
|
if (tgt === 'claude') {
|
|
453
650
|
const skillsSrc = path.join(PKG_ROOT, 'skills');
|
|
454
651
|
installGeneratedCommands(skillsSrc, targetDir, backupDir, manifest);
|
|
652
|
+
if (packPlan.selected.includes('gstack')) {
|
|
653
|
+
const sourceMode = (packPlan.sources && packPlan.sources.gstack) || 'pinned';
|
|
654
|
+
const result = installGstackClaudePack({
|
|
655
|
+
HOME,
|
|
656
|
+
backupDir,
|
|
657
|
+
manifest,
|
|
658
|
+
info,
|
|
659
|
+
ok,
|
|
660
|
+
warn,
|
|
661
|
+
sourceMode,
|
|
662
|
+
projectRoot: packPlan.root,
|
|
663
|
+
fallback: true,
|
|
664
|
+
});
|
|
665
|
+
pushPackReport(manifest, {
|
|
666
|
+
pack: 'gstack',
|
|
667
|
+
host: 'claude',
|
|
668
|
+
status: result.installed ? 'installed' : 'skipped',
|
|
669
|
+
source: resolveEffectivePackSource(sourceMode, result),
|
|
670
|
+
reason: result.reason || null,
|
|
671
|
+
});
|
|
672
|
+
} else if (packPlan.required.includes('gstack') || packPlan.optional.includes('gstack')) {
|
|
673
|
+
const sourceMode = (packPlan.sources && packPlan.sources.gstack) || 'pinned';
|
|
674
|
+
pushPackReport(manifest, {
|
|
675
|
+
pack: 'gstack',
|
|
676
|
+
host: 'claude',
|
|
677
|
+
status: sourceMode === 'disabled' ? 'disabled' : 'skipped',
|
|
678
|
+
source: sourceMode,
|
|
679
|
+
reason: sourceMode === 'disabled' ? 'source-disabled' : `optional-policy-${packPlan.optionalPolicy || 'auto'}`,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
455
682
|
} else if (tgt === 'codex') {
|
|
456
|
-
// Codex
|
|
457
|
-
|
|
458
|
-
|
|
683
|
+
// Codex 走 skills-only:不再生成 ~/.codex/AGENTS.md,项目声明的 pack 自动装入 ~/.agents/skills/
|
|
684
|
+
if (packPlan.selected.includes('gstack')) {
|
|
685
|
+
const sourceMode = (packPlan.sources && packPlan.sources.gstack) || 'pinned';
|
|
686
|
+
const result = installGstackCodexPack({
|
|
687
|
+
HOME,
|
|
688
|
+
backupDir,
|
|
689
|
+
manifest,
|
|
690
|
+
info,
|
|
691
|
+
ok,
|
|
692
|
+
warn,
|
|
693
|
+
sourceMode,
|
|
694
|
+
projectRoot: packPlan.root,
|
|
695
|
+
fallback: true,
|
|
696
|
+
});
|
|
697
|
+
pushPackReport(manifest, {
|
|
698
|
+
pack: 'gstack',
|
|
699
|
+
host: 'codex',
|
|
700
|
+
status: result.installed ? 'installed' : 'skipped',
|
|
701
|
+
source: resolveEffectivePackSource(sourceMode, result),
|
|
702
|
+
reason: result.reason || null,
|
|
703
|
+
});
|
|
704
|
+
} else if (packPlan.required.includes('gstack') || packPlan.optional.includes('gstack')) {
|
|
705
|
+
const sourceMode = (packPlan.sources && packPlan.sources.gstack) || 'pinned';
|
|
706
|
+
pushPackReport(manifest, {
|
|
707
|
+
pack: 'gstack',
|
|
708
|
+
host: 'codex',
|
|
709
|
+
status: sourceMode === 'disabled' ? 'disabled' : 'skipped',
|
|
710
|
+
source: sourceMode,
|
|
711
|
+
reason: sourceMode === 'disabled' ? 'source-disabled' : `optional-policy-${packPlan.optionalPolicy || 'auto'}`,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
} else if (tgt === 'gemini') {
|
|
715
|
+
const skillsSrc = path.join(PKG_ROOT, 'skills');
|
|
716
|
+
installGeneratedGeminiCommands(skillsSrc, targetDir, backupDir, manifest);
|
|
717
|
+
installGeminiContext(targetDir, backupDir, manifest, selectedStyle);
|
|
718
|
+
if (packPlan.selected.includes('gstack')) {
|
|
719
|
+
const sourceMode = (packPlan.sources && packPlan.sources.gstack) || 'pinned';
|
|
720
|
+
const result = installGstackGeminiPack({
|
|
721
|
+
HOME,
|
|
722
|
+
backupDir,
|
|
723
|
+
manifest,
|
|
724
|
+
info,
|
|
725
|
+
ok,
|
|
726
|
+
warn,
|
|
727
|
+
sourceMode,
|
|
728
|
+
projectRoot: packPlan.root,
|
|
729
|
+
fallback: true,
|
|
730
|
+
});
|
|
731
|
+
pushPackReport(manifest, {
|
|
732
|
+
pack: 'gstack',
|
|
733
|
+
host: 'gemini',
|
|
734
|
+
status: result.installed ? 'installed' : 'skipped',
|
|
735
|
+
source: resolveEffectivePackSource(sourceMode, result),
|
|
736
|
+
reason: result.reason || null,
|
|
737
|
+
});
|
|
738
|
+
} else if (packPlan.required.includes('gstack') || packPlan.optional.includes('gstack')) {
|
|
739
|
+
const sourceMode = (packPlan.sources && packPlan.sources.gstack) || 'pinned';
|
|
740
|
+
pushPackReport(manifest, {
|
|
741
|
+
pack: 'gstack',
|
|
742
|
+
host: 'gemini',
|
|
743
|
+
status: sourceMode === 'disabled' ? 'disabled' : 'skipped',
|
|
744
|
+
source: sourceMode,
|
|
745
|
+
reason: sourceMode === 'disabled' ? 'source-disabled' : `optional-policy-${packPlan.optionalPolicy || 'auto'}`,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
459
748
|
}
|
|
460
749
|
|
|
461
750
|
let settingsPath = null;
|
|
@@ -469,15 +758,31 @@ function installCore(tgt, selectedStyle) {
|
|
|
469
758
|
warn('settings.json 解析失败,将使用空配置');
|
|
470
759
|
settings = {};
|
|
471
760
|
}
|
|
472
|
-
fs.
|
|
473
|
-
|
|
761
|
+
fs.mkdirSync(path.join(backupDir, 'claude'), { recursive: true });
|
|
762
|
+
fs.copyFileSync(settingsPath, path.join(backupDir, 'claude', 'settings.json'));
|
|
763
|
+
pushManifestEntry(manifest.backups, 'claude', 'settings.json');
|
|
474
764
|
}
|
|
475
765
|
settings.outputStyle = selectedStyle.slug;
|
|
476
766
|
ok(`outputStyle = ${c.mag(selectedStyle.slug)}`);
|
|
477
767
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
478
|
-
manifest.installed
|
|
768
|
+
pushManifestEntry(manifest.installed, 'claude', 'settings.json');
|
|
769
|
+
} else if (tgt === 'gemini') {
|
|
770
|
+
settingsPath = path.join(targetDir, 'settings.json');
|
|
771
|
+
if (fs.existsSync(settingsPath)) {
|
|
772
|
+
try {
|
|
773
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
774
|
+
} catch (e) {
|
|
775
|
+
warn('Gemini settings.json 解析失败,将使用空配置');
|
|
776
|
+
settings = {};
|
|
777
|
+
}
|
|
778
|
+
fs.mkdirSync(path.join(backupDir, 'gemini'), { recursive: true });
|
|
779
|
+
fs.copyFileSync(settingsPath, path.join(backupDir, 'gemini', 'settings.json'));
|
|
780
|
+
pushManifestEntry(manifest.backups, 'gemini', 'settings.json');
|
|
781
|
+
}
|
|
782
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
783
|
+
pushManifestEntry(manifest.installed, 'gemini', 'settings.json');
|
|
479
784
|
} else {
|
|
480
|
-
pruneLegacyCodexSettings(
|
|
785
|
+
pruneLegacyCodexSettings(tgt, backupDir, manifest);
|
|
481
786
|
}
|
|
482
787
|
|
|
483
788
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
@@ -486,7 +791,7 @@ function installCore(tgt, selectedStyle) {
|
|
|
486
791
|
const uDest = path.join(targetDir, '.sage-uninstall.js');
|
|
487
792
|
if (fs.existsSync(uSrc)) { fs.copyFileSync(uSrc, uDest); fs.chmodSync(uDest, '755'); }
|
|
488
793
|
|
|
489
|
-
return { targetDir, settingsPath, settings, manifest, manifestPath };
|
|
794
|
+
return { targetDir, settingsPath, settings, manifest, manifestPath, packPlan };
|
|
490
795
|
}
|
|
491
796
|
|
|
492
797
|
// ── Claude 后续 ──
|
|
@@ -523,6 +828,21 @@ async function postCodex() {
|
|
|
523
828
|
});
|
|
524
829
|
}
|
|
525
830
|
|
|
831
|
+
async function postGemini(ctx) {
|
|
832
|
+
await postGeminiFlow({
|
|
833
|
+
settingsPath: ctx.settingsPath,
|
|
834
|
+
settings: ctx.settings,
|
|
835
|
+
autoYes,
|
|
836
|
+
HOME,
|
|
837
|
+
PKG_ROOT,
|
|
838
|
+
step,
|
|
839
|
+
ok,
|
|
840
|
+
warn,
|
|
841
|
+
info,
|
|
842
|
+
c,
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
|
|
526
846
|
// ── 主流程 ──
|
|
527
847
|
|
|
528
848
|
async function main() {
|
|
@@ -536,12 +856,20 @@ async function main() {
|
|
|
536
856
|
banner();
|
|
537
857
|
|
|
538
858
|
if (target) {
|
|
539
|
-
if (!
|
|
859
|
+
if (!isSupportedTarget(target)) {
|
|
860
|
+
fail(formatActionableError(`--target 必须是 ${listTargetNames().join('、')}`, 'Try: node bin/install.js --target claude'));
|
|
861
|
+
process.exit(1);
|
|
862
|
+
}
|
|
540
863
|
const style = await resolveInstallStyle(target);
|
|
864
|
+
const packPlan = await resolveProjectPackPlan(target);
|
|
541
865
|
info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
|
|
542
|
-
|
|
866
|
+
if (packPlan.path) {
|
|
867
|
+
info(`项目 packs: required=[${packPlan.required.join(', ')}] optional=[${packPlan.optional.join(', ')}] policy=${packPlan.optionalPolicy}`);
|
|
868
|
+
}
|
|
869
|
+
const ctx = installCore(target, style, packPlan);
|
|
543
870
|
if (target === 'claude') await postClaude(ctx);
|
|
544
|
-
else await postCodex();
|
|
871
|
+
else if (target === 'codex') await postCodex();
|
|
872
|
+
else await postGemini(ctx);
|
|
545
873
|
finish(ctx);
|
|
546
874
|
return;
|
|
547
875
|
}
|
|
@@ -549,43 +877,106 @@ async function main() {
|
|
|
549
877
|
const { select } = await import('@inquirer/prompts');
|
|
550
878
|
const action = await select({
|
|
551
879
|
message: '请选择操作',
|
|
552
|
-
choices:
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
880
|
+
choices: listInstallTargets().flatMap((targetMeta) => {
|
|
881
|
+
const targetDir = resolveManagedRootDir(targetMeta.name);
|
|
882
|
+
return [
|
|
883
|
+
{ name: `安装到 ${targetMeta.actionLabel} ${c.d(`(${targetDir})`)}`, value: `install-${targetMeta.name}` },
|
|
884
|
+
{ name: `${c.red('卸载')} ${targetMeta.actionLabel}`, value: `uninstall-${targetMeta.name}` },
|
|
885
|
+
];
|
|
886
|
+
}),
|
|
558
887
|
});
|
|
559
888
|
|
|
560
889
|
switch (action) {
|
|
561
890
|
case 'install-claude': {
|
|
562
891
|
const style = await resolveInstallStyle('claude');
|
|
892
|
+
const packPlan = await resolveProjectPackPlan('claude');
|
|
563
893
|
info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
|
|
564
|
-
|
|
894
|
+
if (packPlan.path) {
|
|
895
|
+
info(`项目 packs: required=[${packPlan.required.join(', ')}] optional=[${packPlan.optional.join(', ')}] policy=${packPlan.optionalPolicy}`);
|
|
896
|
+
}
|
|
897
|
+
const ctx = installCore('claude', style, packPlan);
|
|
565
898
|
await postClaude(ctx);
|
|
566
899
|
finish(ctx); break;
|
|
567
900
|
}
|
|
568
901
|
case 'install-codex': {
|
|
569
902
|
const style = await resolveInstallStyle('codex');
|
|
903
|
+
const packPlan = await resolveProjectPackPlan('codex');
|
|
570
904
|
info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
|
|
571
|
-
|
|
905
|
+
if (packPlan.path) {
|
|
906
|
+
info(`项目 packs: required=[${packPlan.required.join(', ')}] optional=[${packPlan.optional.join(', ')}] policy=${packPlan.optionalPolicy}`);
|
|
907
|
+
}
|
|
908
|
+
const ctx = installCore('codex', style, packPlan);
|
|
572
909
|
await postCodex();
|
|
573
910
|
finish(ctx); break;
|
|
574
911
|
}
|
|
912
|
+
case 'install-gemini': {
|
|
913
|
+
const style = await resolveInstallStyle('gemini');
|
|
914
|
+
const packPlan = await resolveProjectPackPlan('gemini');
|
|
915
|
+
info(`输出风格: ${c.mag(style.slug)} (${style.label})`);
|
|
916
|
+
const ctx = installCore('gemini', style, packPlan);
|
|
917
|
+
await postGemini(ctx);
|
|
918
|
+
finish(ctx); break;
|
|
919
|
+
}
|
|
575
920
|
case 'uninstall-claude': runUninstall('claude'); break;
|
|
576
921
|
case 'uninstall-codex': runUninstall('codex'); break;
|
|
922
|
+
case 'uninstall-gemini': runUninstall('gemini'); break;
|
|
577
923
|
}
|
|
578
924
|
}
|
|
579
925
|
|
|
580
926
|
function finish(ctx) {
|
|
581
927
|
const tgt = ctx.manifest.target;
|
|
928
|
+
let reportPath = null;
|
|
929
|
+
if (ctx.packPlan && ctx.packPlan.root) {
|
|
930
|
+
reportPath = writeReportArtifact(ctx.packPlan.root, `install-${tgt}`, {
|
|
931
|
+
version: VERSION,
|
|
932
|
+
target: tgt,
|
|
933
|
+
timestamp: new Date().toISOString(),
|
|
934
|
+
cwd: process.cwd(),
|
|
935
|
+
pack_plan: {
|
|
936
|
+
required: ctx.packPlan.required,
|
|
937
|
+
optional: ctx.packPlan.optional,
|
|
938
|
+
selected: ctx.packPlan.selected,
|
|
939
|
+
optional_policy: ctx.packPlan.optionalPolicy,
|
|
940
|
+
sources: ctx.packPlan.sources,
|
|
941
|
+
},
|
|
942
|
+
pack_reports: ctx.manifest.pack_reports || [],
|
|
943
|
+
installed: ctx.manifest.installed || [],
|
|
944
|
+
backups: ctx.manifest.backups || [],
|
|
945
|
+
});
|
|
946
|
+
}
|
|
582
947
|
divider('安装完成');
|
|
583
948
|
console.log('');
|
|
584
949
|
console.log(` ${c.b('目标:')} ${c.cyn(ctx.targetDir)}`);
|
|
585
950
|
console.log(` ${c.b('版本:')} v${VERSION}`);
|
|
586
|
-
if (ctx.manifest.style) {
|
|
951
|
+
if (ctx.manifest.style && tgt !== 'codex') {
|
|
587
952
|
console.log(` ${c.b('风格:')} ${c.mag(ctx.manifest.style)}`);
|
|
588
953
|
}
|
|
954
|
+
if (Array.isArray(ctx.manifest.project_packs) && ctx.manifest.project_packs.length > 0) {
|
|
955
|
+
console.log(` ${c.b('Packs:')} ${ctx.manifest.project_packs.join(', ')}`);
|
|
956
|
+
}
|
|
957
|
+
if (ctx.manifest.optional_policy) {
|
|
958
|
+
console.log(` ${c.b('Pack策略:')} ${ctx.manifest.optional_policy}`);
|
|
959
|
+
}
|
|
960
|
+
if (Array.isArray(ctx.manifest.pack_reports) && ctx.manifest.pack_reports.length > 0) {
|
|
961
|
+
ctx.manifest.pack_reports.forEach((report) => {
|
|
962
|
+
const source = report.source ? ` source=${report.source}` : '';
|
|
963
|
+
const reason = report.reason ? ` reason=${report.reason}` : '';
|
|
964
|
+
console.log(` ${c.b('Pack报告:')} ${report.pack}@${report.host} ${report.status}${source}${reason}`);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
if (ctx.packPlan && ctx.packPlan.root) {
|
|
968
|
+
const projectLock = readProjectPackLock(ctx.packPlan.root);
|
|
969
|
+
if (projectLock) {
|
|
970
|
+
const bootstrap = syncProjectBootstrapArtifacts(ctx.packPlan.root, projectLock.lock);
|
|
971
|
+
const updatedDocs = bootstrap.docs.filter((entry) => entry.action !== 'skipped');
|
|
972
|
+
if (updatedDocs.length > 0) {
|
|
973
|
+
updatedDocs.forEach((entry) => console.log(` ${c.b('文档同步:')} ${entry.action} ${entry.filePath}`));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (reportPath) {
|
|
978
|
+
console.log(` ${c.b('Report:')} ${reportPath}`);
|
|
979
|
+
}
|
|
589
980
|
console.log(` ${c.b('文件:')} ${ctx.manifest.installed.length} 个安装, ${ctx.manifest.backups.length} 个备份`);
|
|
590
981
|
console.log(` ${c.b('卸载:')} ${c.d(`npx code-abyss --uninstall ${tgt}`)}`);
|
|
591
982
|
console.log('');
|
|
@@ -601,7 +992,7 @@ module.exports = {
|
|
|
601
992
|
detectCclineBin, copyRecursive, shouldSkip, SETTINGS_TEMPLATE,
|
|
602
993
|
scanInvocableSkills,
|
|
603
994
|
generateCommandContent,
|
|
604
|
-
|
|
995
|
+
generateGeminiCommandContent,
|
|
605
996
|
installGeneratedCommands,
|
|
606
|
-
|
|
997
|
+
installGeneratedGeminiCommands,
|
|
607
998
|
};
|