start-command 0.7.0 → 0.7.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # start-command
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - fa0fb23: docs: Update documentation to Bun-first approach
8
+ - Remove npm installation option from README.md
9
+ - Update examples to use bun commands instead of npm
10
+ - Change package.json engines from node to bun
11
+ - Update REQUIREMENTS.md to remove Node.js alternative
12
+
13
+ ## 0.7.1
14
+
15
+ ### Patch Changes
16
+
17
+ - d5a7c66: Fix all --version detection issues
18
+ - Fix screen version detection by capturing stderr
19
+ - Show Bun version instead of Node.js version when running with Bun
20
+ - Show macOS ProductVersion instead of kernel version
21
+ - Fix argument parsing to handle `$ --version --` same as `$ --version`
22
+ - Update all scripts and examples to use Bun instead of Node.js
23
+ - Add comprehensive tests for --version flag
24
+
3
25
  ## 0.7.0
4
26
 
5
27
  ### Minor Changes
package/README.md CHANGED
@@ -4,18 +4,12 @@ Gamification of coding - execute any command with automatic logging and ability
4
4
 
5
5
  ## Installation
6
6
 
7
- We recommend using [Bun](https://bun.sh) for the best performance:
7
+ Install using [Bun](https://bun.sh):
8
8
 
9
9
  ```bash
10
10
  bun install -g start-command
11
11
  ```
12
12
 
13
- Or using npm:
14
-
15
- ```bash
16
- npm install -g start-command
17
- ```
18
-
19
13
  ## Usage
20
14
 
21
15
  The `$` command acts as a wrapper for any shell command:
@@ -123,16 +117,16 @@ Run commands in isolated environments using terminal multiplexers or containers:
123
117
 
124
118
  ```bash
125
119
  # Run in tmux (attached by default)
126
- $ --isolated tmux -- npm start
120
+ $ --isolated tmux -- bun start
127
121
 
128
122
  # Run in screen detached
129
- $ --isolated screen --detached -- npm start
123
+ $ --isolated screen --detached -- bun start
130
124
 
131
125
  # Run in docker container
132
- $ --isolated docker --image node:20 -- npm install
126
+ $ --isolated docker --image oven/bun:latest -- bun install
133
127
 
134
128
  # Short form with custom session name
135
- $ -i tmux -s my-session -d npm start
129
+ $ -i tmux -s my-session -d bun start
136
130
  ```
137
131
 
138
132
  #### Supported Backends
@@ -169,7 +163,7 @@ The tool works in any environment:
169
163
 
170
164
  ### Required
171
165
 
172
- - Node.js >= 14.0.0
166
+ - [Bun](https://bun.sh) >= 1.0.0
173
167
 
174
168
  ### Optional (for full auto-reporting)
175
169
 
@@ -183,7 +177,7 @@ To set up auto-reporting:
183
177
  gh auth login
184
178
 
185
179
  # Install log uploader
186
- npm install -g gh-upload-log
180
+ bun install -g gh-upload-log
187
181
  ```
188
182
 
189
183
  ## How It Works
@@ -214,10 +208,10 @@ Example:
214
208
 
215
209
  ```bash
216
210
  # Run without auto-issue creation
217
- START_DISABLE_AUTO_ISSUE=1 $ npm test
211
+ START_DISABLE_AUTO_ISSUE=1 $ bun test
218
212
 
219
213
  # Use custom log directory
220
- START_LOG_DIR=./logs $ npm test
214
+ START_LOG_DIR=./logs $ bun test
221
215
 
222
216
  # Disable substitutions (use raw command)
223
217
  START_DISABLE_SUBSTITUTIONS=1 $ install lodash npm package
@@ -237,10 +231,10 @@ Log files are saved as `start-command-{timestamp}-{random}.log` and contain:
237
231
  ```
238
232
  === Start Command Log ===
239
233
  Timestamp: 2024-01-15 10:30:45.123
240
- Command: npm test
234
+ Command: bun test
241
235
  Shell: /bin/bash
242
236
  Platform: linux
243
- Node Version: v18.17.0
237
+ Bun Version: 1.2.0
244
238
  Working Directory: /home/user/project
245
239
  ==================================================
246
240
 
package/REQUIREMENTS.md CHANGED
@@ -76,7 +76,7 @@ The `$` command is a CLI tool that wraps any shell command and provides automati
76
76
  - Command that was executed
77
77
  - Exit code
78
78
  - Link to uploaded log
79
- - System information (OS, Node version)
79
+ - System information (OS, Bun/Node.js version)
80
80
  - Timestamp
81
81
  - Print the created issue URL to console
82
82
 
@@ -209,7 +209,7 @@ Repository not detected - automatic issue creation skipped
209
209
 
210
210
  ### Required
211
211
 
212
- - Node.js >= 14.0.0
212
+ - **Bun >= 1.0.0** (primary runtime)
213
213
  - `child_process` (built-in)
214
214
  - `os` (built-in)
215
215
  - `fs` (built-in)
@@ -0,0 +1,547 @@
1
+ # Case Study: Issue #22 - Version Detection Issues
2
+
3
+ ## Issue Overview
4
+
5
+ **Issue ID:** #22
6
+ **Title:** --version issues
7
+ **Reported By:** konard
8
+ **Created:** 2025-12-23T17:55:53Z
9
+ **Status:** Open
10
+
11
+ ## Problem Summary
12
+
13
+ The `$ --version` command exhibited multiple issues when run on macOS:
14
+
15
+ 1. **Screen version not detected** - Despite screen being installed, it showed "not installed"
16
+ 2. **Wrong runtime displayed** - Showed Node.js version instead of Bun version
17
+ 3. **Incorrect OS version format** - Showed OS Release (kernel version) instead of macOS version
18
+ 4. **Argument parsing issue** - `$ --version --` resulted in "No command provided" error
19
+
20
+ ## Timeline of Events
21
+
22
+ ### User Environment
23
+
24
+ - **System:** macOS 15.7.2 (ProductVersion)
25
+ - **Kernel:** 24.6.0 (OS Release)
26
+ - **Bun Version:** 1.2.20
27
+ - **Node.js Emulation:** v24.3.0 (provided by Bun)
28
+ - **Architecture:** arm64
29
+ - **Package:** start-command@0.7.0
30
+
31
+ ### Observed Behavior
32
+
33
+ #### Command 1: `$ --version --`
34
+
35
+ ```
36
+ Error: No command provided
37
+ Usage: $ [options] [--] <command> [args...]
38
+ ```
39
+
40
+ **Expected:** Should display version information (same as `$ --version`)
41
+ **Actual:** Treated `--` as command separator and found no command after it
42
+
43
+ #### Command 2: `$ --version`
44
+
45
+ ```
46
+ start-command version: 0.7.0
47
+
48
+ OS: darwin
49
+ OS Release: 24.6.0
50
+ Node Version: v24.3.0
51
+ Architecture: arm64
52
+
53
+ Isolation tools:
54
+ screen: not installed
55
+ tmux: not installed
56
+ docker: Docker version 28.5.1, build e180ab8
57
+ ```
58
+
59
+ **Issues Identified:**
60
+
61
+ 1. Screen shown as "not installed" despite being present
62
+ 2. "Node Version: v24.3.0" when using Bun
63
+ 3. "OS Release: 24.6.0" (kernel) instead of "15.7.2" (macOS version)
64
+
65
+ #### Verification Commands
66
+
67
+ ```bash
68
+ screen -v
69
+ # Output: Screen version 4.00.03 (FAU) 23-Oct-06
70
+
71
+ sw_vers
72
+ # Output:
73
+ # ProductName: macOS
74
+ # ProductVersion: 15.7.2
75
+ # BuildVersion: 24G325
76
+ ```
77
+
78
+ ## Root Cause Analysis
79
+
80
+ ### Issue 1: Screen Version Not Detected
81
+
82
+ **Location:** `src/bin/cli.js:84`
83
+
84
+ **Root Cause:**
85
+
86
+ - Screen outputs version to stderr, not stdout
87
+ - Current implementation uses `execSync()` with default stdio configuration
88
+ - On some systems/versions, screen sends output to stderr
89
+
90
+ **Code Analysis:**
91
+
92
+ ```javascript
93
+ const screenVersion = getToolVersion('screen', '--version');
94
+
95
+ function getToolVersion(toolName, versionFlag) {
96
+ try {
97
+ const result = execSync(`${toolName} ${versionFlag}`, {
98
+ encoding: 'utf8',
99
+ stdio: ['pipe', 'pipe', 'pipe'], // stderr is piped but not captured
100
+ timeout: 5000,
101
+ }).trim();
102
+ // ...
103
+ ```
104
+
105
+ The `execSync()` call captures stdout but the function doesn't merge stderr into the result. On macOS, screen may output to stderr.
106
+
107
+ ### Issue 2: Node.js Version Instead of Bun Version
108
+
109
+ **Location:** `src/bin/cli.js:76`
110
+
111
+ **Root Cause:**
112
+
113
+ - Code uses `process.version` which returns Node.js compatibility version in Bun
114
+ - Bun emulates Node.js v24.3.0 for compatibility
115
+ - Should use `process.versions.bun` or `Bun.version` instead
116
+
117
+ **Code Analysis:**
118
+
119
+ ```javascript
120
+ console.log(`Node Version: ${process.version}`);
121
+ ```
122
+
123
+ **Research Findings:**
124
+
125
+ - `process.version` in Bun → Returns emulated Node.js version (v24.3.0)
126
+ - `process.versions.bun` → Returns actual Bun version (1.2.20)
127
+ - `Bun.version` → Bun-specific API for version
128
+
129
+ **Sources:**
130
+
131
+ - [Bun — Detect the Version at Runtime](https://futurestud.io/tutorials/bun-detect-the-version-at-runtime)
132
+ - [Get the current Bun version - Bun](https://bun.com/docs/guides/util/version)
133
+
134
+ ### Issue 3: Incorrect macOS Version Format
135
+
136
+ **Location:** `src/bin/cli.js:75`
137
+
138
+ **Root Cause:**
139
+
140
+ - Code uses `os.release()` which returns kernel version (24.6.0)
141
+ - Should use `sw_vers -productVersion` for user-facing macOS version (15.7.2)
142
+
143
+ **Code Analysis:**
144
+
145
+ ```javascript
146
+ console.log(`OS Release: ${os.release()}`);
147
+ ```
148
+
149
+ **Kernel vs Product Version:**
150
+
151
+ - `os.release()` → 24.6.0 (Darwin kernel version)
152
+ - `sw_vers -productVersion` → 15.7.2 (macOS product version)
153
+
154
+ **Research Findings:**
155
+ The macOS `sw_vers` command provides user-facing version information:
156
+
157
+ - `sw_vers -productVersion` returns the macOS version (e.g., "15.7.2", "26.0")
158
+ - This is what users recognize as their macOS version
159
+
160
+ **Sources:**
161
+
162
+ - [sw_vers Man Page - macOS - SS64.com](https://ss64.com/mac/sw_vers.html)
163
+ - [Check macOS Latest Version · For Tahoe · 2025](https://mac.install.guide/macos/check-version)
164
+
165
+ ### Issue 4: Argument Parsing for `--version --`
166
+
167
+ **Location:** `src/bin/cli.js:52-55`
168
+
169
+ **Root Cause:**
170
+
171
+ - Version check happens before argument parsing
172
+ - Only checks for exact match: `args.length === 1 && (args[0] === '--version' || args[0] === '-v')`
173
+ - When `--version --` is passed, args = `['--version', '--']`, length is 2, fails the check
174
+ - Falls through to argument parser which treats `--` as separator
175
+
176
+ **Code Analysis:**
177
+
178
+ ```javascript
179
+ if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
180
+ printVersion();
181
+ process.exit(0);
182
+ }
183
+ ```
184
+
185
+ **Expected Behavior:**
186
+
187
+ - `$ --version` → Show version ✓
188
+ - `$ --version --` → Show version (should ignore trailing `--`)
189
+ - `$ --` → "No command provided" error ✓
190
+ - `$` → Show usage ✓
191
+
192
+ ## Proposed Solutions
193
+
194
+ ### Solution 1: Fix Screen Detection
195
+
196
+ Capture both stdout and stderr when detecting tool versions:
197
+
198
+ ```javascript
199
+ function getToolVersion(toolName, versionFlag) {
200
+ try {
201
+ const result = execSync(`${toolName} ${versionFlag} 2>&1`, {
202
+ encoding: 'utf8',
203
+ timeout: 5000,
204
+ }).trim();
205
+ const firstLine = result.split('\n')[0];
206
+ return firstLine;
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ ```
212
+
213
+ ### Solution 2: Use Bun Version
214
+
215
+ Detect runtime and show appropriate version:
216
+
217
+ ```javascript
218
+ // Detect if running in Bun
219
+ const runtime = typeof Bun !== 'undefined' ? 'Bun' : 'Node.js';
220
+ const runtimeVersion =
221
+ typeof Bun !== 'undefined' ? Bun.version : process.version;
222
+
223
+ console.log(`${runtime} Version: ${runtimeVersion}`);
224
+ ```
225
+
226
+ ### Solution 3: Fix macOS Version Detection
227
+
228
+ Use `sw_vers -productVersion` on macOS:
229
+
230
+ ```javascript
231
+ function getOSVersion() {
232
+ if (process.platform === 'darwin') {
233
+ try {
234
+ return execSync('sw_vers -productVersion', { encoding: 'utf8' }).trim();
235
+ } catch {
236
+ return os.release();
237
+ }
238
+ }
239
+ return os.release();
240
+ }
241
+
242
+ console.log(`OS: ${process.platform}`);
243
+ console.log(`OS Version: ${getOSVersion()}`);
244
+ ```
245
+
246
+ ### Solution 4: Fix Argument Parsing
247
+
248
+ Check for `--version` flag regardless of other arguments:
249
+
250
+ ```javascript
251
+ // Check if --version is present (ignore trailing --)
252
+ const hasVersionFlag = args.includes('--version') || args.includes('-v');
253
+ const isOnlyVersionWithSeparator =
254
+ args.length === 2 &&
255
+ (args[0] === '--version' || args[0] === '-v') &&
256
+ args[1] === '--';
257
+
258
+ if ((args.length === 1 && hasVersionFlag) || isOnlyVersionWithSeparator) {
259
+ printVersion();
260
+ process.exit(0);
261
+ }
262
+ ```
263
+
264
+ ## Implementation Plan
265
+
266
+ 1. **Fix argument parsing** - Handle `--version --` case
267
+ 2. **Fix screen detection** - Capture stderr in version detection
268
+ 3. **Replace Node with Bun** - Detect runtime and show correct version
269
+ 4. **Fix OS version** - Use `sw_vers` on macOS
270
+ 5. **Update REQUIREMENTS.md** - Remove npm references, emphasize Bun-first
271
+ 6. **Add comprehensive tests** - Cover all version scenarios
272
+ 7. **Ensure CI runs tests** - Validate quality
273
+
274
+ ## Testing Strategy
275
+
276
+ ### Test Cases Required
277
+
278
+ 1. **Argument Parsing Tests:**
279
+ - `$ --version` → Shows version
280
+ - `$ -v` → Shows version
281
+ - `$ --version --` → Shows version
282
+ - `$ --` → Error: No command provided
283
+ - `$` → Shows usage
284
+
285
+ 2. **Screen Detection Tests:**
286
+ - When screen installed → Shows version
287
+ - When screen not installed → Shows "not installed"
288
+
289
+ 3. **Runtime Detection Tests:**
290
+ - Running with Bun → Shows "Bun Version: X.X.X"
291
+ - Running with Node → Shows "Node.js Version: vX.X.X"
292
+
293
+ 4. **OS Version Tests:**
294
+ - On macOS → Shows ProductVersion (15.7.2 format)
295
+ - On Linux → Shows kernel version
296
+ - On Windows → Shows kernel version
297
+
298
+ ## Documentation Updates
299
+
300
+ ### REQUIREMENTS.md Changes Needed
301
+
302
+ 1. Replace all "npm install" references with "bun install"
303
+ 2. Update "Node.js >= 14.0.0" to "Bun >= 1.0.0"
304
+ 3. Update system information to show "Bun Version" instead of "Node Version"
305
+ 4. Emphasize Bun-first approach
306
+
307
+ ## Additional Notes
308
+
309
+ - The project uses `#!/usr/bin/env bun` shebang correctly
310
+ - Package scripts still use `node --test` which should be changed to `bun test`
311
+ - All references to npm in documentation should be updated to bun
312
+ - Consider removing npm-specific features if they don't work with bun
313
+
314
+ ## Files to Modify
315
+
316
+ 1. `src/bin/cli.js` - Fix all version detection issues
317
+ 2. `src/lib/args-parser.js` - No changes needed (issue is in cli.js)
318
+ 3. `REQUIREMENTS.md` - Update to Bun-first approach
319
+ 4. `package.json` - Update test script to use bun
320
+ 5. `test/cli.test.js` - Add version detection tests
321
+ 6. New: `test/version.test.js` - Comprehensive version tests
322
+
323
+ ## Success Criteria
324
+
325
+ - ✅ `$ --version --` works same as `$ --version`
326
+ - ✅ Screen version detected correctly when installed
327
+ - ✅ Shows "Bun Version" instead of "Node Version"
328
+ - ✅ macOS shows ProductVersion not kernel version
329
+ - ✅ All tests pass locally
330
+ - ✅ CI tests pass
331
+ - ✅ REQUIREMENTS.md updated
332
+ - ✅ No npm references in documentation
333
+
334
+ ## Implementation Status
335
+
336
+ **Status:** 🔄 In Progress (Second Iteration)
337
+
338
+ ### First Iteration (PR #23 - Merged to Main)
339
+
340
+ The initial fixes were merged to `main` branch via PR #23, addressing:
341
+
342
+ - Version flag handling with trailing `--`
343
+ - Runtime detection (Bun vs Node.js)
344
+ - macOS version detection using `sw_vers`
345
+ - Basic screen detection using `2>&1`
346
+
347
+ ### Second Iteration (This PR) - The Screen Detection Bug
348
+
349
+ After v0.7.1 was released, users on macOS still reported:
350
+
351
+ ```
352
+ screen: not installed
353
+ ```
354
+
355
+ Even though screen was installed:
356
+
357
+ ```bash
358
+ $ screen -v
359
+ Screen version 4.00.03 (FAU) 23-Oct-06
360
+ ```
361
+
362
+ #### Deep Root Cause Analysis
363
+
364
+ **The Problem:** The macOS bundled version of GNU Screen (4.00.03) returns a **non-zero exit code** when running `screen --version` or `screen -v`, even though it successfully outputs the version information.
365
+
366
+ **Discovery Process:**
367
+
368
+ 1. Searched for known issues with GNU Screen version flag
369
+ 2. Found reference to [GNU Screen v.4.8.0 release notes](https://savannah.gnu.org/forum/forum.php?forum_id=9665)
370
+ 3. The release notes mention: "Make screen exit code be 0 when checking --version"
371
+
372
+ **Root Cause Confirmed:**
373
+ This was a **known bug in GNU Screen prior to version 4.8.0** where the `--version` flag would exit with a non-zero status code.
374
+
375
+ - macOS bundled version: 4.00.03 (affected by bug)
376
+ - Bug fix version: 4.8.0+
377
+ - Linux typically has newer versions via package managers
378
+
379
+ **Why Previous Fix Didn't Work:**
380
+
381
+ The previous fix used `execSync()` with `2>&1` to capture stderr:
382
+
383
+ ```javascript
384
+ const result = execSync(`${toolName} ${versionFlag} 2>&1`, {
385
+ encoding: 'utf8',
386
+ timeout: 5000,
387
+ }).trim();
388
+ ```
389
+
390
+ **Problem:** `execSync()` throws an exception when the command returns non-zero exit code, so even though the output was captured correctly, the `catch` block returned `null`.
391
+
392
+ #### The Solution
393
+
394
+ Use `spawnSync()` instead of `execSync()` to capture output **regardless of exit code**:
395
+
396
+ ```javascript
397
+ function getToolVersion(toolName, versionFlag, verbose = false) {
398
+ const isWindows = process.platform === 'win32';
399
+ const whichCmd = isWindows ? 'where' : 'which';
400
+
401
+ // First, check if the tool exists in PATH
402
+ try {
403
+ execSync(`${whichCmd} ${toolName}`, {
404
+ encoding: 'utf8',
405
+ timeout: 5000,
406
+ stdio: ['pipe', 'pipe', 'pipe'],
407
+ });
408
+ } catch {
409
+ // Tool not found in PATH
410
+ return null;
411
+ }
412
+
413
+ // Tool exists, try to get version using spawnSync
414
+ // This captures output regardless of exit code
415
+ const result = spawnSync(toolName, [versionFlag], {
416
+ encoding: 'utf8',
417
+ timeout: 5000,
418
+ shell: false,
419
+ });
420
+
421
+ // Combine stdout and stderr
422
+ const output = ((result.stdout || '') + (result.stderr || '')).trim();
423
+
424
+ if (!output) {
425
+ return null;
426
+ }
427
+
428
+ return output.split('\n')[0];
429
+ }
430
+ ```
431
+
432
+ #### Additional Improvements
433
+
434
+ 1. **`--verbose` flag support** - Users can now debug version detection:
435
+
436
+ ```bash
437
+ $ --version --verbose
438
+ ```
439
+
440
+ This shows detailed debugging information about tool detection.
441
+
442
+ 2. **`-v` flag for screen** - Changed from `--version` to `-v` which is more universally supported.
443
+
444
+ 3. **Comprehensive test coverage** - Added 14 tests for version detection scenarios.
445
+
446
+ ### Changes Made (Second Iteration)
447
+
448
+ #### src/bin/cli.js
449
+
450
+ 1. **Added `spawnSync` import:**
451
+
452
+ ```javascript
453
+ const { spawn, execSync, spawnSync } = require('child_process');
454
+ ```
455
+
456
+ 2. **Enhanced version flag handling with verbose support:**
457
+
458
+ ```javascript
459
+ const hasVerboseWithVersion =
460
+ hasVersionFlag &&
461
+ args.some((arg) => arg === '--verbose' || arg === '--debug');
462
+
463
+ if (hasVersionFlag && isVersionOnly) {
464
+ printVersion(hasVerboseWithVersion || config.verbose);
465
+ process.exit(0);
466
+ }
467
+ ```
468
+
469
+ 3. **Fixed getToolVersion to use spawnSync:**
470
+ - First checks if tool exists using `which`/`where`
471
+ - Uses `spawnSync` to capture output regardless of exit code
472
+ - Combines stdout and stderr
473
+ - Supports verbose mode for debugging
474
+
475
+ 4. **Updated printVersion to accept verbose parameter:**
476
+ - Shows `[verbose]` messages when debugging
477
+ - Logs tool detection details
478
+
479
+ #### test/version.test.js
480
+
481
+ Added 3 new tests for verbose mode:
482
+
483
+ - `--version --verbose`
484
+ - `--version --debug`
485
+ - `START_VERBOSE=1` environment variable
486
+
487
+ ### Test Results
488
+
489
+ All 84 tests passing across 4 test files:
490
+
491
+ - `test/version.test.js`: 14 tests
492
+ - `test/cli.test.js`: Passing
493
+ - `test/args-parser.test.js`: Passing
494
+ - `test/isolation.test.js`: Passing
495
+ - `test/substitution.test.js`: 22 tests
496
+
497
+ ### Verified Behavior
498
+
499
+ ```bash
500
+ $ --version
501
+ start-command version: 0.7.1
502
+
503
+ OS: linux
504
+ OS Version: 6.8.0-90-generic
505
+ Bun Version: 1.3.3
506
+ Architecture: x64
507
+
508
+ Isolation tools:
509
+ screen: Screen version 4.09.01 (GNU) 20-Aug-23
510
+ tmux: tmux 3.4
511
+ docker: not installed
512
+ ```
513
+
514
+ ```bash
515
+ $ --version --verbose
516
+ start-command version: 0.7.1
517
+
518
+ OS: linux
519
+ OS Version: 6.8.0-90-generic
520
+ Bun Version: 1.3.3
521
+ Architecture: x64
522
+
523
+ Isolation tools:
524
+ [verbose] Checking isolation tools...
525
+ [verbose] screen -v: exit=0, output="Screen version 4.09.01 (GNU) 20-Aug-23"
526
+ screen: Screen version 4.09.01 (GNU) 20-Aug-23
527
+ [verbose] tmux -V: exit=0, output="tmux 3.4"
528
+ tmux: tmux 3.4
529
+ [verbose] docker: not found in PATH
530
+ docker: not installed
531
+ ```
532
+
533
+ ## Key Learnings
534
+
535
+ 1. **Exit codes matter**: Some tools return non-zero exit codes even for successful operations. Always consider using `spawnSync` when you need to capture output regardless of exit status.
536
+
537
+ 2. **macOS bundled tools are often outdated**: The macOS bundled version of screen (4.00.03 from 2006) has known bugs fixed in newer versions.
538
+
539
+ 3. **Testing on multiple platforms is crucial**: The bug only manifested on macOS with the bundled screen, not on Linux with modern screen versions.
540
+
541
+ 4. **Verbose mode is invaluable for debugging**: Adding `--verbose` support allows users to self-diagnose issues.
542
+
543
+ ## References
544
+
545
+ - [GNU Screen v.4.8.0 Release Notes](https://savannah.gnu.org/forum/forum.php?forum_id=9665) - Documents the exit code fix
546
+ - [screen Man Page - macOS](https://ss64.com/mac/screen.html) - macOS screen documentation
547
+ - [Node.js spawnSync](https://nodejs.org/api/child_process.html#child_processspawnsynccommand-args-options) - Alternative to execSync that doesn't throw on non-zero exit
@@ -0,0 +1,12 @@
1
+ {
2
+ "author": {
3
+ "id": "MDQ6VXNlcjE0MzE5MDQ=",
4
+ "is_bot": false,
5
+ "login": "konard",
6
+ "name": "Konstantin Diachenko"
7
+ },
8
+ "body": "```\nkonard@MacBook-Pro-Konstantin ~ % bun install -g start-command \nbun add v1.2.20 (6ad208bc)\n\ninstalled start-command@0.7.0 with binaries:\n - $\n\n1 package installed [2.40s]\nkonard@MacBook-Pro-Konstantin ~ % $ --version -- \nError: No command provided\nUsage: $ [options] [--] <command> [args...]\n $ <command> [args...]\n\nOptions:\n --isolated, -i <environment> Run in isolated environment (screen, tmux, docker)\n --attached, -a Run in attached mode (foreground)\n --detached, -d Run in detached mode (background)\n --session, -s <name> Session name for isolation\n --image <image> Docker image (required for docker isolation)\n --version, -v Show version information\n\nExamples:\n $ echo \"Hello World\"\n $ npm test\n $ --isolated tmux -- npm start\n $ -i screen -d npm start\n $ --isolated docker --image node:20 -- npm install\n\nFeatures:\n - Logs all output to temporary directory\n - Displays timestamps and exit codes\n - Auto-reports failures for NPM packages (when gh is available)\n - Natural language command aliases (via substitutions.lino)\n - Process isolation via screen, tmux, or docker\n\nAlias examples:\n $ install lodash npm package -> npm install lodash\n $ install 4.17.21 version of lodash npm package -> npm install lodash@4.17.21\n $ clone https://github.com/user/repo repository -> git clone https://github.com/user/repo\nkonard@MacBook-Pro-Konstantin ~ % $ --version \nstart-command version: 0.7.0\n\nOS: darwin\nOS Release: 24.6.0\nNode Version: v24.3.0\nArchitecture: arm64\n\nIsolation tools:\n screen: not installed\n tmux: not installed\n docker: Docker version 28.5.1, build e180ab8\nkonard@MacBook-Pro-Konstantin ~ % screen -v\nScreen version 4.00.03 (FAU) 23-Oct-06\nkonard@MacBook-Pro-Konstantin ~ % docker -v\nDocker version 28.5.1, build e180ab8\nkonard@MacBook-Pro-Konstantin ~ % sw_vers\nProductName:\t\tmacOS\nProductVersion:\t\t15.7.2\nBuildVersion:\t\t24G325\nkonard@MacBook-Pro-Konstantin ~ % \n```\n\nThe problems are:\n\n- `screen` tool version was not detected.\n- We use Bun, not Node.js (double check that, and also remove suggestions to install it using npm in the docs. Our tool should be Bun first and Bun only. For all scripts and shebangs we should use bun, not node. So in `--version` there should be bun version, not `node.js` version.\n- OS version detection was wrong.\n- `$ --version --` should work the same as `$ --version`, only `$ --` or `$` should result in `No command provided`.\n\nMake sure we have tests for all these (and these tests should be executed in CI, so we guarantee the quality). Also [REQUIREMENTS.md](https://github.com/link-foundation/start/blob/main/REQUIREMENTS.md) should be updated as needed.\n\nPlease download all logs and data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), in which we will reconstruct timeline/sequence of events, find root causes of the problem, and propose possible solutions.",
9
+ "comments": [],
10
+ "createdAt": "2025-12-23T17:55:53Z",
11
+ "title": "--version issues"
12
+ }
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  /**
3
3
  * Experiment to test different approaches for running screen in attached mode
4
- * from Node.js without a TTY
4
+ * from Bun without a TTY
5
5
  */
6
6
 
7
7
  const { spawn, spawnSync, execSync } = require('child_process');
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  /**
3
3
  * Experiment to test screen's logfile capture functionality
4
4
  * to understand the root cause of issue #15
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  /**
3
3
  * Experiment to test different screen invocation modes
4
4
  * This helps understand how screen behaves in different contexts
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "start-command",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "$": "./src/bin/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "node --test test/",
10
+ "test": "bun test test/",
11
11
  "lint": "eslint .",
12
12
  "lint:fix": "eslint . --fix",
13
13
  "format": "prettier --write .",
14
14
  "format:check": "prettier --check .",
15
- "check:file-size": "node scripts/check-file-size.mjs",
16
- "check": "npm run lint && npm run format:check && npm run check:file-size",
15
+ "check:file-size": "bun scripts/check-file-size.mjs",
16
+ "check": "bun run lint && bun run format:check && bun run check:file-size",
17
17
  "prepare": "husky || true",
18
18
  "changeset": "changeset",
19
- "changeset:version": "node scripts/changeset-version.mjs",
19
+ "changeset:version": "bun scripts/changeset-version.mjs",
20
20
  "changeset:publish": "changeset publish",
21
21
  "changeset:status": "changeset status --since=origin/main"
22
22
  },
@@ -30,7 +30,7 @@
30
30
  "author": "",
31
31
  "license": "MIT",
32
32
  "engines": {
33
- "node": ">=20.0.0"
33
+ "bun": ">=1.0.0"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
package/src/bin/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- const { spawn, execSync } = require('child_process');
3
+ const { spawn, execSync, spawnSync } = require('child_process');
4
4
  const process = require('process');
5
5
  const os = require('os');
6
6
  const fs = require('fs');
@@ -49,8 +49,28 @@ const config = {
49
49
  const args = process.argv.slice(2);
50
50
 
51
51
  // Handle --version flag
52
- if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
53
- printVersion();
52
+ // Support: $ --version, $ -v, $ --version --
53
+ // The trailing -- should be ignored for version check
54
+ // Also support --verbose flag for debugging: $ --version --verbose
55
+ const hasVersionFlag =
56
+ args.length >= 1 && (args[0] === '--version' || args[0] === '-v');
57
+
58
+ // Check for --verbose flag in version context
59
+ const hasVerboseWithVersion =
60
+ hasVersionFlag &&
61
+ args.some((arg) => arg === '--verbose' || arg === '--debug');
62
+
63
+ // Determine if this is a version-only call
64
+ // Allow: --version, -v, --version --, --version --verbose, etc.
65
+ const versionRelatedArgs = ['--version', '-v', '--', '--verbose', '--debug'];
66
+ const isVersionOnly =
67
+ hasVersionFlag &&
68
+ args.every(
69
+ (arg) => versionRelatedArgs.includes(arg) || arg === args[0] // Allow the version flag itself
70
+ );
71
+
72
+ if (hasVersionFlag && isVersionOnly) {
73
+ printVersion(hasVerboseWithVersion || config.verbose);
54
74
  process.exit(0);
55
75
  }
56
76
 
@@ -61,8 +81,9 @@ if (args.length === 0) {
61
81
 
62
82
  /**
63
83
  * Print version information
84
+ * @param {boolean} verbose - Whether to show verbose debugging info
64
85
  */
65
- function printVersion() {
86
+ function printVersion(verbose = false) {
66
87
  // Get package version
67
88
  const packageJson = require('../../package.json');
68
89
  const startCommandVersion = packageJson.version;
@@ -70,18 +91,50 @@ function printVersion() {
70
91
  console.log(`start-command version: ${startCommandVersion}`);
71
92
  console.log('');
72
93
 
94
+ // Get runtime information (Bun or Node.js)
95
+ const runtime = typeof Bun !== 'undefined' ? 'Bun' : 'Node.js';
96
+ const runtimeVersion =
97
+ typeof Bun !== 'undefined' ? Bun.version : process.version;
98
+
73
99
  // Get OS information
74
100
  console.log(`OS: ${process.platform}`);
75
- console.log(`OS Release: ${os.release()}`);
76
- console.log(`Node Version: ${process.version}`);
101
+
102
+ // Get OS version (use sw_vers on macOS for user-friendly version)
103
+ let osVersion = os.release();
104
+ if (process.platform === 'darwin') {
105
+ try {
106
+ osVersion = execSync('sw_vers -productVersion', {
107
+ encoding: 'utf8',
108
+ timeout: 5000,
109
+ }).trim();
110
+ if (verbose) {
111
+ console.log(`[verbose] macOS version from sw_vers: ${osVersion}`);
112
+ }
113
+ } catch {
114
+ // Fallback to kernel version if sw_vers fails
115
+ osVersion = os.release();
116
+ if (verbose) {
117
+ console.log(
118
+ `[verbose] sw_vers failed, using kernel version: ${osVersion}`
119
+ );
120
+ }
121
+ }
122
+ }
123
+
124
+ console.log(`OS Version: ${osVersion}`);
125
+ console.log(`${runtime} Version: ${runtimeVersion}`);
77
126
  console.log(`Architecture: ${process.arch}`);
78
127
  console.log('');
79
128
 
80
129
  // Check for installed isolation tools
81
130
  console.log('Isolation tools:');
82
131
 
83
- // Check screen
84
- const screenVersion = getToolVersion('screen', '--version');
132
+ if (verbose) {
133
+ console.log('[verbose] Checking isolation tools...');
134
+ }
135
+
136
+ // Check screen (use -v flag for compatibility with older versions)
137
+ const screenVersion = getToolVersion('screen', '-v', verbose);
85
138
  if (screenVersion) {
86
139
  console.log(` screen: ${screenVersion}`);
87
140
  } else {
@@ -89,7 +142,7 @@ function printVersion() {
89
142
  }
90
143
 
91
144
  // Check tmux
92
- const tmuxVersion = getToolVersion('tmux', '-V');
145
+ const tmuxVersion = getToolVersion('tmux', '-V', verbose);
93
146
  if (tmuxVersion) {
94
147
  console.log(` tmux: ${tmuxVersion}`);
95
148
  } else {
@@ -97,7 +150,7 @@ function printVersion() {
97
150
  }
98
151
 
99
152
  // Check docker
100
- const dockerVersion = getToolVersion('docker', '--version');
153
+ const dockerVersion = getToolVersion('docker', '--version', verbose);
101
154
  if (dockerVersion) {
102
155
  console.log(` docker: ${dockerVersion}`);
103
156
  } else {
@@ -109,23 +162,53 @@ function printVersion() {
109
162
  * Get version of an installed tool
110
163
  * @param {string} toolName - Name of the tool
111
164
  * @param {string} versionFlag - Flag to get version (e.g., '--version', '-V')
165
+ * @param {boolean} verbose - Whether to log verbose information
112
166
  * @returns {string|null} Version string or null if not installed
113
167
  */
114
- function getToolVersion(toolName, versionFlag) {
168
+ function getToolVersion(toolName, versionFlag, verbose = false) {
169
+ const isWindows = process.platform === 'win32';
170
+ const whichCmd = isWindows ? 'where' : 'which';
171
+
172
+ // First, check if the tool exists in PATH
115
173
  try {
116
- const result = execSync(`${toolName} ${versionFlag}`, {
174
+ execSync(`${whichCmd} ${toolName}`, {
117
175
  encoding: 'utf8',
118
- stdio: ['pipe', 'pipe', 'pipe'],
119
176
  timeout: 5000,
120
- }).trim();
121
-
122
- // Extract version number from output
123
- // Most tools output version in various formats, so we'll return the first line
124
- const firstLine = result.split('\n')[0];
125
- return firstLine;
177
+ stdio: ['pipe', 'pipe', 'pipe'],
178
+ });
126
179
  } catch {
180
+ // Tool not found in PATH
181
+ if (verbose) {
182
+ console.log(`[verbose] ${toolName}: not found in PATH`);
183
+ }
184
+ return null;
185
+ }
186
+
187
+ // Tool exists, try to get version using spawnSync
188
+ // This captures output regardless of exit code (some tools like older screen
189
+ // versions return non-zero exit code even when showing version successfully)
190
+ const result = spawnSync(toolName, [versionFlag], {
191
+ encoding: 'utf8',
192
+ timeout: 5000,
193
+ shell: false,
194
+ });
195
+
196
+ // Combine stdout and stderr (some tools output version to stderr)
197
+ const output = ((result.stdout || '') + (result.stderr || '')).trim();
198
+
199
+ if (verbose) {
200
+ console.log(
201
+ `[verbose] ${toolName} ${versionFlag}: exit=${result.status}, output="${output.substring(0, 100)}"`
202
+ );
203
+ }
204
+
205
+ if (!output) {
127
206
  return null;
128
207
  }
208
+
209
+ // Return the first line of output
210
+ const firstLine = output.split('\n')[0];
211
+ return firstLine || null;
129
212
  }
130
213
 
131
214
  /**
@@ -149,10 +232,10 @@ function printUsage() {
149
232
  console.log('');
150
233
  console.log('Examples:');
151
234
  console.log(' $ echo "Hello World"');
152
- console.log(' $ npm test');
153
- console.log(' $ --isolated tmux -- npm start');
154
- console.log(' $ -i screen -d npm start');
155
- console.log(' $ --isolated docker --image node:20 -- npm install');
235
+ console.log(' $ bun test');
236
+ console.log(' $ --isolated tmux -- bun start');
237
+ console.log(' $ -i screen -d bun start');
238
+ console.log(' $ --isolated docker --image oven/bun:latest -- bun install');
156
239
  console.log('');
157
240
  console.log('Features:');
158
241
  console.log(' - Logs all output to temporary directory');
@@ -313,6 +396,11 @@ function runDirect(cmd) {
313
396
  let logContent = '';
314
397
  const startTime = getTimestamp();
315
398
 
399
+ // Get runtime information
400
+ const runtime = typeof Bun !== 'undefined' ? 'Bun' : 'Node.js';
401
+ const runtimeVersion =
402
+ typeof Bun !== 'undefined' ? Bun.version : process.version;
403
+
316
404
  // Log header
317
405
  logContent += `=== Start Command Log ===\n`;
318
406
  logContent += `Timestamp: ${startTime}\n`;
@@ -325,7 +413,7 @@ function runDirect(cmd) {
325
413
  }
326
414
  logContent += `Shell: ${shell}\n`;
327
415
  logContent += `Platform: ${process.platform}\n`;
328
- logContent += `Node Version: ${process.version}\n`;
416
+ logContent += `${runtime} Version: ${runtimeVersion}\n`;
329
417
  logContent += `Working Directory: ${process.cwd()}\n`;
330
418
  logContent += `${'='.repeat(50)}\n\n`;
331
419
 
@@ -474,7 +562,7 @@ function handleFailure(cmdName, fullCommand, exitCode, logPath) {
474
562
  }
475
563
  } else {
476
564
  console.log('gh-upload-log not installed - log upload skipped');
477
- console.log('Install with: npm install -g gh-upload-log');
565
+ console.log('Install with: bun install -g gh-upload-log');
478
566
  }
479
567
 
480
568
  // Check if we can create issues in this repository
@@ -745,6 +833,11 @@ function createIssue(repoInfo, fullCommand, exitCode, logUrl) {
745
833
  try {
746
834
  const title = `Command failed with exit code ${exitCode}: ${fullCommand.substring(0, 50)}${fullCommand.length > 50 ? '...' : ''}`;
747
835
 
836
+ // Get runtime information
837
+ const runtime = typeof Bun !== 'undefined' ? 'Bun' : 'Node.js';
838
+ const runtimeVersion =
839
+ typeof Bun !== 'undefined' ? Bun.version : process.version;
840
+
748
841
  let body = `## Command Execution Failure Report\n\n`;
749
842
  body += `**Command:** \`${fullCommand}\`\n\n`;
750
843
  body += `**Exit Code:** ${exitCode}\n\n`;
@@ -752,7 +845,7 @@ function createIssue(repoInfo, fullCommand, exitCode, logUrl) {
752
845
  body += `### System Information\n\n`;
753
846
  body += `- **Platform:** ${process.platform}\n`;
754
847
  body += `- **OS Release:** ${os.release()}\n`;
755
- body += `- **Node Version:** ${process.version}\n`;
848
+ body += `- **${runtime} Version:** ${runtimeVersion}\n`;
756
849
  body += `- **Architecture:** ${process.arch}\n\n`;
757
850
 
758
851
  if (logUrl) {
package/test/cli.test.js CHANGED
@@ -38,12 +38,15 @@ describe('CLI version flag', () => {
38
38
  );
39
39
  assert.ok(result.stdout.includes('OS:'), 'Should display OS');
40
40
  assert.ok(
41
- result.stdout.includes('OS Release:'),
42
- 'Should display OS Release'
41
+ result.stdout.includes('OS Version:'),
42
+ 'Should display OS Version'
43
43
  );
44
+ // Check for either Bun or Node.js version depending on runtime
45
+ const hasBunVersion = result.stdout.includes('Bun Version:');
46
+ const hasNodeVersion = result.stdout.includes('Node.js Version:');
44
47
  assert.ok(
45
- result.stdout.includes('Node Version:'),
46
- 'Should display Node Version'
48
+ hasBunVersion || hasNodeVersion,
49
+ 'Should display Bun Version or Node.js Version'
47
50
  );
48
51
  assert.ok(
49
52
  result.stdout.includes('Architecture:'),
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Tests for --version flag behavior
4
+ * Tests for issue #22: --version issues
5
+ */
6
+
7
+ const { describe, it } = require('node:test');
8
+ const assert = require('node:assert');
9
+ const { execSync, spawnSync } = require('child_process');
10
+ const path = require('path');
11
+ const os = require('os');
12
+
13
+ // Path to the CLI
14
+ const cliPath = path.resolve(__dirname, '../src/bin/cli.js');
15
+
16
+ /**
17
+ * Helper to run the CLI command
18
+ */
19
+ function runCli(args) {
20
+ try {
21
+ // Use '--' separator to ensure args are passed to the script, not consumed by bun
22
+ // This is important for testing edge cases like passing '--' as an argument
23
+ const result = spawnSync('bun', [cliPath, '--', ...args], {
24
+ encoding: 'utf8',
25
+ timeout: 5000,
26
+ env: {
27
+ ...process.env,
28
+ START_DISABLE_AUTO_ISSUE: '1',
29
+ START_DISABLE_LOG_UPLOAD: '1',
30
+ },
31
+ });
32
+ return {
33
+ stdout: result.stdout || '',
34
+ stderr: result.stderr || '',
35
+ exitCode: result.status,
36
+ error: result.error,
37
+ };
38
+ } catch (error) {
39
+ return {
40
+ stdout: '',
41
+ stderr: error.message,
42
+ exitCode: 1,
43
+ error,
44
+ };
45
+ }
46
+ }
47
+
48
+ describe('Version Flag Tests', () => {
49
+ describe('Basic version flag', () => {
50
+ it('should show version with --version', () => {
51
+ const result = runCli(['--version']);
52
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
53
+ assert.match(
54
+ result.stdout,
55
+ /start-command version:/,
56
+ 'Should show start-command version'
57
+ );
58
+ });
59
+
60
+ it('should show version with -v', () => {
61
+ const result = runCli(['-v']);
62
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
63
+ assert.match(
64
+ result.stdout,
65
+ /start-command version:/,
66
+ 'Should show start-command version'
67
+ );
68
+ });
69
+
70
+ it('should show version with --version -- (trailing separator)', () => {
71
+ const result = runCli(['--version', '--']);
72
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
73
+ assert.match(
74
+ result.stdout,
75
+ /start-command version:/,
76
+ 'Should show start-command version with trailing --'
77
+ );
78
+ assert.doesNotMatch(
79
+ result.stderr,
80
+ /No command provided/,
81
+ 'Should not show "No command provided" error'
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('Runtime detection', () => {
87
+ it('should show Bun version when running with Bun', () => {
88
+ const result = runCli(['--version']);
89
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
90
+
91
+ // When running with Bun, should show "Bun Version"
92
+ if (typeof Bun !== 'undefined') {
93
+ assert.match(
94
+ result.stdout,
95
+ /Bun Version:/,
96
+ 'Should show "Bun Version:" when running with Bun'
97
+ );
98
+ assert.doesNotMatch(
99
+ result.stdout,
100
+ /Node\.js Version:/,
101
+ 'Should not show "Node.js Version:" when running with Bun'
102
+ );
103
+ } else {
104
+ // When running with Node.js, should show "Node.js Version"
105
+ assert.match(
106
+ result.stdout,
107
+ /Node\.js Version:/,
108
+ 'Should show "Node.js Version:" when running with Node.js'
109
+ );
110
+ }
111
+ });
112
+ });
113
+
114
+ describe('OS version detection', () => {
115
+ it('should show OS information', () => {
116
+ const result = runCli(['--version']);
117
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
118
+ assert.match(result.stdout, /OS:/, 'Should show OS');
119
+ assert.match(result.stdout, /OS Version:/, 'Should show OS Version');
120
+ });
121
+
122
+ it('should show macOS ProductVersion on darwin', () => {
123
+ const result = runCli(['--version']);
124
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
125
+
126
+ if (process.platform === 'darwin') {
127
+ // Get the actual macOS version using sw_vers
128
+ const macOSVersion = execSync('sw_vers -productVersion', {
129
+ encoding: 'utf8',
130
+ }).trim();
131
+
132
+ // Version output should contain the ProductVersion, not the kernel version
133
+ assert.match(
134
+ result.stdout,
135
+ new RegExp(`OS Version: ${macOSVersion.replace('.', '\\.')}`),
136
+ `Should show macOS ProductVersion (${macOSVersion}), not kernel version`
137
+ );
138
+
139
+ // Should NOT show kernel version (which starts with 2x on modern macOS)
140
+ const kernelVersion = os.release();
141
+ if (kernelVersion.startsWith('2')) {
142
+ assert.doesNotMatch(
143
+ result.stdout,
144
+ new RegExp(`OS Version: ${kernelVersion.replace('.', '\\.')}`),
145
+ `Should not show kernel version (${kernelVersion})`
146
+ );
147
+ }
148
+ }
149
+ });
150
+ });
151
+
152
+ describe('Tool version detection', () => {
153
+ it('should detect screen version if installed', () => {
154
+ const result = runCli(['--version']);
155
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
156
+
157
+ // Check if screen is actually installed
158
+ try {
159
+ const screenVersion = execSync('screen --version 2>&1', {
160
+ encoding: 'utf8',
161
+ timeout: 5000,
162
+ });
163
+
164
+ if (screenVersion) {
165
+ // If screen is installed, it should not show "not installed"
166
+ assert.doesNotMatch(
167
+ result.stdout,
168
+ /screen: not installed/,
169
+ 'Should not show "screen: not installed" when screen is available'
170
+ );
171
+ assert.match(
172
+ result.stdout,
173
+ /screen:/,
174
+ 'Should show screen version info'
175
+ );
176
+ }
177
+ } catch {
178
+ // Screen is not installed, should show "not installed"
179
+ assert.match(
180
+ result.stdout,
181
+ /screen: not installed/,
182
+ 'Should show "screen: not installed" when screen is unavailable'
183
+ );
184
+ }
185
+ });
186
+
187
+ it('should detect tmux version if installed', () => {
188
+ const result = runCli(['--version']);
189
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
190
+
191
+ // Check if tmux is actually installed
192
+ try {
193
+ const tmuxVersion = execSync('tmux -V 2>&1', {
194
+ encoding: 'utf8',
195
+ timeout: 5000,
196
+ });
197
+
198
+ if (tmuxVersion) {
199
+ // If tmux is installed, it should not show "not installed"
200
+ assert.doesNotMatch(
201
+ result.stdout,
202
+ /tmux: not installed/,
203
+ 'Should not show "tmux: not installed" when tmux is available'
204
+ );
205
+ }
206
+ } catch {
207
+ // tmux is not installed, should show "not installed"
208
+ assert.match(
209
+ result.stdout,
210
+ /tmux: not installed/,
211
+ 'Should show "tmux: not installed" when tmux is unavailable'
212
+ );
213
+ }
214
+ });
215
+
216
+ it('should detect docker version if installed', () => {
217
+ const result = runCli(['--version']);
218
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
219
+
220
+ // Check if docker is actually installed
221
+ try {
222
+ const dockerVersion = execSync('docker --version 2>&1', {
223
+ encoding: 'utf8',
224
+ timeout: 5000,
225
+ });
226
+
227
+ if (dockerVersion) {
228
+ // If docker is installed, it should not show "not installed"
229
+ assert.doesNotMatch(
230
+ result.stdout,
231
+ /docker: not installed/,
232
+ 'Should not show "docker: not installed" when docker is available'
233
+ );
234
+ }
235
+ } catch {
236
+ // docker is not installed, should show "not installed"
237
+ assert.match(
238
+ result.stdout,
239
+ /docker: not installed/,
240
+ 'Should show "docker: not installed" when docker is unavailable'
241
+ );
242
+ }
243
+ });
244
+ });
245
+
246
+ describe('Verbose mode', () => {
247
+ it('should show verbose output with --version --verbose', () => {
248
+ const result = runCli(['--version', '--verbose']);
249
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
250
+ assert.match(
251
+ result.stdout,
252
+ /start-command version:/,
253
+ 'Should show start-command version'
254
+ );
255
+ assert.match(
256
+ result.stdout,
257
+ /\[verbose\]/,
258
+ 'Should show verbose output markers'
259
+ );
260
+ });
261
+
262
+ it('should show verbose output with --version --debug', () => {
263
+ const result = runCli(['--version', '--debug']);
264
+ assert.strictEqual(result.exitCode, 0, 'Exit code should be 0');
265
+ assert.match(
266
+ result.stdout,
267
+ /\[verbose\]/,
268
+ 'Should show verbose output with --debug flag'
269
+ );
270
+ });
271
+
272
+ it('should show verbose output with START_VERBOSE=1', () => {
273
+ const result = spawnSync('bun', [cliPath, '--version'], {
274
+ encoding: 'utf8',
275
+ timeout: 5000,
276
+ env: {
277
+ ...process.env,
278
+ START_VERBOSE: '1',
279
+ START_DISABLE_AUTO_ISSUE: '1',
280
+ START_DISABLE_LOG_UPLOAD: '1',
281
+ },
282
+ });
283
+ assert.strictEqual(result.status, 0, 'Exit code should be 0');
284
+ assert.match(
285
+ result.stdout,
286
+ /\[verbose\]/,
287
+ 'Should show verbose output with START_VERBOSE=1'
288
+ );
289
+ });
290
+ });
291
+
292
+ describe('Error cases', () => {
293
+ it('should error with "No command provided" for $ --', () => {
294
+ const result = runCli(['--']);
295
+ assert.strictEqual(result.exitCode, 1, 'Exit code should be 1');
296
+ const output = result.stdout + result.stderr;
297
+ assert.match(
298
+ output,
299
+ /No command provided/,
300
+ 'Should show "No command provided" error for --'
301
+ );
302
+ });
303
+
304
+ it('should error with "No command provided" for no args', () => {
305
+ const result = runCli([]);
306
+ assert.strictEqual(result.exitCode, 0, 'Should show usage and exit 0');
307
+ assert.match(result.stdout, /Usage:/, 'Should show usage message');
308
+ });
309
+ });
310
+ });