theslopmachine 0.3.4 → 0.3.6

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.
@@ -17,6 +17,14 @@ function die(message) {
17
17
  process.exit(1)
18
18
  }
19
19
 
20
+ function quoteShellArg(value) {
21
+ if (/^[A-Za-z0-9_./:-]+$/.test(value)) {
22
+ return value
23
+ }
24
+
25
+ return `'${value.replaceAll(`'`, `'\\''`)}'`
26
+ }
27
+
20
28
  function run(command, args, options = {}) {
21
29
  return new Promise((resolve, reject) => {
22
30
  const child = spawn(command, args, {
@@ -86,9 +94,10 @@ async function chooseNoninteractiveInitModeFlag(isGitRepo) {
86
94
  async function configureGitMergeDriver() {
87
95
  const attributesFile = path.join(target, '.git', 'info', 'attributes')
88
96
  const attributesLine = '.beads/*.jsonl merge=beads'
97
+ const mergeDriverCommand = `${quoteShellArg(bdCommand)} merge %A %O %A %B`
89
98
 
90
99
  log('Configuring local git merge driver for Beads')
91
- let result = await run('git', ['-C', target, 'config', 'merge.beads.driver', 'bd merge %A %O %A %B'])
100
+ let result = await run('git', ['-C', target, 'config', 'merge.beads.driver', mergeDriverCommand])
92
101
  if (result.code !== 0) die(result.stderr || 'Failed configuring git merge driver')
93
102
 
94
103
  await fs.mkdir(path.dirname(attributesFile), { recursive: true })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theslopmachine",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "SlopMachine installer and project bootstrap CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/init.js CHANGED
@@ -21,6 +21,16 @@ const GITIGNORE_ENTRIES = [
21
21
  'antigravity-logs/',
22
22
  ]
23
23
 
24
+ function getUnixBeadsCommandCandidates(paths) {
25
+ return [
26
+ path.join(paths.home, '.local', 'bin', 'bd'),
27
+ path.join(paths.home, '.linuxbrew', 'bin', 'bd'),
28
+ '/opt/homebrew/bin/bd',
29
+ '/usr/local/bin/bd',
30
+ '/home/linuxbrew/.linuxbrew/bin/bd',
31
+ ]
32
+ }
33
+
24
34
  function getWindowsBeadsCommandCandidates() {
25
35
  if (process.platform !== 'win32') {
26
36
  return []
@@ -32,8 +42,12 @@ function getWindowsBeadsCommandCandidates() {
32
42
  ].filter(Boolean)
33
43
  }
34
44
 
35
- async function resolveBeadsCommand() {
36
- return resolveCommand('bd', { additionalCandidates: getWindowsBeadsCommandCandidates() })
45
+ async function resolveBeadsCommand(paths) {
46
+ const candidates = process.platform === 'win32'
47
+ ? getWindowsBeadsCommandCandidates()
48
+ : getUnixBeadsCommandCandidates(paths)
49
+
50
+ return resolveCommand('bd', { additionalCandidates: candidates, preferCandidates: true })
37
51
  }
38
52
 
39
53
  function parseInitArgs(args) {
@@ -119,7 +133,7 @@ async function runBeadsBootstrap(paths, targetPath, beadsScript) {
119
133
  throw new Error('Unable to locate the current Node.js executable for Beads bootstrap.')
120
134
  }
121
135
 
122
- const beadsCommand = await resolveBeadsCommand()
136
+ const beadsCommand = await resolveBeadsCommand(paths)
123
137
 
124
138
  log('Running Beads setup')
125
139
  const result = await runCommand(nodeExecutable, [beadsScript, targetPath], {
package/src/install.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs/promises'
2
+ import os from 'node:os'
2
3
  import path from 'node:path'
3
4
 
4
5
  import {
@@ -16,6 +17,7 @@ import {
16
17
  copyDirIfMissing,
17
18
  copyFileIfMissing,
18
19
  ensureDir,
20
+ findBashExecutable,
19
21
  getGlobalNpmPackageVersion,
20
22
  log,
21
23
  makeExecutableIfShellScript,
@@ -34,6 +36,21 @@ function assetsRoot() {
34
36
  return path.join(PACKAGE_ROOT, 'assets')
35
37
  }
36
38
 
39
+ function getHomeDir() {
40
+ return buildPaths().home
41
+ }
42
+
43
+ function getUnixBeadsBinDirs() {
44
+ const home = getHomeDir()
45
+ return [
46
+ path.join(home, '.local', 'bin'),
47
+ path.join(home, '.linuxbrew', 'bin'),
48
+ '/opt/homebrew/bin',
49
+ '/usr/local/bin',
50
+ '/home/linuxbrew/.linuxbrew/bin',
51
+ ]
52
+ }
53
+
37
54
  function getWindowsBeadsBinDirs() {
38
55
  if (process.platform !== 'win32') {
39
56
  return []
@@ -47,12 +64,16 @@ function getWindowsBeadsBinDirs() {
47
64
  return [...new Set(dirs)]
48
65
  }
49
66
 
50
- function getWindowsBeadsCommandCandidates() {
51
- return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
67
+ function getBeadsCommandCandidates() {
68
+ if (process.platform === 'win32') {
69
+ return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
70
+ }
71
+
72
+ return getUnixBeadsBinDirs().map((dir) => path.join(dir, 'bd'))
52
73
  }
53
74
 
54
75
  async function resolveBeadsCommand() {
55
- return resolveCommand('bd', { additionalCandidates: getWindowsBeadsCommandCandidates() })
76
+ return resolveCommand('bd', { additionalCandidates: getBeadsCommandCandidates(), preferCandidates: true })
56
77
  }
57
78
 
58
79
  async function getBeadsVersion() {
@@ -69,6 +90,25 @@ async function getBeadsVersion() {
69
90
  return { command, version: (result.stdout || result.stderr).trim() }
70
91
  }
71
92
 
93
+ async function probeBeadsRuntime(command) {
94
+ const probeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'slopmachine-beads-probe-'))
95
+
96
+ try {
97
+ const result = await runCommand(command, ['init', '--quiet', '--stealth', '--skip-hooks'], {
98
+ cwd: probeDir,
99
+ env: { ...process.env, CI: '1' },
100
+ })
101
+
102
+ const output = `${result.stdout}${result.stderr}`.trim()
103
+ return {
104
+ ok: result.code === 0,
105
+ output,
106
+ }
107
+ } finally {
108
+ await fs.rm(probeDir, { recursive: true, force: true })
109
+ }
110
+ }
111
+
72
112
  function quotePowerShell(value) {
73
113
  return `'${String(value).replaceAll(`'`, `''`)}'`
74
114
  }
@@ -116,6 +156,20 @@ async function ensureWindowsBeadsPath() {
116
156
  }
117
157
  }
118
158
 
159
+ async function ensureUnixBeadsPath() {
160
+ if (process.platform === 'win32') {
161
+ return
162
+ }
163
+
164
+ for (const dir of getUnixBeadsBinDirs()) {
165
+ if (!(await pathExists(path.join(dir, 'bd')))) {
166
+ continue
167
+ }
168
+
169
+ prependToProcessPath(dir)
170
+ }
171
+ }
172
+
119
173
  async function installBeadsOnWindows() {
120
174
  const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
121
175
  if (!shell) {
@@ -135,6 +189,37 @@ async function installBeadsOnWindows() {
135
189
  return result
136
190
  }
137
191
 
192
+ async function installBeadsOnUnix() {
193
+ const managers = await detectPackageManagers()
194
+ if (managers.brew) {
195
+ log('Installing Beads via Homebrew')
196
+ const result = await runCommand('brew', ['install', 'beads'], { stdio: 'inherit' })
197
+ await ensureUnixBeadsPath()
198
+ return result
199
+ }
200
+
201
+ const bashExecutable = await findBashExecutable()
202
+ if (!bashExecutable) {
203
+ return { code: 1, stdout: '', stderr: 'bash is required to run the Beads installer on this platform' }
204
+ }
205
+
206
+ if (await commandExists('curl')) {
207
+ log('Installing Beads with the upstream Unix install script')
208
+ const result = await runCommand(bashExecutable, ['-lc', 'curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
209
+ await ensureUnixBeadsPath()
210
+ return result
211
+ }
212
+
213
+ if (await commandExists('wget')) {
214
+ log('Installing Beads with the upstream Unix install script')
215
+ const result = await runCommand(bashExecutable, ['-lc', 'wget -qO- https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
216
+ await ensureUnixBeadsPath()
217
+ return result
218
+ }
219
+
220
+ return { code: 1, stdout: '', stderr: 'curl or wget is required to install Beads on this platform' }
221
+ }
222
+
138
223
  async function getCommandVersion(command, args = ['--version']) {
139
224
  const exists = await commandExists(command)
140
225
  if (!exists) return null
@@ -184,8 +269,7 @@ async function tryInstallCoreDependency(name) {
184
269
  return installBeadsOnWindows()
185
270
  }
186
271
 
187
- log(`Installing @beads/bd@${BEADS_VERSION} globally via npm`)
188
- return runCommand('npm', ['install', '-g', `@beads/bd@${BEADS_VERSION}`], { stdio: 'inherit' })
272
+ return installBeadsOnUnix()
189
273
  }
190
274
 
191
275
  const managers = await detectPackageManagers()
@@ -263,11 +347,16 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
263
347
  if (checkCommand === 'bd') {
264
348
  const beads = await getBeadsVersion()
265
349
  if (beads) {
266
- log(`${name} detected via ${beads.command}: ${beads.version}`)
267
- if (requiredVersion && !beads.version.includes(requiredVersion)) {
268
- warn(`${name} version differs from tested reference ${requiredVersion}`)
350
+ const probe = await probeBeadsRuntime(beads.command)
351
+ if (!probe.ok) {
352
+ warn(`${name} was found at ${beads.command}, but it failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
353
+ } else {
354
+ log(`${name} detected via ${beads.command}: ${beads.version}`)
355
+ if (requiredVersion && !beads.version.includes(requiredVersion)) {
356
+ warn(`${name} version differs from tested reference ${requiredVersion}`)
357
+ }
358
+ return
269
359
  }
270
- return
271
360
  }
272
361
  }
273
362
 
@@ -288,7 +377,7 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
288
377
 
289
378
  const shouldInstall = await promptYesNo(`Attempt to install ${name} automatically?`, true)
290
379
  if (!shouldInstall) {
291
- warn(`Skipping ${name} installation. Please install it manually before using theslopmachine.`)
380
+ warn(`Skipping ${name} installation. Please install it manually before using theslopmachine.`)
292
381
  return
293
382
  }
294
383
 
@@ -315,10 +404,16 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
315
404
  if (checkCommand === 'bd') {
316
405
  const beads = await getBeadsVersion()
317
406
  if (beads) {
318
- log(`Installed ${name} via ${beads.command}: ${beads.version}`)
319
- if (process.platform === 'win32' && !(await commandExists('bd'))) {
320
- warn('Beads was installed, but `bd` is not visible in this shell PATH yet. The installer added the common Windows Beads directories to PATH for future shells.')
407
+ const probe = await probeBeadsRuntime(beads.command)
408
+ if (probe.ok) {
409
+ log(`Installed ${name} via ${beads.command}: ${beads.version}`)
410
+ if (process.platform === 'win32' && !(await commandExists('bd'))) {
411
+ warn('Beads was installed, but `bd` is not visible in this shell PATH yet. The installer added the common Windows Beads directories to PATH for future shells.')
412
+ }
413
+ return
321
414
  }
415
+
416
+ warn(`${name} was installed at ${beads.command}, but it still failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
322
417
  return
323
418
  }
324
419
 
package/src/utils.js CHANGED
@@ -155,12 +155,25 @@ async function getGlobalNpmPrefix() {
155
155
  }
156
156
 
157
157
  export async function resolveCommand(command, options = {}) {
158
+ const candidates = []
159
+
160
+ if (Array.isArray(options.additionalCandidates)) {
161
+ candidates.push(...options.additionalCandidates)
162
+ }
163
+
164
+ if (options.preferCandidates) {
165
+ for (const candidate of candidates) {
166
+ if (await pathExists(candidate)) {
167
+ return candidate
168
+ }
169
+ }
170
+ }
171
+
158
172
  if (await commandExists(command)) {
159
173
  return command
160
174
  }
161
175
 
162
176
  const prefix = await getGlobalNpmPrefix()
163
- const candidates = []
164
177
 
165
178
  if (prefix) {
166
179
  candidates.push(...(process.platform === 'win32'
@@ -175,10 +188,6 @@ export async function resolveCommand(command, options = {}) {
175
188
  ]))
176
189
  }
177
190
 
178
- if (Array.isArray(options.additionalCandidates)) {
179
- candidates.push(...options.additionalCandidates)
180
- }
181
-
182
191
  for (const candidate of candidates) {
183
192
  if (await pathExists(candidate)) {
184
193
  return candidate