theslopmachine 0.3.3 → 0.3.4

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # theslopmachine 0.3
1
+ # theslopmachine
2
2
 
3
3
  Installer package for the theslopmachine workflow owner, developer agent, required skills, templates, and local support files.
4
4
 
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process'
6
6
 
7
7
  const targetInput = process.argv[2] || '.'
8
8
  const target = path.resolve(process.cwd(), targetInput)
9
+ const bdCommand = process.env.BD_COMMAND || 'bd'
9
10
 
10
11
  function log(message) {
11
12
  console.log(`[beads-init] ${message}`)
@@ -57,11 +58,11 @@ async function pathExists(targetPath) {
57
58
  }
58
59
 
59
60
  async function runBd(args, options = {}) {
60
- return run('bd', args, { cwd: target, ...options })
61
+ return run(bdCommand, args, { cwd: target, ...options })
61
62
  }
62
63
 
63
64
  async function runBdNoninteractive(args) {
64
- return run('bd', args, {
65
+ return run(bdCommand, args, {
65
66
  cwd: target,
66
67
  env: { ...process.env, CI: '1' },
67
68
  input: '',
@@ -69,13 +70,13 @@ async function runBdNoninteractive(args) {
69
70
  }
70
71
 
71
72
  async function supportsInitFlag(flag) {
72
- const help = await run('bd', ['init', '--help'])
73
+ const help = await run(bdCommand, ['init', '--help'])
73
74
  return `${help.stdout}${help.stderr}`.includes(flag)
74
75
  }
75
76
 
76
77
  async function chooseNoninteractiveInitModeFlag(isGitRepo) {
77
78
  if (!isGitRepo) return null
78
- const help = await run('bd', ['init', '--help'])
79
+ const help = await run(bdCommand, ['init', '--help'])
79
80
  const text = `${help.stdout}${help.stderr}`
80
81
  if (text.includes('--setup-exclude')) return '--setup-exclude'
81
82
  if (text.includes('--stealth')) return '--stealth'
@@ -289,8 +290,9 @@ async function installGitHooksNonfatal() {
289
290
  }
290
291
 
291
292
  async function main() {
292
- if (!(await commandExists('bd'))) {
293
- die("'bd' is not installed or not in PATH. Install Beads first.")
293
+ const bdAvailable = bdCommand !== 'bd' ? await pathExists(bdCommand) : await commandExists('bd')
294
+ if (!bdAvailable) {
295
+ die(`'${bdCommand}' is not available. Install Beads first.`)
294
296
  }
295
297
 
296
298
  if (!(await pathExists(target))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theslopmachine",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
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,21 @@ const GITIGNORE_ENTRIES = [
21
21
  'antigravity-logs/',
22
22
  ]
23
23
 
24
+ function getWindowsBeadsCommandCandidates() {
25
+ if (process.platform !== 'win32') {
26
+ return []
27
+ }
28
+
29
+ return [
30
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Programs', 'bd', 'bd.exe') : null,
31
+ process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'go', 'bin', 'bd.exe') : null,
32
+ ].filter(Boolean)
33
+ }
34
+
35
+ async function resolveBeadsCommand() {
36
+ return resolveCommand('bd', { additionalCandidates: getWindowsBeadsCommandCandidates() })
37
+ }
38
+
24
39
  function parseInitArgs(args) {
25
40
  let openAfterInit = false
26
41
  let targetInput = '.'
@@ -104,11 +119,14 @@ async function runBeadsBootstrap(paths, targetPath, beadsScript) {
104
119
  throw new Error('Unable to locate the current Node.js executable for Beads bootstrap.')
105
120
  }
106
121
 
122
+ const beadsCommand = await resolveBeadsCommand()
123
+
107
124
  log('Running Beads setup')
108
125
  const result = await runCommand(nodeExecutable, [beadsScript, targetPath], {
109
126
  stdio: 'inherit',
110
127
  env: {
111
128
  ...process.env,
129
+ BD_COMMAND: beadsCommand || '',
112
130
  HOME: paths.home,
113
131
  },
114
132
  })
package/src/install.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  log,
21
21
  makeExecutableIfShellScript,
22
22
  pathExists,
23
+ prependToProcessPath,
23
24
  promptText,
24
25
  promptYesNo,
25
26
  readJsonIfExists,
@@ -33,6 +34,107 @@ function assetsRoot() {
33
34
  return path.join(PACKAGE_ROOT, 'assets')
34
35
  }
35
36
 
37
+ function getWindowsBeadsBinDirs() {
38
+ if (process.platform !== 'win32') {
39
+ return []
40
+ }
41
+
42
+ const dirs = [
43
+ process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Programs', 'bd') : null,
44
+ process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'go', 'bin') : null,
45
+ ].filter(Boolean)
46
+
47
+ return [...new Set(dirs)]
48
+ }
49
+
50
+ function getWindowsBeadsCommandCandidates() {
51
+ return getWindowsBeadsBinDirs().map((dir) => path.join(dir, 'bd.exe'))
52
+ }
53
+
54
+ async function resolveBeadsCommand() {
55
+ return resolveCommand('bd', { additionalCandidates: getWindowsBeadsCommandCandidates() })
56
+ }
57
+
58
+ async function getBeadsVersion() {
59
+ const command = await resolveBeadsCommand()
60
+ if (!command) {
61
+ return null
62
+ }
63
+
64
+ const result = await runCommand(command, ['version'])
65
+ if (result.code !== 0) {
66
+ return null
67
+ }
68
+
69
+ return { command, version: (result.stdout || result.stderr).trim() }
70
+ }
71
+
72
+ function quotePowerShell(value) {
73
+ return `'${String(value).replaceAll(`'`, `''`)}'`
74
+ }
75
+
76
+ async function updateWindowsUserPath(entry) {
77
+ const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
78
+ if (!shell) {
79
+ warn(`Unable to persist ${entry} into the Windows user PATH automatically.`)
80
+ return false
81
+ }
82
+
83
+ const quotedEntry = quotePowerShell(entry)
84
+ const script = [
85
+ `$entry = ${quotedEntry}`,
86
+ `$existing = [Environment]::GetEnvironmentVariable('Path', 'User')`,
87
+ `$parts = @()`,
88
+ `if ($existing) { $parts = $existing -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ } }`,
89
+ `if ($parts -notcontains $entry) {`,
90
+ ` $newValue = if ($existing -and $existing.Trim()) { "$existing;$entry" } else { $entry }`,
91
+ ` [Environment]::SetEnvironmentVariable('Path', $newValue, 'User')`,
92
+ `}`,
93
+ ].join('; ')
94
+
95
+ const result = await runCommand(shell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script])
96
+ if (result.code !== 0) {
97
+ warn(`Unable to persist ${entry} into the Windows user PATH automatically.`)
98
+ return false
99
+ }
100
+
101
+ return true
102
+ }
103
+
104
+ async function ensureWindowsBeadsPath() {
105
+ if (process.platform !== 'win32') {
106
+ return
107
+ }
108
+
109
+ for (const dir of getWindowsBeadsBinDirs()) {
110
+ if (!(await pathExists(dir))) {
111
+ continue
112
+ }
113
+
114
+ prependToProcessPath(dir)
115
+ await updateWindowsUserPath(dir)
116
+ }
117
+ }
118
+
119
+ async function installBeadsOnWindows() {
120
+ const shell = await resolveCommand('pwsh') || await resolveCommand('powershell')
121
+ if (!shell) {
122
+ return { code: 1, stdout: '', stderr: 'PowerShell is required to install Beads on Windows' }
123
+ }
124
+
125
+ log('Installing Beads with the upstream Windows PowerShell installer')
126
+ const result = await runCommand(shell, [
127
+ '-NoProfile',
128
+ '-ExecutionPolicy',
129
+ 'Bypass',
130
+ '-Command',
131
+ 'irm https://raw.githubusercontent.com/steveyegge/beads/main/install.ps1 | iex',
132
+ ], { stdio: 'inherit' })
133
+
134
+ await ensureWindowsBeadsPath()
135
+ return result
136
+ }
137
+
36
138
  async function getCommandVersion(command, args = ['--version']) {
37
139
  const exists = await commandExists(command)
38
140
  if (!exists) return null
@@ -78,6 +180,10 @@ async function tryInstallCoreDependency(name) {
78
180
  return runCommand('npm', ['install', '-g', 'opencode-ai@latest'], { stdio: 'inherit' })
79
181
  }
80
182
  if (name === 'bd') {
183
+ if (process.platform === 'win32') {
184
+ return installBeadsOnWindows()
185
+ }
186
+
81
187
  log(`Installing @beads/bd@${BEADS_VERSION} globally via npm`)
82
188
  return runCommand('npm', ['install', '-g', `@beads/bd@${BEADS_VERSION}`], { stdio: 'inherit' })
83
189
  }
@@ -154,6 +260,17 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
154
260
  }
155
261
  }
156
262
 
263
+ if (checkCommand === 'bd') {
264
+ const beads = await getBeadsVersion()
265
+ 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}`)
269
+ }
270
+ return
271
+ }
272
+ }
273
+
157
274
  const version = await getCommandVersion(checkCommand)
158
275
  if (version) {
159
276
  log(`${name} detected: ${version}`)
@@ -194,6 +311,19 @@ async function ensureDependency({ name, checkCommand, requiredVersion, installab
194
311
 
195
312
  warn('opencode-ai install completed, but the package could not be verified. Open a new terminal and run `opencode --version` before using `slopmachine init -o`.')
196
313
  }
314
+
315
+ if (checkCommand === 'bd') {
316
+ const beads = await getBeadsVersion()
317
+ 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.')
321
+ }
322
+ return
323
+ }
324
+
325
+ warn('Beads installation completed, but the `bd` command could not be verified. Re-open PowerShell or Command Prompt and run `bd version`.')
326
+ }
197
327
  }
198
328
 
199
329
  async function checkDocker() {
package/src/utils.js CHANGED
@@ -154,26 +154,30 @@ async function getGlobalNpmPrefix() {
154
154
  return prefix || null
155
155
  }
156
156
 
157
- export async function resolveCommand(command) {
157
+ export async function resolveCommand(command, options = {}) {
158
158
  if (await commandExists(command)) {
159
159
  return command
160
160
  }
161
161
 
162
162
  const prefix = await getGlobalNpmPrefix()
163
- if (!prefix) {
164
- return null
163
+ const candidates = []
164
+
165
+ if (prefix) {
166
+ candidates.push(...(process.platform === 'win32'
167
+ ? [
168
+ path.join(prefix, `${command}.cmd`),
169
+ path.join(prefix, `${command}.exe`),
170
+ path.join(prefix, command),
171
+ ]
172
+ : [
173
+ path.join(prefix, 'bin', command),
174
+ path.join(prefix, command),
175
+ ]))
165
176
  }
166
177
 
167
- const candidates = process.platform === 'win32'
168
- ? [
169
- path.join(prefix, `${command}.cmd`),
170
- path.join(prefix, `${command}.exe`),
171
- path.join(prefix, command),
172
- ]
173
- : [
174
- path.join(prefix, 'bin', command),
175
- path.join(prefix, command),
176
- ]
178
+ if (Array.isArray(options.additionalCandidates)) {
179
+ candidates.push(...options.additionalCandidates)
180
+ }
177
181
 
178
182
  for (const candidate of candidates) {
179
183
  if (await pathExists(candidate)) {
@@ -184,6 +188,20 @@ export async function resolveCommand(command) {
184
188
  return null
185
189
  }
186
190
 
191
+ export function prependToProcessPath(entry) {
192
+ if (!entry) {
193
+ return
194
+ }
195
+
196
+ const currentPath = process.env.PATH || ''
197
+ const entries = currentPath.split(path.delimiter).filter(Boolean)
198
+ if (entries.includes(entry)) {
199
+ return
200
+ }
201
+
202
+ process.env.PATH = currentPath ? `${entry}${path.delimiter}${currentPath}` : entry
203
+ }
204
+
187
205
  export async function findBashExecutable() {
188
206
  if (await commandExists('bash')) {
189
207
  return 'bash'