opencode-onboard 0.4.1 → 0.4.3
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/LICENSE +21 -0
- package/README.md +303 -289
- package/content/.agents/agents/basic-engineer.md +4 -2
- package/content/.agents/skills/ob-global/SKILL.md +35 -0
- package/content/.opencode/commands/init.md +8 -0
- package/content/.opencode/commands/main.md +17 -0
- package/content/.opencode/commands/opsx-apply.md +131 -70
- package/content/.opencode/commands/plan.md +37 -0
- package/content/.opencode/skills/openspec-apply-change/SKILL.md +86 -64
- package/content/AGENTS.md +48 -22
- package/package.json +1 -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 +66 -0
- package/src/commands/wizard.js +113 -0
- package/src/index.js +25 -168
- package/src/presets/source.json +7 -1
- package/src/steps/browser/browser.test.js +1 -1
- package/src/steps/copy/agents.js +28 -0
- package/src/steps/copy/copy.test.js +1 -0
- package/src/steps/copy/index.js +2 -1
- package/src/steps/metadata/index.js +1 -0
- package/src/steps/metadata/metadata.test.js +8 -5
- package/src/steps/models/format.test.js +8 -7
- package/src/steps/models/write.test.js +11 -13
- package/src/steps/openspec/ensemble.js +30 -7
- package/src/steps/optimization/optimization.test.js +20 -0
- package/src/steps/platform/platform.test.js +1 -1
- package/src/steps/source/index.js +48 -0
- package/src/steps/source/source.test.js +38 -3
- package/src/utils/models-pricing.test.js +0 -1
package/content/AGENTS.md
CHANGED
|
@@ -63,13 +63,30 @@ The output must be a real, populated `ARCHITECTURE.md` based on what you found i
|
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
66
|
-
### Step 4,
|
|
66
|
+
### Step 4, Populate OpenSpec config
|
|
67
|
+
|
|
68
|
+
Read `openspec/config.yaml`. It contains a template with commented-out examples. Fill in the `context:` field with real project information discovered during steps 1-3:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
context: |
|
|
72
|
+
Tech stack: <languages, frameworks, libraries found in the codebase>
|
|
73
|
+
Build system: <build tools, package managers>
|
|
74
|
+
Architecture: <monolith, microservices, monorepo, etc.>
|
|
75
|
+
Conventions: <coding style, commit conventions, branching strategy if found>
|
|
76
|
+
Domain: <what this project does, in one line>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Keep the `schema: spec-driven` line. Add `rules:` 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
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### Step 5, Rewrite this file
|
|
67
84
|
|
|
68
85
|
Replace the entire contents of this file (`AGENTS.md`) with everything below the line `<!-- AGENTS-TEMPLATE-START -->` in this same file. Delete the bootstrap section and the template marker, the file should contain only the template content when done.
|
|
69
86
|
|
|
70
87
|
---
|
|
71
88
|
|
|
72
|
-
### Step
|
|
89
|
+
### Step 6, Confirm
|
|
73
90
|
|
|
74
91
|
Tell the user:
|
|
75
92
|
|
|
@@ -80,6 +97,7 @@ Tell the user:
|
|
|
80
97
|
|
|
81
98
|
- ARCHITECTURE.md generated
|
|
82
99
|
- DESIGN.md generated
|
|
100
|
+
- openspec/config.yaml populated
|
|
83
101
|
- Project history archived in openspec
|
|
84
102
|
- AGENTS.md updated with real guidance
|
|
85
103
|
|
|
@@ -98,7 +116,7 @@ After restarting you are ready to work.
|
|
|
98
116
|
- Do NOT create branches or PRs
|
|
99
117
|
- Do NOT modify any project source files
|
|
100
118
|
- Do NOT create CLI wrapper files or scripts
|
|
101
|
-
- Only read source files for analysis, write only to ARCHITECTURE.md, DESIGN.md, AGENTS.md, and openspec/
|
|
119
|
+
- Only read source files for analysis, write only to ARCHITECTURE.md, DESIGN.md, AGENTS.md, openspec/config.yaml, and openspec/
|
|
102
120
|
|
|
103
121
|
<!-- AGENTS-TEMPLATE-START -->
|
|
104
122
|
# AGENTS.md
|
|
@@ -138,18 +156,25 @@ Trigger patterns, I recognize ALL of these, exact wording does not matter:
|
|
|
138
156
|
|
|
139
157
|
**Never delegate without a plan. Never write implementation code directly, always spawn specialists, no exceptions. "Small feature", "faster to do it directly", or "environment issues" are not valid reasons to skip ensemble.**
|
|
140
158
|
|
|
141
|
-
## Multi-Agent Execution, opencode-ensemble
|
|
142
|
-
|
|
143
|
-
Parallel execution uses the `opencode-ensemble` plugin (`team_create`, `team_spawn`, etc.).
|
|
144
|
-
Works on **all platforms** (Windows, macOS, Linux) via OpenCode's built-in worktree support.
|
|
145
|
-
|
|
146
|
-
Core tools used in this workflow:
|
|
147
|
-
- `team_create`, `team_spawn`, `team_shutdown`, `team_merge`, `team_cleanup`
|
|
148
|
-
- `team_tasks_add`, `team_tasks_list`, `team_claim`, `team_tasks_complete`
|
|
149
|
-
- `team_message`, `team_results`, `team_status`
|
|
159
|
+
## Multi-Agent Execution, opencode-ensemble
|
|
160
|
+
|
|
161
|
+
Parallel execution uses the `opencode-ensemble` plugin (`team_create`, `team_spawn`, etc.).
|
|
162
|
+
Works on **all platforms** (Windows, macOS, Linux) via OpenCode's built-in worktree support.
|
|
163
|
+
|
|
164
|
+
Core tools used in this workflow:
|
|
165
|
+
- `team_create`, `team_spawn`, `team_shutdown`, `team_merge`, `team_cleanup`
|
|
166
|
+
- `team_tasks_add`, `team_tasks_list`, `team_claim`, `team_tasks_complete`
|
|
167
|
+
- `team_message`, `team_results`, `team_status`
|
|
150
168
|
|
|
151
169
|
**Dashboard**: Monitor running agents at **http://localhost:4747/**
|
|
152
170
|
|
|
171
|
+
**Hard limits:**
|
|
172
|
+
- **Max {{MAX_CONCURRENT_AGENTS}} truly concurrent agents.** All {{MAX_CONCURRENT_AGENTS}} must be spawned and running simultaneously, not sequentially. Spawn in waves if more than {{MAX_CONCURRENT_AGENTS}} are needed. Wait for wave N to finish before spawning wave N+1.
|
|
173
|
+
- **Non-overlapping file domains.** Each agent owns exclusive directories. Two agents must NEVER touch the same file.
|
|
174
|
+
- **Immediate shutdown on completion.** The moment an agent's domain has no more pending tasks → `team_shutdown` → `team_merge`. Keep agents alive if more tasks in their domain are pending (rolling batch).
|
|
175
|
+
- **Rolling batch assignment.** Agents receive up to 3 tasks initially. When they complete a batch, lead assigns the next batch of up to 3 from the board. Never leave pending tasks orphaned.
|
|
176
|
+
- **Stall detection at 5 minutes.** No commits after 5 min → nudge message → 2 min grace → force shutdown + respawn.
|
|
177
|
+
|
|
153
178
|
**Progress inspection commands (tell user explicitly after spawning):**
|
|
154
179
|
- `team_status` for live team snapshot
|
|
155
180
|
- `team_tasks_list` for task board state
|
|
@@ -159,7 +184,7 @@ Core tools used in this workflow:
|
|
|
159
184
|
If a teammate stalls due to model quota/rate-limit exhaustion:
|
|
160
185
|
1. `team_shutdown name:"<stuck-member>" force:true`
|
|
161
186
|
2. `team_spawn` same member/task with an available model
|
|
162
|
-
3. `team_message` start instruction with the exact next task ID
|
|
187
|
+
3. `team_message` start instruction with the exact next task ID
|
|
163
188
|
|
|
164
189
|
---
|
|
165
190
|
|
|
@@ -191,15 +216,16 @@ devops-manager (ship mode)
|
|
|
191
216
|
5. STOP. Ask user: "Ready to implement? (yes/no)", DO NOT proceed until confirmed.
|
|
192
217
|
```
|
|
193
218
|
|
|
194
|
-
### Phase 2, Implement
|
|
195
|
-
|
|
196
|
-
```
|
|
197
|
-
1. Run /opsx-apply.
|
|
198
|
-
- Lead adds all tasks to board.
|
|
199
|
-
- Lead spawns
|
|
200
|
-
- Each engineer
|
|
201
|
-
- Lead
|
|
202
|
-
|
|
219
|
+
### Phase 2, Implement
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
1. Run /opsx-apply.
|
|
223
|
+
- Lead adds all tasks to board.
|
|
224
|
+
- Lead spawns engineers with initial batch of up to 3 tasks each (rolling batch model).
|
|
225
|
+
- Each engineer claims tasks, implements, completes, messages lead.
|
|
226
|
+
- Lead assigns next batch (up to 3) to agents that report done. Repeat until board empty.
|
|
227
|
+
- Lead merges each engineer branch after shutdown, then marks tasks done in tasks.md.
|
|
228
|
+
2. Verify with tests/build/lint according to task scope.
|
|
203
229
|
```
|
|
204
230
|
|
|
205
231
|
### Phase 3, Ship
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { installBrowser } from '../steps/browser/index.js'
|
|
3
|
+
import { checkRtk } from '../steps/optimization/index.js'
|
|
4
|
+
import { checkPlatform, choosePlatform } from '../steps/platform/index.js'
|
|
5
|
+
import { header, info } from '../utils/exec.js'
|
|
6
|
+
import { readOnboardConfig } from './shared.js'
|
|
7
|
+
|
|
8
|
+
export async function runJoin() {
|
|
9
|
+
const logo = chalk.hex('#fe3d57')
|
|
10
|
+
console.log()
|
|
11
|
+
console.log(logo(' 🤝 opencode-onboard join'))
|
|
12
|
+
console.log(chalk.dim(' New team member setup, checks & local installs only'))
|
|
13
|
+
console.log(chalk.dim(' This will NOT modify any project files.'))
|
|
14
|
+
console.log()
|
|
15
|
+
|
|
16
|
+
// Step 1: Platform CLI check
|
|
17
|
+
header('Step 1, Platform CLI check')
|
|
18
|
+
const saved = await readOnboardConfig()
|
|
19
|
+
const savedPlatform = saved?.wizard?.platform
|
|
20
|
+
if (savedPlatform) {
|
|
21
|
+
info(`Detected project platform: ${savedPlatform === 'github' ? 'GitHub' : 'Azure DevOps'}`)
|
|
22
|
+
await checkPlatform(savedPlatform)
|
|
23
|
+
} else {
|
|
24
|
+
const platform = await choosePlatform()
|
|
25
|
+
void platform // result not persisted in join mode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Step 2: rtk check
|
|
29
|
+
header('Step 2, Checking rtk')
|
|
30
|
+
await checkRtk({ skipHeader: true })
|
|
31
|
+
|
|
32
|
+
// Step 3: Browser extension
|
|
33
|
+
await installBrowser()
|
|
34
|
+
|
|
35
|
+
console.log()
|
|
36
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
37
|
+
console.log(chalk.bold.green(' Join setup complete!'))
|
|
38
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
39
|
+
console.log()
|
|
40
|
+
console.log(' Your local environment is ready.')
|
|
41
|
+
console.log(' Open the project in OpenCode and start coding!')
|
|
42
|
+
console.log()
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fse from 'fs-extra'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
export async function readOnboardConfig() {
|
|
5
|
+
const cfgPath = path.join(process.cwd(), '.opencode', 'opencode-onboard.json')
|
|
6
|
+
if (!await fse.pathExists(cfgPath)) return null
|
|
7
|
+
try {
|
|
8
|
+
return await fse.readJson(cfgPath)
|
|
9
|
+
} catch {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import { readOnboardConfig } from './shared.js'
|
|
6
|
+
|
|
7
|
+
describe('readOnboardConfig()', () => {
|
|
8
|
+
let tmpDir, originalCwd
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'shared-test-'))
|
|
12
|
+
originalCwd = process.cwd()
|
|
13
|
+
process.chdir(tmpDir)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
process.chdir(originalCwd)
|
|
18
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('returns null when config file does not exist', async () => {
|
|
22
|
+
const result = await readOnboardConfig()
|
|
23
|
+
|
|
24
|
+
expect(result).toBeNull()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('returns parsed config when file exists', async () => {
|
|
28
|
+
const configDir = path.join(tmpDir, '.opencode')
|
|
29
|
+
fs.mkdirSync(configDir)
|
|
30
|
+
fs.writeFileSync(
|
|
31
|
+
path.join(configDir, 'opencode-onboard.json'),
|
|
32
|
+
JSON.stringify({ schema: 1, wizard: { platform: 'github' } }),
|
|
33
|
+
'utf-8'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const result = await readOnboardConfig()
|
|
37
|
+
|
|
38
|
+
expect(result).not.toBeNull()
|
|
39
|
+
expect(result.schema).toBe(1)
|
|
40
|
+
expect(result.wizard.platform).toBe('github')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('returns null when file contains invalid JSON', async () => {
|
|
44
|
+
const configDir = path.join(tmpDir, '.opencode')
|
|
45
|
+
fs.mkdirSync(configDir)
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
path.join(configDir, 'opencode-onboard.json'),
|
|
48
|
+
'not valid json',
|
|
49
|
+
'utf-8'
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const result = await readOnboardConfig()
|
|
53
|
+
|
|
54
|
+
expect(result).toBeNull()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { cleanAiFiles } from '../steps/clean/index.js'
|
|
2
|
+
import { copyContentStep } from '../steps/copy/index.js'
|
|
3
|
+
import { chooseModels } from '../steps/models/index.js'
|
|
4
|
+
import { initOpenspec } from '../steps/openspec/index.js'
|
|
5
|
+
import { tokenOptimizationStep } from '../steps/optimization/index.js'
|
|
6
|
+
import { choosePlatform } from '../steps/platform/index.js'
|
|
7
|
+
import { installBrowser } from '../steps/browser/index.js'
|
|
8
|
+
import { writeOnboardConfig } from '../steps/metadata/index.js'
|
|
9
|
+
import { readOnboardConfig } from './shared.js'
|
|
10
|
+
|
|
11
|
+
export async function runSingleCommand(command) {
|
|
12
|
+
const saved = await readOnboardConfig()
|
|
13
|
+
const savedWizard = saved?.wizard ?? {}
|
|
14
|
+
const ctx = {
|
|
15
|
+
hasDesign: !!savedWizard?.preserved?.design,
|
|
16
|
+
hasArchitecture: !!savedWizard?.preserved?.architecture,
|
|
17
|
+
hasOpenspec: !!savedWizard?.preserved?.openspec,
|
|
18
|
+
sourceMode: savedWizard?.sourceMode ?? 'current',
|
|
19
|
+
sourceRoots: Array.isArray(savedWizard?.sourceRoots) ? savedWizard.sourceRoots : [],
|
|
20
|
+
maxConcurrentAgents: savedWizard?.maxConcurrentAgents ?? 4,
|
|
21
|
+
}
|
|
22
|
+
const platform = savedWizard?.platform
|
|
23
|
+
const resolvedPlatform = platform === 'azure' || platform === 'github' ? platform : 'github'
|
|
24
|
+
|
|
25
|
+
const handlers = {
|
|
26
|
+
clean: async () => {
|
|
27
|
+
await cleanAiFiles()
|
|
28
|
+
},
|
|
29
|
+
platform: async () => {
|
|
30
|
+
await choosePlatform()
|
|
31
|
+
},
|
|
32
|
+
copy: async () => {
|
|
33
|
+
await copyContentStep(resolvedPlatform, ctx)
|
|
34
|
+
},
|
|
35
|
+
openspec: async () => {
|
|
36
|
+
await initOpenspec()
|
|
37
|
+
},
|
|
38
|
+
models: async () => {
|
|
39
|
+
await chooseModels()
|
|
40
|
+
},
|
|
41
|
+
optimization: async () => {
|
|
42
|
+
await tokenOptimizationStep({ ctx })
|
|
43
|
+
},
|
|
44
|
+
browser: async () => {
|
|
45
|
+
await installBrowser()
|
|
46
|
+
},
|
|
47
|
+
metadata: async () => {
|
|
48
|
+
await writeOnboardConfig({
|
|
49
|
+
...ctx,
|
|
50
|
+
platform: resolvedPlatform,
|
|
51
|
+
maxConcurrentAgents: savedWizard?.maxConcurrentAgents ?? 4,
|
|
52
|
+
additionalSkillsProvider: 'npx-skills',
|
|
53
|
+
planModel: savedWizard?.models?.plan ?? null,
|
|
54
|
+
buildModel: savedWizard?.models?.build ?? null,
|
|
55
|
+
fastModel: savedWizard?.models?.fast ?? null,
|
|
56
|
+
optionalTools: savedWizard?.optionalTools ?? null,
|
|
57
|
+
cavemanGuidance: savedWizard?.cavemanGuidance ?? null,
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handler = handlers[command]
|
|
63
|
+
if (!handler) return false
|
|
64
|
+
await handler()
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
@@ -0,0 +1,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 preserve = await cleanAiFiles()
|
|
67
|
+
const ctx = { ...preserve, ...scope, maxConcurrentAgents }
|
|
68
|
+
|
|
69
|
+
const platform = await choosePlatform()
|
|
70
|
+
|
|
71
|
+
await copyContentStep(platform, ctx)
|
|
72
|
+
|
|
73
|
+
await initOpenspec()
|
|
74
|
+
|
|
75
|
+
const selectedModels = await chooseModels()
|
|
76
|
+
|
|
77
|
+
const tokenOpt = await tokenOptimizationStep({ ctx })
|
|
78
|
+
const { rtk, quota, caveman, cavemanGuidance } = tokenOpt
|
|
79
|
+
|
|
80
|
+
await installBrowser()
|
|
81
|
+
|
|
82
|
+
await writeOnboardConfig({
|
|
83
|
+
...ctx,
|
|
84
|
+
platform,
|
|
85
|
+
maxConcurrentAgents,
|
|
86
|
+
additionalSkillsProvider: 'npx-skills',
|
|
87
|
+
...selectedModels,
|
|
88
|
+
optionalTools: { rtk, quota, caveman },
|
|
89
|
+
cavemanGuidance,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const toGenerate = [
|
|
93
|
+
!ctx.hasDesign && 'DESIGN.md',
|
|
94
|
+
!ctx.hasArchitecture && 'ARCHITECTURE.md',
|
|
95
|
+
].filter(Boolean)
|
|
96
|
+
|
|
97
|
+
console.log()
|
|
98
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
99
|
+
console.log(chalk.bold.green(' Onboarding complete!'))
|
|
100
|
+
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
101
|
+
console.log()
|
|
102
|
+
console.log(' Open this project in OpenCode and type:')
|
|
103
|
+
console.log(chalk.bold(' "init"'))
|
|
104
|
+
console.log()
|
|
105
|
+
if (toGenerate.length > 0) {
|
|
106
|
+
console.log(` OpenCode will generate ${toGenerate.join(' and ')}`)
|
|
107
|
+
console.log(' from your actual codebase, then activate the agent team.')
|
|
108
|
+
} else {
|
|
109
|
+
console.log(' OpenCode will activate the agent team.')
|
|
110
|
+
}
|
|
111
|
+
console.log(` Source scope: ${ctx.sourceMode === 'parent-selected' ? ctx.sourceRoots.map(p => `../${p.split(/[/\\]/).pop()}`).join(', ') : 'current folder'}`)
|
|
112
|
+
console.log()
|
|
113
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,17 +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 { copyContentStep } from './steps/copy/index.js'
|
|
9
|
-
import { chooseModels } from './steps/models/index.js'
|
|
10
|
-
import { initOpenspec } from './steps/openspec/index.js'
|
|
11
|
-
import { tokenOptimizationStep } from './steps/optimization/index.js'
|
|
12
|
-
import { choosePlatform } from './steps/platform/index.js'
|
|
13
|
-
import { chooseSourceScope } from './steps/source/index.js'
|
|
14
|
-
import { writeOnboardConfig } from './steps/metadata/index.js'
|
|
4
|
+
import { runJoin } from './commands/join.js'
|
|
5
|
+
import { runSingleCommand } from './commands/single.js'
|
|
6
|
+
import { runWizard } from './commands/wizard.js'
|
|
15
7
|
|
|
16
8
|
function printHelp(version) {
|
|
17
9
|
console.log(`opencode-onboard v${version}`)
|
|
@@ -21,6 +13,7 @@ function printHelp(version) {
|
|
|
21
13
|
console.log(' npx opencode-onboard <command> Run a single step command')
|
|
22
14
|
console.log()
|
|
23
15
|
console.log('Commands:')
|
|
16
|
+
console.log(' join New team member setup (checks & local installs only)')
|
|
24
17
|
console.log(' clean Run AI files cleanup step')
|
|
25
18
|
console.log(' platform Run platform selection step')
|
|
26
19
|
console.log(' copy Run content copy step')
|
|
@@ -34,71 +27,6 @@ function printHelp(version) {
|
|
|
34
27
|
console.log(' -h, --help Show this help message')
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
async function readOnboardConfig() {
|
|
38
|
-
const cfgPath = path.join(process.cwd(), '.opencode', 'opencode-onboard.json')
|
|
39
|
-
if (!await fse.pathExists(cfgPath)) return null
|
|
40
|
-
try {
|
|
41
|
-
return await fse.readJson(cfgPath)
|
|
42
|
-
} catch {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function runSingleCommand(command) {
|
|
48
|
-
const saved = await readOnboardConfig()
|
|
49
|
-
const savedWizard = saved?.wizard ?? {}
|
|
50
|
-
const ctx = {
|
|
51
|
-
hasDesign: !!savedWizard?.preserved?.design,
|
|
52
|
-
hasArchitecture: !!savedWizard?.preserved?.architecture,
|
|
53
|
-
hasOpenspec: !!savedWizard?.preserved?.openspec,
|
|
54
|
-
sourceMode: savedWizard?.sourceMode ?? 'current',
|
|
55
|
-
sourceRoots: Array.isArray(savedWizard?.sourceRoots) ? savedWizard.sourceRoots : [],
|
|
56
|
-
}
|
|
57
|
-
const platform = savedWizard?.platform
|
|
58
|
-
const resolvedPlatform = platform === 'azure' || platform === 'github' ? platform : 'github'
|
|
59
|
-
|
|
60
|
-
const handlers = {
|
|
61
|
-
clean: async () => {
|
|
62
|
-
await cleanAiFiles()
|
|
63
|
-
},
|
|
64
|
-
platform: async () => {
|
|
65
|
-
await choosePlatform()
|
|
66
|
-
},
|
|
67
|
-
copy: async () => {
|
|
68
|
-
await copyContentStep(resolvedPlatform, ctx)
|
|
69
|
-
},
|
|
70
|
-
openspec: async () => {
|
|
71
|
-
await initOpenspec()
|
|
72
|
-
},
|
|
73
|
-
models: async () => {
|
|
74
|
-
await chooseModels()
|
|
75
|
-
},
|
|
76
|
-
optimization: async () => {
|
|
77
|
-
await tokenOptimizationStep({ ctx })
|
|
78
|
-
},
|
|
79
|
-
browser: async () => {
|
|
80
|
-
await installBrowser()
|
|
81
|
-
},
|
|
82
|
-
metadata: async () => {
|
|
83
|
-
await writeOnboardConfig({
|
|
84
|
-
...ctx,
|
|
85
|
-
platform: resolvedPlatform,
|
|
86
|
-
additionalSkillsProvider: 'npx-skills',
|
|
87
|
-
planModel: savedWizard?.models?.plan ?? null,
|
|
88
|
-
buildModel: savedWizard?.models?.build ?? null,
|
|
89
|
-
fastModel: savedWizard?.models?.fast ?? null,
|
|
90
|
-
optionalTools: savedWizard?.optionalTools ?? null,
|
|
91
|
-
cavemanGuidance: savedWizard?.cavemanGuidance ?? null,
|
|
92
|
-
})
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const handler = handlers[command]
|
|
97
|
-
if (!handler) return false
|
|
98
|
-
await handler()
|
|
99
|
-
return true
|
|
100
|
-
}
|
|
101
|
-
|
|
102
30
|
if (process.stdout.isTTY) console.clear()
|
|
103
31
|
console.log()
|
|
104
32
|
const require = createRequire(import.meta.url)
|
|
@@ -111,103 +39,32 @@ if (args.includes('-h') || args.includes('--help')) {
|
|
|
111
39
|
}
|
|
112
40
|
|
|
113
41
|
if (args.length > 0) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
62
|
}
|
|
121
63
|
process.exit(0)
|
|
122
64
|
}
|
|
123
65
|
|
|
124
|
-
const logo = chalk.hex('#fe3d57')
|
|
125
|
-
const bannerLines = [
|
|
126
|
-
logo(' '),
|
|
127
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
|
|
128
|
-
logo(' ▒▒▓ ▓▒▓ '),
|
|
129
|
-
logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
|
|
130
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
131
|
-
logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
|
|
132
|
-
logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
|
|
133
|
-
logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
|
|
134
|
-
logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
|
|
135
|
-
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
136
|
-
logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
|
|
137
|
-
logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
|
|
138
|
-
'',
|
|
139
|
-
chalk.bold(' 🧰 opencode-onboard') + chalk.dim(` v${version}`),
|
|
140
|
-
chalk.dim(' Prepare your codebase for AI agents'),
|
|
141
|
-
]
|
|
142
|
-
|
|
143
|
-
for (const line of bannerLines) console.log(line)
|
|
144
|
-
console.log()
|
|
145
|
-
console.log(' This tool will set up your project with a team of AI agents,')
|
|
146
|
-
console.log(' install skills, select models, and configure OpenCode.')
|
|
147
|
-
console.log()
|
|
148
|
-
|
|
149
|
-
// Only wait for Enter in a real interactive TTY
|
|
150
|
-
if (process.stdin.isTTY) {
|
|
151
|
-
console.log(chalk.bold(' Press Enter to begin...'))
|
|
152
|
-
console.log()
|
|
153
|
-
await new Promise(resolve => {
|
|
154
|
-
process.stdin.resume()
|
|
155
|
-
process.stdin.once('data', () => {
|
|
156
|
-
process.stdin.pause()
|
|
157
|
-
resolve()
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
66
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const preserve = await cleanAiFiles()
|
|
166
|
-
const ctx = { ...preserve, ...scope }
|
|
167
|
-
|
|
168
|
-
const platform = await choosePlatform()
|
|
169
|
-
|
|
170
|
-
await copyContentStep(platform, ctx)
|
|
171
|
-
|
|
172
|
-
await initOpenspec()
|
|
173
|
-
|
|
174
|
-
const selectedModels = await chooseModels()
|
|
175
|
-
|
|
176
|
-
const tokenOpt = await tokenOptimizationStep({ ctx })
|
|
177
|
-
const { rtk, quota, caveman, cavemanGuidance } = tokenOpt
|
|
178
|
-
|
|
179
|
-
await installBrowser()
|
|
180
|
-
|
|
181
|
-
await writeOnboardConfig({
|
|
182
|
-
...ctx,
|
|
183
|
-
platform,
|
|
184
|
-
additionalSkillsProvider: 'npx-skills',
|
|
185
|
-
...selectedModels,
|
|
186
|
-
optionalTools: { rtk, quota, caveman },
|
|
187
|
-
cavemanGuidance,
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
const toGenerate = [
|
|
191
|
-
!ctx.hasDesign && 'DESIGN.md',
|
|
192
|
-
!ctx.hasArchitecture && 'ARCHITECTURE.md',
|
|
193
|
-
].filter(Boolean)
|
|
194
|
-
|
|
195
|
-
console.log()
|
|
196
|
-
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
197
|
-
console.log(chalk.bold.green(' Onboarding complete!'))
|
|
198
|
-
console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
|
|
199
|
-
console.log()
|
|
200
|
-
console.log(' Open this project in OpenCode and type:')
|
|
201
|
-
console.log(chalk.bold(' "init"'))
|
|
202
|
-
console.log()
|
|
203
|
-
if (toGenerate.length > 0) {
|
|
204
|
-
console.log(` OpenCode will generate ${toGenerate.join(' and ')}`)
|
|
205
|
-
console.log(' from your actual codebase, then activate the agent team.')
|
|
206
|
-
} else {
|
|
207
|
-
console.log(' OpenCode will activate the agent team.')
|
|
208
|
-
}
|
|
209
|
-
console.log(` Source scope: ${ctx.sourceMode === 'parent-selected' ? ctx.sourceRoots.map(p => `../${p.split(/[/\\]/).pop()}`).join(', ') : 'current folder'}`)
|
|
210
|
-
console.log()
|
|
67
|
+
await runWizard(version)
|
|
211
68
|
} catch (err) {
|
|
212
69
|
if (err.name === 'ExitPromptError') {
|
|
213
70
|
console.log()
|
package/src/presets/source.json
CHANGED
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"name": "Select folders in parent (../)",
|
|
12
12
|
"value": "parent",
|
|
13
13
|
"description": "Use when this repo only contains agent config"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "Select child folders (./*/)",
|
|
17
|
+
"value": "children",
|
|
18
|
+
"description": "Use when source code lives in subdirectories of this repo"
|
|
14
19
|
}
|
|
15
20
|
],
|
|
16
|
-
"parentSelectionMessage": "Select source folders from parent directory:"
|
|
21
|
+
"parentSelectionMessage": "Select source folders from parent directory:",
|
|
22
|
+
"childrenSelectionMessage": "Select child folders to include as source:"
|
|
17
23
|
}
|
|
@@ -44,7 +44,7 @@ describe('installBrowser()', () => {
|
|
|
44
44
|
|
|
45
45
|
await installBrowser()
|
|
46
46
|
|
|
47
|
-
expect(execa).toHaveBeenCalledWith('npx', expect.arrayContaining('@different-ai/opencode-browser'), expect.any(Object))
|
|
47
|
+
expect(execa).toHaveBeenCalledWith('npx', expect.arrayContaining(['@different-ai/opencode-browser']), expect.any(Object))
|
|
48
48
|
})
|
|
49
49
|
|
|
50
50
|
it('logs success when exit code is 0', async () => {
|