opencode-onboard 0.4.3 → 0.4.5
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 +41 -40
- package/content/.agents/agents/devops-manager.md +123 -123
- package/content/.agents/skills/ob-default/SKILL.md +25 -21
- package/content/.agents/skills/ob-generic-guardrails/SKILL.md +36 -32
- package/content/.agents/skills/ob-global/SKILL.md +92 -84
- package/content/.agents/skills/ob-pullrequest-az/SKILL.md +168 -160
- package/content/.agents/skills/ob-pullrequest-gh/SKILL.md +140 -136
- package/content/.opencode/commands/create-engineer.md +109 -0
- package/content/.opencode/plugins/session-log.js +523 -519
- package/content/AGENTS.md +32 -21
- package/package.json +1 -1
- package/src/commands/wizard.js +124 -113
- package/src/presets/browser.json +22 -18
- package/src/presets/optimization.json +27 -22
- package/src/steps/browser/browser.test.js +115 -81
- package/src/steps/browser/index.js +62 -54
- package/src/steps/clean/index.js +108 -107
- package/src/steps/metadata/index.js +63 -62
- package/src/steps/models/format.js +61 -60
- package/src/steps/models/write.test.js +117 -117
- package/src/steps/openspec/ensemble.test.js +79 -79
- package/src/steps/openspec/index.js +121 -32
- package/src/steps/openspec/index.test.js +63 -0
- package/src/steps/optimization/caveman.js +34 -29
- package/src/steps/optimization/codegraph.js +103 -0
- package/src/steps/optimization/codegraph.test.js +104 -0
- package/src/steps/optimization/global.js +88 -64
- package/src/steps/optimization/global.test.js +99 -0
- package/src/steps/optimization/index.js +109 -101
- package/src/steps/optimization/optimization.test.js +101 -93
- package/src/steps/optimization/quota.js +84 -84
- package/src/steps/source/source.test.js +124 -124
- package/src/utils/__tests__/copy.test.js +117 -117
- package/src/utils/exec-spinner.js +47 -47
- package/src/utils/exec.js +134 -131
- package/src/utils/terminal.js +6 -0
package/content/AGENTS.md
CHANGED
|
@@ -65,9 +65,11 @@ The output must be a real, populated `ARCHITECTURE.md` based on what you found i
|
|
|
65
65
|
|
|
66
66
|
### Step 4, Populate OpenSpec config
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Write `openspec/config.yaml` with the real project information discovered during steps 1-3. Overwrite whatever is currently in the file. The output must contain `schema: spec-driven` and a populated `context:` block. Do not leave placeholder text.
|
|
69
69
|
|
|
70
70
|
```yaml
|
|
71
|
+
schema: spec-driven
|
|
72
|
+
|
|
71
73
|
context: |
|
|
72
74
|
Tech stack: <languages, frameworks, libraries found in the codebase>
|
|
73
75
|
Build system: <build tools, package managers>
|
|
@@ -76,7 +78,7 @@ context: |
|
|
|
76
78
|
Domain: <what this project does, in one line>
|
|
77
79
|
```
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
Replace every `<…>` with real values from the codebase. Add a `rules:` section only if the codebase has clear conventions worth enforcing (e.g., max task size, proposal format). Do not invent rules that aren't evidenced by the codebase.
|
|
80
82
|
|
|
81
83
|
---
|
|
82
84
|
|
|
@@ -184,11 +186,11 @@ Core tools used in this workflow:
|
|
|
184
186
|
If a teammate stalls due to model quota/rate-limit exhaustion:
|
|
185
187
|
1. `team_shutdown name:"<stuck-member>" force:true`
|
|
186
188
|
2. `team_spawn` same member/task with an available model
|
|
187
|
-
3. `team_message` start instruction with the exact next task ID
|
|
189
|
+
3. `team_message` start instruction with the exact next task ID
|
|
188
190
|
|
|
189
191
|
---
|
|
190
192
|
|
|
191
|
-
## Pipeline
|
|
193
|
+
## Pipeline
|
|
192
194
|
|
|
193
195
|
```
|
|
194
196
|
devops-manager (lead mode)
|
|
@@ -206,7 +208,7 @@ devops-manager (ship mode)
|
|
|
206
208
|
→ verify completion → commit → push → PR → post comment
|
|
207
209
|
```
|
|
208
210
|
|
|
209
|
-
### Phase 1, Parse & Propose
|
|
211
|
+
### Phase 1, Parse & Propose
|
|
210
212
|
|
|
211
213
|
```
|
|
212
214
|
1. Detect URL type → load matching skill (ob-userstory-gh or ob-userstory-az)
|
|
@@ -226,7 +228,7 @@ devops-manager (ship mode)
|
|
|
226
228
|
- Lead assigns next batch (up to 3) to agents that report done. Repeat until board empty.
|
|
227
229
|
- Lead merges each engineer branch after shutdown, then marks tasks done in tasks.md.
|
|
228
230
|
2. Verify with tests/build/lint according to task scope.
|
|
229
|
-
```
|
|
231
|
+
```
|
|
230
232
|
|
|
231
233
|
### Phase 3, Ship
|
|
232
234
|
|
|
@@ -280,7 +282,7 @@ Default `basic-engineer` abilities:
|
|
|
280
282
|
- Infrastructure: @ob-default
|
|
281
283
|
```
|
|
282
284
|
|
|
283
|
-
## Skills
|
|
285
|
+
## Skills
|
|
284
286
|
|
|
285
287
|
Skills provide platform and tech-specific knowledge. Agents detect and load them automatically, **you never tell an agent which skill to use**.
|
|
286
288
|
|
|
@@ -299,10 +301,10 @@ Skills are located in `.agents/skills/`. Each skill has a `SKILL.md` with a desc
|
|
|
299
301
|
| `ob-pullrequest-gh` | Create PR on GitHub |
|
|
300
302
|
| `openspec-propose` | Propose change artifacts (proposal, specs, tasks) |
|
|
301
303
|
| `openspec-apply-change` | Implement change with agent team |
|
|
302
|
-
| `openspec-archive-change` | Archive completed change |
|
|
303
|
-
| `browser-automation` | Browser automation for localhost UI, screenshots, clicks, queries |
|
|
304
|
-
|
|
305
|
-
Execution rules live in skills. Keep AGENTS.md focused on orchestration and routing.
|
|
304
|
+
| `openspec-archive-change` | Archive completed change |
|
|
305
|
+
| `browser-automation` | Browser automation for localhost UI, screenshots, clicks, queries |
|
|
306
|
+
|
|
307
|
+
Execution rules live in skills. Keep AGENTS.md focused on orchestration and routing.
|
|
306
308
|
|
|
307
309
|
---
|
|
308
310
|
|
|
@@ -343,16 +345,25 @@ When `## Source Roots` lists multiple roots, each root is an independent git rep
|
|
|
343
345
|
|
|
344
346
|
---
|
|
345
347
|
|
|
346
|
-
## Guardrails
|
|
347
|
-
|
|
348
|
-
Guardrails are mandatory via `ob-global` and ability-loaded skills.
|
|
349
|
-
|
|
350
|
-
Minimal non-negotiables:
|
|
351
|
-
- Never commit or push to `main`.
|
|
352
|
-
- Never force push.
|
|
353
|
-
- Never expose or commit secrets.
|
|
354
|
-
- Use `gh`/`az` CLI for platform operations.
|
|
355
|
-
- In multi-repo source scope, run git operations per repository.
|
|
348
|
+
## Guardrails
|
|
349
|
+
|
|
350
|
+
Guardrails are mandatory via `ob-global` and ability-loaded skills.
|
|
351
|
+
|
|
352
|
+
Minimal non-negotiables:
|
|
353
|
+
- Never commit or push to `main`.
|
|
354
|
+
- Never force push.
|
|
355
|
+
- Never expose or commit secrets.
|
|
356
|
+
- Use `gh`/`az` CLI for platform operations.
|
|
357
|
+
- In multi-repo source scope, run git operations per repository.
|
|
358
|
+
|
|
359
|
+
### Config file conflict: `opencode.jsonc` vs `.opencode/opencode.json`
|
|
360
|
+
|
|
361
|
+
This project uses `.opencode/opencode.json` as the single OpenCode configuration file. Some tools (e.g., codegraph) may create an `opencode.jsonc` file at the project root. **These two files cannot coexist.**
|
|
362
|
+
|
|
363
|
+
If you detect both `opencode.jsonc` (project root) and `.opencode/opencode.json` exist:
|
|
364
|
+
1. **Stop immediately** and warn the user: "Conflicting OpenCode config files detected. This project uses `.opencode/opencode.json` only. The root `opencode.jsonc` must be removed or its contents merged into `.opencode/opencode.json`."
|
|
365
|
+
2. Do NOT proceed with any task until the conflict is resolved.
|
|
366
|
+
3. If the user asks you to fix it: merge any `mcpServers` or other config from `opencode.jsonc` into `.opencode/opencode.json`, then delete `opencode.jsonc`.
|
|
356
367
|
|
|
357
368
|
---
|
|
358
369
|
|
package/package.json
CHANGED
package/src/commands/wizard.js
CHANGED
|
@@ -1,113 +1,124 @@
|
|
|
1
|
-
import { select as wizardSelect } from '@inquirer/prompts'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import { chooseSourceScope } from '../steps/source/index.js'
|
|
4
|
-
import { cleanAiFiles } from '../steps/clean/index.js'
|
|
5
|
-
import { choosePlatform } from '../steps/platform/index.js'
|
|
6
|
-
import { copyContentStep } from '../steps/copy/index.js'
|
|
7
|
-
import { initOpenspec } from '../steps/openspec/index.js'
|
|
8
|
-
import { chooseModels } from '../steps/models/index.js'
|
|
9
|
-
import { tokenOptimizationStep } from '../steps/optimization/index.js'
|
|
10
|
-
import { installBrowser } from '../steps/browser/index.js'
|
|
11
|
-
import { writeOnboardConfig } from '../steps/metadata/index.js'
|
|
12
|
-
|
|
13
|
-
export async function runWizard(version) {
|
|
14
|
-
const logo = chalk.hex('#fe3d57')
|
|
15
|
-
const bannerLines = [
|
|
16
|
-
logo(' '),
|
|
17
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
|
|
18
|
-
logo(' ▒▒▓ ▓▒▓ '),
|
|
19
|
-
logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
|
|
20
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
21
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
|
|
22
|
-
logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
|
|
23
|
-
logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
|
|
24
|
-
logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
|
|
25
|
-
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
26
|
-
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
27
|
-
logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
|
|
28
|
-
'',
|
|
29
|
-
chalk.bold(' 🧰 opencode-onboard') + chalk.dim(` v${version}`),
|
|
30
|
-
chalk.dim(' Prepare your codebase for AI agents'),
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
for (const line of bannerLines) console.log(line)
|
|
34
|
-
console.log()
|
|
35
|
-
console.log(' This tool will set up your project with a team of AI agents,')
|
|
36
|
-
console.log(' install skills, select models, and configure OpenCode.')
|
|
37
|
-
console.log()
|
|
38
|
-
|
|
39
|
-
// Only wait for Enter in a real interactive TTY
|
|
40
|
-
if (process.stdin.isTTY) {
|
|
41
|
-
console.log(chalk.bold(' Press Enter to begin...'))
|
|
42
|
-
console.log()
|
|
43
|
-
await new Promise(resolve => {
|
|
44
|
-
process.stdin.resume()
|
|
45
|
-
process.stdin.once('data', () => {
|
|
46
|
-
process.stdin.pause()
|
|
47
|
-
resolve()
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const scope = await chooseSourceScope()
|
|
53
|
-
|
|
54
|
-
const maxConcurrentAgents = await wizardSelect({
|
|
55
|
-
message: 'Max concurrent agents:',
|
|
56
|
-
default: 4,
|
|
57
|
-
choices: [
|
|
58
|
-
{ name: '2', value: 2, description: 'Conservative — lower resource usage' },
|
|
59
|
-
{ name: '3', value: 3, description: 'Moderate parallelism' },
|
|
60
|
-
{ name: '4 (default)', value: 4, description: 'Recommended for most projects' },
|
|
61
|
-
{ name: '5', value: 5, description: 'High parallelism — requires more resources' },
|
|
62
|
-
{ name: '6', value: 6, description: 'Maximum parallelism' },
|
|
63
|
-
],
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
await
|
|
81
|
-
|
|
82
|
-
await
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.log(
|
|
112
|
-
console.log()
|
|
113
|
-
|
|
1
|
+
import { select as wizardSelect } from '@inquirer/prompts'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { chooseSourceScope } from '../steps/source/index.js'
|
|
4
|
+
import { cleanAiFiles } from '../steps/clean/index.js'
|
|
5
|
+
import { choosePlatform } from '../steps/platform/index.js'
|
|
6
|
+
import { copyContentStep } from '../steps/copy/index.js'
|
|
7
|
+
import { initOpenspec } from '../steps/openspec/index.js'
|
|
8
|
+
import { chooseModels } from '../steps/models/index.js'
|
|
9
|
+
import { tokenOptimizationStep } from '../steps/optimization/index.js'
|
|
10
|
+
import { installBrowser } from '../steps/browser/index.js'
|
|
11
|
+
import { writeOnboardConfig } from '../steps/metadata/index.js'
|
|
12
|
+
|
|
13
|
+
export async function runWizard(version) {
|
|
14
|
+
const logo = chalk.hex('#fe3d57')
|
|
15
|
+
const bannerLines = [
|
|
16
|
+
logo(' '),
|
|
17
|
+
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
|
|
18
|
+
logo(' ▒▒▓ ▓▒▓ '),
|
|
19
|
+
logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
|
|
20
|
+
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
21
|
+
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
|
|
22
|
+
logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
|
|
23
|
+
logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
|
|
24
|
+
logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
|
|
25
|
+
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
26
|
+
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
27
|
+
logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
|
|
28
|
+
'',
|
|
29
|
+
chalk.bold(' 🧰 opencode-onboard') + chalk.dim(` v${version}`),
|
|
30
|
+
chalk.dim(' Prepare your codebase for AI agents'),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for (const line of bannerLines) console.log(line)
|
|
34
|
+
console.log()
|
|
35
|
+
console.log(' This tool will set up your project with a team of AI agents,')
|
|
36
|
+
console.log(' install skills, select models, and configure OpenCode.')
|
|
37
|
+
console.log()
|
|
38
|
+
|
|
39
|
+
// Only wait for Enter in a real interactive TTY
|
|
40
|
+
if (process.stdin.isTTY) {
|
|
41
|
+
console.log(chalk.bold(' Press Enter to begin...'))
|
|
42
|
+
console.log()
|
|
43
|
+
await new Promise(resolve => {
|
|
44
|
+
process.stdin.resume()
|
|
45
|
+
process.stdin.once('data', () => {
|
|
46
|
+
process.stdin.pause()
|
|
47
|
+
resolve()
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const scope = await chooseSourceScope()
|
|
53
|
+
|
|
54
|
+
const maxConcurrentAgents = await wizardSelect({
|
|
55
|
+
message: 'Max concurrent agents:',
|
|
56
|
+
default: 4,
|
|
57
|
+
choices: [
|
|
58
|
+
{ name: '2', value: 2, description: 'Conservative — lower resource usage' },
|
|
59
|
+
{ name: '3', value: 3, description: 'Moderate parallelism' },
|
|
60
|
+
{ name: '4 (default)', value: 4, description: 'Recommended for most projects' },
|
|
61
|
+
{ name: '5', value: 5, description: 'High parallelism — requires more resources' },
|
|
62
|
+
{ name: '6', value: 6, description: 'Maximum parallelism' },
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const installScope = await wizardSelect({
|
|
67
|
+
message: 'Install dependencies:',
|
|
68
|
+
default: 'local',
|
|
69
|
+
choices: [
|
|
70
|
+
{ name: 'Locally (default)', value: 'local', description: 'Install tools project-locally where possible' },
|
|
71
|
+
{ name: 'Globally', value: 'global', description: 'Install tools globally (affects all projects on this machine)' },
|
|
72
|
+
],
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const preserve = await cleanAiFiles()
|
|
76
|
+
const ctx = { ...preserve, ...scope, maxConcurrentAgents, installScope }
|
|
77
|
+
|
|
78
|
+
const platform = await choosePlatform()
|
|
79
|
+
|
|
80
|
+
await copyContentStep(platform, ctx)
|
|
81
|
+
|
|
82
|
+
const openspec = await initOpenspec()
|
|
83
|
+
|
|
84
|
+
const selectedModels = await chooseModels()
|
|
85
|
+
|
|
86
|
+
const tokenOpt = await tokenOptimizationStep({ ctx })
|
|
87
|
+
const { rtk, quota, caveman, cavemanGuidance } = tokenOpt
|
|
88
|
+
|
|
89
|
+
await installBrowser(ctx)
|
|
90
|
+
|
|
91
|
+
await writeOnboardConfig({
|
|
92
|
+
...ctx,
|
|
93
|
+
platform,
|
|
94
|
+
openspec,
|
|
95
|
+
maxConcurrentAgents,
|
|
96
|
+
installScope,
|
|
97
|
+
additionalSkillsProvider: 'npx-skills',
|
|
98
|
+
...selectedModels,
|
|
99
|
+
optionalTools: { rtk, quota, caveman },
|
|
100
|
+
cavemanGuidance,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const toGenerate = [
|
|
104
|
+
!ctx.hasDesign && 'DESIGN.md',
|
|
105
|
+
!ctx.hasArchitecture && 'ARCHITECTURE.md',
|
|
106
|
+
].filter(Boolean)
|
|
107
|
+
|
|
108
|
+
console.log()
|
|
109
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
110
|
+
console.log(chalk.bold.green(' Onboarding complete!'))
|
|
111
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
112
|
+
console.log()
|
|
113
|
+
console.log(' Open this project in OpenCode and type:')
|
|
114
|
+
console.log(chalk.bold(' "init"'))
|
|
115
|
+
console.log()
|
|
116
|
+
if (toGenerate.length > 0) {
|
|
117
|
+
console.log(` OpenCode will generate ${toGenerate.join(' and ')}`)
|
|
118
|
+
console.log(' from your actual codebase, then activate the agent team.')
|
|
119
|
+
} else {
|
|
120
|
+
console.log(' OpenCode will activate the agent team.')
|
|
121
|
+
}
|
|
122
|
+
console.log(` Source scope: ${ctx.sourceMode === 'parent-selected' ? ctx.sourceRoots.map(p => `../${p.split(/[/\\]/).pop()}`).join(', ') : 'current folder'}`)
|
|
123
|
+
console.log()
|
|
124
|
+
}
|
package/src/presets/browser.json
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
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
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{ "trigger": "
|
|
16
|
-
{ "trigger": "
|
|
17
|
-
|
|
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
|
+
"locationChoices": {
|
|
11
|
+
"local": "2",
|
|
12
|
+
"global": "1"
|
|
13
|
+
},
|
|
14
|
+
"autoAnswers": [
|
|
15
|
+
{ "trigger": "Press Enter when", "response": "" },
|
|
16
|
+
{ "trigger": "Choose config location", "response": "__LOCATION__" },
|
|
17
|
+
{ "trigger": "Add plugin automatically?", "response": "y" },
|
|
18
|
+
{ "trigger": "Create one?", "response": "y" },
|
|
19
|
+
{ "trigger": "Add browser-automation skill", "response": "n" },
|
|
20
|
+
{ "trigger": "Check broker", "response": "n" }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -1,22 +1,27 @@
|
|
|
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
|
+
{
|
|
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
|
+
"name": "codegraph semantic index (recommended)",
|
|
23
|
+
"value": "codegraph",
|
|
24
|
+
"checked": true
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -1,81 +1,115 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { installBrowser } from './index.js'
|
|
3
|
+
|
|
4
|
+
vi.mock('../../utils/exec.js', () => ({
|
|
5
|
+
header: vi.fn(),
|
|
6
|
+
info: vi.fn(),
|
|
7
|
+
success: vi.fn(),
|
|
8
|
+
warn: vi.fn(),
|
|
9
|
+
error: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
vi.mock('fs-extra', () => ({
|
|
13
|
+
default: {
|
|
14
|
+
readJson: vi.fn().mockResolvedValue({
|
|
15
|
+
installer: { command: 'npx', args: ['@different-ai/opencode-browser', 'install'] },
|
|
16
|
+
output: { showAfter: '===', hideAfter: '===' },
|
|
17
|
+
locationChoices: { local: '2', global: '1' },
|
|
18
|
+
autoAnswers: [
|
|
19
|
+
{ trigger: 'Install', response: 'y' },
|
|
20
|
+
{ trigger: 'Choose config location', response: '__LOCATION__' },
|
|
21
|
+
],
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
vi.mock('execa', () => ({
|
|
27
|
+
execa: vi.fn(),
|
|
28
|
+
}))
|
|
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
|
+
|
|
82
|
+
it('resolves __LOCATION__ to local answer by default', async () => {
|
|
83
|
+
const { execa } = await import('execa')
|
|
84
|
+
let capturedTriggers = null
|
|
85
|
+
const mockChild = {
|
|
86
|
+
stdout: { on: vi.fn((_, cb) => { capturedTriggers = cb }) },
|
|
87
|
+
stderr: { on: vi.fn() },
|
|
88
|
+
stdin: { write: vi.fn() },
|
|
89
|
+
then: (cb) => cb({ exitCode: 0 }),
|
|
90
|
+
}
|
|
91
|
+
execa.mockReturnValue(mockChild)
|
|
92
|
+
|
|
93
|
+
await installBrowser()
|
|
94
|
+
|
|
95
|
+
if (capturedTriggers) capturedTriggers(Buffer.from('Choose config location'))
|
|
96
|
+
expect(mockChild.stdin.write).toHaveBeenCalledWith('2\n')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('resolves __LOCATION__ to global answer when installScope is global', async () => {
|
|
100
|
+
const { execa } = await import('execa')
|
|
101
|
+
let capturedTriggers = null
|
|
102
|
+
const mockChild = {
|
|
103
|
+
stdout: { on: vi.fn((_, cb) => { capturedTriggers = cb }) },
|
|
104
|
+
stderr: { on: vi.fn() },
|
|
105
|
+
stdin: { write: vi.fn() },
|
|
106
|
+
then: (cb) => cb({ exitCode: 0 }),
|
|
107
|
+
}
|
|
108
|
+
execa.mockReturnValue(mockChild)
|
|
109
|
+
|
|
110
|
+
await installBrowser({ installScope: 'global' })
|
|
111
|
+
|
|
112
|
+
if (capturedTriggers) capturedTriggers(Buffer.from('Choose config location'))
|
|
113
|
+
expect(mockChild.stdin.write).toHaveBeenCalledWith('1\n')
|
|
114
|
+
})
|
|
115
|
+
})
|