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.
Files changed (65) hide show
  1. package/CHANGELOG.md +34 -227
  2. package/bun.lock +5 -0
  3. package/eslint.config.mjs +1 -1
  4. package/package.json +11 -6
  5. package/src/bin/cli.js +332 -171
  6. package/src/lib/args-parser.js +118 -0
  7. package/src/lib/execution-store.js +722 -0
  8. package/src/lib/isolation.js +51 -0
  9. package/src/lib/output-blocks.js +357 -0
  10. package/src/lib/status-formatter.js +148 -0
  11. package/src/lib/version.js +143 -0
  12. package/test/args-parser.test.js +107 -0
  13. package/test/cli.test.js +11 -1
  14. package/test/docker-autoremove.test.js +11 -16
  15. package/test/execution-store.test.js +483 -0
  16. package/test/isolation-cleanup.test.js +11 -16
  17. package/test/isolation.test.js +11 -17
  18. package/test/output-blocks.test.js +197 -0
  19. package/test/public-exports.test.js +105 -0
  20. package/test/status-query.test.js +197 -0
  21. package/.github/workflows/release.yml +0 -352
  22. package/.husky/pre-commit +0 -1
  23. package/ARCHITECTURE.md +0 -297
  24. package/LICENSE +0 -24
  25. package/README.md +0 -339
  26. package/REQUIREMENTS.md +0 -299
  27. package/docs/PIPES.md +0 -243
  28. package/docs/USAGE.md +0 -194
  29. package/docs/case-studies/issue-15/README.md +0 -208
  30. package/docs/case-studies/issue-18/README.md +0 -343
  31. package/docs/case-studies/issue-18/issue-comments.json +0 -1
  32. package/docs/case-studies/issue-18/issue-data.json +0 -7
  33. package/docs/case-studies/issue-22/analysis.md +0 -547
  34. package/docs/case-studies/issue-22/issue-data.json +0 -12
  35. package/docs/case-studies/issue-25/README.md +0 -232
  36. package/docs/case-studies/issue-25/issue-data.json +0 -21
  37. package/docs/case-studies/issue-28/README.md +0 -405
  38. package/docs/case-studies/issue-28/issue-data.json +0 -105
  39. package/docs/case-studies/issue-28/raw-issue-data.md +0 -92
  40. package/experiments/debug-regex.js +0 -49
  41. package/experiments/isolation-design.md +0 -131
  42. package/experiments/screen-output-test.js +0 -265
  43. package/experiments/test-cli.sh +0 -42
  44. package/experiments/test-command-stream-cjs.cjs +0 -30
  45. package/experiments/test-command-stream-wrapper.js +0 -54
  46. package/experiments/test-command-stream.mjs +0 -56
  47. package/experiments/test-screen-attached.js +0 -126
  48. package/experiments/test-screen-logfile.js +0 -286
  49. package/experiments/test-screen-modes.js +0 -128
  50. package/experiments/test-screen-output.sh +0 -27
  51. package/experiments/test-screen-tee-debug.js +0 -237
  52. package/experiments/test-screen-tee-fallback.js +0 -230
  53. package/experiments/test-substitution.js +0 -143
  54. package/experiments/user-isolation-research.md +0 -83
  55. package/scripts/changeset-version.mjs +0 -38
  56. package/scripts/check-file-size.mjs +0 -103
  57. package/scripts/create-github-release.mjs +0 -93
  58. package/scripts/create-manual-changeset.mjs +0 -89
  59. package/scripts/format-github-release.mjs +0 -83
  60. package/scripts/format-release-notes.mjs +0 -219
  61. package/scripts/instant-version-bump.mjs +0 -121
  62. package/scripts/publish-to-npm.mjs +0 -129
  63. package/scripts/setup-npm.mjs +0 -37
  64. package/scripts/validate-changeset.mjs +0 -107
  65. package/scripts/version-and-commit.mjs +0 -237
@@ -238,22 +238,15 @@ describe('Isolation Resource Cleanup Verification', () => {
238
238
  });
239
239
 
240
240
  describe('docker resource cleanup', () => {
241
- // Helper function to check if docker daemon is running
242
- function isDockerRunning() {
243
- if (!isCommandAvailable('docker')) {
244
- return false;
245
- }
246
- try {
247
- execSync('docker info', { stdio: 'ignore', timeout: 5000 });
248
- return true;
249
- } catch {
250
- return false;
251
- }
252
- }
241
+ // Use the canRunLinuxDockerImages function from isolation module
242
+ // to properly detect if Linux containers can run (handles Windows containers mode)
243
+ const { canRunLinuxDockerImages } = require('../src/lib/isolation');
253
244
 
254
245
  it('should show docker container as exited after command completes (auto-exit by default)', async () => {
255
- if (!isDockerRunning()) {
256
- console.log(' Skipping: docker not available or daemon not running');
246
+ if (!canRunLinuxDockerImages()) {
247
+ console.log(
248
+ ' Skipping: docker not available, daemon not running, or Linux containers not supported'
249
+ );
257
250
  return;
258
251
  }
259
252
 
@@ -325,8 +318,10 @@ describe('Isolation Resource Cleanup Verification', () => {
325
318
  });
326
319
 
327
320
  it('should keep docker container running when keepAlive is true', async () => {
328
- if (!isDockerRunning()) {
329
- console.log(' Skipping: docker not available or daemon not running');
321
+ if (!canRunLinuxDockerImages()) {
322
+ console.log(
323
+ ' Skipping: docker not available, daemon not running, or Linux containers not supported'
324
+ );
330
325
  return;
331
326
  }
332
327
 
@@ -444,23 +444,15 @@ describe('Isolation Keep-Alive Behavior', () => {
444
444
  });
445
445
 
446
446
  describe('runInDocker keep-alive messages', () => {
447
- // Helper function to check if docker daemon is running
448
- function isDockerRunning() {
449
- if (!isCommandAvailable('docker')) {
450
- return false;
451
- }
452
- try {
453
- // Try to ping the docker daemon
454
- execSync('docker info', { stdio: 'ignore', timeout: 5000 });
455
- return true;
456
- } catch {
457
- return false;
458
- }
459
- }
447
+ // Use the canRunLinuxDockerImages function from isolation module
448
+ // to properly detect if Linux containers can run (handles Windows containers mode)
449
+ const { canRunLinuxDockerImages } = require('../src/lib/isolation');
460
450
 
461
451
  it('should include auto-exit message by default in detached mode', async () => {
462
- if (!isDockerRunning()) {
463
- console.log(' Skipping: docker not available or daemon not running');
452
+ if (!canRunLinuxDockerImages()) {
453
+ console.log(
454
+ ' Skipping: docker not available, daemon not running, or Linux containers not supported'
455
+ );
464
456
  return;
465
457
  }
466
458
 
@@ -487,8 +479,10 @@ describe('Isolation Keep-Alive Behavior', () => {
487
479
  });
488
480
 
489
481
  it('should include keep-alive message when keepAlive is true', async () => {
490
- if (!isDockerRunning()) {
491
- console.log(' Skipping: docker not available or daemon not running');
482
+ if (!canRunLinuxDockerImages()) {
483
+ console.log(
484
+ ' Skipping: docker not available, daemon not running, or Linux containers not supported'
485
+ );
492
486
  return;
493
487
  }
494
488
 
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Tests for output-blocks module
3
+ */
4
+
5
+ const { describe, it, expect } = require('bun:test');
6
+
7
+ const {
8
+ BOX_STYLES,
9
+ DEFAULT_STYLE,
10
+ DEFAULT_WIDTH,
11
+ getBoxStyle,
12
+ createStartBlock,
13
+ createFinishBlock,
14
+ formatDuration,
15
+ escapeForLinksNotation,
16
+ formatAsNestedLinksNotation,
17
+ } = require('../src/lib/output-blocks');
18
+
19
+ describe('output-blocks module', () => {
20
+ describe('BOX_STYLES', () => {
21
+ it('should have all expected styles', () => {
22
+ expect(BOX_STYLES).toHaveProperty('rounded');
23
+ expect(BOX_STYLES).toHaveProperty('heavy');
24
+ expect(BOX_STYLES).toHaveProperty('double');
25
+ expect(BOX_STYLES).toHaveProperty('simple');
26
+ expect(BOX_STYLES).toHaveProperty('ascii');
27
+ });
28
+
29
+ it('should have correct rounded style characters', () => {
30
+ expect(BOX_STYLES.rounded.topLeft).toBe('╭');
31
+ expect(BOX_STYLES.rounded.topRight).toBe('╮');
32
+ expect(BOX_STYLES.rounded.bottomLeft).toBe('╰');
33
+ expect(BOX_STYLES.rounded.bottomRight).toBe('╯');
34
+ });
35
+ });
36
+
37
+ describe('getBoxStyle', () => {
38
+ it('should return rounded style by default', () => {
39
+ const style = getBoxStyle();
40
+ expect(style).toEqual(BOX_STYLES.rounded);
41
+ });
42
+
43
+ it('should return requested style', () => {
44
+ expect(getBoxStyle('heavy')).toEqual(BOX_STYLES.heavy);
45
+ expect(getBoxStyle('double')).toEqual(BOX_STYLES.double);
46
+ expect(getBoxStyle('ascii')).toEqual(BOX_STYLES.ascii);
47
+ });
48
+
49
+ it('should return rounded for unknown style', () => {
50
+ const style = getBoxStyle('unknown');
51
+ expect(style).toEqual(BOX_STYLES.rounded);
52
+ });
53
+ });
54
+
55
+ describe('createStartBlock', () => {
56
+ it('should create a start block with session ID', () => {
57
+ const block = createStartBlock({
58
+ sessionId: 'test-uuid-1234',
59
+ timestamp: '2025-01-01 00:00:00',
60
+ command: 'echo hello',
61
+ });
62
+
63
+ expect(block).toContain('╭');
64
+ expect(block).toContain('╰');
65
+ expect(block).toContain('Session ID: test-uuid-1234');
66
+ expect(block).toContain('Starting at 2025-01-01 00:00:00: echo hello');
67
+ });
68
+
69
+ it('should use specified style', () => {
70
+ const block = createStartBlock({
71
+ sessionId: 'test-uuid',
72
+ timestamp: '2025-01-01 00:00:00',
73
+ command: 'echo hello',
74
+ style: 'ascii',
75
+ });
76
+
77
+ expect(block).toContain('+');
78
+ expect(block).toContain('-');
79
+ });
80
+ });
81
+
82
+ describe('createFinishBlock', () => {
83
+ it('should create a finish block with session ID and exit code', () => {
84
+ const block = createFinishBlock({
85
+ sessionId: 'test-uuid-1234',
86
+ timestamp: '2025-01-01 00:00:01',
87
+ exitCode: 0,
88
+ logPath: '/tmp/test.log',
89
+ durationMs: 17,
90
+ });
91
+
92
+ expect(block).toContain('╭');
93
+ expect(block).toContain('╰');
94
+ expect(block).toContain('Session ID: test-uuid-1234');
95
+ expect(block).toContain(
96
+ 'Finished at 2025-01-01 00:00:01 in 0.017 seconds'
97
+ );
98
+ expect(block).toContain('Exit code: 0');
99
+ expect(block).toContain('Log: /tmp/test.log');
100
+ });
101
+
102
+ it('should create a finish block without duration when not provided', () => {
103
+ const block = createFinishBlock({
104
+ sessionId: 'test-uuid-1234',
105
+ timestamp: '2025-01-01 00:00:01',
106
+ exitCode: 0,
107
+ logPath: '/tmp/test.log',
108
+ });
109
+
110
+ expect(block).toContain('Finished at 2025-01-01 00:00:01');
111
+ expect(block).not.toContain('seconds');
112
+ });
113
+ });
114
+
115
+ describe('formatDuration', () => {
116
+ it('should format very small durations', () => {
117
+ expect(formatDuration(0.5)).toBe('0.001');
118
+ });
119
+
120
+ it('should format millisecond durations', () => {
121
+ expect(formatDuration(17)).toBe('0.017');
122
+ expect(formatDuration(500)).toBe('0.500');
123
+ });
124
+
125
+ it('should format second durations', () => {
126
+ expect(formatDuration(1000)).toBe('1.000');
127
+ expect(formatDuration(5678)).toBe('5.678');
128
+ });
129
+
130
+ it('should format longer durations with less precision', () => {
131
+ expect(formatDuration(12345)).toBe('12.35');
132
+ expect(formatDuration(123456)).toBe('123.5');
133
+ });
134
+ });
135
+
136
+ describe('escapeForLinksNotation', () => {
137
+ it('should not quote simple values', () => {
138
+ expect(escapeForLinksNotation('simple')).toBe('simple');
139
+ expect(escapeForLinksNotation('123')).toBe('123');
140
+ expect(escapeForLinksNotation('true')).toBe('true');
141
+ });
142
+
143
+ it('should quote values with spaces', () => {
144
+ expect(escapeForLinksNotation('hello world')).toBe('"hello world"');
145
+ });
146
+
147
+ it('should quote values with colons', () => {
148
+ expect(escapeForLinksNotation('key:value')).toBe('"key:value"');
149
+ });
150
+
151
+ it('should use single quotes for values with double quotes', () => {
152
+ expect(escapeForLinksNotation('say "hello"')).toBe('\'say "hello"\'');
153
+ });
154
+
155
+ it('should use double quotes for values with single quotes', () => {
156
+ expect(escapeForLinksNotation("it's cool")).toBe('"it\'s cool"');
157
+ });
158
+
159
+ it('should escape quotes when both types are present', () => {
160
+ const result = escapeForLinksNotation('say "hello" it\'s');
161
+ // Should wrap in one quote type and escape the other
162
+ expect(result).toMatch(/^["'].*["']$/);
163
+ });
164
+
165
+ it('should handle null values', () => {
166
+ expect(escapeForLinksNotation(null)).toBe('null');
167
+ expect(escapeForLinksNotation(undefined)).toBe('null');
168
+ });
169
+ });
170
+
171
+ describe('formatAsNestedLinksNotation', () => {
172
+ it('should format simple objects', () => {
173
+ const obj = { key: 'value', number: 123 };
174
+ const result = formatAsNestedLinksNotation(obj);
175
+
176
+ expect(result).toContain('key value');
177
+ expect(result).toContain('number 123');
178
+ });
179
+
180
+ it('should quote values with spaces', () => {
181
+ const obj = { message: 'hello world' };
182
+ const result = formatAsNestedLinksNotation(obj);
183
+
184
+ expect(result).toContain('message "hello world"');
185
+ });
186
+
187
+ it('should handle empty objects', () => {
188
+ expect(formatAsNestedLinksNotation({})).toBe('()');
189
+ });
190
+
191
+ it('should handle null', () => {
192
+ expect(formatAsNestedLinksNotation(null)).toBe('null');
193
+ });
194
+ });
195
+ });
196
+
197
+ console.log('=== Output Blocks Unit Tests ===');
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tests for public exports of start-command package
3
+ * Verifies that ExecutionStore can be imported via package exports
4
+ */
5
+
6
+ const { describe, it, expect } = require('bun:test');
7
+
8
+ describe('Public Exports', () => {
9
+ describe('execution-store export', () => {
10
+ it('should export ExecutionStore class', () => {
11
+ const { ExecutionStore } = require('../src/lib/execution-store');
12
+ expect(ExecutionStore).toBeDefined();
13
+ expect(typeof ExecutionStore).toBe('function');
14
+ });
15
+
16
+ it('should export ExecutionRecord class', () => {
17
+ const { ExecutionRecord } = require('../src/lib/execution-store');
18
+ expect(ExecutionRecord).toBeDefined();
19
+ expect(typeof ExecutionRecord).toBe('function');
20
+ });
21
+
22
+ it('should export ExecutionStatus enum', () => {
23
+ const { ExecutionStatus } = require('../src/lib/execution-store');
24
+ expect(ExecutionStatus).toBeDefined();
25
+ expect(ExecutionStatus.EXECUTING).toBe('executing');
26
+ expect(ExecutionStatus.EXECUTED).toBe('executed');
27
+ });
28
+
29
+ it('should export LockManager class', () => {
30
+ const { LockManager } = require('../src/lib/execution-store');
31
+ expect(LockManager).toBeDefined();
32
+ expect(typeof LockManager).toBe('function');
33
+ });
34
+
35
+ it('should export isClinkInstalled function', () => {
36
+ const { isClinkInstalled } = require('../src/lib/execution-store');
37
+ expect(isClinkInstalled).toBeDefined();
38
+ expect(typeof isClinkInstalled).toBe('function');
39
+ });
40
+
41
+ it('should export configuration constants', () => {
42
+ const {
43
+ DEFAULT_APP_FOLDER,
44
+ LINO_DB_FILE,
45
+ LINKS_DB_FILE,
46
+ LOCK_FILE,
47
+ } = require('../src/lib/execution-store');
48
+ expect(DEFAULT_APP_FOLDER).toBeDefined();
49
+ expect(typeof DEFAULT_APP_FOLDER).toBe('string');
50
+ expect(LINO_DB_FILE).toBe('executions.lino');
51
+ expect(LINKS_DB_FILE).toBe('executions.links');
52
+ expect(LOCK_FILE).toBe('executions.lock');
53
+ });
54
+
55
+ it('should allow creating and using ExecutionStore instance', () => {
56
+ const os = require('os');
57
+ const path = require('path');
58
+ const fs = require('fs');
59
+
60
+ const {
61
+ ExecutionStore,
62
+ ExecutionRecord,
63
+ ExecutionStatus,
64
+ } = require('../src/lib/execution-store');
65
+
66
+ // Create a temporary folder for testing
67
+ const testFolder = path.join(
68
+ os.tmpdir(),
69
+ `public-export-test-${Date.now()}`
70
+ );
71
+
72
+ try {
73
+ const store = new ExecutionStore({ appFolder: testFolder });
74
+ expect(store).toBeDefined();
75
+
76
+ // Create and save a record
77
+ const record = new ExecutionRecord({
78
+ command: 'echo "test"',
79
+ logPath: '/tmp/test.log',
80
+ });
81
+ expect(record.status).toBe(ExecutionStatus.EXECUTING);
82
+
83
+ store.save(record);
84
+
85
+ // Retrieve the record
86
+ const retrieved = store.get(record.uuid);
87
+ expect(retrieved).toBeDefined();
88
+ expect(retrieved.command).toBe('echo "test"');
89
+
90
+ // Complete the record
91
+ record.complete(0);
92
+ store.save(record);
93
+
94
+ const completed = store.get(record.uuid);
95
+ expect(completed.status).toBe(ExecutionStatus.EXECUTED);
96
+ expect(completed.exitCode).toBe(0);
97
+ } finally {
98
+ // Cleanup
99
+ if (fs.existsSync(testFolder)) {
100
+ fs.rmSync(testFolder, { recursive: true, force: true });
101
+ }
102
+ }
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Integration tests for --status query functionality
3
+ */
4
+
5
+ const { describe, it, expect, beforeEach, afterEach } = require('bun:test');
6
+ const { spawnSync } = require('child_process');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+
11
+ const {
12
+ ExecutionStore,
13
+ ExecutionRecord,
14
+ ExecutionStatus,
15
+ } = require('../src/lib/execution-store');
16
+
17
+ // Use temp directory for tests
18
+ const TEST_APP_FOLDER = path.join(
19
+ os.tmpdir(),
20
+ `status-query-test-${Date.now()}`
21
+ );
22
+
23
+ // Path to CLI
24
+ const CLI_PATH = path.join(__dirname, '../src/bin/cli.js');
25
+
26
+ // Helper to clean up test directory
27
+ function cleanupTestDir() {
28
+ if (fs.existsSync(TEST_APP_FOLDER)) {
29
+ fs.rmSync(TEST_APP_FOLDER, { recursive: true, force: true });
30
+ }
31
+ }
32
+
33
+ // Helper to run CLI command
34
+ function runCli(args, env = {}) {
35
+ const result = spawnSync('bun', [CLI_PATH, ...args], {
36
+ encoding: 'utf8',
37
+ env: {
38
+ ...process.env,
39
+ START_APP_FOLDER: TEST_APP_FOLDER,
40
+ ...env,
41
+ },
42
+ timeout: 10000,
43
+ });
44
+ return {
45
+ stdout: result.stdout || '',
46
+ stderr: result.stderr || '',
47
+ exitCode: result.status,
48
+ };
49
+ }
50
+
51
+ describe('--status query functionality', () => {
52
+ let store;
53
+ let testRecord;
54
+
55
+ beforeEach(() => {
56
+ cleanupTestDir();
57
+ store = new ExecutionStore({
58
+ appFolder: TEST_APP_FOLDER,
59
+ useLinks: false,
60
+ });
61
+
62
+ // Create a test execution record
63
+ testRecord = new ExecutionRecord({
64
+ command: 'echo hello world',
65
+ pid: 12345,
66
+ logPath: '/tmp/test.log',
67
+ workingDirectory: '/home/test',
68
+ shell: '/bin/bash',
69
+ });
70
+ testRecord.complete(0);
71
+ store.save(testRecord);
72
+ });
73
+
74
+ afterEach(() => {
75
+ cleanupTestDir();
76
+ });
77
+
78
+ describe('links-notation format (default)', () => {
79
+ it('should output status in links-notation indented format by default', () => {
80
+ const result = runCli(['--status', testRecord.uuid]);
81
+
82
+ expect(result.exitCode).toBe(0);
83
+ // Should start with UUID on its own line
84
+ expect(result.stdout).toMatch(new RegExp(`^${testRecord.uuid}\\n`));
85
+ // Should have indented properties (values without special chars are not quoted)
86
+ expect(result.stdout).toContain(` uuid ${testRecord.uuid}`);
87
+ expect(result.stdout).toContain(' status executed');
88
+ // Command with space should be quoted
89
+ expect(result.stdout).toContain(' command "echo hello world"');
90
+ });
91
+
92
+ it('should output status in links-notation format with explicit flag', () => {
93
+ const result = runCli([
94
+ '--status',
95
+ testRecord.uuid,
96
+ '--output-format',
97
+ 'links-notation',
98
+ ]);
99
+
100
+ expect(result.exitCode).toBe(0);
101
+ // Should start with UUID on its own line
102
+ expect(result.stdout).toMatch(new RegExp(`^${testRecord.uuid}\\n`));
103
+ // UUID without special chars is not quoted
104
+ expect(result.stdout).toContain(` uuid ${testRecord.uuid}`);
105
+ });
106
+ });
107
+
108
+ describe('json format', () => {
109
+ it('should output status in JSON format', () => {
110
+ const result = runCli([
111
+ '--status',
112
+ testRecord.uuid,
113
+ '--output-format',
114
+ 'json',
115
+ ]);
116
+
117
+ expect(result.exitCode).toBe(0);
118
+
119
+ // Parse the JSON output
120
+ const parsed = JSON.parse(result.stdout);
121
+ expect(parsed.uuid).toBe(testRecord.uuid);
122
+ expect(parsed.command).toBe('echo hello world');
123
+ expect(parsed.status).toBe('executed');
124
+ expect(parsed.exitCode).toBe(0);
125
+ expect(parsed.pid).toBe(12345);
126
+ });
127
+ });
128
+
129
+ describe('text format', () => {
130
+ it('should output status in human-readable text format', () => {
131
+ const result = runCli([
132
+ '--status',
133
+ testRecord.uuid,
134
+ '--output-format',
135
+ 'text',
136
+ ]);
137
+
138
+ expect(result.exitCode).toBe(0);
139
+ expect(result.stdout).toContain('Execution Status');
140
+ expect(result.stdout).toContain('UUID:');
141
+ expect(result.stdout).toContain(testRecord.uuid);
142
+ expect(result.stdout).toContain('Status:');
143
+ expect(result.stdout).toContain('executed');
144
+ expect(result.stdout).toContain('Command:');
145
+ expect(result.stdout).toContain('echo hello world');
146
+ expect(result.stdout).toContain('Exit Code:');
147
+ expect(result.stdout).toContain('0');
148
+ });
149
+ });
150
+
151
+ describe('error handling', () => {
152
+ it('should show error for non-existent UUID', () => {
153
+ const fakeUuid = '00000000-0000-0000-0000-000000000000';
154
+ const result = runCli(['--status', fakeUuid]);
155
+
156
+ expect(result.exitCode).toBe(1);
157
+ expect(result.stderr).toContain('No execution found with UUID');
158
+ expect(result.stderr).toContain(fakeUuid);
159
+ });
160
+
161
+ it('should show error when tracking is disabled', () => {
162
+ const result = runCli(['--status', testRecord.uuid], {
163
+ START_DISABLE_TRACKING: '1',
164
+ });
165
+
166
+ expect(result.exitCode).toBe(1);
167
+ expect(result.stderr).toContain('tracking is disabled');
168
+ });
169
+ });
170
+
171
+ describe('executing status', () => {
172
+ it('should show executing status for ongoing commands', () => {
173
+ // Create an executing (not completed) record
174
+ const executingRecord = new ExecutionRecord({
175
+ command: 'sleep 100',
176
+ pid: 99999,
177
+ logPath: '/tmp/executing.log',
178
+ });
179
+ store.save(executingRecord);
180
+
181
+ const result = runCli([
182
+ '--status',
183
+ executingRecord.uuid,
184
+ '--output-format',
185
+ 'json',
186
+ ]);
187
+
188
+ expect(result.exitCode).toBe(0);
189
+ const parsed = JSON.parse(result.stdout);
190
+ expect(parsed.status).toBe('executing');
191
+ expect(parsed.exitCode).toBeNull();
192
+ expect(parsed.endTime).toBeNull();
193
+ });
194
+ });
195
+ });
196
+
197
+ console.log('=== Status Query Integration Tests ===');