opencode-starterkit 1.0.4 → 1.0.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.
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Patch proposal: close the beads CLI installer gap in `install-global.mjs`
|
|
2
|
+
|
|
3
|
+
## Problem statement
|
|
4
|
+
`npx opencode-starterkit install` currently installs the **global baseline files** only:
|
|
5
|
+
- agents
|
|
6
|
+
- skills
|
|
7
|
+
- plugins
|
|
8
|
+
- commands
|
|
9
|
+
- tools
|
|
10
|
+
- templates/docs
|
|
11
|
+
- merged `opencode.json`
|
|
12
|
+
- `ocp` shim
|
|
13
|
+
|
|
14
|
+
However, multiple baseline skills/commands operationally depend on **`br` (beads_rust CLI)**, especially:
|
|
15
|
+
- `baseline/skill/beads/SKILL.md`
|
|
16
|
+
- `baseline/skill/beads-bridge/SKILL.md`
|
|
17
|
+
- `baseline/command/create.md`
|
|
18
|
+
- `baseline/command/plan.md`
|
|
19
|
+
- `baseline/command/resume.md`
|
|
20
|
+
- `baseline/command/ship.md`
|
|
21
|
+
- `baseline/command/status.md`
|
|
22
|
+
- `baseline/command/verify.md`
|
|
23
|
+
|
|
24
|
+
Today the installer does **not**:
|
|
25
|
+
- detect whether `br` exists
|
|
26
|
+
- install it
|
|
27
|
+
- fail fast when it is missing
|
|
28
|
+
- or print a clear remediation path
|
|
29
|
+
|
|
30
|
+
This creates a broken onboarding path:
|
|
31
|
+
1. user runs `npx opencode-starterkit install`
|
|
32
|
+
2. baseline appears installed
|
|
33
|
+
3. user invokes beads workflow
|
|
34
|
+
4. runtime fails because `br` is missing
|
|
35
|
+
|
|
36
|
+
## Desired outcome
|
|
37
|
+
After global install, one of these must be true:
|
|
38
|
+
1. `br` is available on PATH, or
|
|
39
|
+
2. installer exits non-zero with a clear, OS-specific remediation message
|
|
40
|
+
|
|
41
|
+
## Recommended policy
|
|
42
|
+
Use a **2-tier strategy**:
|
|
43
|
+
|
|
44
|
+
### Tier A — default safe behavior
|
|
45
|
+
- Detect whether `br` is present.
|
|
46
|
+
- If present: continue and print version.
|
|
47
|
+
- If missing: **fail fast** by default with exact install instructions.
|
|
48
|
+
|
|
49
|
+
### Tier B — opt-in auto-install
|
|
50
|
+
Add optional flag later, e.g.:
|
|
51
|
+
- `--install-beads`
|
|
52
|
+
|
|
53
|
+
Only when this flag is passed should the installer attempt automatic installation.
|
|
54
|
+
This avoids surprising system mutations during a global bootstrap.
|
|
55
|
+
|
|
56
|
+
## Why fail-fast first?
|
|
57
|
+
Because automatic installation is OS/package-manager specific and can be fragile:
|
|
58
|
+
- macOS may need `brew`
|
|
59
|
+
- Linux may need package manager or cargo
|
|
60
|
+
- some environments may prefer pinned binary releases
|
|
61
|
+
- CI/devbox/sandbox shells may not allow package manager writes
|
|
62
|
+
|
|
63
|
+
Fail-fast is the minimum viable fix that closes the onboarding ambiguity.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
# Proposed implementation
|
|
68
|
+
|
|
69
|
+
## New helper functions in `src/install-global.mjs`
|
|
70
|
+
|
|
71
|
+
### 1) `checkBeadsCli()`
|
|
72
|
+
Purpose:
|
|
73
|
+
- detect whether `br` exists
|
|
74
|
+
- capture version if available
|
|
75
|
+
|
|
76
|
+
Pseudo-code:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import { spawnSync } from 'node:child_process'
|
|
80
|
+
|
|
81
|
+
function checkBeadsCli() {
|
|
82
|
+
const whichResult = spawnSync('bash', ['-lc', 'command -v br'], { encoding: 'utf8' })
|
|
83
|
+
const found = whichResult.status === 0 && whichResult.stdout.trim().length > 0
|
|
84
|
+
if (!found) {
|
|
85
|
+
return { ok: false, path: null, version: null }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const versionResult = spawnSync('bash', ['-lc', 'br --version || br version'], { encoding: 'utf8' })
|
|
89
|
+
const version = `${versionResult.stdout || ''}${versionResult.stderr || ''}`.trim() || null
|
|
90
|
+
return { ok: true, path: whichResult.stdout.trim(), version }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2) `getBeadsInstallHint()`
|
|
95
|
+
Purpose:
|
|
96
|
+
- print platform-appropriate remediation guidance
|
|
97
|
+
|
|
98
|
+
Pseudo-code:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
function getBeadsInstallHint() {
|
|
102
|
+
const platform = process.platform
|
|
103
|
+
if (platform === 'darwin') {
|
|
104
|
+
return [
|
|
105
|
+
'Beads CLI (br) is required by the baseline but is not installed.',
|
|
106
|
+
'Suggested next steps on macOS:',
|
|
107
|
+
' 1. Install Homebrew if missing',
|
|
108
|
+
' 2. Install beads_rust / br using the project-approved method',
|
|
109
|
+
' 3. Re-run: npx opencode-starterkit install',
|
|
110
|
+
'If your team standard is a specific install command, document it here explicitly.'
|
|
111
|
+
].join('\n')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (platform === 'linux') {
|
|
115
|
+
return [
|
|
116
|
+
'Beads CLI (br) is required by the baseline but is not installed.',
|
|
117
|
+
'Suggested next steps on Linux:',
|
|
118
|
+
' 1. Install br using the team-approved package/binary method',
|
|
119
|
+
' 2. Re-run: npx opencode-starterkit install'
|
|
120
|
+
].join('\n')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return [
|
|
124
|
+
'Beads CLI (br) is required by the baseline but is not installed.',
|
|
125
|
+
'Install it using the team-approved method, then re-run the installer.'
|
|
126
|
+
].join('\n')
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 3) `ensureBeadsCliOrExit()`
|
|
131
|
+
Purpose:
|
|
132
|
+
- enforce hard precondition before claiming global install success
|
|
133
|
+
|
|
134
|
+
Pseudo-code:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
function ensureBeadsCliOrExit() {
|
|
138
|
+
const check = checkBeadsCli()
|
|
139
|
+
if (!check.ok) {
|
|
140
|
+
console.error('[opencode-starterkit] ERROR: beads CLI (br) not found on PATH')
|
|
141
|
+
console.error(getBeadsInstallHint())
|
|
142
|
+
process.exitCode = 1
|
|
143
|
+
throw new Error('Missing required dependency: br')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(`[opencode-starterkit] beads CLI found: ${check.path}`)
|
|
147
|
+
if (check.version) {
|
|
148
|
+
console.log(`[opencode-starterkit] beads CLI version: ${check.version}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
# Minimal patch placement
|
|
156
|
+
Insert the dependency gate near the top of `installGlobal()` before printing success.
|
|
157
|
+
|
|
158
|
+
## Current flow
|
|
159
|
+
```js
|
|
160
|
+
export async function installGlobal({ cwd }) {
|
|
161
|
+
console.log('[opencode-starterkit] Global install starting')
|
|
162
|
+
...
|
|
163
|
+
installPackageRuntime()
|
|
164
|
+
copyMarkdownAgents()
|
|
165
|
+
copySkills()
|
|
166
|
+
...
|
|
167
|
+
installOcpShim()
|
|
168
|
+
console.log('[opencode-starterkit] Installed ...')
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Proposed flow
|
|
173
|
+
```js
|
|
174
|
+
export async function installGlobal({ cwd }) {
|
|
175
|
+
console.log('[opencode-starterkit] Global install starting')
|
|
176
|
+
console.log(`cwd=${cwd}`)
|
|
177
|
+
console.log(`baseline=${MRC_OPC_BASELINE_ROOT}`)
|
|
178
|
+
|
|
179
|
+
installPackageRuntime()
|
|
180
|
+
copyMarkdownAgents()
|
|
181
|
+
copySkills()
|
|
182
|
+
copyPlugins()
|
|
183
|
+
copyCommands()
|
|
184
|
+
copyTools()
|
|
185
|
+
copyTemplatesAndDocs()
|
|
186
|
+
|
|
187
|
+
const mergeResult = mergeGlobalOpencodeJson()
|
|
188
|
+
installOcpShim()
|
|
189
|
+
|
|
190
|
+
// NEW: hard dependency check
|
|
191
|
+
ensureBeadsCliOrExit()
|
|
192
|
+
|
|
193
|
+
console.log(`[opencode-starterkit] Installed package runtime at ${GLOBAL_PACKAGE_ROOT}`)
|
|
194
|
+
...
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
# Optional phase-2 enhancement: `--install-beads`
|
|
201
|
+
If the project wants auto-install later, extend `cli.mjs` args with:
|
|
202
|
+
- `--install-beads`
|
|
203
|
+
|
|
204
|
+
Then implement a guarded install function:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
function installBeadsCliIfRequested(options) {
|
|
208
|
+
if (!options.installBeads) return
|
|
209
|
+
// platform-specific install attempts here
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Important:
|
|
214
|
+
- do **not** auto-install silently by default
|
|
215
|
+
- only run with explicit operator intent
|
|
216
|
+
- log exact command executed
|
|
217
|
+
- fail if command is unavailable
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
# Additional UX improvement
|
|
222
|
+
Update README install section to state clearly:
|
|
223
|
+
|
|
224
|
+
```md
|
|
225
|
+
Global install installs the baseline files and validates required operational CLIs.
|
|
226
|
+
If `br` (beads CLI) is missing, the installer will stop with a remediation message.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
This removes the false impression that "install completed" implies the full workflow backend is ready.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
# Acceptance criteria
|
|
234
|
+
1. Running `npx opencode-starterkit install` on a machine **without** `br`:
|
|
235
|
+
- exits non-zero
|
|
236
|
+
- prints clear remediation
|
|
237
|
+
- does not claim success
|
|
238
|
+
2. Running on a machine **with** `br`:
|
|
239
|
+
- succeeds
|
|
240
|
+
- prints detected path/version
|
|
241
|
+
3. README and help text mention the validation behavior.
|
|
242
|
+
4. No hidden package-manager mutation occurs without explicit opt-in.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
# Recommendation to team
|
|
247
|
+
Ship the **fail-fast dependency validation** first.
|
|
248
|
+
Do not block on auto-install implementation.
|
|
249
|
+
That closes the onboarding ambiguity immediately with minimal risk.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Patch proposal v2: interactive beads CLI detection + suggested install in `install-global.mjs`
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Close the current onboarding gap where:
|
|
5
|
+
- `npx opencode-starterkit install` installs the global baseline
|
|
6
|
+
- baseline workflows assume `br` exists
|
|
7
|
+
- but installer does not detect/install/help the user obtain `br`
|
|
8
|
+
|
|
9
|
+
This v2 proposal follows the product rule:
|
|
10
|
+
1. detect `br`
|
|
11
|
+
2. if missing, detect OS
|
|
12
|
+
3. suggest the correct install command
|
|
13
|
+
4. ask user for confirmation in interactive mode
|
|
14
|
+
5. install only if user agrees
|
|
15
|
+
6. verify `br --version`
|
|
16
|
+
7. continue only when the dependency is satisfied (or exit clearly)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Source-backed install commands to use
|
|
21
|
+
The repo itself does not define an official beads installer command, so we should not invent one.
|
|
22
|
+
From public upstream sources we can support explicit provider choices:
|
|
23
|
+
|
|
24
|
+
### Option A — `beads-rs` (delightful-ai)
|
|
25
|
+
Source indicates:
|
|
26
|
+
```bash
|
|
27
|
+
curl -fsSL https://raw.githubusercontent.com/delightful-ai/beads-rs/main/scripts/install.sh | bash
|
|
28
|
+
```
|
|
29
|
+
Alternative:
|
|
30
|
+
```bash
|
|
31
|
+
cargo install beads-rs
|
|
32
|
+
```
|
|
33
|
+
Binary there is primarily `bd`.
|
|
34
|
+
|
|
35
|
+
### Option B — `beads_rust` / `br` (Dicklesworthstone)
|
|
36
|
+
Source indicates:
|
|
37
|
+
```bash
|
|
38
|
+
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/beads_rust/main/install.sh?$(date +%s)" | bash
|
|
39
|
+
```
|
|
40
|
+
This one explicitly installs `br`.
|
|
41
|
+
|
|
42
|
+
## Recommendation
|
|
43
|
+
Because the baseline docs explicitly refer to **`br`**, the installer should prefer the `beads_rust` / `br` install path first.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Proposed UX
|
|
48
|
+
|
|
49
|
+
### Interactive shell
|
|
50
|
+
If `br` is missing:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
[opencode-starterkit] beads CLI (br) is required by the baseline but was not found.
|
|
54
|
+
Detected OS: macOS
|
|
55
|
+
Recommended install command:
|
|
56
|
+
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/beads_rust/main/install.sh?$(date +%s)" | bash
|
|
57
|
+
Install now? [y/N]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If user says yes:
|
|
61
|
+
- run the command
|
|
62
|
+
- verify `command -v br`
|
|
63
|
+
- verify `br --version`
|
|
64
|
+
- continue install
|
|
65
|
+
|
|
66
|
+
If user says no:
|
|
67
|
+
- exit non-zero
|
|
68
|
+
- print a short message that beads-based workflows will not function until `br` is installed
|
|
69
|
+
|
|
70
|
+
### Non-interactive / CI
|
|
71
|
+
If `br` is missing:
|
|
72
|
+
- print the recommended install command
|
|
73
|
+
- exit non-zero
|
|
74
|
+
- do **not** attempt silent install
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Proposed implementation sketch
|
|
79
|
+
|
|
80
|
+
### New helpers in `src/install-global.mjs`
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import { spawnSync } from 'node:child_process'
|
|
84
|
+
import readline from 'node:readline'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 1) Detect `br`
|
|
88
|
+
```js
|
|
89
|
+
function checkBeadsCli() {
|
|
90
|
+
const whichResult = spawnSync('bash', ['-lc', 'command -v br'], { encoding: 'utf8' })
|
|
91
|
+
const found = whichResult.status === 0 && whichResult.stdout.trim()
|
|
92
|
+
if (!found) return { ok: false, path: null, version: null }
|
|
93
|
+
|
|
94
|
+
const versionResult = spawnSync('bash', ['-lc', 'br --version || br version'], { encoding: 'utf8' })
|
|
95
|
+
const version = `${versionResult.stdout || ''}${versionResult.stderr || ''}`.trim() || null
|
|
96
|
+
return { ok: true, path: whichResult.stdout.trim(), version }
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2) Detect OS and choose install command
|
|
101
|
+
```js
|
|
102
|
+
function getSuggestedBeadsInstall() {
|
|
103
|
+
const platform = process.platform
|
|
104
|
+
|
|
105
|
+
const primary = 'curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/beads_rust/main/install.sh?$(date +%s)" | bash'
|
|
106
|
+
const fallback = 'cargo install beads-rs'
|
|
107
|
+
|
|
108
|
+
if (platform === 'darwin') {
|
|
109
|
+
return {
|
|
110
|
+
osLabel: 'macOS',
|
|
111
|
+
primary,
|
|
112
|
+
fallback,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (platform === 'linux') {
|
|
117
|
+
return {
|
|
118
|
+
osLabel: 'Linux',
|
|
119
|
+
primary,
|
|
120
|
+
fallback,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (platform === 'win32') {
|
|
125
|
+
return {
|
|
126
|
+
osLabel: 'Windows',
|
|
127
|
+
primary: 'Use WSL and run: ' + primary,
|
|
128
|
+
fallback: 'Use a supported Rust/cargo install path inside WSL',
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
osLabel: process.platform,
|
|
134
|
+
primary,
|
|
135
|
+
fallback,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 3) Ask user
|
|
141
|
+
```js
|
|
142
|
+
async function askYesNo(question) {
|
|
143
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
144
|
+
return await new Promise((resolve) => {
|
|
145
|
+
rl.question(question, (answer) => {
|
|
146
|
+
rl.close()
|
|
147
|
+
const normalized = String(answer || '').trim().toLowerCase()
|
|
148
|
+
resolve(normalized === 'y' || normalized === 'yes')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 4) Install if approved
|
|
155
|
+
```js
|
|
156
|
+
function runShellCommand(command) {
|
|
157
|
+
return spawnSync('bash', ['-lc', command], { stdio: 'inherit', encoding: 'utf8' })
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 5) Main gate
|
|
162
|
+
```js
|
|
163
|
+
async function ensureBeadsCliInteractive() {
|
|
164
|
+
const check = checkBeadsCli()
|
|
165
|
+
if (check.ok) {
|
|
166
|
+
console.log(`[opencode-starterkit] beads CLI found: ${check.path}`)
|
|
167
|
+
if (check.version) console.log(`[opencode-starterkit] beads CLI version: ${check.version}`)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const install = getSuggestedBeadsInstall()
|
|
172
|
+
console.warn('[opencode-starterkit] beads CLI (br) is required by the baseline but was not found.')
|
|
173
|
+
console.warn(`[opencode-starterkit] Detected OS: ${install.osLabel}`)
|
|
174
|
+
console.warn(`[opencode-starterkit] Recommended install command:\n ${install.primary}`)
|
|
175
|
+
console.warn(`[opencode-starterkit] Fallback command:\n ${install.fallback}`)
|
|
176
|
+
|
|
177
|
+
if (!process.stdin.isTTY) {
|
|
178
|
+
throw new Error('Missing beads CLI (br). Non-interactive mode cannot auto-install; run the suggested install command and retry.')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const approved = await askYesNo('Install beads CLI now? [y/N] ')
|
|
182
|
+
if (!approved) {
|
|
183
|
+
throw new Error('User declined beads CLI installation. Beads-based workflows will not function until br is installed.')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const result = runShellCommand(install.primary)
|
|
187
|
+
if (result.status !== 0) {
|
|
188
|
+
throw new Error(`Automatic beads install failed. Try manually: ${install.primary}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const verify = checkBeadsCli()
|
|
192
|
+
if (!verify.ok) {
|
|
193
|
+
throw new Error('beads install command completed but br is still not available on PATH.')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log(`[opencode-starterkit] beads CLI installed: ${verify.path}`)
|
|
197
|
+
if (verify.version) console.log(`[opencode-starterkit] beads CLI version: ${verify.version}`)
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Required code shape change
|
|
204
|
+
Because the new gate is async, `installGlobal()` should await it.
|
|
205
|
+
|
|
206
|
+
### Before
|
|
207
|
+
```js
|
|
208
|
+
export async function installGlobal({ cwd }) {
|
|
209
|
+
...
|
|
210
|
+
installOcpShim()
|
|
211
|
+
console.log('done')
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### After
|
|
216
|
+
```js
|
|
217
|
+
export async function installGlobal({ cwd }) {
|
|
218
|
+
...
|
|
219
|
+
installOcpShim()
|
|
220
|
+
await ensureBeadsCliInteractive()
|
|
221
|
+
console.log('done')
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## README change required
|
|
228
|
+
Update install docs to say:
|
|
229
|
+
|
|
230
|
+
```md
|
|
231
|
+
Global install validates the beads CLI (`br`) because several baseline workflows depend on it.
|
|
232
|
+
If `br` is missing, the installer will suggest the correct install command for your OS and ask whether to install it.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Acceptance criteria
|
|
238
|
+
1. On a machine with `br` already installed:
|
|
239
|
+
- installer prints path/version and continues
|
|
240
|
+
2. On a machine without `br` in interactive mode:
|
|
241
|
+
- installer detects OS
|
|
242
|
+
- prints install command
|
|
243
|
+
- asks user
|
|
244
|
+
- installs only after explicit approval
|
|
245
|
+
- verifies `br --version`
|
|
246
|
+
3. On a machine without `br` in non-interactive mode:
|
|
247
|
+
- installer exits non-zero with remediation text
|
|
248
|
+
4. No silent package-manager mutation occurs without user approval.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Recommendation
|
|
253
|
+
Implement this interactive detection/suggest/install path now, and keep the command source explicit and documented.
|
package/package.json
CHANGED
package/src/install-global.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { spawnSync } from 'node:child_process'
|
|
4
|
+
import readline from 'node:readline'
|
|
3
5
|
import {
|
|
4
6
|
GLOBAL_AGENTS_DIR,
|
|
5
7
|
GLOBAL_BIN_DIR,
|
|
@@ -54,7 +56,7 @@ function copyTemplatesAndDocs() {
|
|
|
54
56
|
|
|
55
57
|
function installOcpShim() {
|
|
56
58
|
ensureDir(GLOBAL_BIN_DIR)
|
|
57
|
-
const shim = `#!/usr/bin/env bash\nnode
|
|
59
|
+
const shim = `#!/usr/bin/env bash\nnode "${path.join(GLOBAL_PACKAGE_ROOT, 'bin', 'ocp.mjs')}" "$@"\n`
|
|
58
60
|
writeText(GLOBAL_OCP_SHIM, shim)
|
|
59
61
|
fs.chmodSync(GLOBAL_OCP_SHIM, 0o755)
|
|
60
62
|
}
|
|
@@ -73,6 +75,86 @@ function mergeGlobalOpencodeJson() {
|
|
|
73
75
|
return { merged: true, globalPath }
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
function checkBeadsCli() {
|
|
79
|
+
const whichResult = spawnSync('bash', ['-lc', 'command -v br'], { encoding: 'utf8' })
|
|
80
|
+
const found = whichResult.status === 0 && whichResult.stdout.trim().length > 0
|
|
81
|
+
if (!found) {
|
|
82
|
+
return { ok: false, path: null, version: null }
|
|
83
|
+
}
|
|
84
|
+
const versionResult = spawnSync('bash', ['-lc', 'br --version || br version'], { encoding: 'utf8' })
|
|
85
|
+
const version = `${versionResult.stdout || ''}${versionResult.stderr || ''}`.trim() || null
|
|
86
|
+
return { ok: true, path: whichResult.stdout.trim(), version }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getSuggestedBeadsInstall() {
|
|
90
|
+
const platform = process.platform
|
|
91
|
+
const primary = 'curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/beads_rust/main/install.sh | bash'
|
|
92
|
+
const fallback = 'cargo install beads-rs'
|
|
93
|
+
|
|
94
|
+
if (platform === 'darwin') {
|
|
95
|
+
return { osLabel: 'macOS', primary, fallback }
|
|
96
|
+
}
|
|
97
|
+
if (platform === 'linux') {
|
|
98
|
+
return { osLabel: 'Linux', primary, fallback }
|
|
99
|
+
}
|
|
100
|
+
if (platform === 'win32') {
|
|
101
|
+
return { osLabel: 'Windows', primary: 'Use WSL and run: ' + primary, fallback: 'Use a supported Rust/cargo install path inside WSL' }
|
|
102
|
+
}
|
|
103
|
+
return { osLabel: platform, primary, fallback }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function askYesNo(question) {
|
|
107
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
108
|
+
return await new Promise((resolve) => {
|
|
109
|
+
rl.question(question, (answer) => {
|
|
110
|
+
rl.close()
|
|
111
|
+
const normalized = String(answer || '').trim().toLowerCase()
|
|
112
|
+
resolve(normalized === 'y' || normalized === 'yes')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function runShellCommand(command) {
|
|
118
|
+
return spawnSync('bash', ['-lc', command], { stdio: 'inherit', encoding: 'utf8' })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function ensureBeadsCliInteractive() {
|
|
122
|
+
const check = checkBeadsCli()
|
|
123
|
+
if (check.ok) {
|
|
124
|
+
console.log(`[opencode-starterkit] beads CLI found: ${check.path}`)
|
|
125
|
+
if (check.version) console.log(`[opencode-starterkit] beads CLI version: ${check.version}`)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const install = getSuggestedBeadsInstall()
|
|
130
|
+
console.warn('[opencode-starterkit] beads CLI (br) is required by the baseline but was not found.')
|
|
131
|
+
console.warn(`[opencode-starterkit] Detected OS: ${install.osLabel}`)
|
|
132
|
+
console.warn(`[opencode-starterkit] Recommended install command:\n ${install.primary}`)
|
|
133
|
+
console.warn(`[opencode-starterkit] Fallback command:\n ${install.fallback}`)
|
|
134
|
+
|
|
135
|
+
if (!process.stdin.isTTY) {
|
|
136
|
+
throw new Error('Missing beads CLI (br). Non-interactive mode cannot auto-install; run the suggested install command and retry.')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const approved = await askYesNo('Install beads CLI now? [y/N] ')
|
|
140
|
+
if (!approved) {
|
|
141
|
+
throw new Error('User declined beads CLI installation. Beads-based workflows will not function until br is installed.')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const result = runShellCommand(install.primary)
|
|
145
|
+
if (result.status !== 0) {
|
|
146
|
+
throw new Error(`Automatic beads install failed. Try manually: ${install.primary}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const verify = checkBeadsCli()
|
|
150
|
+
if (!verify.ok) {
|
|
151
|
+
throw new Error('beads install command completed but br is still not available on PATH.')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`[opencode-starterkit] beads CLI installed: ${verify.path}`)
|
|
155
|
+
if (verify.version) console.log(`[opencode-starterkit] beads CLI version: ${verify.version}`)
|
|
156
|
+
}
|
|
157
|
+
|
|
76
158
|
export async function installGlobal({ cwd }) {
|
|
77
159
|
console.log('[opencode-starterkit] Global install starting')
|
|
78
160
|
console.log(`cwd=${cwd}`)
|
|
@@ -86,6 +168,7 @@ export async function installGlobal({ cwd }) {
|
|
|
86
168
|
copyTemplatesAndDocs()
|
|
87
169
|
const mergeResult = mergeGlobalOpencodeJson()
|
|
88
170
|
installOcpShim()
|
|
171
|
+
await ensureBeadsCliInteractive()
|
|
89
172
|
console.log(`[opencode-starterkit] Installed package runtime at ${GLOBAL_PACKAGE_ROOT}`)
|
|
90
173
|
console.log(`[opencode-starterkit] Synced agents -> ${GLOBAL_AGENTS_DIR}`)
|
|
91
174
|
console.log(`[opencode-starterkit] Synced skills -> ${GLOBAL_SKILLS_DIR}`)
|