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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-starterkit",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Global baseline + thin project overlay installer for OpenCode.",
@@ -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 \"${path.join(GLOBAL_PACKAGE_ROOT, 'bin', 'ocp.mjs')}\" \"$@\"\n`
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}`)