opencode-onboard 0.3.3 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -214
- package/content/.agents/agents/basic-engineer.md +30 -0
- package/content/.agents/agents/devops-manager.md +38 -29
- package/content/.agents/session-log.json +41 -0
- package/content/.agents/skills/ob-default/SKILL.md +21 -0
- package/content/.agents/skills/ob-generic-guardrails/SKILL.md +32 -0
- package/content/.agents/skills/ob-global/SKILL.md +49 -0
- package/content/.agents/skills/ob-pullrequest-az/SKILL.md +11 -21
- package/content/.agents/skills/ob-pullrequest-gh/SKILL.md +14 -24
- package/content/.agents/skills/ob-userstory-az/SKILL.md +8 -14
- package/content/.agents/skills/ob-userstory-gh/SKILL.md +6 -14
- package/content/.opencode/commands/init.md +8 -0
- package/content/.opencode/commands/main.md +17 -0
- package/content/.opencode/commands/opsx-apply.md +50 -33
- package/content/.opencode/commands/plan.md +37 -0
- package/content/.opencode/plugins/session-log.js +1 -1
- package/content/.opencode/skills/openspec-apply-change/SKILL.md +50 -33
- package/content/AGENTS.md +94 -144
- package/content/skills-lock.json +4 -0
- package/package.json +6 -1
- package/src/commands/join.js +43 -0
- package/src/commands/shared.js +12 -0
- package/src/commands/shared.test.js +56 -0
- package/src/commands/single.js +64 -0
- package/src/commands/wizard.js +99 -0
- package/src/index.js +25 -202
- package/src/presets/browser.json +18 -0
- package/src/presets/clean.json +21 -0
- package/src/presets/models.json +33 -0
- package/src/presets/optimization.json +22 -0
- package/src/presets/platforms.json +29 -2
- package/src/presets/quota.json +14 -0
- package/src/presets/source.json +17 -0
- package/src/steps/browser/browser.test.js +81 -0
- package/src/steps/{install-browser.js → browser/index.js} +12 -15
- package/src/steps/{__tests__/clean-ai-files.test.js → clean/clean.test.js} +28 -13
- package/src/steps/{clean-ai-files.js → clean/index.js} +32 -30
- package/src/steps/copy/agents.js +106 -0
- package/src/steps/{__tests__/copy-content.test.js → copy/copy.test.js} +10 -1
- package/src/steps/copy/index.js +33 -0
- package/src/steps/copy/skills.js +55 -0
- package/src/steps/{write-onboard-config.js → metadata/index.js} +3 -3
- package/src/steps/metadata/metadata.test.js +99 -0
- package/src/steps/models/format.js +60 -0
- package/src/steps/models/format.test.js +75 -0
- package/src/steps/models/index.js +52 -0
- package/src/steps/models/write.js +54 -0
- package/src/steps/models/write.test.js +117 -0
- package/src/steps/{init-openspec.js → openspec/ensemble.js} +20 -57
- package/src/steps/openspec/ensemble.test.js +79 -0
- package/src/steps/openspec/index.js +32 -0
- package/src/steps/optimization/caveman-guidance.js +11 -0
- package/src/steps/{install-caveman.js → optimization/caveman.js} +5 -19
- package/src/steps/optimization/global.js +64 -0
- package/src/steps/optimization/index.js +101 -0
- package/src/steps/{__tests__/token-optimization.test.js → optimization/optimization.test.js} +37 -22
- package/src/steps/{install-quota.js → optimization/quota.js} +12 -10
- package/src/steps/platform/index.js +81 -0
- package/src/steps/platform/platform.test.js +129 -0
- package/src/steps/{choose-source-scope.js → source/index.js} +11 -17
- package/src/steps/source/source.test.js +91 -0
- package/src/utils/__tests__/copy.test.js +12 -5
- package/src/utils/copy.js +4 -24
- package/src/utils/exec-spinner.js +47 -0
- package/src/utils/exec.js +120 -162
- package/src/utils/models-cache.js +25 -68
- package/src/utils/models-pricing.js +42 -0
- package/src/utils/models-pricing.test.js +93 -0
- package/content/.agents/agents/back-engineer.md +0 -87
- package/content/.agents/agents/front-engineer.md +0 -86
- package/content/.agents/agents/infra-engineer.md +0 -85
- package/content/.agents/agents/quality-engineer.md +0 -86
- package/content/.agents/agents/security-auditor.md +0 -86
- package/src/steps/__tests__/check-env.test.js +0 -70
- package/src/steps/__tests__/check-platform.test.js +0 -104
- package/src/steps/__tests__/check-rtk.test.js +0 -38
- package/src/steps/__tests__/choose-platform.test.js +0 -38
- package/src/steps/check-env.js +0 -26
- package/src/steps/check-platform.js +0 -80
- package/src/steps/check-rtk.js +0 -38
- package/src/steps/choose-models.js +0 -165
- package/src/steps/choose-platform.js +0 -22
- package/src/steps/choose-skills-provider.js +0 -79
- package/src/steps/copy-content.js +0 -89
- package/src/steps/enable-caveman-guidance.js +0 -78
- package/src/steps/patch-agents-md.js +0 -153
- package/src/steps/token-optimization.js +0 -59
package/src/index.js
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
-
import fse from 'fs-extra'
|
|
4
3
|
import { createRequire } from 'node:module'
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { checkRtk } from './steps/check-rtk.js'
|
|
9
|
-
import { chooseModels } from './steps/choose-models.js'
|
|
10
|
-
import { choosePlatform } from './steps/choose-platform.js'
|
|
11
|
-
import { chooseSkillsProvider } from './steps/choose-skills-provider.js'
|
|
12
|
-
import { chooseSourceScope } from './steps/choose-source-scope.js'
|
|
13
|
-
import { cleanAiFiles } from './steps/clean-ai-files.js'
|
|
14
|
-
import { copyContentStep } from './steps/copy-content.js'
|
|
15
|
-
import { enableCavemanGuidance } from './steps/enable-caveman-guidance.js'
|
|
16
|
-
import { initOpenspec } from './steps/init-openspec.js'
|
|
17
|
-
import { installBrowser } from './steps/install-browser.js'
|
|
18
|
-
import { installCaveman } from './steps/install-caveman.js'
|
|
19
|
-
import { installQuota } from './steps/install-quota.js'
|
|
20
|
-
import { patchAgentsMd } from './steps/patch-agents-md.js'
|
|
21
|
-
import { tokenOptimizationStep } from './steps/token-optimization.js'
|
|
22
|
-
import { writeOnboardConfig } from './steps/write-onboard-config.js'
|
|
4
|
+
import { runJoin } from './commands/join.js'
|
|
5
|
+
import { runSingleCommand } from './commands/single.js'
|
|
6
|
+
import { runWizard } from './commands/wizard.js'
|
|
23
7
|
|
|
24
8
|
function printHelp(version) {
|
|
25
9
|
console.log(`opencode-onboard v${version}`)
|
|
@@ -29,16 +13,13 @@ function printHelp(version) {
|
|
|
29
13
|
console.log(' npx opencode-onboard <command> Run a single step command')
|
|
30
14
|
console.log()
|
|
31
15
|
console.log('Commands:')
|
|
16
|
+
console.log(' join New team member setup (checks & local installs only)')
|
|
32
17
|
console.log(' clean Run AI files cleanup step')
|
|
33
18
|
console.log(' platform Run platform selection step')
|
|
34
19
|
console.log(' copy Run content copy step')
|
|
35
20
|
console.log(' openspec Run OpenSpec initialization step')
|
|
36
|
-
console.log(' skills Run skills install step')
|
|
37
21
|
console.log(' models Run models selection step')
|
|
38
22
|
console.log(' optimization Run token optimization tools step')
|
|
39
|
-
console.log(' quota Run opencode-quota installer step')
|
|
40
|
-
console.log(' rtk Run rtk check step')
|
|
41
|
-
console.log(' caveman Run caveman install + guidance steps')
|
|
42
23
|
console.log(' browser Run opencode-browser installer step')
|
|
43
24
|
console.log(' metadata Write onboarding metadata step')
|
|
44
25
|
console.log()
|
|
@@ -46,85 +27,6 @@ function printHelp(version) {
|
|
|
46
27
|
console.log(' -h, --help Show this help message')
|
|
47
28
|
}
|
|
48
29
|
|
|
49
|
-
async function readOnboardConfig() {
|
|
50
|
-
const cfgPath = path.join(process.cwd(), '.opencode', 'opencode-onboard.json')
|
|
51
|
-
if (!await fse.pathExists(cfgPath)) return null
|
|
52
|
-
try {
|
|
53
|
-
return await fse.readJson(cfgPath)
|
|
54
|
-
} catch {
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function runSingleCommand(command) {
|
|
60
|
-
const saved = await readOnboardConfig()
|
|
61
|
-
const savedWizard = saved?.wizard ?? {}
|
|
62
|
-
const ctx = {
|
|
63
|
-
hasDesign: !!savedWizard?.preserved?.design,
|
|
64
|
-
hasArchitecture: !!savedWizard?.preserved?.architecture,
|
|
65
|
-
hasOpenspec: !!savedWizard?.preserved?.openspec,
|
|
66
|
-
sourceMode: savedWizard?.sourceMode ?? 'current',
|
|
67
|
-
sourceRoots: Array.isArray(savedWizard?.sourceRoots) ? savedWizard.sourceRoots : [],
|
|
68
|
-
}
|
|
69
|
-
const platform = savedWizard?.platform
|
|
70
|
-
const resolvedPlatform = platform === 'azure' || platform === 'github' ? platform : 'github'
|
|
71
|
-
|
|
72
|
-
const handlers = {
|
|
73
|
-
clean: async () => {
|
|
74
|
-
await cleanAiFiles()
|
|
75
|
-
},
|
|
76
|
-
platform: async () => {
|
|
77
|
-
await choosePlatform()
|
|
78
|
-
},
|
|
79
|
-
copy: async () => {
|
|
80
|
-
await copyContentStep(resolvedPlatform, ctx)
|
|
81
|
-
await patchAgentsMd(ctx)
|
|
82
|
-
},
|
|
83
|
-
openspec: async () => {
|
|
84
|
-
await initOpenspec()
|
|
85
|
-
},
|
|
86
|
-
skills: async () => {
|
|
87
|
-
await chooseSkillsProvider()
|
|
88
|
-
},
|
|
89
|
-
models: async () => {
|
|
90
|
-
await chooseModels()
|
|
91
|
-
},
|
|
92
|
-
optimization: async () => {
|
|
93
|
-
await tokenOptimizationStep({ skillsProvider: savedWizard?.additionalSkillsProvider })
|
|
94
|
-
},
|
|
95
|
-
quota: async () => {
|
|
96
|
-
await installQuota()
|
|
97
|
-
},
|
|
98
|
-
rtk: async () => {
|
|
99
|
-
await checkRtk()
|
|
100
|
-
},
|
|
101
|
-
caveman: async () => {
|
|
102
|
-
const caveman = await installCaveman({ skillsProvider: savedWizard?.additionalSkillsProvider })
|
|
103
|
-
await enableCavemanGuidance(caveman)
|
|
104
|
-
},
|
|
105
|
-
browser: async () => {
|
|
106
|
-
await installBrowser()
|
|
107
|
-
},
|
|
108
|
-
metadata: async () => {
|
|
109
|
-
await writeOnboardConfig({
|
|
110
|
-
...ctx,
|
|
111
|
-
platform: resolvedPlatform,
|
|
112
|
-
additionalSkillsProvider: savedWizard?.additionalSkillsProvider ?? 'none',
|
|
113
|
-
planModel: savedWizard?.models?.plan ?? null,
|
|
114
|
-
buildModel: savedWizard?.models?.build ?? null,
|
|
115
|
-
fastModel: savedWizard?.models?.fast ?? null,
|
|
116
|
-
optionalTools: savedWizard?.optionalTools ?? null,
|
|
117
|
-
cavemanGuidance: savedWizard?.cavemanGuidance ?? null,
|
|
118
|
-
})
|
|
119
|
-
},
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const handler = handlers[command]
|
|
123
|
-
if (!handler) return false
|
|
124
|
-
await handler()
|
|
125
|
-
return true
|
|
126
|
-
}
|
|
127
|
-
|
|
128
30
|
if (process.stdout.isTTY) console.clear()
|
|
129
31
|
console.log()
|
|
130
32
|
const require = createRequire(import.meta.url)
|
|
@@ -137,111 +39,32 @@ if (args.includes('-h') || args.includes('--help')) {
|
|
|
137
39
|
}
|
|
138
40
|
|
|
139
41
|
if (args.length > 0) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
42
|
+
try {
|
|
43
|
+
if (args[0] === 'join') {
|
|
44
|
+
await runJoin()
|
|
45
|
+
} else {
|
|
46
|
+
const ok = await runSingleCommand(args[0])
|
|
47
|
+
if (!ok) {
|
|
48
|
+
console.log(chalk.red(`Unknown command: ${args[0]}`))
|
|
49
|
+
console.log()
|
|
50
|
+
printHelp(version)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err.name === 'ExitPromptError') {
|
|
56
|
+
console.log()
|
|
57
|
+
console.log(chalk.yellow('Cancelled.'))
|
|
58
|
+
} else {
|
|
59
|
+
console.error(chalk.red('\nUnexpected error:'), err.message)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
146
62
|
}
|
|
147
63
|
process.exit(0)
|
|
148
64
|
}
|
|
149
65
|
|
|
150
|
-
const logo = chalk.hex('#fe3d57')
|
|
151
|
-
const bannerLines = [
|
|
152
|
-
logo(' '),
|
|
153
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
|
|
154
|
-
logo(' ▒▒▓ ▓▒▓ '),
|
|
155
|
-
logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
|
|
156
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
157
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
|
|
158
|
-
logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
|
|
159
|
-
logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
|
|
160
|
-
logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
|
|
161
|
-
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
162
|
-
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
163
|
-
logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
|
|
164
|
-
'',
|
|
165
|
-
chalk.bold(' 🧰 opencode-onboard') + chalk.dim(` v${version}`),
|
|
166
|
-
chalk.dim(' Prepare your codebase for AI agents'),
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
for (const line of bannerLines) console.log(line)
|
|
170
|
-
console.log()
|
|
171
|
-
console.log(' This tool will set up your project with a team of AI agents,')
|
|
172
|
-
console.log(' install skills, select models, and configure OpenCode.')
|
|
173
|
-
console.log()
|
|
174
|
-
|
|
175
|
-
// Only wait for Enter in a real interactive TTY
|
|
176
|
-
if (process.stdin.isTTY) {
|
|
177
|
-
console.log(chalk.bold(' Press Enter to begin...'))
|
|
178
|
-
console.log()
|
|
179
|
-
await new Promise(resolve => {
|
|
180
|
-
process.stdin.resume()
|
|
181
|
-
process.stdin.once('data', () => {
|
|
182
|
-
process.stdin.pause()
|
|
183
|
-
resolve()
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
}
|
|
187
|
-
|
|
188
66
|
try {
|
|
189
|
-
await
|
|
190
|
-
|
|
191
|
-
const scope = await chooseSourceScope()
|
|
192
|
-
|
|
193
|
-
const preserve = await cleanAiFiles()
|
|
194
|
-
const ctx = { ...preserve, ...scope }
|
|
195
|
-
|
|
196
|
-
const platform = await choosePlatform()
|
|
197
|
-
|
|
198
|
-
await checkPlatform(platform)
|
|
199
|
-
|
|
200
|
-
await copyContentStep(platform, ctx)
|
|
201
|
-
|
|
202
|
-
await patchAgentsMd(ctx)
|
|
203
|
-
|
|
204
|
-
await initOpenspec()
|
|
205
|
-
|
|
206
|
-
const skillsSelection = await chooseSkillsProvider()
|
|
207
|
-
|
|
208
|
-
const selectedModels = await chooseModels()
|
|
209
|
-
|
|
210
|
-
const tokenOpt = await tokenOptimizationStep({ skillsProvider: skillsSelection.additionalSkillsProvider })
|
|
211
|
-
const { rtk, quota, caveman, cavemanGuidance } = tokenOpt
|
|
212
|
-
|
|
213
|
-
await installBrowser()
|
|
214
|
-
|
|
215
|
-
await writeOnboardConfig({
|
|
216
|
-
...ctx,
|
|
217
|
-
platform,
|
|
218
|
-
...skillsSelection,
|
|
219
|
-
...selectedModels,
|
|
220
|
-
optionalTools: { rtk, quota, caveman },
|
|
221
|
-
cavemanGuidance,
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
const toGenerate = [
|
|
225
|
-
!ctx.hasDesign && 'DESIGN.md',
|
|
226
|
-
!ctx.hasArchitecture && 'ARCHITECTURE.md',
|
|
227
|
-
].filter(Boolean)
|
|
228
|
-
|
|
229
|
-
console.log()
|
|
230
|
-
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
231
|
-
console.log(chalk.bold.green(' Onboarding complete!'))
|
|
232
|
-
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
233
|
-
console.log()
|
|
234
|
-
console.log(' Open this project in OpenCode and type:')
|
|
235
|
-
console.log(chalk.bold(' "init"'))
|
|
236
|
-
console.log()
|
|
237
|
-
if (toGenerate.length > 0) {
|
|
238
|
-
console.log(` OpenCode will generate ${toGenerate.join(' and ')}`)
|
|
239
|
-
console.log(' from your actual codebase, then activate the agent team.')
|
|
240
|
-
} else {
|
|
241
|
-
console.log(' OpenCode will activate the agent team.')
|
|
242
|
-
}
|
|
243
|
-
console.log(` Source scope: ${ctx.sourceMode === 'parent-selected' ? ctx.sourceRoots.map(p => `../${p.split(/[/\\]/).pop()}`).join(', ') : 'current folder'}`)
|
|
244
|
-
console.log()
|
|
67
|
+
await runWizard(version)
|
|
245
68
|
} catch (err) {
|
|
246
69
|
if (err.name === 'ExitPromptError') {
|
|
247
70
|
console.log()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"installer": {
|
|
3
|
+
"command": "npx",
|
|
4
|
+
"args": ["@different-ai/opencode-browser", "install"]
|
|
5
|
+
},
|
|
6
|
+
"output": {
|
|
7
|
+
"showAfter": "To load the extension",
|
|
8
|
+
"hideAfter": "Press Enter when"
|
|
9
|
+
},
|
|
10
|
+
"autoAnswers": [
|
|
11
|
+
{ "trigger": "Press Enter when", "response": "" },
|
|
12
|
+
{ "trigger": "Choose config location", "response": "2" },
|
|
13
|
+
{ "trigger": "Add plugin automatically?", "response": "y" },
|
|
14
|
+
{ "trigger": "Create one?", "response": "y" },
|
|
15
|
+
{ "trigger": "Add browser-automation skill", "response": "n" },
|
|
16
|
+
{ "trigger": "Check broker", "response": "n" }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"preserve": ["DESIGN.md", "ARCHITECTURE.md", "openspec"],
|
|
3
|
+
"detectFiles": [
|
|
4
|
+
"AGENTS.md",
|
|
5
|
+
"CLAUDE.md",
|
|
6
|
+
"ARCHITECTURE.md",
|
|
7
|
+
"DESIGN.md",
|
|
8
|
+
".cursorrules",
|
|
9
|
+
".clinerules",
|
|
10
|
+
".windsurfrules",
|
|
11
|
+
".github/copilot-instructions.md",
|
|
12
|
+
"copilot-instructions.md",
|
|
13
|
+
".aider.conf.yml",
|
|
14
|
+
".aider",
|
|
15
|
+
".opencode",
|
|
16
|
+
".agents"
|
|
17
|
+
],
|
|
18
|
+
"directoryTargets": [".opencode", ".agents"],
|
|
19
|
+
"preserveSubfolders": ["skills"],
|
|
20
|
+
"selectionMessage": "Select AI config files to remove:"
|
|
21
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"costTiers": [
|
|
3
|
+
{ "max": 1, "label": "[$]" },
|
|
4
|
+
{ "max": 10, "label": "[$$]" },
|
|
5
|
+
{ "label": "[$$$]" }
|
|
6
|
+
],
|
|
7
|
+
"roles": {
|
|
8
|
+
"plan": {
|
|
9
|
+
"prompt": "Plan model:",
|
|
10
|
+
"info": [
|
|
11
|
+
"PLAN model: used by the main agent to read issues, write proposals, coordinate the team.",
|
|
12
|
+
"This model needs to be strong. Use Claude Sonnet/Opus, GPT-4o, o3, or equivalent.",
|
|
13
|
+
"A weak model here will silently skip steps and break the workflow."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"build": {
|
|
17
|
+
"prompt": "Build model:",
|
|
18
|
+
"info": [
|
|
19
|
+
"BUILD model: used by front-engineer, back-engineer, infra-engineer, quality-engineer, security-auditor.",
|
|
20
|
+
"Needs to be capable for implementation work. Claude Sonnet, GPT-4o, or equivalent."
|
|
21
|
+
],
|
|
22
|
+
"agents": ["front-engineer", "back-engineer", "infra-engineer", "quality-engineer", "security-auditor"]
|
|
23
|
+
},
|
|
24
|
+
"fast": {
|
|
25
|
+
"prompt": "Fast model:",
|
|
26
|
+
"info": [
|
|
27
|
+
"FAST model: used by devops-manager for reading issues and classifying PR comments.",
|
|
28
|
+
"Something fast and cheap is fine here, no heavy reasoning needed."
|
|
29
|
+
],
|
|
30
|
+
"agents": ["devops-manager"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"message": "Enable tools:",
|
|
3
|
+
"info": "Choose which optimization tools to enable (recommended: all).",
|
|
4
|
+
"timeoutMs": 30000,
|
|
5
|
+
"choices": [
|
|
6
|
+
{
|
|
7
|
+
"name": "RTK check (recommended)",
|
|
8
|
+
"value": "rtk",
|
|
9
|
+
"checked": true
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"name": "opencode-quota plugin (recommended)",
|
|
13
|
+
"value": "quota",
|
|
14
|
+
"checked": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "caveman concise mode (recommended)",
|
|
18
|
+
"value": "caveman",
|
|
19
|
+
"checked": true
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
3
|
"value": "github",
|
|
4
|
-
"name": "GitHub"
|
|
4
|
+
"name": "GitHub",
|
|
5
|
+
"cli": {
|
|
6
|
+
"command": "gh",
|
|
7
|
+
"displayName": "GitHub CLI (gh)",
|
|
8
|
+
"installUrl": "https://cli.github.com",
|
|
9
|
+
"authCheck": {
|
|
10
|
+
"args": ["auth", "status"],
|
|
11
|
+
"notAuthenticatedMessage": "GitHub CLI not authenticated. Run:",
|
|
12
|
+
"commands": ["gh auth login"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
5
15
|
},
|
|
6
16
|
{
|
|
7
17
|
"value": "azure",
|
|
8
|
-
"name": "Azure DevOps"
|
|
18
|
+
"name": "Azure DevOps",
|
|
19
|
+
"cli": {
|
|
20
|
+
"command": "az",
|
|
21
|
+
"displayName": "Azure CLI (az)",
|
|
22
|
+
"installUrl": "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli",
|
|
23
|
+
"extensionCheck": {
|
|
24
|
+
"args": ["extension", "list", "--query", "[?name=='azure-devops']", "-o", "tsv"],
|
|
25
|
+
"expectedOutput": "azure-devops",
|
|
26
|
+
"missingMessage": "azure-devops extension not found. Run:",
|
|
27
|
+
"errorMessage": "Could not check azure-devops extension. Run:",
|
|
28
|
+
"commands": [
|
|
29
|
+
"az extension add --name azure-devops",
|
|
30
|
+
"az config set extension.dynamic_install_allow_preview=true",
|
|
31
|
+
"az login",
|
|
32
|
+
"az devops login --organization https://dev.azure.com/<your-org>"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
9
36
|
}
|
|
10
37
|
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugin": "@slkiser/opencode-quota@latest",
|
|
3
|
+
"prompt": {
|
|
4
|
+
"message": "Install opencode-quota with recommended defaults?",
|
|
5
|
+
"default": true,
|
|
6
|
+
"timeoutMs": 20000
|
|
7
|
+
},
|
|
8
|
+
"defaults": {
|
|
9
|
+
"enabledProviders": "auto",
|
|
10
|
+
"formatStyle": "singleWindow",
|
|
11
|
+
"percentDisplayMode": "used",
|
|
12
|
+
"showSessionTokens": true
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"message": "Source code location:",
|
|
3
|
+
"default": "current",
|
|
4
|
+
"choices": [
|
|
5
|
+
{
|
|
6
|
+
"name": "Current folder (default)",
|
|
7
|
+
"value": "current",
|
|
8
|
+
"description": "Use this repository only"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "Select folders in parent (../)",
|
|
12
|
+
"value": "parent",
|
|
13
|
+
"description": "Use when this repo only contains agent config"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"parentSelectionMessage": "Select source folders from parent directory:"
|
|
17
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('../../utils/exec.js', () => ({
|
|
4
|
+
header: vi.fn(),
|
|
5
|
+
info: vi.fn(),
|
|
6
|
+
success: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
error: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
vi.mock('fs-extra', () => ({
|
|
12
|
+
default: {
|
|
13
|
+
readJson: vi.fn().mockResolvedValue({
|
|
14
|
+
installer: { command: 'npx', args: ['@different-ai/opencode-browser', 'install'] },
|
|
15
|
+
output: { showAfter: '===', hideAfter: '===' },
|
|
16
|
+
autoAnswers: [
|
|
17
|
+
{ trigger: 'Install', response: 'y' },
|
|
18
|
+
],
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
vi.mock('execa', () => ({
|
|
24
|
+
execa: vi.fn(),
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
import fse from 'fs-extra'
|
|
28
|
+
import { installBrowser } from './index.js'
|
|
29
|
+
|
|
30
|
+
describe('installBrowser()', () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('calls installer command from preset', async () => {
|
|
36
|
+
const { execa } = await import('execa')
|
|
37
|
+
const mockChild = {
|
|
38
|
+
stdout: { on: vi.fn() },
|
|
39
|
+
stderr: { on: vi.fn() },
|
|
40
|
+
stdin: { write: vi.fn() },
|
|
41
|
+
then: (cb) => cb({ exitCode: 0 }),
|
|
42
|
+
}
|
|
43
|
+
execa.mockReturnValue(mockChild)
|
|
44
|
+
|
|
45
|
+
await installBrowser()
|
|
46
|
+
|
|
47
|
+
expect(execa).toHaveBeenCalledWith('npx', expect.arrayContaining(['@different-ai/opencode-browser']), expect.any(Object))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('logs success when exit code is 0', async () => {
|
|
51
|
+
const { execa } = await import('execa')
|
|
52
|
+
const mockChild = {
|
|
53
|
+
stdout: { on: vi.fn() },
|
|
54
|
+
stderr: { on: vi.fn() },
|
|
55
|
+
stdin: { write: vi.fn() },
|
|
56
|
+
then: (cb) => cb({ exitCode: 0 }),
|
|
57
|
+
}
|
|
58
|
+
execa.mockReturnValue(mockChild)
|
|
59
|
+
const { success } = await import('../../utils/exec.js')
|
|
60
|
+
|
|
61
|
+
await installBrowser()
|
|
62
|
+
|
|
63
|
+
expect(success).toHaveBeenCalledWith('opencode-browser installed')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('logs warning when exit code is non-zero', async () => {
|
|
67
|
+
const { execa } = await import('execa')
|
|
68
|
+
const mockChild = {
|
|
69
|
+
stdout: { on: vi.fn() },
|
|
70
|
+
stderr: { on: vi.fn() },
|
|
71
|
+
stdin: { write: vi.fn() },
|
|
72
|
+
then: (cb) => cb({ exitCode: 1 }),
|
|
73
|
+
}
|
|
74
|
+
execa.mockReturnValue(mockChild)
|
|
75
|
+
const { warn } = await import('../../utils/exec.js')
|
|
76
|
+
|
|
77
|
+
await installBrowser()
|
|
78
|
+
|
|
79
|
+
expect(warn).toHaveBeenCalledWith('opencode-browser install exited with non-zero code')
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
import { execa } from 'execa'
|
|
2
|
-
import
|
|
2
|
+
import fse from 'fs-extra'
|
|
3
|
+
import { header, info, success, warn, error } from '../../utils/exec.js'
|
|
3
4
|
import os from 'os'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
4
7
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{ trigger: 'Add plugin automatically?', response: 'y' },
|
|
9
|
-
{ trigger: 'Create one?', response: 'y' },
|
|
10
|
-
{ trigger: 'Add browser-automation skill', response: 'n' },
|
|
11
|
-
{ trigger: 'Check broker', response: 'n' },
|
|
12
|
-
]
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const BROWSER_PRESET_PATH = path.resolve(__dirname, '../../presets/browser.json')
|
|
10
|
+
const browserPreset = await fse.readJson(BROWSER_PRESET_PATH)
|
|
13
11
|
|
|
14
12
|
export async function installBrowser() {
|
|
15
|
-
header('Step
|
|
13
|
+
header('Step 9, Installing opencode-browser')
|
|
16
14
|
|
|
17
15
|
try {
|
|
18
|
-
const child = execa(
|
|
16
|
+
const child = execa(browserPreset.installer.command, browserPreset.installer.args, {
|
|
19
17
|
cwd: os.homedir(),
|
|
20
18
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
21
19
|
reject: false,
|
|
22
20
|
})
|
|
23
21
|
|
|
24
|
-
const pendingTriggers = [...
|
|
22
|
+
const pendingTriggers = [...browserPreset.autoAnswers]
|
|
25
23
|
let show = false
|
|
26
24
|
|
|
27
25
|
child.stdout.on('data', (chunk) => {
|
|
28
26
|
const text = chunk.toString()
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
if (text.includes(
|
|
32
|
-
if (text.includes('Press Enter when')) show = false
|
|
28
|
+
if (text.includes(browserPreset.output.showAfter)) show = true
|
|
29
|
+
if (text.includes(browserPreset.output.hideAfter)) show = false
|
|
33
30
|
|
|
34
31
|
if (show) process.stdout.write(chunk)
|
|
35
32
|
|
|
@@ -3,6 +3,10 @@ import fse from 'fs-extra'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
|
|
6
|
+
vi.mock('@inquirer/prompts', () => ({
|
|
7
|
+
checkbox: vi.fn(),
|
|
8
|
+
}))
|
|
9
|
+
|
|
6
10
|
vi.mock('../../utils/exec.js', () => ({
|
|
7
11
|
header: vi.fn(),
|
|
8
12
|
success: vi.fn(),
|
|
@@ -11,7 +15,8 @@ vi.mock('../../utils/exec.js', () => ({
|
|
|
11
15
|
prompt: vi.fn(),
|
|
12
16
|
}))
|
|
13
17
|
|
|
14
|
-
import { success
|
|
18
|
+
import { success } from '../../utils/exec.js'
|
|
19
|
+
import { checkbox } from '@inquirer/prompts'
|
|
15
20
|
|
|
16
21
|
describe('cleanAiFiles()', () => {
|
|
17
22
|
let tmpDir
|
|
@@ -31,24 +36,22 @@ describe('cleanAiFiles()', () => {
|
|
|
31
36
|
})
|
|
32
37
|
|
|
33
38
|
it('prints success when no AI files are found', async () => {
|
|
34
|
-
const { cleanAiFiles } = await import('
|
|
35
|
-
|
|
36
|
-
// Simulate immediate Enter key
|
|
37
|
-
const stdinPush = () => process.stdin.emit('data', '\n')
|
|
38
|
-
setTimeout(stdinPush, 10)
|
|
39
|
+
const { cleanAiFiles } = await import('./index.js')
|
|
39
40
|
|
|
40
41
|
await cleanAiFiles()
|
|
41
42
|
|
|
42
43
|
expect(success).toHaveBeenCalledWith('No existing AI config files to remove')
|
|
43
44
|
})
|
|
44
45
|
|
|
45
|
-
it('removes
|
|
46
|
+
it('removes selected AI files', async () => {
|
|
46
47
|
await fse.writeFile(path.join(tmpDir, 'AGENTS.md'), '# agents')
|
|
47
48
|
await fse.writeFile(path.join(tmpDir, 'CLAUDE.md'), '# claude')
|
|
49
|
+
checkbox.mockResolvedValue([
|
|
50
|
+
path.join(tmpDir, 'AGENTS.md'),
|
|
51
|
+
path.join(tmpDir, 'CLAUDE.md'),
|
|
52
|
+
])
|
|
48
53
|
|
|
49
|
-
const { cleanAiFiles } = await import('
|
|
50
|
-
|
|
51
|
-
setTimeout(() => process.stdin.emit('data', '\n'), 10)
|
|
54
|
+
const { cleanAiFiles } = await import('./index.js')
|
|
52
55
|
|
|
53
56
|
await cleanAiFiles()
|
|
54
57
|
|
|
@@ -57,16 +60,28 @@ describe('cleanAiFiles()', () => {
|
|
|
57
60
|
expect(success).toHaveBeenCalledWith('Removed existing AI config files')
|
|
58
61
|
})
|
|
59
62
|
|
|
63
|
+
it('keeps unselected AI files', async () => {
|
|
64
|
+
await fse.writeFile(path.join(tmpDir, 'AGENTS.md'), '# agents')
|
|
65
|
+
await fse.writeFile(path.join(tmpDir, 'CLAUDE.md'), '# claude')
|
|
66
|
+
checkbox.mockResolvedValue([path.join(tmpDir, 'AGENTS.md')])
|
|
67
|
+
|
|
68
|
+
const { cleanAiFiles } = await import('./index.js')
|
|
69
|
+
|
|
70
|
+
await cleanAiFiles()
|
|
71
|
+
|
|
72
|
+
expect(await fse.pathExists(path.join(tmpDir, 'AGENTS.md'))).toBe(false)
|
|
73
|
+
expect(await fse.pathExists(path.join(tmpDir, 'CLAUDE.md'))).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
60
76
|
it('removes .agents sub-entries but preserves .agents/skills', async () => {
|
|
61
77
|
const agentsDir = path.join(tmpDir, '.agents')
|
|
62
78
|
await fse.ensureDir(path.join(agentsDir, 'agents'))
|
|
63
79
|
await fse.ensureDir(path.join(agentsDir, 'skills', 'my-skill'))
|
|
64
80
|
await fse.writeFile(path.join(agentsDir, 'agents', 'front-engineer.md'), 'agent')
|
|
65
81
|
await fse.writeFile(path.join(agentsDir, 'skills', 'my-skill', 'SKILL.md'), 'skill')
|
|
82
|
+
checkbox.mockResolvedValue([path.join(agentsDir, 'agents')])
|
|
66
83
|
|
|
67
|
-
const { cleanAiFiles } = await import('
|
|
68
|
-
|
|
69
|
-
setTimeout(() => process.stdin.emit('data', '\n'), 10)
|
|
84
|
+
const { cleanAiFiles } = await import('./index.js')
|
|
70
85
|
|
|
71
86
|
await cleanAiFiles()
|
|
72
87
|
|