theslopmachine 0.3.5 → 0.3.7

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/RELEASE.md CHANGED
@@ -41,13 +41,13 @@ npm pack
41
41
  This should produce a tarball such as:
42
42
 
43
43
  ```bash
44
- theslopmachine-0.3.0.tgz
44
+ theslopmachine-0.3.7.tgz
45
45
  ```
46
46
 
47
47
  ## Inspect package contents
48
48
 
49
49
  ```bash
50
- tar -tzf theslopmachine-0.3.0.tgz
50
+ tar -tzf theslopmachine-0.3.7.tgz
51
51
  ```
52
52
 
53
53
  Check that the tarball includes:
@@ -78,4 +78,4 @@ npm publish --dry-run
78
78
 
79
79
  - bump `package.json` version before each release
80
80
  - keep the CLI command as `slopmachine`
81
- - keep the npm package name as `slopmachine`
81
+ - keep the npm package name as `theslopmachine`
@@ -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.5",
3
+ "version": "0.3.7",
4
4
  "description": "SlopMachine installer and project bootstrap CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { PACKAGE_VERSION } from './constants.js'
1
2
  import { runInit } from './init.js'
2
3
  import { runInstall } from './install.js'
3
4
 
4
5
  function printHelp() {
5
- console.log(`theslopmachine 0.3
6
+ console.log(`theslopmachine ${PACKAGE_VERSION}
6
7
 
7
8
  Commands:
8
9
  setup Configure theslopmachine in the local user environment
package/src/constants.js CHANGED
@@ -2,7 +2,7 @@ import os from 'node:os'
2
2
  import path from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
 
5
- export const PACKAGE_VERSION = '0.3.0'
5
+ export const PACKAGE_VERSION = '0.3.7'
6
6
  export const OPCODE_VERSION = '1.3.5'
7
7
  export const BEADS_VERSION = '0.52.0'
8
8
  export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
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,26 @@ 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, 'go', 'bin'),
48
+ path.join(home, '.linuxbrew', 'bin'),
49
+ '/opt/homebrew/bin',
50
+ '/usr/local/bin',
51
+ '/home/linuxbrew/.linuxbrew/bin',
52
+ ]
53
+ }
54
+
55
+ function getLinuxBeadsBinDir() {
56
+ return path.join(getHomeDir(), '.local', 'bin')
57
+ }
58
+
37
59
  function getWindowsBeadsBinDirs() {
38
60
  if (process.platform !== 'win32') {
39
61
  return []
@@ -47,12 +69,16 @@ function getWindowsBeadsBinDirs() {
47
69
  return [...new Set(dirs)]
48
70
  }
49
71
 
50
- function getWindowsBeadsCommandCandidates() {
51
- return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
72
+ function getBeadsCommandCandidates() {
73
+ if (process.platform === 'win32') {
74
+ return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
75
+ }
76
+
77
+ return getUnixBeadsBinDirs().map((dir) => path.join(dir, 'bd'))
52
78
  }
53
79
 
54
80
  async function resolveBeadsCommand() {
55
- return resolveCommand('bd', { additionalCandidates: getWindowsBeadsCommandCandidates() })
81
+ return resolveCommand('bd', { additionalCandidates: getBeadsCommandCandidates(), preferCandidates: true })
56
82
  }
57
83
 
58
84
  async function getBeadsVersion() {
@@ -69,6 +95,25 @@ async function getBeadsVersion() {
69
95
  return { command, version: (result.stdout || result.stderr).trim() }
70
96
  }
71
97
 
98
+ async function probeBeadsRuntime(command) {
99
+ const probeDir = await fs.mkdtemp(path.join(os.tmpdir(), 'slopmachine-beads-probe-'))
100
+
101
+ try {
102
+ const result = await runCommand(command, ['init', '--quiet', '--stealth', '--skip-hooks'], {
103
+ cwd: probeDir,
104
+ env: { ...process.env, CI: '1' },
105
+ })
106
+
107
+ const output = `${result.stdout}${result.stderr}`.trim()
108
+ return {
109
+ ok: result.code === 0,
110
+ output,
111
+ }
112
+ } finally {
113
+ await fs.rm(probeDir, { recursive: true, force: true })
114
+ }
115
+ }
116
+
72
117
  function quotePowerShell(value) {
73
118
  return `'${String(value).replaceAll(`'`, `''`)}'`
74
119
  }
@@ -116,6 +161,61 @@ async function ensureWindowsBeadsPath() {
116
161
  }
117
162
  }
118
163
 
164
+ async function ensureUnixBeadsPath() {
165
+ if (process.platform === 'win32') {
166
+ return
167
+ }
168
+
169
+ for (const dir of getUnixBeadsBinDirs()) {
170
+ if (!(await pathExists(path.join(dir, 'bd')))) {
171
+ continue
172
+ }
173
+
174
+ prependToProcessPath(dir)
175
+ }
176
+ }
177
+
178
+ async function installLinuxBeadsPrerequisites() {
179
+ const managers = await detectPackageManagers()
180
+
181
+ if (managers.apt) {
182
+ log('Installing Linux Beads build dependencies via apt')
183
+ const updateResult = await runCommand('sudo', ['apt-get', 'update'], { stdio: 'inherit' })
184
+ if (updateResult.code !== 0) {
185
+ return updateResult
186
+ }
187
+
188
+ return runCommand('sudo', [
189
+ 'apt-get',
190
+ 'install',
191
+ '-y',
192
+ 'build-essential',
193
+ 'pkg-config',
194
+ 'libicu-dev',
195
+ 'libzstd-dev',
196
+ 'golang-go',
197
+ ], { stdio: 'inherit' })
198
+ }
199
+
200
+ if (managers.dnf) {
201
+ log('Installing Linux Beads build dependencies via dnf')
202
+ return runCommand('sudo', [
203
+ 'dnf',
204
+ 'install',
205
+ '-y',
206
+ 'gcc',
207
+ 'gcc-c++',
208
+ 'make',
209
+ 'pkgconf-pkg-config',
210
+ 'libicu-devel',
211
+ 'libzstd-devel',
212
+ 'golang',
213
+ ], { stdio: 'inherit' })
214
+ }
215
+
216
+ return { code: 0, stdout: '', stderr: '' }
217
+ }
218
+
119
219
  async function installBeadsOnWindows() {
120
220
  const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
121
221
  if (!shell) {
@@ -135,6 +235,64 @@ async function installBeadsOnWindows() {
135
235
  return result
136
236
  }
137
237
 
238
+ async function installBeadsOnUnix() {
239
+ if (process.platform === 'linux') {
240
+ const depsResult = await installLinuxBeadsPrerequisites()
241
+ if (depsResult.code !== 0) {
242
+ return depsResult
243
+ }
244
+
245
+ if (!(await commandExists('go'))) {
246
+ return { code: 1, stdout: '', stderr: 'Go is required to build a CGO-enabled Beads binary on Linux' }
247
+ }
248
+
249
+ const binDir = getLinuxBeadsBinDir()
250
+ await ensureDir(binDir)
251
+
252
+ log(`Installing Beads ${BEADS_VERSION} on Linux with CGO_ENABLED=1`)
253
+ const result = await runCommand('go', ['install', `github.com/steveyegge/beads/cmd/bd@v${BEADS_VERSION}`], {
254
+ stdio: 'inherit',
255
+ env: {
256
+ ...process.env,
257
+ CGO_ENABLED: '1',
258
+ GOBIN: binDir,
259
+ },
260
+ })
261
+
262
+ await ensureUnixBeadsPath()
263
+ return result
264
+ }
265
+
266
+ const managers = await detectPackageManagers()
267
+ if (managers.brew) {
268
+ log('Installing Beads via Homebrew')
269
+ const result = await runCommand('brew', ['install', 'beads'], { stdio: 'inherit' })
270
+ await ensureUnixBeadsPath()
271
+ return result
272
+ }
273
+
274
+ const bashExecutable = await findBashExecutable()
275
+ if (!bashExecutable) {
276
+ return { code: 1, stdout: '', stderr: 'bash is required to run the Beads installer on this platform' }
277
+ }
278
+
279
+ if (await commandExists('curl')) {
280
+ log('Installing Beads with the upstream Unix install script')
281
+ const result = await runCommand(bashExecutable, ['-lc', 'curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
282
+ await ensureUnixBeadsPath()
283
+ return result
284
+ }
285
+
286
+ if (await commandExists('wget')) {
287
+ log('Installing Beads with the upstream Unix install script')
288
+ const result = await runCommand(bashExecutable, ['-lc', 'wget -qO- https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash'], { stdio: 'inherit' })
289
+ await ensureUnixBeadsPath()
290
+ return result
291
+ }
292
+
293
+ return { code: 1, stdout: '', stderr: 'curl or wget is required to install Beads on this platform' }
294
+ }
295
+
138
296
  async function getCommandVersion(command, args = ['--version']) {
139
297
  const exists = await commandExists(command)
140
298
  if (!exists) return null
@@ -184,8 +342,7 @@ async function tryInstallCoreDependency(name) {
184
342
  return installBeadsOnWindows()
185
343
  }
186
344
 
187
- log(`Installing @beads/bd@${BEADS_VERSION} globally via npm`)
188
- return runCommand('npm', ['install', '-g', `@beads/bd@${BEADS_VERSION}`], { stdio: 'inherit' })
345
+ return installBeadsOnUnix()
189
346
  }
190
347
 
191
348
  const managers = await detectPackageManagers()
@@ -231,6 +388,8 @@ async function tryInstallCoreDependency(name) {
231
388
  }
232
389
 
233
390
  async function ensureDependency({ name, checkCommand, requiredVersion, installable }) {
391
+ let needsReinstall = false
392
+
234
393
  if (name === 'python3') {
235
394
  const python = await getPythonVersion()
236
395
  if (python) {
@@ -263,24 +422,36 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
263
422
  if (checkCommand === 'bd') {
264
423
  const beads = await getBeadsVersion()
265
424
  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}`)
425
+ const probe = await probeBeadsRuntime(beads.command)
426
+ if (!probe.ok) {
427
+ warn(`${name} was found at ${beads.command}, but it failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
428
+ needsReinstall = true
429
+ } else {
430
+ log(`${name} detected via ${beads.command}: ${beads.version}`)
431
+ if (requiredVersion && !beads.version.includes(requiredVersion)) {
432
+ warn(`${name} version differs from tested reference ${requiredVersion}`)
433
+ }
434
+ return
269
435
  }
270
- return
271
436
  }
272
437
  }
273
438
 
274
- const version = await getCommandVersion(checkCommand)
275
- if (version) {
276
- log(`${name} detected: ${version}`)
277
- if (requiredVersion && !version.includes(requiredVersion)) {
278
- warn(`${name} version differs from tested reference ${requiredVersion}`)
439
+ if (!needsReinstall) {
440
+ const version = await getCommandVersion(checkCommand)
441
+ if (version) {
442
+ log(`${name} detected: ${version}`)
443
+ if (requiredVersion && !version.includes(requiredVersion)) {
444
+ warn(`${name} version differs from tested reference ${requiredVersion}`)
445
+ }
446
+ return
279
447
  }
280
- return
281
448
  }
282
449
 
283
- warn(`${name} is not installed or not available in PATH`)
450
+ if (needsReinstall) {
451
+ warn(`${name} needs to be reinstalled with a working runtime.`)
452
+ } else {
453
+ warn(`${name} is not installed or not available in PATH`)
454
+ }
284
455
 
285
456
  if (!installable) {
286
457
  return
@@ -288,7 +459,7 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
288
459
 
289
460
  const shouldInstall = await promptYesNo(`Attempt to install ${name} automatically?`, true)
290
461
  if (!shouldInstall) {
291
- warn(`Skipping ${name} installation. Please install it manually before using theslopmachine.`)
462
+ warn(`Skipping ${name} installation. Please install it manually before using theslopmachine.`)
292
463
  return
293
464
  }
294
465
 
@@ -315,10 +486,16 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
315
486
  if (checkCommand === 'bd') {
316
487
  const beads = await getBeadsVersion()
317
488
  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.')
489
+ const probe = await probeBeadsRuntime(beads.command)
490
+ if (probe.ok) {
491
+ log(`Installed ${name} via ${beads.command}: ${beads.version}`)
492
+ if (process.platform === 'win32' && !(await commandExists('bd'))) {
493
+ 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.')
494
+ }
495
+ return
321
496
  }
497
+
498
+ warn(`${name} was installed at ${beads.command}, but it still failed a runtime probe.${probe.output ? ` ${probe.output}` : ''}`)
322
499
  return
323
500
  }
324
501
 
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