start-command 0.13.0 → 0.16.0
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 +34 -227
- package/bun.lock +5 -0
- package/eslint.config.mjs +1 -1
- package/package.json +11 -6
- package/src/bin/cli.js +332 -171
- package/src/lib/args-parser.js +118 -0
- package/src/lib/execution-store.js +722 -0
- package/src/lib/isolation.js +51 -0
- package/src/lib/output-blocks.js +357 -0
- package/src/lib/status-formatter.js +148 -0
- package/src/lib/version.js +143 -0
- package/test/args-parser.test.js +107 -0
- package/test/cli.test.js +11 -1
- package/test/docker-autoremove.test.js +11 -16
- package/test/execution-store.test.js +483 -0
- package/test/isolation-cleanup.test.js +11 -16
- package/test/isolation.test.js +11 -17
- package/test/output-blocks.test.js +197 -0
- package/test/public-exports.test.js +105 -0
- package/test/status-query.test.js +197 -0
- package/.github/workflows/release.yml +0 -352
- package/.husky/pre-commit +0 -1
- package/ARCHITECTURE.md +0 -297
- package/LICENSE +0 -24
- package/README.md +0 -339
- package/REQUIREMENTS.md +0 -299
- package/docs/PIPES.md +0 -243
- package/docs/USAGE.md +0 -194
- package/docs/case-studies/issue-15/README.md +0 -208
- package/docs/case-studies/issue-18/README.md +0 -343
- package/docs/case-studies/issue-18/issue-comments.json +0 -1
- package/docs/case-studies/issue-18/issue-data.json +0 -7
- package/docs/case-studies/issue-22/analysis.md +0 -547
- package/docs/case-studies/issue-22/issue-data.json +0 -12
- package/docs/case-studies/issue-25/README.md +0 -232
- package/docs/case-studies/issue-25/issue-data.json +0 -21
- package/docs/case-studies/issue-28/README.md +0 -405
- package/docs/case-studies/issue-28/issue-data.json +0 -105
- package/docs/case-studies/issue-28/raw-issue-data.md +0 -92
- package/experiments/debug-regex.js +0 -49
- package/experiments/isolation-design.md +0 -131
- package/experiments/screen-output-test.js +0 -265
- package/experiments/test-cli.sh +0 -42
- package/experiments/test-command-stream-cjs.cjs +0 -30
- package/experiments/test-command-stream-wrapper.js +0 -54
- package/experiments/test-command-stream.mjs +0 -56
- package/experiments/test-screen-attached.js +0 -126
- package/experiments/test-screen-logfile.js +0 -286
- package/experiments/test-screen-modes.js +0 -128
- package/experiments/test-screen-output.sh +0 -27
- package/experiments/test-screen-tee-debug.js +0 -237
- package/experiments/test-screen-tee-fallback.js +0 -230
- package/experiments/test-substitution.js +0 -143
- package/experiments/user-isolation-research.md +0 -83
- package/scripts/changeset-version.mjs +0 -38
- package/scripts/check-file-size.mjs +0 -103
- package/scripts/create-github-release.mjs +0 -93
- package/scripts/create-manual-changeset.mjs +0 -89
- package/scripts/format-github-release.mjs +0 -83
- package/scripts/format-release-notes.mjs +0 -219
- package/scripts/instant-version-bump.mjs +0 -121
- package/scripts/publish-to-npm.mjs +0 -129
- package/scripts/setup-npm.mjs +0 -37
- package/scripts/validate-changeset.mjs +0 -107
- package/scripts/version-and-commit.mjs +0 -237
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"issue": {
|
|
3
|
-
"number": 28,
|
|
4
|
-
"title": "Can we somehow overcome the need of `'` quotes with `|` and `\"` in a sequence?",
|
|
5
|
-
"url": "https://github.com/link-foundation/start/issues/28",
|
|
6
|
-
"state": "open",
|
|
7
|
-
"labels": ["bug", "documentation", "enhancement", "question"],
|
|
8
|
-
"author": "konard",
|
|
9
|
-
"created_at": "2025-12-24"
|
|
10
|
-
},
|
|
11
|
-
"environment": {
|
|
12
|
-
"platform": "darwin",
|
|
13
|
-
"shell": "/bin/zsh",
|
|
14
|
-
"bun_version": "1.2.20",
|
|
15
|
-
"working_directory": "/Users/konard"
|
|
16
|
-
},
|
|
17
|
-
"scenarios": [
|
|
18
|
-
{
|
|
19
|
-
"name": "unquoted_command",
|
|
20
|
-
"input": "$ echo \"hi\" | agent",
|
|
21
|
-
"timestamp": "2025-12-24 15:07:08.894",
|
|
22
|
-
"shell_interpretation": {
|
|
23
|
-
"command_1": "$ echo \"hi\"",
|
|
24
|
-
"operator": "|",
|
|
25
|
-
"command_2": "agent"
|
|
26
|
-
},
|
|
27
|
-
"actual_execution": {
|
|
28
|
-
"start_command_received": "echo \"hi\"",
|
|
29
|
-
"start_command_output": "hi",
|
|
30
|
-
"agent_received": "JSON output from start-command stdout"
|
|
31
|
-
},
|
|
32
|
-
"user_expectation": "agent should receive 'hi' from echo",
|
|
33
|
-
"actual_result": "agent received start-command JSON output",
|
|
34
|
-
"exit_code": 0
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"name": "quoted_command",
|
|
38
|
-
"input": "$ 'echo \"hi\" | agent'",
|
|
39
|
-
"timestamp": "2025-12-24 15:07:33.420",
|
|
40
|
-
"shell_interpretation": {
|
|
41
|
-
"command": "$",
|
|
42
|
-
"argument": "echo \"hi\" | agent"
|
|
43
|
-
},
|
|
44
|
-
"actual_execution": {
|
|
45
|
-
"start_command_received": "echo \"hi\" | agent",
|
|
46
|
-
"pipeline_executed": true,
|
|
47
|
-
"echo_output": "hi",
|
|
48
|
-
"agent_received": "hi"
|
|
49
|
-
},
|
|
50
|
-
"user_expectation": "agent should receive 'hi' from echo",
|
|
51
|
-
"actual_result": "agent received 'hi' from echo",
|
|
52
|
-
"exit_code": 0
|
|
53
|
-
}
|
|
54
|
-
],
|
|
55
|
-
"root_cause": {
|
|
56
|
-
"category": "shell_parsing_behavior",
|
|
57
|
-
"is_bug": false,
|
|
58
|
-
"description": "The pipe operator (|) is a shell metacharacter that is parsed by the shell before command arguments are passed. This is standard POSIX shell behavior, not a bug in start-command.",
|
|
59
|
-
"affected_shells": ["bash", "zsh", "sh", "dash", "ksh"],
|
|
60
|
-
"shell_parsing_order": [
|
|
61
|
-
"1. Tokenization - command line split into tokens",
|
|
62
|
-
"2. Operator recognition - | && || ; & recognized as metacharacters",
|
|
63
|
-
"3. Command grouping - commands grouped based on operators",
|
|
64
|
-
"4. Quote processing - quoted strings preserved",
|
|
65
|
-
"5. Expansion - variables, commands, etc. expanded"
|
|
66
|
-
]
|
|
67
|
-
},
|
|
68
|
-
"solutions": {
|
|
69
|
-
"recommended": "documentation_enhancement",
|
|
70
|
-
"options": [
|
|
71
|
-
{
|
|
72
|
-
"id": "documentation_enhancement",
|
|
73
|
-
"name": "Documentation Enhancement",
|
|
74
|
-
"description": "Add clear documentation about quoting requirements",
|
|
75
|
-
"pros": ["No code changes", "Educates users", "POSIX compliant"],
|
|
76
|
-
"cons": ["Requires users to learn quoting rules"],
|
|
77
|
-
"complexity": "low"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"id": "warning_detection",
|
|
81
|
-
"name": "Optional Warning Detection",
|
|
82
|
-
"description": "Detect when stdout is piped and emit helpful warning",
|
|
83
|
-
"pros": ["Helps users understand behavior", "Non-breaking"],
|
|
84
|
-
"cons": ["May add noise to output"],
|
|
85
|
-
"complexity": "medium"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"id": "here_document",
|
|
89
|
-
"name": "Here-Document Wrapper",
|
|
90
|
-
"description": "Support $$ <<'CMD' syntax for multi-line commands",
|
|
91
|
-
"pros": ["No quoting issues", "Clear syntax"],
|
|
92
|
-
"cons": ["More verbose", "New syntax to learn"],
|
|
93
|
-
"complexity": "high"
|
|
94
|
-
}
|
|
95
|
-
]
|
|
96
|
-
},
|
|
97
|
-
"references": {
|
|
98
|
-
"bash_manual": "https://www.gnu.org/software/bash/manual/bash.html",
|
|
99
|
-
"pipelines_doc": "https://www.gnu.org/software/bash/manual/html_node/Pipelines.html",
|
|
100
|
-
"quoting_doc": "https://www.gnu.org/software/bash/manual/html_node/Quoting.html",
|
|
101
|
-
"posix_shell": "https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html"
|
|
102
|
-
},
|
|
103
|
-
"analysis_date": "2025-12-24",
|
|
104
|
-
"analyst": "AI Issue Solver"
|
|
105
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# Issue #28 Raw Data
|
|
2
|
-
|
|
3
|
-
## Issue Title
|
|
4
|
-
|
|
5
|
-
Can we somehow overcome the need of `'` quotes with `|` and `"` in a sequence?
|
|
6
|
-
|
|
7
|
-
## Issue State
|
|
8
|
-
|
|
9
|
-
OPEN
|
|
10
|
-
|
|
11
|
-
## Issue Labels
|
|
12
|
-
|
|
13
|
-
bug, documentation, enhancement, question
|
|
14
|
-
|
|
15
|
-
## Issue Author
|
|
16
|
-
|
|
17
|
-
konard
|
|
18
|
-
|
|
19
|
-
## Issue Content
|
|
20
|
-
|
|
21
|
-
The issue shows two terminal sessions demonstrating different behaviors:
|
|
22
|
-
|
|
23
|
-
### Session 1: Without quoting the entire command
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
konard@MacBook-Pro-Konstantin ~ % $ echo "hi" | agent
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**Result:** The `echo "hi"` command ran directly in the shell, and only its OUTPUT was piped to `agent`. The agent then processed the log file that was created by the `$` command (start-command), not the original command input.
|
|
30
|
-
|
|
31
|
-
The agent received the OUTPUT of `$ echo "hi"` which was captured in a log file, and then read that log file.
|
|
32
|
-
|
|
33
|
-
### Session 2: With quoting the entire command
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
konard@MacBook-Pro-Konstantin ~ % $ 'echo "hi" | agent'
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**Result:** The entire string `echo "hi" | agent` was passed TO the `$` command (start-command). The start-command then executed this complete pipeline, which resulted in the `echo "hi"` output being piped to `agent` correctly.
|
|
40
|
-
|
|
41
|
-
## Terminal Logs
|
|
42
|
-
|
|
43
|
-
### Session 1 Output (problematic behavior)
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"type": "status",
|
|
48
|
-
"mode": "stdin-stream",
|
|
49
|
-
"message": "Agent CLI in continuous listening mode. Accepts JSON and plain text input.",
|
|
50
|
-
"hint": "Press CTRL+C to exit. Use --help for options.",
|
|
51
|
-
"acceptedFormats": ["JSON object with \"message\" field", "Plain text"],
|
|
52
|
-
"options": {
|
|
53
|
-
"interactive": true,
|
|
54
|
-
"autoMergeQueuedMessages": true,
|
|
55
|
-
"alwaysAcceptStdin": true,
|
|
56
|
-
"compactJson": false
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The agent then read a log file:
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
=== Start Command Log ===
|
|
65
|
-
Timestamp: 2025-12-24 15:07:08.894
|
|
66
|
-
Command: echo hi
|
|
67
|
-
Shell: /bin/zsh
|
|
68
|
-
Platform: darwin
|
|
69
|
-
Bun Version: 1.2.20
|
|
70
|
-
Working Directory: /Users/konard
|
|
71
|
-
==================================================
|
|
72
|
-
|
|
73
|
-
hi
|
|
74
|
-
|
|
75
|
-
==================================================
|
|
76
|
-
Finished: 2025-12-24 15:07:08.906
|
|
77
|
-
Exit Code: 0
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
And responded:
|
|
81
|
-
|
|
82
|
-
> "It looks like you executed the command `echo hi`, which successfully output \"hi\" with exit code 0..."
|
|
83
|
-
|
|
84
|
-
### Session 2 Output (desired behavior)
|
|
85
|
-
|
|
86
|
-
The agent received "hi" directly as input and responded:
|
|
87
|
-
|
|
88
|
-
> "Hello! How can I help you today?"
|
|
89
|
-
|
|
90
|
-
## User Request
|
|
91
|
-
|
|
92
|
-
> Please 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.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Debug script to understand regex generation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { createRule, parseLinoContent } = require('../src/lib/substitution');
|
|
7
|
-
|
|
8
|
-
// Test creating a simple rule
|
|
9
|
-
const rule = createRule(
|
|
10
|
-
'install $packageName npm package',
|
|
11
|
-
'npm install $packageName'
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
console.log('Pattern:', 'install $packageName npm package');
|
|
15
|
-
console.log('Replacement:', 'npm install $packageName');
|
|
16
|
-
console.log('Generated Regex:', rule.regex);
|
|
17
|
-
console.log('Variables:', rule.variables);
|
|
18
|
-
console.log('');
|
|
19
|
-
|
|
20
|
-
// Test matching
|
|
21
|
-
const input = 'install gh-upload-log npm package';
|
|
22
|
-
const match = input.match(rule.regex);
|
|
23
|
-
console.log('Input:', input);
|
|
24
|
-
console.log('Match result:', match);
|
|
25
|
-
|
|
26
|
-
if (match) {
|
|
27
|
-
console.log('Groups:', match.groups);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Test simpler lino content
|
|
31
|
-
const simpleContent = `
|
|
32
|
-
(install $packageName npm package)
|
|
33
|
-
(npm install $packageName)
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
const rules = parseLinoContent(simpleContent);
|
|
37
|
-
console.log(
|
|
38
|
-
'\nParsed rules:',
|
|
39
|
-
JSON.stringify(
|
|
40
|
-
rules,
|
|
41
|
-
(key, value) => {
|
|
42
|
-
if (key === 'regex') {
|
|
43
|
-
return value.toString();
|
|
44
|
-
}
|
|
45
|
-
return value;
|
|
46
|
-
},
|
|
47
|
-
2
|
|
48
|
-
)
|
|
49
|
-
);
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# Isolation Support Design
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This document outlines the design for adding process isolation support to start-command.
|
|
6
|
-
|
|
7
|
-
## Supported Isolation Backends
|
|
8
|
-
|
|
9
|
-
### Terminal Multiplexers
|
|
10
|
-
|
|
11
|
-
1. **screen** - GNU Screen, classic session manager
|
|
12
|
-
2. **tmux** - Modern terminal multiplexer
|
|
13
|
-
|
|
14
|
-
### Container Isolation
|
|
15
|
-
|
|
16
|
-
3. **docker** - Docker containers
|
|
17
|
-
|
|
18
|
-
## Command Syntax
|
|
19
|
-
|
|
20
|
-
Two syntax patterns are supported:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# Pattern 1: Using -- separator
|
|
24
|
-
$ [wrapper-options] -- [command] [command-options]
|
|
25
|
-
|
|
26
|
-
# Pattern 2: Options before command
|
|
27
|
-
$ [wrapper-options] command [command-options]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Wrapper Options
|
|
31
|
-
|
|
32
|
-
- `--isolated <backend>` or `-i <backend>`: Run command in isolated environment
|
|
33
|
-
- Backends: `screen`, `tmux`, `docker`
|
|
34
|
-
- `--attached` or `-a`: Run in attached mode (foreground)
|
|
35
|
-
- `--detached` or `-d`: Run in detached mode (background)
|
|
36
|
-
- `--session <name>` or `-s <name>`: Name for the session (optional)
|
|
37
|
-
|
|
38
|
-
### Examples
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# Run in tmux (attached by default for multiplexers)
|
|
42
|
-
$ --isolated tmux -- npm test
|
|
43
|
-
|
|
44
|
-
# Run in screen detached
|
|
45
|
-
$ --isolated screen --detached -- npm start
|
|
46
|
-
|
|
47
|
-
# Run in docker container
|
|
48
|
-
$ --isolated docker --image node:20 -- npm install
|
|
49
|
-
|
|
50
|
-
# Short form
|
|
51
|
-
$ -i tmux -d npm start
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Mode Behavior
|
|
55
|
-
|
|
56
|
-
### Attached Mode (--attached)
|
|
57
|
-
|
|
58
|
-
- Default for terminal multiplexers (screen, tmux)
|
|
59
|
-
- Command runs in foreground
|
|
60
|
-
- User can interact with the terminal
|
|
61
|
-
- For docker: runs with -it flags
|
|
62
|
-
|
|
63
|
-
### Detached Mode (--detached)
|
|
64
|
-
|
|
65
|
-
- Command runs in background
|
|
66
|
-
- For multiplexers: creates a session that can be reattached later
|
|
67
|
-
- For docker: runs with -d flag
|
|
68
|
-
- Session info is printed for later access
|
|
69
|
-
|
|
70
|
-
### Error Handling
|
|
71
|
-
|
|
72
|
-
- If both --attached and --detached are specified, throw an error
|
|
73
|
-
- Error message should ask user to choose only one
|
|
74
|
-
|
|
75
|
-
## Implementation Details
|
|
76
|
-
|
|
77
|
-
### Session Naming
|
|
78
|
-
|
|
79
|
-
- Auto-generate session name: `start-{timestamp}-{random}`
|
|
80
|
-
- Allow custom name via --session option
|
|
81
|
-
- Session names used for reattachment
|
|
82
|
-
|
|
83
|
-
### Backend Commands
|
|
84
|
-
|
|
85
|
-
#### Screen
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
# Attached
|
|
89
|
-
screen -S <session> bash -c '<command>'
|
|
90
|
-
|
|
91
|
-
# Detached
|
|
92
|
-
screen -dmS <session> bash -c '<command>'
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
#### Tmux
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
# Attached
|
|
99
|
-
tmux new-session -s <session> '<command>'
|
|
100
|
-
|
|
101
|
-
# Detached
|
|
102
|
-
tmux new-session -d -s <session> '<command>'
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### Docker
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
# Attached
|
|
109
|
-
docker run -it --name <name> <image> <command>
|
|
110
|
-
|
|
111
|
-
# Detached
|
|
112
|
-
docker run -d --name <name> <image> <command>
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Testing Strategy
|
|
116
|
-
|
|
117
|
-
### Unit Tests
|
|
118
|
-
|
|
119
|
-
- Argument parsing tests
|
|
120
|
-
- Backend detection tests
|
|
121
|
-
- Session name generation tests
|
|
122
|
-
- Conflict detection (attached + detached)
|
|
123
|
-
|
|
124
|
-
### Integration Tests
|
|
125
|
-
|
|
126
|
-
- Actual execution with mocked backends
|
|
127
|
-
- End-to-end tests with real backends (when available)
|
|
128
|
-
|
|
129
|
-
### E2E Tests
|
|
130
|
-
|
|
131
|
-
- Full workflow tests with screen/tmux (if installed)
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Experiment: Test screen output capture behavior
|
|
4
|
-
*
|
|
5
|
-
* This experiment tests different approaches to capture output from GNU screen
|
|
6
|
-
* sessions, specifically addressing issue #25 where output is lost on macOS
|
|
7
|
-
* with screen 4.00.03.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { execSync, spawn, spawnSync } = require('child_process');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const os = require('os');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
// Check if screen is available
|
|
16
|
-
function isScreenAvailable() {
|
|
17
|
-
try {
|
|
18
|
-
execSync('which screen', { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
19
|
-
return true;
|
|
20
|
-
} catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Get screen version
|
|
26
|
-
function getScreenVersion() {
|
|
27
|
-
try {
|
|
28
|
-
const output = execSync('screen --version', {
|
|
29
|
-
encoding: 'utf8',
|
|
30
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
-
});
|
|
32
|
-
const match = output.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
33
|
-
if (match) {
|
|
34
|
-
return {
|
|
35
|
-
major: parseInt(match[1], 10),
|
|
36
|
-
minor: parseInt(match[2], 10),
|
|
37
|
-
patch: parseInt(match[3], 10),
|
|
38
|
-
raw: output.trim(),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
} catch {
|
|
42
|
-
// Ignore
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Check if -Logfile is supported (screen >= 4.5.1)
|
|
48
|
-
function supportsLogfileOption(version) {
|
|
49
|
-
if (!version) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
if (version.major > 4) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
if (version.major < 4) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
if (version.minor > 5) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
if (version.minor < 5) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
return version.patch >= 1;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Test 1: Direct screen invocation (current approach for TTY)
|
|
68
|
-
async function testDirectScreen(command) {
|
|
69
|
-
console.log('\n=== Test 1: Direct screen invocation (TTY mode) ===');
|
|
70
|
-
const sessionName = `test-direct-${Date.now()}`;
|
|
71
|
-
const shell = process.env.SHELL || '/bin/sh';
|
|
72
|
-
|
|
73
|
-
console.log(`Command: screen -S ${sessionName} ${shell} -c '${command}'`);
|
|
74
|
-
console.log('Note: This approach loses output for quick commands');
|
|
75
|
-
|
|
76
|
-
// This is what happens currently with TTY
|
|
77
|
-
return new Promise((resolve) => {
|
|
78
|
-
const child = spawn('screen', ['-S', sessionName, shell, '-c', command], {
|
|
79
|
-
stdio: 'inherit',
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
child.on('exit', (code) => {
|
|
83
|
-
console.log(`Exit code: ${code}`);
|
|
84
|
-
resolve({ success: code === 0, output: '(output not captured)' });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
child.on('error', (err) => {
|
|
88
|
-
console.error(`Error: ${err.message}`);
|
|
89
|
-
resolve({ success: false, output: '' });
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Test 2: Detached screen with log file (current approach for no-TTY)
|
|
95
|
-
async function testDetachedWithLog(command) {
|
|
96
|
-
console.log('\n=== Test 2: Detached screen with log capture ===');
|
|
97
|
-
const sessionName = `test-detached-${Date.now()}`;
|
|
98
|
-
const shell = process.env.SHELL || '/bin/sh';
|
|
99
|
-
const logFile = path.join(os.tmpdir(), `screen-test-${sessionName}.log`);
|
|
100
|
-
|
|
101
|
-
const version = getScreenVersion();
|
|
102
|
-
const useNativeLogging = supportsLogfileOption(version);
|
|
103
|
-
|
|
104
|
-
console.log(`Screen version: ${version ? version.raw : 'unknown'}`);
|
|
105
|
-
console.log(`Supports -Logfile: ${useNativeLogging}`);
|
|
106
|
-
|
|
107
|
-
let screenArgs;
|
|
108
|
-
let effectiveCommand = command;
|
|
109
|
-
|
|
110
|
-
if (useNativeLogging) {
|
|
111
|
-
// Modern screen
|
|
112
|
-
screenArgs = [
|
|
113
|
-
'-dmS',
|
|
114
|
-
sessionName,
|
|
115
|
-
'-L',
|
|
116
|
-
'-Logfile',
|
|
117
|
-
logFile,
|
|
118
|
-
shell,
|
|
119
|
-
'-c',
|
|
120
|
-
command,
|
|
121
|
-
];
|
|
122
|
-
} else {
|
|
123
|
-
// Older screen - use tee fallback
|
|
124
|
-
effectiveCommand = `(${command}) 2>&1 | tee "${logFile}"`;
|
|
125
|
-
screenArgs = ['-dmS', sessionName, shell, '-c', effectiveCommand];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
console.log(`Command: screen ${screenArgs.join(' ')}`);
|
|
129
|
-
|
|
130
|
-
return new Promise((resolve) => {
|
|
131
|
-
try {
|
|
132
|
-
const result = spawnSync('screen', screenArgs, { stdio: 'inherit' });
|
|
133
|
-
|
|
134
|
-
if (result.error) {
|
|
135
|
-
console.error(`Error: ${result.error.message}`);
|
|
136
|
-
resolve({ success: false, output: '' });
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Poll for session completion
|
|
141
|
-
const checkInterval = 100;
|
|
142
|
-
const maxWait = 10000;
|
|
143
|
-
let waited = 0;
|
|
144
|
-
|
|
145
|
-
const checkCompletion = () => {
|
|
146
|
-
try {
|
|
147
|
-
const sessions = execSync('screen -ls', {
|
|
148
|
-
encoding: 'utf8',
|
|
149
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (!sessions.includes(sessionName)) {
|
|
153
|
-
// Session ended
|
|
154
|
-
let output = '';
|
|
155
|
-
try {
|
|
156
|
-
output = fs.readFileSync(logFile, 'utf8');
|
|
157
|
-
console.log(`Captured output: "${output.trim()}"`);
|
|
158
|
-
fs.unlinkSync(logFile);
|
|
159
|
-
} catch {
|
|
160
|
-
console.log('Log file not found or empty');
|
|
161
|
-
}
|
|
162
|
-
resolve({ success: true, output });
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
waited += checkInterval;
|
|
167
|
-
if (waited >= maxWait) {
|
|
168
|
-
resolve({ success: false, output: 'timeout' });
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
setTimeout(checkCompletion, checkInterval);
|
|
173
|
-
} catch {
|
|
174
|
-
let output = '';
|
|
175
|
-
try {
|
|
176
|
-
output = fs.readFileSync(logFile, 'utf8');
|
|
177
|
-
console.log(`Captured output: "${output.trim()}"`);
|
|
178
|
-
fs.unlinkSync(logFile);
|
|
179
|
-
} catch {
|
|
180
|
-
// Ignore
|
|
181
|
-
}
|
|
182
|
-
resolve({ success: true, output });
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
setTimeout(checkCompletion, checkInterval);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.error(`Error: ${err.message}`);
|
|
189
|
-
resolve({ success: false, output: '' });
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Test 3: Script command for output capture (alternative approach)
|
|
195
|
-
async function testScriptCapture(command) {
|
|
196
|
-
console.log('\n=== Test 3: Using script command for output capture ===');
|
|
197
|
-
const logFile = path.join(os.tmpdir(), `script-test-${Date.now()}.log`);
|
|
198
|
-
const shell = process.env.SHELL || '/bin/sh';
|
|
199
|
-
|
|
200
|
-
// Use 'script' command which is available on both macOS and Linux
|
|
201
|
-
// script -q logfile command (macOS/BSD)
|
|
202
|
-
// script -q -c command logfile (Linux)
|
|
203
|
-
const isMac = process.platform === 'darwin';
|
|
204
|
-
|
|
205
|
-
let scriptArgs;
|
|
206
|
-
if (isMac) {
|
|
207
|
-
scriptArgs = ['-q', logFile, shell, '-c', command];
|
|
208
|
-
} else {
|
|
209
|
-
scriptArgs = ['-q', '-c', `${shell} -c '${command}'`, logFile];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
console.log(`Command: script ${scriptArgs.join(' ')}`);
|
|
213
|
-
|
|
214
|
-
return new Promise((resolve) => {
|
|
215
|
-
const child = spawn('script', scriptArgs, {
|
|
216
|
-
stdio: 'inherit',
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
child.on('exit', (code) => {
|
|
220
|
-
let output = '';
|
|
221
|
-
try {
|
|
222
|
-
output = fs.readFileSync(logFile, 'utf8');
|
|
223
|
-
console.log(`Captured output: "${output.trim()}"`);
|
|
224
|
-
fs.unlinkSync(logFile);
|
|
225
|
-
} catch {
|
|
226
|
-
console.log('Log file not found');
|
|
227
|
-
}
|
|
228
|
-
resolve({ success: code === 0, output });
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
child.on('error', (err) => {
|
|
232
|
-
console.error(`Error: ${err.message}`);
|
|
233
|
-
resolve({ success: false, output: '' });
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Main
|
|
239
|
-
async function main() {
|
|
240
|
-
console.log('Screen Output Capture Experiment');
|
|
241
|
-
console.log('=================================');
|
|
242
|
-
console.log(`Platform: ${process.platform}`);
|
|
243
|
-
console.log(
|
|
244
|
-
`TTY: stdin=${process.stdin.isTTY}, stdout=${process.stdout.isTTY}`
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
if (!isScreenAvailable()) {
|
|
248
|
-
console.log('Screen is not installed. Exiting.');
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const version = getScreenVersion();
|
|
253
|
-
console.log(`Screen version: ${version ? version.raw : 'unknown'}`);
|
|
254
|
-
|
|
255
|
-
const testCommand = 'echo "hello from screen"';
|
|
256
|
-
|
|
257
|
-
// Test 2 is the recommended approach
|
|
258
|
-
const result = await testDetachedWithLog(testCommand);
|
|
259
|
-
console.log('\n=== Summary ===');
|
|
260
|
-
console.log(
|
|
261
|
-
`Test 2 (detached with log): Success=${result.success}, Output captured=${result.output.includes('hello')}`
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
main().catch(console.error);
|
package/experiments/test-cli.sh
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Test script for start-command CLI
|
|
3
|
-
|
|
4
|
-
set -e
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
CLI_PATH="${SCRIPT_DIR}/../bin/cli.js"
|
|
8
|
-
|
|
9
|
-
echo "=== Testing start-command CLI ==="
|
|
10
|
-
echo ""
|
|
11
|
-
|
|
12
|
-
# Test 1: No arguments - should show usage
|
|
13
|
-
echo "Test 1: No arguments (should show usage)"
|
|
14
|
-
node "$CLI_PATH" || true
|
|
15
|
-
echo ""
|
|
16
|
-
|
|
17
|
-
# Test 2: Successful command
|
|
18
|
-
echo "Test 2: Successful command (echo)"
|
|
19
|
-
node "$CLI_PATH" echo "Hello from test!"
|
|
20
|
-
echo ""
|
|
21
|
-
|
|
22
|
-
# Test 3: List directory
|
|
23
|
-
echo "Test 3: List directory (ls)"
|
|
24
|
-
node "$CLI_PATH" ls -la "$SCRIPT_DIR"
|
|
25
|
-
echo ""
|
|
26
|
-
|
|
27
|
-
# Test 4: Failing command (system command)
|
|
28
|
-
echo "Test 4: Failing system command (false) - should NOT detect repository"
|
|
29
|
-
node "$CLI_PATH" false || true
|
|
30
|
-
echo ""
|
|
31
|
-
|
|
32
|
-
# Test 5: Non-existent command
|
|
33
|
-
echo "Test 5: Non-existent command - should NOT detect repository"
|
|
34
|
-
node "$CLI_PATH" this_command_does_not_exist_xyz123 || true
|
|
35
|
-
echo ""
|
|
36
|
-
|
|
37
|
-
# Test 6: Check log file creation
|
|
38
|
-
echo "Test 6: Verify log files are created"
|
|
39
|
-
ls -la /tmp/start-command-*.log 2>/dev/null | head -5 || echo "No log files found (unexpected)"
|
|
40
|
-
echo ""
|
|
41
|
-
|
|
42
|
-
echo "=== All tests completed ==="
|