start-command 0.18.0 → 0.19.1

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,31 @@
1
1
  # start-command
2
2
 
3
+ ## 0.19.1
4
+
5
+ ### Patch Changes
6
+
7
+ - c84c1bb: fix: Always display session/container name in isolation output
8
+
9
+ When using isolation backends (screen, docker, tmux), the output now shows the actual session/container name that users need to reconnect to sessions, especially in detached mode. Previously, only the session UUID was shown, but users need the actual backend name to:
10
+ - Reconnect to detached screen sessions: `screen -r <name>`
11
+ - Attach to tmux sessions: `tmux attach -t <name>`
12
+ - View Docker container logs: `docker logs <name>`
13
+ - Remove containers: `docker rm -f <name>`
14
+
15
+ Fixes #67
16
+
17
+ ## 0.19.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 112a78e: Replace fixed-width box output with status spine format
22
+ - Width-independent output that doesn't truncate or create jagged boxes
23
+ - All metadata visible and copy-pasteable (log paths, session IDs)
24
+ - Works uniformly in TTY, tmux, SSH, CI, and log files
25
+ - Clear visual distinction: │ for metadata, $ for command, no prefix for output
26
+ - Result markers ✓ and ✗ for success/failure
27
+ - Isolation metadata repeated in footer for context
28
+
3
29
  ## 0.18.0
4
30
 
5
31
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-command",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
5
  "main": "src/bin/cli.js",
6
6
  "exports": {
package/src/bin/cli.js CHANGED
@@ -470,9 +470,10 @@ async function runWithIsolation(
470
470
  // Add isolation info to extra lines
471
471
  if (environment) {
472
472
  extraLines.push(`[Isolation] Environment: ${environment}, Mode: ${mode}`);
473
- }
474
- if (options.session) {
475
- extraLines.push(`[Isolation] Session: ${options.session}`);
473
+ // Always add the session name so users can reconnect to detached sessions
474
+ // This is important for screen, tmux, docker where the session/container name
475
+ // is different from the session UUID used for tracking (see issue #67)
476
+ extraLines.push(`[Isolation] Session: ${sessionName}`);
476
477
  }
477
478
  if (effectiveImage) {
478
479
  extraLines.push(`[Isolation] Image: ${effectiveImage}`);
@@ -623,7 +624,7 @@ async function runWithIsolation(
623
624
  console.log('');
624
625
  }
625
626
 
626
- // Print finish block with result message inside
627
+ // Print finish block with isolation metadata repeated
627
628
  // Add empty line before finish block for visual separation
628
629
  console.log('');
629
630
  const durationMs = Date.now() - startTimeMs;
@@ -634,7 +635,7 @@ async function runWithIsolation(
634
635
  exitCode,
635
636
  logPath: logFilePath,
636
637
  durationMs,
637
- resultMessage: result.message,
638
+ extraLines, // Pass extraLines for isolation metadata repetition in footer
638
639
  })
639
640
  );
640
641
 
@@ -1,189 +1,205 @@
1
1
  /**
2
2
  * Output formatting utilities for nicely rendered command blocks
3
3
  *
4
- * Provides various styles for start/finish blocks to distinguish
5
- * command output from the $ wrapper output.
4
+ * Provides "status spine" format: a width-independent, lossless output format
5
+ * that works in TTY, tmux, SSH, CI, and logs.
6
6
  *
7
- * Available styles:
8
- * 1. 'rounded' (default): Rounded unicode box borders (╭─╮ ╰─╯)
9
- * 2. 'heavy': Heavy unicode box borders (┏━┓ ┗━┛)
10
- * 3. 'double': Double line box borders (╔═╗ ╚═╝)
11
- * 4. 'simple': Simple dash lines (────────)
12
- * 5. 'ascii': Pure ASCII compatible (-------- +------+)
7
+ * Core concepts:
8
+ * - `│` prefix tool metadata
9
+ * - `$` executed command
10
+ * - No prefix program output (stdout/stderr)
11
+ * - Result marker (`✓` / `✗`) appears after output
13
12
  */
14
13
 
15
- // Box drawing characters for different styles
16
- const BOX_STYLES = {
17
- rounded: {
18
- topLeft: '╭',
19
- topRight: '╮',
20
- bottomLeft: '╰',
21
- bottomRight: '╯',
22
- horizontal: '─',
23
- vertical: '│',
24
- },
25
- heavy: {
26
- topLeft: '┏',
27
- topRight: '┓',
28
- bottomLeft: '┗',
29
- bottomRight: '┛',
30
- horizontal: '━',
31
- vertical: '┃',
32
- },
33
- double: {
34
- topLeft: '╔',
35
- topRight: '╗',
36
- bottomLeft: '╚',
37
- bottomRight: '╝',
38
- horizontal: '═',
39
- vertical: '║',
40
- },
41
- simple: {
42
- topLeft: '',
43
- topRight: '',
44
- bottomLeft: '',
45
- bottomRight: '',
46
- horizontal: '─',
47
- vertical: '',
48
- },
49
- ascii: {
50
- topLeft: '+',
51
- topRight: '+',
52
- bottomLeft: '+',
53
- bottomRight: '+',
54
- horizontal: '-',
55
- vertical: '|',
56
- },
57
- };
58
-
59
- // Default style (can be overridden via environment variable)
60
- const DEFAULT_STYLE = process.env.START_OUTPUT_STYLE || 'rounded';
14
+ // Metadata spine character
15
+ const SPINE = '│';
61
16
 
62
- // Default block width
63
- const DEFAULT_WIDTH = 60;
17
+ // Result markers
18
+ const SUCCESS_MARKER = '✓';
19
+ const FAILURE_MARKER = '✗';
64
20
 
65
21
  /**
66
- * Get the box style configuration
67
- * @param {string} [styleName] - Style name (rounded, heavy, double, simple, ascii)
68
- * @returns {object} Box style configuration
22
+ * Create a metadata line with spine prefix
23
+ * @param {string} label - Label (e.g., 'session', 'start', 'exit')
24
+ * @param {string} value - Value for the label
25
+ * @returns {string} Formatted line with spine prefix
69
26
  */
70
- function getBoxStyle(styleName = DEFAULT_STYLE) {
71
- return BOX_STYLES[styleName] || BOX_STYLES.rounded;
27
+ function createSpineLine(label, value) {
28
+ // Pad label to 10 characters for alignment
29
+ const paddedLabel = label.padEnd(10);
30
+ return `${SPINE} ${paddedLabel}${value}`;
72
31
  }
73
32
 
74
33
  /**
75
- * Create a horizontal line
76
- * @param {number} width - Line width
77
- * @param {object} style - Box style
78
- * @returns {string} Horizontal line
34
+ * Create an empty spine line (just the spine character)
35
+ * @returns {string} Empty spine line
79
36
  */
80
- function createHorizontalLine(width, style) {
81
- return style.horizontal.repeat(width);
37
+ function createEmptySpineLine() {
38
+ return SPINE;
82
39
  }
83
40
 
84
41
  /**
85
- * Pad or truncate text to fit a specific width
86
- * @param {string} text - Text to pad
87
- * @param {number} width - Target width
88
- * @param {boolean} [allowOverflow=false] - If true, don't truncate long text
89
- * @returns {string} Padded text
42
+ * Create a command line with $ prefix
43
+ * @param {string} command - The command being executed
44
+ * @returns {string} Formatted command line
90
45
  */
91
- function padText(text, width, allowOverflow = false) {
92
- if (text.length >= width) {
93
- // If overflow is allowed, return text as-is (for copyable content like paths)
94
- if (allowOverflow) {
95
- return text;
96
- }
97
- return text.substring(0, width);
98
- }
99
- return text + ' '.repeat(width - text.length);
46
+ function createCommandLine(command) {
47
+ return `$ ${command}`;
100
48
  }
101
49
 
102
50
  /**
103
- * Create a bordered line with text
104
- * @param {string} text - Text content
105
- * @param {number} width - Total width (including borders)
106
- * @param {object} style - Box style
107
- * @param {boolean} [allowOverflow=false] - If true, allow text to overflow (for copyable content)
108
- * @returns {string} Bordered line
51
+ * Get the result marker based on exit code
52
+ * @param {number} exitCode - Exit code (0 = success)
53
+ * @returns {string} Result marker (✓ or )
109
54
  */
110
- function createBorderedLine(text, width, style, allowOverflow = false) {
111
- if (style.vertical) {
112
- const innerWidth = width - 4; // 2 for borders, 2 for padding
113
- const paddedText = padText(text, innerWidth, allowOverflow);
114
- // If text overflows, extend the right border position
115
- if (allowOverflow && text.length > innerWidth) {
116
- return `${style.vertical} ${paddedText} ${style.vertical}`;
117
- }
118
- return `${style.vertical} ${paddedText} ${style.vertical}`;
119
- }
120
- return text;
55
+ function getResultMarker(exitCode) {
56
+ return exitCode === 0 ? SUCCESS_MARKER : FAILURE_MARKER;
121
57
  }
122
58
 
123
59
  /**
124
- * Create the top border of a box
125
- * @param {number} width - Box width
126
- * @param {object} style - Box style
127
- * @returns {string} Top border
60
+ * Parse isolation metadata from extraLines
61
+ * Extracts key-value pairs from lines like "[Isolation] Environment: docker, Mode: attached"
62
+ * @param {string[]} extraLines - Extra lines containing isolation info
63
+ * @returns {object} Parsed isolation metadata
128
64
  */
129
- function createTopBorder(width, style) {
130
- if (style.topLeft) {
131
- const lineWidth = width - 2; // Subtract corners
132
- return `${style.topLeft}${createHorizontalLine(lineWidth, style)}${style.topRight}`;
65
+ function parseIsolationMetadata(extraLines) {
66
+ const metadata = {
67
+ isolation: null,
68
+ mode: null,
69
+ image: null,
70
+ container: null,
71
+ screen: null,
72
+ session: null,
73
+ endpoint: null,
74
+ user: null,
75
+ };
76
+
77
+ for (const line of extraLines) {
78
+ // Parse [Isolation] Environment: docker, Mode: attached
79
+ const envModeMatch = line.match(
80
+ /\[Isolation\] Environment: (\w+), Mode: (\w+)/
81
+ );
82
+ if (envModeMatch) {
83
+ metadata.isolation = envModeMatch[1];
84
+ metadata.mode = envModeMatch[2];
85
+ continue;
86
+ }
87
+
88
+ // Parse [Isolation] Session: name
89
+ const sessionMatch = line.match(/\[Isolation\] Session: (.+)/);
90
+ if (sessionMatch) {
91
+ metadata.session = sessionMatch[1];
92
+ continue;
93
+ }
94
+
95
+ // Parse [Isolation] Image: name
96
+ const imageMatch = line.match(/\[Isolation\] Image: (.+)/);
97
+ if (imageMatch) {
98
+ metadata.image = imageMatch[1];
99
+ continue;
100
+ }
101
+
102
+ // Parse [Isolation] Endpoint: user@host
103
+ const endpointMatch = line.match(/\[Isolation\] Endpoint: (.+)/);
104
+ if (endpointMatch) {
105
+ metadata.endpoint = endpointMatch[1];
106
+ continue;
107
+ }
108
+
109
+ // Parse [Isolation] User: name (isolated)
110
+ const userMatch = line.match(/\[Isolation\] User: (\w+)/);
111
+ if (userMatch) {
112
+ metadata.user = userMatch[1];
113
+ continue;
114
+ }
133
115
  }
134
- return createHorizontalLine(width, style);
116
+
117
+ return metadata;
135
118
  }
136
119
 
137
120
  /**
138
- * Create the bottom border of a box
139
- * @param {number} width - Box width
140
- * @param {object} style - Box style
141
- * @returns {string} Bottom border
121
+ * Generate isolation metadata lines for spine format
122
+ * @param {object} metadata - Parsed isolation metadata
123
+ * @param {string} [containerOrScreenName] - Container or screen session name
124
+ * @returns {string[]} Array of spine-formatted isolation lines
142
125
  */
143
- function createBottomBorder(width, style) {
144
- if (style.bottomLeft) {
145
- const lineWidth = width - 2; // Subtract corners
146
- return `${style.bottomLeft}${createHorizontalLine(lineWidth, style)}${style.bottomRight}`;
126
+ function generateIsolationLines(metadata, containerOrScreenName = null) {
127
+ const lines = [];
128
+
129
+ if (metadata.isolation) {
130
+ lines.push(createSpineLine('isolation', metadata.isolation));
131
+ }
132
+
133
+ if (metadata.mode) {
134
+ lines.push(createSpineLine('mode', metadata.mode));
135
+ }
136
+
137
+ if (metadata.image) {
138
+ lines.push(createSpineLine('image', metadata.image));
147
139
  }
148
- return createHorizontalLine(width, style);
140
+
141
+ // Use provided container/screen name or fall back to metadata.session
142
+ if (metadata.isolation === 'docker') {
143
+ const containerName = containerOrScreenName || metadata.session;
144
+ if (containerName) {
145
+ lines.push(createSpineLine('container', containerName));
146
+ }
147
+ } else if (metadata.isolation === 'screen') {
148
+ const screenName = containerOrScreenName || metadata.session;
149
+ if (screenName) {
150
+ lines.push(createSpineLine('screen', screenName));
151
+ }
152
+ } else if (metadata.isolation === 'tmux') {
153
+ const tmuxName = containerOrScreenName || metadata.session;
154
+ if (tmuxName) {
155
+ lines.push(createSpineLine('tmux', tmuxName));
156
+ }
157
+ } else if (metadata.isolation === 'ssh') {
158
+ if (metadata.endpoint) {
159
+ lines.push(createSpineLine('endpoint', metadata.endpoint));
160
+ }
161
+ }
162
+
163
+ if (metadata.user) {
164
+ lines.push(createSpineLine('user', metadata.user));
165
+ }
166
+
167
+ return lines;
149
168
  }
150
169
 
151
170
  /**
152
- * Create a start block for command execution
171
+ * Create a start block for command execution using status spine format
153
172
  * @param {object} options - Options for the block
154
173
  * @param {string} options.sessionId - Session UUID
155
174
  * @param {string} options.timestamp - Timestamp string
156
175
  * @param {string} options.command - Command being executed
157
- * @param {string[]} [options.extraLines] - Additional lines to show after the command line
158
- * @param {string} [options.style] - Box style name
159
- * @param {number} [options.width] - Box width
160
- * @returns {string} Formatted start block
176
+ * @param {string[]} [options.extraLines] - Additional lines with isolation info
177
+ * @param {string} [options.style] - Ignored (kept for backward compatibility)
178
+ * @param {number} [options.width] - Ignored (kept for backward compatibility)
179
+ * @returns {string} Formatted start block in spine format
161
180
  */
162
181
  function createStartBlock(options) {
163
- const {
164
- sessionId,
165
- timestamp,
166
- command,
167
- extraLines = [],
168
- style: styleName = DEFAULT_STYLE,
169
- width = DEFAULT_WIDTH,
170
- } = options;
182
+ const { sessionId, timestamp, command, extraLines = [] } = options;
171
183
 
172
- const style = getBoxStyle(styleName);
173
184
  const lines = [];
174
185
 
175
- lines.push(createTopBorder(width, style));
176
- lines.push(createBorderedLine(`Session ID: ${sessionId}`, width, style));
177
- lines.push(
178
- createBorderedLine(`Starting at ${timestamp}: ${command}`, width, style)
179
- );
186
+ // Header: session and start time
187
+ lines.push(createSpineLine('session', sessionId));
188
+ lines.push(createSpineLine('start', timestamp));
180
189
 
181
- // Add extra lines (e.g., isolation info, docker image, etc.)
182
- for (const line of extraLines) {
183
- lines.push(createBorderedLine(line, width, style));
190
+ // Parse and add isolation metadata if present
191
+ const metadata = parseIsolationMetadata(extraLines);
192
+
193
+ if (metadata.isolation) {
194
+ lines.push(createEmptySpineLine());
195
+ lines.push(...generateIsolationLines(metadata));
184
196
  }
185
197
 
186
- lines.push(createBottomBorder(width, style));
198
+ // Empty spine line before command
199
+ lines.push(createEmptySpineLine());
200
+
201
+ // Command line
202
+ lines.push(createCommandLine(command));
187
203
 
188
204
  return lines.join('\n');
189
205
  }
@@ -191,34 +207,46 @@ function createStartBlock(options) {
191
207
  /**
192
208
  * Format duration in seconds with appropriate precision
193
209
  * @param {number} durationMs - Duration in milliseconds
194
- * @returns {string} Formatted duration string
210
+ * @returns {string} Formatted duration string (e.g., "0.273s")
195
211
  */
196
212
  function formatDuration(durationMs) {
197
213
  const seconds = durationMs / 1000;
198
214
  if (seconds < 0.001) {
199
- return '0.001';
215
+ return '0.001s';
200
216
  } else if (seconds < 10) {
201
217
  // For durations under 10 seconds, show 3 decimal places
202
- return seconds.toFixed(3);
218
+ return `${seconds.toFixed(3)}s`;
203
219
  } else if (seconds < 100) {
204
- return seconds.toFixed(2);
220
+ return `${seconds.toFixed(2)}s`;
205
221
  } else {
206
- return seconds.toFixed(1);
222
+ return `${seconds.toFixed(1)}s`;
207
223
  }
208
224
  }
209
225
 
210
226
  /**
211
- * Create a finish block for command execution
227
+ * Create a finish block for command execution using status spine format
228
+ *
229
+ * Bottom block ordering rules:
230
+ * 1. Result marker (✓ or ✗)
231
+ * 2. finish timestamp
232
+ * 3. duration
233
+ * 4. exit code
234
+ * 5. (repeated isolation metadata, if any)
235
+ * 6. empty spine line
236
+ * 7. log path (always second-to-last)
237
+ * 8. session ID (always last)
238
+ *
212
239
  * @param {object} options - Options for the block
213
240
  * @param {string} options.sessionId - Session UUID
214
241
  * @param {string} options.timestamp - Timestamp string
215
242
  * @param {number} options.exitCode - Exit code
216
243
  * @param {string} options.logPath - Path to log file
217
244
  * @param {number} [options.durationMs] - Duration in milliseconds
218
- * @param {string} [options.resultMessage] - Result message (e.g., "Screen session exited...")
219
- * @param {string} [options.style] - Box style name
220
- * @param {number} [options.width] - Box width
221
- * @returns {string} Formatted finish block
245
+ * @param {string} [options.resultMessage] - Result message (ignored in new format)
246
+ * @param {string[]} [options.extraLines] - Isolation info for repetition in footer
247
+ * @param {string} [options.style] - Ignored (kept for backward compatibility)
248
+ * @param {number} [options.width] - Ignored (kept for backward compatibility)
249
+ * @returns {string} Formatted finish block in spine format
222
250
  */
223
251
  function createFinishBlock(options) {
224
252
  const {
@@ -227,36 +255,36 @@ function createFinishBlock(options) {
227
255
  exitCode,
228
256
  logPath,
229
257
  durationMs,
230
- resultMessage,
231
- style: styleName = DEFAULT_STYLE,
232
- width = DEFAULT_WIDTH,
258
+ extraLines = [],
233
259
  } = options;
234
260
 
235
- const style = getBoxStyle(styleName);
236
261
  const lines = [];
237
262
 
238
- // Format the finished message with optional duration
239
- let finishedMsg = `Finished at ${timestamp}`;
263
+ // Result marker appears first in footer (after program output)
264
+ lines.push(getResultMarker(exitCode));
265
+
266
+ // Finish metadata
267
+ lines.push(createSpineLine('finish', timestamp));
268
+
240
269
  if (durationMs !== undefined && durationMs !== null) {
241
- finishedMsg += ` in ${formatDuration(durationMs)} seconds`;
270
+ lines.push(createSpineLine('duration', formatDuration(durationMs)));
242
271
  }
243
272
 
244
- lines.push(createTopBorder(width, style));
273
+ lines.push(createSpineLine('exit', String(exitCode)));
245
274
 
246
- // Add result message first if provided (e.g., "Docker container exited...")
247
- // Allow overflow so the full message is visible and copyable
248
- if (resultMessage) {
249
- lines.push(createBorderedLine(resultMessage, width, style, true));
275
+ // Repeat isolation metadata if present
276
+ const metadata = parseIsolationMetadata(extraLines);
277
+ if (metadata.isolation) {
278
+ lines.push(createEmptySpineLine());
279
+ lines.push(...generateIsolationLines(metadata));
250
280
  }
251
281
 
252
- lines.push(createBorderedLine(finishedMsg, width, style));
253
- lines.push(createBorderedLine(`Exit code: ${exitCode}`, width, style));
254
- // Allow overflow for log path and session ID so they can be copied completely
255
- lines.push(createBorderedLine(`Log: ${logPath}`, width, style, true));
256
- lines.push(
257
- createBorderedLine(`Session ID: ${sessionId}`, width, style, true)
258
- );
259
- lines.push(createBottomBorder(width, style));
282
+ // Empty spine line before final two entries
283
+ lines.push(createEmptySpineLine());
284
+
285
+ // Log and session are ALWAYS last (in that order)
286
+ lines.push(createSpineLine('log', logPath));
287
+ lines.push(createSpineLine('session', sessionId));
260
288
 
261
289
  return lines.join('\n');
262
290
  }
@@ -371,17 +399,23 @@ function formatAsNestedLinksNotation(obj, indent = 2, depth = 0) {
371
399
  }
372
400
 
373
401
  module.exports = {
374
- BOX_STYLES,
375
- DEFAULT_STYLE,
376
- DEFAULT_WIDTH,
377
- getBoxStyle,
378
- createHorizontalLine,
379
- createBorderedLine,
380
- createTopBorder,
381
- createBottomBorder,
402
+ // Status spine format API
403
+ SPINE,
404
+ SUCCESS_MARKER,
405
+ FAILURE_MARKER,
406
+ createSpineLine,
407
+ createEmptySpineLine,
408
+ createCommandLine,
409
+ getResultMarker,
410
+ parseIsolationMetadata,
411
+ generateIsolationLines,
412
+
413
+ // Main block creation functions
382
414
  createStartBlock,
383
415
  createFinishBlock,
384
416
  formatDuration,
417
+
418
+ // Links notation utilities
385
419
  escapeForLinksNotation,
386
420
  formatAsNestedLinksNotation,
387
421
  };
package/test/cli.test.js CHANGED
@@ -122,3 +122,97 @@ describe('CLI basic behavior', () => {
122
122
  assert.ok(result.stdout.includes('Usage:'), 'Should display usage');
123
123
  });
124
124
  });
125
+
126
+ describe('CLI isolation output (issue #67)', () => {
127
+ const { isCommandAvailable } = require('../src/lib/isolation');
128
+
129
+ it('should display screen session name when using screen isolation', async () => {
130
+ if (!isCommandAvailable('screen')) {
131
+ console.log(' Skipping: screen not installed');
132
+ return;
133
+ }
134
+
135
+ const result = runCLI(['-i', 'screen', '--', 'echo', 'hello']);
136
+
137
+ // The output should contain the screen session name (in format screen-timestamp-random)
138
+ // Check that the session UUID is displayed
139
+ assert.ok(
140
+ result.stdout.includes('│ session'),
141
+ 'Should display session UUID'
142
+ );
143
+ // Check that screen isolation info is displayed
144
+ assert.ok(
145
+ result.stdout.includes('│ isolation screen'),
146
+ 'Should display screen isolation'
147
+ );
148
+ // Check that the actual screen session name is displayed (issue #67 fix)
149
+ assert.ok(
150
+ result.stdout.includes('│ screen screen-'),
151
+ 'Should display actual screen session name for reconnection (issue #67)'
152
+ );
153
+ });
154
+
155
+ it('should display tmux session name when using tmux isolation', async () => {
156
+ if (!isCommandAvailable('tmux')) {
157
+ console.log(' Skipping: tmux not installed');
158
+ return;
159
+ }
160
+
161
+ const result = runCLI(['-i', 'tmux', '--', 'echo', 'hello']);
162
+
163
+ // The output should contain the tmux session name
164
+ assert.ok(
165
+ result.stdout.includes('│ session'),
166
+ 'Should display session UUID'
167
+ );
168
+ assert.ok(
169
+ result.stdout.includes('│ isolation tmux'),
170
+ 'Should display tmux isolation'
171
+ );
172
+ // Check that the actual tmux session name is displayed (issue #67 fix)
173
+ assert.ok(
174
+ result.stdout.includes('│ tmux tmux-'),
175
+ 'Should display actual tmux session name for reconnection (issue #67)'
176
+ );
177
+ });
178
+
179
+ it('should display docker container name when using docker isolation', async () => {
180
+ const { canRunLinuxDockerImages } = require('../src/lib/isolation');
181
+
182
+ if (!canRunLinuxDockerImages()) {
183
+ console.log(
184
+ ' Skipping: docker not available or cannot run Linux images'
185
+ );
186
+ return;
187
+ }
188
+
189
+ const result = runCLI([
190
+ '-i',
191
+ 'docker',
192
+ '--image',
193
+ 'alpine:latest',
194
+ '--',
195
+ 'echo',
196
+ 'hello',
197
+ ]);
198
+
199
+ // The output should contain the docker container name
200
+ assert.ok(
201
+ result.stdout.includes('│ session'),
202
+ 'Should display session UUID'
203
+ );
204
+ assert.ok(
205
+ result.stdout.includes('│ isolation docker'),
206
+ 'Should display docker isolation'
207
+ );
208
+ assert.ok(
209
+ result.stdout.includes('│ image alpine:latest'),
210
+ 'Should display docker image'
211
+ );
212
+ // Check that the actual container name is displayed (issue #67 fix)
213
+ assert.ok(
214
+ result.stdout.includes('│ container docker-'),
215
+ 'Should display actual container name for reconnection (issue #67)'
216
+ );
217
+ });
218
+ });