@haposoft/cafekit 0.8.16 → 0.9.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/README.md +63 -67
- package/bin/install.js +141 -194
- package/bin/lib/copy-utils.js +61 -0
- package/bin/lib/opencode-install.js +866 -0
- package/package.json +4 -1
- package/src/claude/CLAUDE.md +2 -2
- package/src/claude/migration-manifest.json +1 -0
- package/src/claude/rules/skill-domain-routing.md +2 -0
- package/src/claude/rules/skill-workflow-routing.md +3 -1
- package/src/claude/skills/qs/SKILL.md +177 -0
- package/src/claude/skills/qs/templates/qs.md +30 -0
- package/src/opencode/AGENTS.md +140 -0
- package/src/opencode/plugins/docs-sync.ts +181 -0
- package/src/opencode/plugins/inspect-block.ts +164 -0
- package/src/opencode/plugins/privacy-block.ts +182 -0
- package/src/opencode/plugins/rules.ts +163 -0
- package/src/opencode/plugins/session.ts +179 -0
- package/src/opencode/plugins/state.ts +253 -0
- package/src/opencode/plugins/usage.ts +178 -0
- package/src/antigravity/GEMINI.md +0 -162
- package/src/antigravity/workflows/code.md +0 -16
- package/src/antigravity/workflows/docs-init.md +0 -432
- package/src/antigravity/workflows/docs-update.md +0 -245
- package/src/antigravity/workflows/impact-analysis-output-example.md +0 -313
- package/src/antigravity/workflows/impact-analysis.md +0 -730
- package/src/antigravity/workflows/review.md +0 -15
- package/src/antigravity/workflows/spec-design.md +0 -220
- package/src/antigravity/workflows/spec-init.md +0 -78
- package/src/antigravity/workflows/spec-requirements.md +0 -122
- package/src/antigravity/workflows/spec-status.md +0 -95
- package/src/antigravity/workflows/spec-tasks.md +0 -156
- package/src/antigravity/workflows/spec-validate.md +0 -106
- package/src/antigravity/workflows/test.md +0 -13
package/bin/install.js
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* CafeKit Installer
|
|
5
5
|
* Multi-platform installer for AI coding assistants
|
|
6
6
|
*
|
|
7
|
-
* Supported platforms:
|
|
8
|
-
* - claude: Claude Code
|
|
9
|
-
* -
|
|
7
|
+
* Supported platforms (each install is fully self-contained):
|
|
8
|
+
* - claude: Claude Code → .claude/ (skills, rules, scripts, references, runtime.json)
|
|
9
|
+
* - opencode: OpenCode → .opencode/ (same layout, content rewritten on copy)
|
|
10
|
+
*
|
|
11
|
+
* Selecting both creates both folders independently. There is no shared
|
|
12
|
+
* runtime directory between platforms.
|
|
10
13
|
*
|
|
11
14
|
* To add a new platform:
|
|
12
15
|
* 1. Add to PLATFORMS registry below
|
|
@@ -20,6 +23,19 @@ const os = require('os');
|
|
|
20
23
|
const readline = require('readline');
|
|
21
24
|
const { execSync } = require('child_process');
|
|
22
25
|
const packageJson = require('../package.json');
|
|
26
|
+
const { copyRecursive, isTextAsset, readJsonFile } = require('./lib/copy-utils');
|
|
27
|
+
const {
|
|
28
|
+
normalizeOpenCodeBody,
|
|
29
|
+
getOpenCodeCopyOptions,
|
|
30
|
+
convertOpenCodeAgentContent,
|
|
31
|
+
convertOpenCodeCommandContent,
|
|
32
|
+
copyOpenCodeAgentsMdFile,
|
|
33
|
+
copyOpenCodeSharedRuntimeFiles,
|
|
34
|
+
copyOpenCodePlugins,
|
|
35
|
+
copyOpenCodeCommandTemplates,
|
|
36
|
+
mergeOpenCodeConfig,
|
|
37
|
+
setupOpenCodeModel
|
|
38
|
+
} = require('./lib/opencode-install');
|
|
23
39
|
const INSTALL_COMMAND = `npx ${packageJson.name}@${packageJson.version}`;
|
|
24
40
|
|
|
25
41
|
function validateManifestV2(manifest) {
|
|
@@ -57,65 +73,11 @@ const CLAUDE_MIGRATION_MANIFEST = loadClaudeMigrationManifest();
|
|
|
57
73
|
const DEPENDENCY_TEMPLATES = {
|
|
58
74
|
commands: {
|
|
59
75
|
claude: {},
|
|
60
|
-
|
|
61
|
-
'code.md': `---
|
|
62
|
-
description: Implement approved work from specification tasks and then hand off to test and hapo:code-review.
|
|
63
|
-
allowed-tools: Read, Glob, Grep, Edit, Write, Bash
|
|
64
|
-
argument-hint: <feature-name>
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
# /code
|
|
68
|
-
|
|
69
|
-
Use this workflow after /spec-tasks.
|
|
70
|
-
|
|
71
|
-
1. Read .specs/$ARGUMENTS/tasks.md and identify the next pending task.
|
|
72
|
-
2. Implement only that task following project standards.
|
|
73
|
-
3. Run /test.
|
|
74
|
-
4. Run /hapo:code-review.
|
|
75
|
-
|
|
76
|
-
Preferred flow: /spec-init -> /spec-requirements -> /spec-design -> /spec-tasks -> /code -> /test -> /hapo:code-review
|
|
77
|
-
`,
|
|
78
|
-
'test.md': `---
|
|
79
|
-
description: Run project tests and report failures concisely.
|
|
80
|
-
allowed-tools: Bash, Read, Grep
|
|
81
|
-
argument-hint: [scope]
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
# /test
|
|
85
|
-
|
|
86
|
-
Run the project's test command and report:
|
|
87
|
-
- total passed/failed
|
|
88
|
-
- failing test names
|
|
89
|
-
- root cause hints
|
|
90
|
-
- next fix action
|
|
91
|
-
`,
|
|
92
|
-
}
|
|
76
|
+
opencode: {}
|
|
93
77
|
},
|
|
94
78
|
agents: {
|
|
95
79
|
claude: {},
|
|
96
|
-
|
|
97
|
-
'frontend-specialist.md': `---
|
|
98
|
-
name: frontend-specialist
|
|
99
|
-
description: Implement approved UI and interaction tasks from specifications.
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
Implement UI tasks from approved specs with accessibility and responsive behavior.
|
|
103
|
-
`,
|
|
104
|
-
'test-engineer.md': `---
|
|
105
|
-
name: test-engineer
|
|
106
|
-
description: Execute tests and report reliability issues.
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
Run test suites, highlight failures, and propose precise fixes.
|
|
110
|
-
`,
|
|
111
|
-
'code-archaeologist.md': `---
|
|
112
|
-
name: code-archaeologist
|
|
113
|
-
description: Review recent changes for regressions and hidden impacts.
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
Inspect changed code paths, dependencies, and potential regressions.
|
|
117
|
-
`
|
|
118
|
-
}
|
|
80
|
+
opencode: {}
|
|
119
81
|
}
|
|
120
82
|
};
|
|
121
83
|
|
|
@@ -136,18 +98,19 @@ const PLATFORMS = {
|
|
|
136
98
|
sourceDir: 'claude', // Maps to src/claude/
|
|
137
99
|
sourceSubdir: 'commands' // Source subfolder within src/claude/
|
|
138
100
|
},
|
|
139
|
-
|
|
140
|
-
id: '
|
|
141
|
-
name: '
|
|
142
|
-
description: '
|
|
143
|
-
folder: '.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
101
|
+
opencode: {
|
|
102
|
+
id: 'opencode',
|
|
103
|
+
name: 'OpenCode',
|
|
104
|
+
description: 'OpenCode terminal AI coding agent',
|
|
105
|
+
folder: '.opencode',
|
|
106
|
+
detectFiles: ['.opencode', 'opencode.json', 'opencode.jsonc'],
|
|
107
|
+
commandsDir: '.opencode/commands',
|
|
108
|
+
skillsDir: '.opencode/skills',
|
|
109
|
+
agentsDir: '.opencode/agents',
|
|
110
|
+
skillsRef: '.opencode/skills',
|
|
148
111
|
commandPrefix: '/',
|
|
149
|
-
sourceDir: '
|
|
150
|
-
sourceSubdir: '
|
|
112
|
+
sourceDir: 'claude',
|
|
113
|
+
sourceSubdir: 'archive-command'
|
|
151
114
|
}
|
|
152
115
|
// Add new platforms here:
|
|
153
116
|
// cursor: {
|
|
@@ -169,7 +132,8 @@ function detectPlatforms() {
|
|
|
169
132
|
const detected = [];
|
|
170
133
|
|
|
171
134
|
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
172
|
-
|
|
135
|
+
const markers = Array.isArray(config.detectFiles) ? config.detectFiles : [config.folder];
|
|
136
|
+
if (markers.some(marker => fs.existsSync(marker))) {
|
|
173
137
|
detected.push(key);
|
|
174
138
|
}
|
|
175
139
|
}
|
|
@@ -189,6 +153,30 @@ function getPlatformKeys() {
|
|
|
189
153
|
return Object.keys(PLATFORMS);
|
|
190
154
|
}
|
|
191
155
|
|
|
156
|
+
// Warn opencode-only installs when a legacy .claude/ folder remains from
|
|
157
|
+
// pre-0.9.0 installs (when OpenCode shared the .claude/ runtime). We do not
|
|
158
|
+
// delete anything; user must remove it explicitly to opt in.
|
|
159
|
+
function warnLegacyClaudeFolder(platforms) {
|
|
160
|
+
if (!platforms.includes('opencode')) return;
|
|
161
|
+
if (platforms.includes('claude')) return;
|
|
162
|
+
if (!fs.existsSync('.claude')) return;
|
|
163
|
+
|
|
164
|
+
console.log('');
|
|
165
|
+
console.log('⚠ Legacy .claude/ folder detected.');
|
|
166
|
+
console.log(' As of CafeKit 0.9.0, OpenCode installs are self-contained under .opencode/.');
|
|
167
|
+
console.log(' The .claude/ folder from older installs is no longer used by OpenCode.');
|
|
168
|
+
console.log(' After this install finishes you can remove it: rm -rf .claude');
|
|
169
|
+
console.log('');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isClaudeCompatibleRuntime(platformKey) {
|
|
173
|
+
return platformKey === 'claude' || platformKey === 'opencode';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getRuntimeSupportTargetDir(platformKey, subdir) {
|
|
177
|
+
return path.join(PLATFORMS[platformKey].folder, subdir);
|
|
178
|
+
}
|
|
179
|
+
|
|
192
180
|
function parseInstallerArgs(argv) {
|
|
193
181
|
const args = {
|
|
194
182
|
upgrade: false
|
|
@@ -205,6 +193,13 @@ function parseInstallerArgs(argv) {
|
|
|
205
193
|
return args;
|
|
206
194
|
}
|
|
207
195
|
|
|
196
|
+
function getCopyOptions(platformKey, baseOptions = {}) {
|
|
197
|
+
if (platformKey === 'opencode') {
|
|
198
|
+
return getOpenCodeCopyOptions(baseOptions);
|
|
199
|
+
}
|
|
200
|
+
return baseOptions;
|
|
201
|
+
}
|
|
202
|
+
|
|
208
203
|
// ═══════════════════════════════════════════════════════════
|
|
209
204
|
// USER INTERACTION
|
|
210
205
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -271,30 +266,6 @@ async function promptMultiPlatformConfirm(detected) {
|
|
|
271
266
|
// FILE OPERATIONS
|
|
272
267
|
// ═══════════════════════════════════════════════════════════
|
|
273
268
|
|
|
274
|
-
function copyRecursive(src, dest, options = {}) {
|
|
275
|
-
const exists = fs.existsSync(src);
|
|
276
|
-
const stats = exists && fs.statSync(src);
|
|
277
|
-
const isDirectory = exists && stats.isDirectory();
|
|
278
|
-
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
279
|
-
|
|
280
|
-
if (isDirectory) {
|
|
281
|
-
if (!fs.existsSync(dest)) {
|
|
282
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
283
|
-
}
|
|
284
|
-
fs.readdirSync(src).forEach(childItemName => {
|
|
285
|
-
const destItemName = childItemName === 'gitignore' ? '.gitignore' : childItemName;
|
|
286
|
-
copyRecursive(path.join(src, childItemName), path.join(dest, destItemName), options);
|
|
287
|
-
});
|
|
288
|
-
} else {
|
|
289
|
-
if (fs.existsSync(dest) && !shouldOverwriteManagedFiles) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
294
|
-
fs.copyFileSync(src, dest);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
269
|
function ensureDependencyFile(targetPath, content, results, label, options = {}) {
|
|
299
270
|
const destinationExists = fs.existsSync(targetPath);
|
|
300
271
|
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
@@ -340,18 +311,6 @@ function ensureWorkflowDependencies(platformKey, platform, results, options = {}
|
|
|
340
311
|
});
|
|
341
312
|
}
|
|
342
313
|
|
|
343
|
-
function readJsonFile(filePath) {
|
|
344
|
-
if (!fs.existsSync(filePath)) {
|
|
345
|
-
return {};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
350
|
-
} catch {
|
|
351
|
-
return {};
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
314
|
function writePlatformVersionMetadata(platformKey, results) {
|
|
356
315
|
const platform = PLATFORMS[platformKey];
|
|
357
316
|
const targetPath = path.join(platform.folder, 'cafekit.json');
|
|
@@ -409,18 +368,8 @@ function getPlatformSpecFiles(platformKey) {
|
|
|
409
368
|
];
|
|
410
369
|
}
|
|
411
370
|
|
|
412
|
-
if (platformKey === '
|
|
413
|
-
return [
|
|
414
|
-
'spec-init.md',
|
|
415
|
-
'spec-requirements.md',
|
|
416
|
-
'spec-design.md',
|
|
417
|
-
'spec-validate.md',
|
|
418
|
-
'spec-tasks.md',
|
|
419
|
-
'spec-status.md',
|
|
420
|
-
'impact-analysis.md',
|
|
421
|
-
'docs-init.md',
|
|
422
|
-
'docs-update.md'
|
|
423
|
-
];
|
|
371
|
+
if (platformKey === 'opencode') {
|
|
372
|
+
return [];
|
|
424
373
|
}
|
|
425
374
|
|
|
426
375
|
return [];
|
|
@@ -444,9 +393,10 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
444
393
|
const agentsSourceDir = path.join(__dirname, `../src/${platform.sourceDir}/agents`);
|
|
445
394
|
|
|
446
395
|
// Create directories
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
396
|
+
// commandsDir is created on-demand per platform: Claude only mkdirs when
|
|
397
|
+
// getPlatformSpecFiles returns files; OpenCode mkdirs inside
|
|
398
|
+
// copyOpenCodeCommandTemplates. Skip the unconditional mkdir to avoid
|
|
399
|
+
// leaving empty commands/ folders behind.
|
|
450
400
|
if (!fs.existsSync(platform.skillsDir)) {
|
|
451
401
|
fs.mkdirSync(platform.skillsDir, { recursive: true });
|
|
452
402
|
}
|
|
@@ -461,7 +411,7 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
461
411
|
|
|
462
412
|
if (fs.existsSync(specSkillSource)) {
|
|
463
413
|
const skillExisted = fs.existsSync(specSkillDest);
|
|
464
|
-
copyRecursive(specSkillSource, specSkillDest, options);
|
|
414
|
+
copyRecursive(specSkillSource, specSkillDest, getCopyOptions(platformKey, options));
|
|
465
415
|
results.installedSkills++;
|
|
466
416
|
|
|
467
417
|
if (shouldOverwriteManagedFiles && skillExisted) {
|
|
@@ -525,11 +475,8 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
525
475
|
|
|
526
476
|
// Copy additional required skills based on platform
|
|
527
477
|
let requiredSkills = [];
|
|
528
|
-
if (platformKey
|
|
478
|
+
if (isClaudeCompatibleRuntime(platformKey)) {
|
|
529
479
|
requiredSkills = CLAUDE_MIGRATION_MANIFEST?.skills?.required || [];
|
|
530
|
-
} else if (platformKey === 'antigravity') {
|
|
531
|
-
// Antigravity also needs shared investigation and impact-analysis skills
|
|
532
|
-
requiredSkills = ['impact-analysis', 'debug', 'ai-multimodal', 'generate-graph'];
|
|
533
480
|
}
|
|
534
481
|
|
|
535
482
|
requiredSkills
|
|
@@ -540,7 +487,7 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
540
487
|
|
|
541
488
|
if (fs.existsSync(skillSource)) {
|
|
542
489
|
const skillExisted = fs.existsSync(skillDest);
|
|
543
|
-
copyRecursive(skillSource, skillDest, options);
|
|
490
|
+
copyRecursive(skillSource, skillDest, getCopyOptions(platformKey, options));
|
|
544
491
|
results.installedSkills++;
|
|
545
492
|
|
|
546
493
|
if (shouldOverwriteManagedFiles && skillExisted) {
|
|
@@ -557,7 +504,7 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
557
504
|
}
|
|
558
505
|
|
|
559
506
|
// Copy agents
|
|
560
|
-
if (platformKey
|
|
507
|
+
if (isClaudeCompatibleRuntime(platformKey)) {
|
|
561
508
|
if (fs.existsSync(agentsSourceDir)) {
|
|
562
509
|
const requiredAgents = CLAUDE_MIGRATION_MANIFEST?.agents?.required || [
|
|
563
510
|
'tester.md',
|
|
@@ -586,7 +533,12 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
586
533
|
}
|
|
587
534
|
|
|
588
535
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
589
|
-
|
|
536
|
+
if (platformKey === 'opencode') {
|
|
537
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
538
|
+
fs.writeFileSync(dest, convertOpenCodeAgentContent(content, fileName), 'utf8');
|
|
539
|
+
} else {
|
|
540
|
+
fs.copyFileSync(source, dest);
|
|
541
|
+
}
|
|
590
542
|
results.dependencyChecks++;
|
|
591
543
|
results.installedDependencies++;
|
|
592
544
|
|
|
@@ -601,9 +553,9 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
601
553
|
// Copy agent reference manuals (debugger manuals, etc.) recursively
|
|
602
554
|
const refsSource = path.join(__dirname, '../src', platform.sourceDir, 'references');
|
|
603
555
|
if (fs.existsSync(refsSource)) {
|
|
604
|
-
const refsDest =
|
|
556
|
+
const refsDest = getRuntimeSupportTargetDir(platformKey, 'references');
|
|
605
557
|
const refsExisted = fs.existsSync(refsDest);
|
|
606
|
-
copyRecursive(refsSource, refsDest, options);
|
|
558
|
+
copyRecursive(refsSource, refsDest, getCopyOptions(platformKey, options));
|
|
607
559
|
|
|
608
560
|
if (shouldOverwriteManagedFiles && refsExisted) {
|
|
609
561
|
console.log(` ↻ Agent reference manuals updated`);
|
|
@@ -622,9 +574,9 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
622
574
|
// Copy native scripts (browser-tool, docs-fetch, validate-docs)
|
|
623
575
|
const scriptsSourceDir = path.join(__dirname, '../src/claude/scripts');
|
|
624
576
|
if (fs.existsSync(scriptsSourceDir)) {
|
|
625
|
-
const scriptsDest =
|
|
577
|
+
const scriptsDest = getRuntimeSupportTargetDir(platformKey, 'scripts');
|
|
626
578
|
const scriptsExisted = fs.existsSync(scriptsDest);
|
|
627
|
-
copyRecursive(scriptsSourceDir, scriptsDest, options);
|
|
579
|
+
copyRecursive(scriptsSourceDir, scriptsDest, getCopyOptions(platformKey, options));
|
|
628
580
|
|
|
629
581
|
if (shouldOverwriteManagedFiles && scriptsExisted) {
|
|
630
582
|
console.log(` ↻ Native scripts updated`);
|
|
@@ -662,6 +614,9 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
662
614
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
663
615
|
let content = fs.readFileSync(source, 'utf8');
|
|
664
616
|
content = content.replace(/\{\{SKILLS_DIR\}\}/g, platform.skillsRef);
|
|
617
|
+
if (platformKey === 'opencode') {
|
|
618
|
+
content = convertOpenCodeCommandContent(content, file);
|
|
619
|
+
}
|
|
665
620
|
fs.writeFileSync(dest, content);
|
|
666
621
|
|
|
667
622
|
if (destinationExists && shouldOverwriteManagedFiles) {
|
|
@@ -692,6 +647,9 @@ function copyRoutingFile(platformKey, results, options = {}) {
|
|
|
692
647
|
|
|
693
648
|
let content = fs.readFileSync(source, 'utf8');
|
|
694
649
|
content = content.replace(/\{\{SKILLS_DIR\}\}/g, platform.skillsRef);
|
|
650
|
+
if (platformKey === 'opencode') {
|
|
651
|
+
content = normalizeOpenCodeBody(content);
|
|
652
|
+
}
|
|
695
653
|
fs.writeFileSync(dest, content);
|
|
696
654
|
|
|
697
655
|
if (destinationExists && shouldOverwriteManagedFiles) {
|
|
@@ -704,40 +662,6 @@ function copyRoutingFile(platformKey, results, options = {}) {
|
|
|
704
662
|
}
|
|
705
663
|
}
|
|
706
664
|
|
|
707
|
-
// Copy GEMINI.md rule file to .agent/rules/ for Antigravity
|
|
708
|
-
function copyGeminiFile(platformKey, results, options = {}) {
|
|
709
|
-
const platform = PLATFORMS[platformKey];
|
|
710
|
-
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
711
|
-
const rulesDir = path.join(platform.folder, 'rules');
|
|
712
|
-
const source = path.join(__dirname, `../src/${platform.sourceDir}/GEMINI.md`);
|
|
713
|
-
const dest = path.join(rulesDir, 'GEMINI.md');
|
|
714
|
-
|
|
715
|
-
if (fs.existsSync(source)) {
|
|
716
|
-
// Create rules directory if not exists
|
|
717
|
-
if (!fs.existsSync(rulesDir)) {
|
|
718
|
-
fs.mkdirSync(rulesDir, { recursive: true });
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
const destinationExists = fs.existsSync(dest);
|
|
722
|
-
|
|
723
|
-
if (destinationExists && !shouldOverwriteManagedFiles) {
|
|
724
|
-
console.log(` → Skipped: rules/GEMINI.md (already exists)`);
|
|
725
|
-
results.skipped++;
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
fs.copyFileSync(source, dest);
|
|
730
|
-
|
|
731
|
-
if (destinationExists && shouldOverwriteManagedFiles) {
|
|
732
|
-
console.log(` ↻ Updated: rules/GEMINI.md`);
|
|
733
|
-
results.updated++;
|
|
734
|
-
} else {
|
|
735
|
-
console.log(` ✓ Copied: rules/GEMINI.md`);
|
|
736
|
-
results.copied++;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
665
|
// Copy Claude runtime files (statusline bundle)
|
|
742
666
|
function copyClaudeRuntimeFiles(platformKey, results, options = {}) {
|
|
743
667
|
if (platformKey !== 'claude') return;
|
|
@@ -944,13 +868,13 @@ function copyClaudeMdFile(platformKey, results, options = {}) {
|
|
|
944
868
|
|
|
945
869
|
// Copy rules directory (always overwrite)
|
|
946
870
|
function copyRulesDirectory(platformKey, results, options = {}) {
|
|
947
|
-
if (platformKey
|
|
871
|
+
if (!isClaudeCompatibleRuntime(platformKey)) return;
|
|
948
872
|
|
|
949
873
|
const source = path.join(__dirname, '../src/claude/rules');
|
|
950
|
-
const dest =
|
|
874
|
+
const dest = getRuntimeSupportTargetDir(platformKey, 'rules');
|
|
951
875
|
|
|
952
876
|
if (fs.existsSync(source)) {
|
|
953
|
-
copyRecursive(source, dest, { upgrade: true });
|
|
877
|
+
copyRecursive(source, dest, getCopyOptions(platformKey, { upgrade: true }));
|
|
954
878
|
console.log(` ↻ rules/ directory overwritten`);
|
|
955
879
|
results.updated++;
|
|
956
880
|
} else {
|
|
@@ -1061,26 +985,37 @@ async function promptGeminiAPIKey() {
|
|
|
1061
985
|
});
|
|
1062
986
|
}
|
|
1063
987
|
|
|
1064
|
-
function configureGeminiKey(apiKey) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
988
|
+
function configureGeminiKey(apiKey, platforms = ['claude']) {
|
|
989
|
+
const envBody = `GEMINI_API_KEY=${apiKey}\nVISUAL_MODEL=gemma-4-31b-it\nSEARCH_MODEL=gemini-2.5-pro\n`;
|
|
990
|
+
const targets = platforms
|
|
991
|
+
.filter(key => PLATFORMS[key])
|
|
992
|
+
.map(key => PLATFORMS[key].folder);
|
|
1068
993
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
994
|
+
if (targets.length === 0) {
|
|
995
|
+
targets.push('.claude');
|
|
996
|
+
}
|
|
1072
997
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
998
|
+
let success = true;
|
|
999
|
+
for (const folder of targets) {
|
|
1000
|
+
try {
|
|
1001
|
+
const targetDir = path.join(process.cwd(), folder);
|
|
1002
|
+
const localEnvFile = path.join(targetDir, '.env');
|
|
1076
1003
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1004
|
+
if (!fs.existsSync(targetDir)) {
|
|
1005
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
fs.writeFileSync(localEnvFile, envBody, { mode: 0o600 });
|
|
1009
|
+
console.log(` ✓ Gemini API key configured securely in project (${folder}/.env)`);
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
success = false;
|
|
1012
|
+
console.log(' ✗ Failed to configure Gemini API key');
|
|
1013
|
+
console.log(` Error: ${error.message}`);
|
|
1014
|
+
console.log(' You can manually set: export GEMINI_API_KEY="your-key"');
|
|
1015
|
+
}
|
|
1083
1016
|
}
|
|
1017
|
+
|
|
1018
|
+
return success;
|
|
1084
1019
|
}
|
|
1085
1020
|
|
|
1086
1021
|
async function setupGeminiCLI(platforms) {
|
|
@@ -1102,7 +1037,7 @@ async function setupGeminiCLI(platforms) {
|
|
|
1102
1037
|
// Always prompt for API config since we need it for hapo:test Multimodal
|
|
1103
1038
|
const apiKey = await promptGeminiAPIKey();
|
|
1104
1039
|
if (apiKey) {
|
|
1105
|
-
configureGeminiKey(apiKey);
|
|
1040
|
+
configureGeminiKey(apiKey, platforms);
|
|
1106
1041
|
} else {
|
|
1107
1042
|
console.log('\n ℹ Skipped API key configuration');
|
|
1108
1043
|
console.log(' Set later: export GEMINI_API_KEY="your-key"');
|
|
@@ -1149,6 +1084,8 @@ async function main() {
|
|
|
1149
1084
|
const platformNames = platforms.map(key => PLATFORMS[key].name).join(', ');
|
|
1150
1085
|
console.log(`\nInstalling for: ${platformNames}`);
|
|
1151
1086
|
|
|
1087
|
+
warnLegacyClaudeFolder(platforms);
|
|
1088
|
+
|
|
1152
1089
|
if (installerOptions.upgrade) {
|
|
1153
1090
|
console.log('Mode: upgrade (overwrite managed files)\n');
|
|
1154
1091
|
} else {
|
|
@@ -1185,9 +1122,13 @@ async function main() {
|
|
|
1185
1122
|
copyRulesDirectory(platformKey, results, installerOptions);
|
|
1186
1123
|
}
|
|
1187
1124
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1125
|
+
if (platformKey === 'opencode') {
|
|
1126
|
+
copyOpenCodeSharedRuntimeFiles(platformKey, results, installerOptions);
|
|
1127
|
+
copyOpenCodePlugins(platformKey, results, installerOptions);
|
|
1128
|
+
copyOpenCodeCommandTemplates(platformKey, results, installerOptions);
|
|
1129
|
+
copyOpenCodeAgentsMdFile(platformKey, results, installerOptions);
|
|
1130
|
+
copyRulesDirectory(platformKey, results, installerOptions);
|
|
1131
|
+
mergeOpenCodeConfig(platformKey, results);
|
|
1191
1132
|
}
|
|
1192
1133
|
|
|
1193
1134
|
writePlatformVersionMetadata(platformKey, results);
|
|
@@ -1202,7 +1143,8 @@ async function main() {
|
|
|
1202
1143
|
ensureGitignore(results, installerOptions);
|
|
1203
1144
|
console.log();
|
|
1204
1145
|
|
|
1205
|
-
// Setup
|
|
1146
|
+
// Setup runtime-specific optional model/API configuration
|
|
1147
|
+
await setupOpenCodeModel(platforms, results);
|
|
1206
1148
|
await setupGeminiCLI(platforms);
|
|
1207
1149
|
|
|
1208
1150
|
// Summary
|
|
@@ -1238,8 +1180,13 @@ async function main() {
|
|
|
1238
1180
|
}
|
|
1239
1181
|
}
|
|
1240
1182
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1183
|
+
if (platforms.includes('claude')) {
|
|
1184
|
+
console.log('\n 2. Claude Code will automatically sync docs/ (Continuous Documentation)');
|
|
1185
|
+
} else {
|
|
1186
|
+
console.log('\n 2. CafeKit skills and AGENTS.md are installed for the selected runtime');
|
|
1187
|
+
}
|
|
1188
|
+
const envHosts = platforms.map(key => `${PLATFORMS[key].folder}/.env`).join(' and ');
|
|
1189
|
+
console.log(` 3. Project API Keys are now securely isolated in ${envHosts}`);
|
|
1243
1190
|
if (!installerOptions.upgrade) {
|
|
1244
1191
|
console.log(' 4. To refresh managed templates later, run installer with --upgrade');
|
|
1245
1192
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const TEXT_REWRITE_EXTENSIONS = new Set([
|
|
7
|
+
'.md', '.mdx', '.txt', '.json', '.cjs', '.js', '.mjs', '.ts', '.sh', '.yml', '.yaml'
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
function isTextAsset(filePath) {
|
|
11
|
+
return TEXT_REWRITE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function copyRecursive(src, dest, options = {}) {
|
|
15
|
+
const exists = fs.existsSync(src);
|
|
16
|
+
const stats = exists && fs.statSync(src);
|
|
17
|
+
const isDirectory = exists && stats.isDirectory();
|
|
18
|
+
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
19
|
+
|
|
20
|
+
if (isDirectory) {
|
|
21
|
+
if (!fs.existsSync(dest)) {
|
|
22
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
fs.readdirSync(src).forEach(childItemName => {
|
|
25
|
+
const destItemName = childItemName === 'gitignore' ? '.gitignore' : childItemName;
|
|
26
|
+
copyRecursive(path.join(src, childItemName), path.join(dest, destItemName), options);
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
if (fs.existsSync(dest) && !shouldOverwriteManagedFiles) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
34
|
+
|
|
35
|
+
if (typeof options.transform === 'function' && isTextAsset(src)) {
|
|
36
|
+
const original = fs.readFileSync(src, 'utf8');
|
|
37
|
+
fs.writeFileSync(dest, options.transform(original, src), 'utf8');
|
|
38
|
+
} else {
|
|
39
|
+
fs.copyFileSync(src, dest);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readJsonFile(filePath) {
|
|
45
|
+
if (!fs.existsSync(filePath)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
51
|
+
} catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
TEXT_REWRITE_EXTENSIONS,
|
|
58
|
+
isTextAsset,
|
|
59
|
+
copyRecursive,
|
|
60
|
+
readJsonFile
|
|
61
|
+
};
|