command-stream 0.3.2 โ†’ 0.5.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/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![npm](https://img.shields.io/npm/v/command-stream.svg)](https://npmjs.com/command-stream)
2
+ [![License](https://img.shields.io/badge/license-Unlicense-blue.svg)](https://github.com/link-foundation/command-stream/blob/main/LICENSE)
3
+ [![GitHub stars](https://img.shields.io/github/stars/link-foundation/command-stream?style=social)](https://github.com/link-foundation/command-stream/stargazers)
4
+
1
5
  [![Open in Gitpod](https://img.shields.io/badge/Gitpod-ready--to--code-f29718?logo=gitpod)](https://gitpod.io/#https://github.com/link-foundation/command-stream)
2
6
  [![Open in GitHub Codespaces](https://img.shields.io/badge/GitHub%20Codespaces-Open-181717?logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=link-foundation/command-stream)
3
7
 
@@ -23,31 +27,48 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
23
27
 
24
28
  ## Comparison with Other Libraries
25
29
 
26
- | Feature | [command-stream](https://github.com/link-foundation/command-stream) | [Bun.$](https://bun.sh/docs/runtime/shell) | [execa](https://github.com/sindresorhus/execa) | [zx](https://github.com/google/zx) |
27
- |---------|----------------|-------|-------|-----|
28
- | **Runtime Support** | โœ… Bun + Node.js | ๐ŸŸก Bun only | โœ… Node.js | โœ… Node.js |
29
- | **Template Literals** | โœ… `` $`cmd` `` | โœ… `` $`cmd` `` | โœ… `` $`cmd` `` | โœ… `` $`cmd` `` |
30
- | **Real-time Streaming** | โœ… Live output | โŒ Buffer only | ๐ŸŸก Limited | โŒ Buffer only |
31
- | **Synchronous Execution** | โœ… `.sync()` with events | โŒ No | โœ… `execaSync` | โŒ No |
32
- | **Async Iteration** | โœ… `for await (chunk of $.stream())` | โŒ No | โŒ No | โŒ No |
33
- | **EventEmitter Pattern** | โœ… `.on('data', ...)` | โŒ No | ๐ŸŸก Limited events | โŒ No |
34
- | **Mixed Patterns** | โœ… Events + await/sync | โŒ No | โŒ No | โŒ No |
35
- | **Bun.$ Compatibility** | โœ… `.text()` method support | โœ… Native API | โŒ No | โŒ No |
36
- | **Shell Injection Protection** | โœ… Auto-quoting | โœ… Built-in | โœ… Safe by default | โœ… Safe by default |
37
- | **Cross-platform** | โœ… macOS/Linux/Windows | โœ… Yes | โœ… Yes | โœ… Yes |
38
- | **Performance** | โšก Fast (Bun optimized) | โšก Very fast | ๐ŸŒ Moderate | ๐ŸŒ Slow |
39
- | **Memory Efficiency** | โœ… Streaming prevents buildup | ๐ŸŸก Buffers in memory | ๐ŸŸก Buffers in memory | ๐ŸŸก Buffers in memory |
40
- | **Error Handling** | โœ… Configurable (`set -e`/`set +e`, non-zero OK by default) | โœ… Throws on error | โœ… Throws on error | โœ… Throws on error |
41
- | **Shell Settings** | โœ… `set -e`/`set +e` equivalent | โŒ No | โŒ No | โŒ No |
42
- | **Stdout Support** | โœ… Real-time streaming + events | โœ… Shell redirection + buffered | โœ… Node.js streams + interleaved | โœ… Readable streams + `.pipe.stdout` |
43
- | **Stderr Support** | โœ… Real-time streaming + events | โœ… Redirection + `.quiet()` access | โœ… Streams + interleaved output | โœ… Readable streams + `.pipe.stderr` |
44
- | **Stdin Support** | โœ… string/Buffer/inherit/ignore | โœ… Pipe operations | โœ… Input/output streams | โœ… Basic stdin |
45
- | **Built-in Commands** | โœ… **18 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes + all Bun.$ commands | โœ… echo, cd, etc. | โŒ Uses system | โŒ Uses system |
46
- | **Virtual Commands Engine** | โœ… **Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | โŒ No extensibility | โŒ No custom commands | โŒ No custom commands |
47
- | **Pipeline/Piping Support** | โœ… **Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | โœ… Standard shell piping | โœ… Programmatic `.pipe()` + multi-destination | โœ… Shell piping + `.pipe()` method |
48
- | **Bundle Size** | ๐Ÿ“ฆ ~15KB | ๐ŸŽฏ 0KB (built-in) | ๐Ÿ“ฆ ~25KB | ๐Ÿ“ฆ ~50KB |
49
- | **TypeScript** | ๐Ÿ”„ Coming soon | โœ… Built-in | โœ… Full support | โœ… Full support |
50
- | **License** | โœ… **Unlicense (Public Domain)** | ๐ŸŸก MIT (+ LGPL dependencies) | ๐ŸŸก MIT | ๐ŸŸก Apache 2.0 |
30
+ | Feature | [**command-stream**](https://github.com/link-foundation/command-stream) | [**execa**](https://github.com/sindresorhus/execa) | [**cross-spawn**](https://github.com/moxystudio/node-cross-spawn) | [**Bun.$**](https://github.com/oven-sh/bun) | [**ShellJS**](https://github.com/shelljs/shelljs) | [**zx**](https://github.com/google/zx) |
31
+ |---------|----------------|-------|-------|-----|-------|-------|
32
+ | **๐Ÿ“ฆ NPM Package** | [![npm](https://img.shields.io/npm/v/command-stream.svg)](https://www.npmjs.com/package/command-stream) | [![npm](https://img.shields.io/npm/v/execa.svg)](https://www.npmjs.com/package/execa) | [![npm](https://img.shields.io/npm/v/cross-spawn.svg)](https://www.npmjs.com/package/cross-spawn) | N/A (Built-in) | [![npm](https://img.shields.io/npm/v/shelljs.svg)](https://www.npmjs.com/package/shelljs) | [![npm](https://img.shields.io/npm/v/zx.svg)](https://www.npmjs.com/package/zx) |
33
+ | **โญ GitHub Stars** | [**โญ 2** (Please โญ us!)](https://github.com/link-foundation/command-stream) | [โญ 7,264](https://github.com/sindresorhus/execa) | [โญ 1,149](https://github.com/moxystudio/node-cross-spawn) | [โญ 80,169](https://github.com/oven-sh/bun) (Full Runtime) | [โญ 14,375](https://github.com/shelljs/shelljs) | [โญ 44,569](https://github.com/google/zx) |
34
+ | **๐Ÿ“Š Monthly Downloads** | **893** (New project!) | **381M** | **409M** | N/A (Built-in) | **35M** | **4.2M** |
35
+ | **๐Ÿ“ˆ Total Downloads** | **Growing** | **6B+** | **5.4B** | N/A (Built-in) | **596M** | **37M** |
36
+ | **Runtime Support** | โœ… Bun + Node.js | โœ… Node.js | โœ… Node.js | ๐ŸŸก Bun only | โœ… Node.js | โœ… Node.js |
37
+ | **Template Literals** | โœ… `` $`cmd` `` | โœ… `` $`cmd` `` | โŒ Function calls | โœ… `` $`cmd` `` | โŒ Function calls | โœ… `` $`cmd` `` |
38
+ | **Real-time Streaming** | โœ… Live output | ๐ŸŸก Limited | โŒ Buffer only | โŒ Buffer only | โŒ Buffer only | โŒ Buffer only |
39
+ | **Synchronous Execution** | โœ… `.sync()` with events | โœ… `execaSync` | โœ… `spawnSync` | โŒ No | โœ… Sync by default | โŒ No |
40
+ | **Async Iteration** | โœ… `for await (chunk of $.stream())` | โŒ No | โŒ No | โŒ No | โŒ No | โŒ No |
41
+ | **EventEmitter Pattern** | โœ… `.on('data', ...)` | ๐ŸŸก Limited events | ๐ŸŸก Child process events | โŒ No | โŒ No | โŒ No |
42
+ | **Mixed Patterns** | โœ… Events + await/sync | โŒ No | โŒ No | โŒ No | โŒ No | โŒ No |
43
+ | **Bun.$ Compatibility** | โœ… `.text()` method support | โŒ No | โŒ No | โœ… Native API | โŒ No | โŒ No |
44
+ | **Shell Injection Protection** | โœ… Auto-quoting | โœ… Safe by default | โœ… Safe by default | โœ… Built-in | ๐ŸŸก Manual escaping | โœ… Safe by default |
45
+ | **Cross-platform** | โœ… macOS/Linux/Windows | โœ… Yes | โœ… **Specialized** cross-platform | โœ… Yes | โœ… Yes | โœ… Yes |
46
+ | **Performance** | โšก Fast (Bun optimized) | ๐ŸŒ Moderate | โšก Fast | โšก Very fast | ๐ŸŒ Moderate | ๐ŸŒ Slow |
47
+ | **Memory Efficiency** | โœ… Streaming prevents buildup | ๐ŸŸก Buffers in memory | ๐ŸŸก Buffers in memory | ๐ŸŸก Buffers in memory | ๐ŸŸก Buffers in memory | ๐ŸŸก Buffers in memory |
48
+ | **Error Handling** | โœ… Configurable (`set -e`/`set +e`, non-zero OK by default) | โœ… Throws on error | โŒ Basic (exit codes) | โœ… Throws on error | โœ… Configurable | โœ… Throws on error |
49
+ | **Shell Settings** | โœ… `set -e`/`set +e` equivalent | โŒ No | โŒ No | โŒ No | ๐ŸŸก Limited (`set()`) | โŒ No |
50
+ | **Stdout Support** | โœ… Real-time streaming + events | โœ… Node.js streams + interleaved | โœ… Inherited/buffered | โœ… Shell redirection + buffered | โœ… Direct output | โœ… Readable streams + `.pipe.stdout` |
51
+ | **Stderr Support** | โœ… Real-time streaming + events | โœ… Streams + interleaved output | โœ… Inherited/buffered | โœ… Redirection + `.quiet()` access | โœ… Error output | โœ… Readable streams + `.pipe.stderr` |
52
+ | **Stdin Support** | โœ… string/Buffer/inherit/ignore | โœ… Input/output streams | โœ… Full stdio support | โœ… Pipe operations | ๐ŸŸก Basic | โœ… Basic stdin |
53
+ | **Built-in Commands** | โœ… **18 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes + all Bun.$ commands | โŒ Uses system | โŒ Uses system | โœ… echo, cd, etc. | โœ… **20+ commands**: cat, ls, mkdir, rm, mv, cp, etc. | โŒ Uses system |
54
+ | **Virtual Commands Engine** | โœ… **Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | โŒ No custom commands | โŒ No custom commands | โŒ No extensibility | โŒ No custom commands | โŒ No custom commands |
55
+ | **Pipeline/Piping Support** | โœ… **Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | โœ… Programmatic `.pipe()` + multi-destination | โŒ No piping | โœ… Standard shell piping | โœ… Shell piping + `.to()` method | โœ… Shell piping + `.pipe()` method |
56
+ | **Bundle Size** | ๐Ÿ“ฆ **~20KB gzipped** | ๐Ÿ“ฆ ~400KB+ (packagephobia) | ๐Ÿ“ฆ ~2KB gzipped | ๐ŸŽฏ 0KB (built-in) | ๐Ÿ“ฆ ~15KB gzipped | ๐Ÿ“ฆ ~50KB+ (estimated) |
57
+ | **Signal Handling** | โœ… **Advanced SIGINT/SIGTERM forwarding** with cleanup | ๐ŸŸก Basic | โœ… **Excellent** cross-platform | ๐ŸŸก Basic | ๐ŸŸก Basic | ๐ŸŸก Basic |
58
+ | **Process Management** | โœ… **Robust child process lifecycle** with proper termination | โœ… Good | โœ… **Excellent** spawn wrapper | โŒ Basic | ๐ŸŸก Limited | ๐ŸŸก Limited |
59
+ | **Debug Tracing** | โœ… **Comprehensive VERBOSE logging** for CI/debugging | ๐ŸŸก Limited | โŒ No | โŒ No | ๐ŸŸก Basic | โŒ No |
60
+ | **Test Coverage** | โœ… **410 tests, 909 assertions** | โœ… Excellent | โœ… Good | ๐ŸŸก Good coverage | โœ… Good | ๐ŸŸก Good |
61
+ | **CI Reliability** | โœ… **Platform-specific handling** (macOS/Ubuntu) | โœ… Good | โœ… **Excellent** | ๐ŸŸก Basic | โœ… Good | ๐ŸŸก Basic |
62
+ | **Documentation** | โœ… **Comprehensive examples + guides** | โœ… Excellent | ๐ŸŸก Basic | โœ… Good | โœ… Good | ๐ŸŸก Limited |
63
+ | **TypeScript** | ๐Ÿ”„ Coming soon | โœ… Full support | โœ… Built-in | โœ… Built-in | ๐ŸŸก Community types | โœ… Full support |
64
+ | **License** | โœ… **Unlicense (Public Domain)** | ๐ŸŸก MIT | ๐ŸŸก MIT | ๐ŸŸก MIT (+ LGPL dependencies) | ๐ŸŸก BSD-3-Clause | ๐ŸŸก Apache 2.0 |
65
+
66
+ **๐Ÿ“Š Popularity & Adoption:**
67
+ - **โญ GitHub Stars:** [Bun: 80,169](https://github.com/oven-sh/bun) โ€ข [zx: 44,569](https://github.com/google/zx) โ€ข [ShellJS: 14,375](https://github.com/shelljs/shelljs) โ€ข [execa: 7,264](https://github.com/sindresorhus/execa) โ€ข [cross-spawn: 1,149](https://github.com/moxystudio/node-cross-spawn) โ€ข [**command-stream: 2 โญ us!**](https://github.com/link-foundation/command-stream)
68
+ - **๐Ÿ“ˆ Total Downloads:** [execa: 6B+](https://www.npmjs.com/package/execa) โ€ข [cross-spawn: 5.4B](https://www.npmjs.com/package/cross-spawn) โ€ข [ShellJS: 596M](https://www.npmjs.com/package/shelljs) โ€ข [zx: 37M](https://www.npmjs.com/package/zx) โ€ข [command-stream: Growing](https://www.npmjs.com/package/command-stream)
69
+ - **๐Ÿ“Š Monthly Downloads:** [cross-spawn: 409M](https://www.npmjs.com/package/cross-spawn) โ€ข [execa: 381M](https://www.npmjs.com/package/execa) โ€ข [ShellJS: 35M](https://www.npmjs.com/package/shelljs) โ€ข [zx: 4.2M](https://www.npmjs.com/package/zx) โ€ข [command-stream: 893 (growing!)](https://www.npmjs.com/package/command-stream)
70
+
71
+ **โญ Help Us Grow!** If command-stream's **revolutionary virtual commands** and **advanced streaming capabilities** help your project, [**please star us on GitHub**](https://github.com/link-foundation/command-stream) to help the project grow!
51
72
 
52
73
  ### Why Choose command-stream?
53
74
 
@@ -60,7 +81,9 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
60
81
  - **๐Ÿš Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
61
82
  - **โšก Bun Optimized**: Designed for Bun with Node.js fallback compatibility
62
83
  - **๐Ÿ’พ Memory Efficient**: Streaming prevents large buffer accumulation
63
- - **๐Ÿ›ก๏ธ Production Ready**: 266+ tests with comprehensive coverage
84
+ - **๐Ÿ›ก๏ธ Production Ready**: **410 tests, 909 assertions** with comprehensive coverage including CI reliability
85
+ - **๐ŸŽฏ Advanced Signal Handling**: Robust SIGINT/SIGTERM forwarding with proper child process cleanup
86
+ - **๐Ÿ” Debug-Friendly**: Comprehensive VERBOSE tracing for CI debugging and troubleshooting
64
87
 
65
88
  ## Built-in Commands (๐Ÿš€ NEW!)
66
89
 
@@ -140,6 +163,42 @@ console.log(result.stdout);
140
163
  console.log(result.code); // exit code
141
164
  ```
142
165
 
166
+ ### Custom Options with $({ options }) Syntax (NEW!)
167
+
168
+ ```javascript
169
+ import { $ } from 'command-stream';
170
+
171
+ // Create a $ with custom options
172
+ const $silent = $({ mirror: false, capture: true });
173
+ const result = await $silent`echo "quiet operation"`;
174
+
175
+ // Options for stdin handling
176
+ const $withInput = $({ stdin: 'input data\n' });
177
+ await $withInput`cat`; // Pipes the input to cat
178
+
179
+ // Custom environment variables
180
+ const $withEnv = $({ env: { ...process.env, MY_VAR: 'value' } });
181
+ await $withEnv`printenv MY_VAR`; // Prints: value
182
+
183
+ // Custom working directory
184
+ const $inTmp = $({ cwd: '/tmp' });
185
+ await $inTmp`pwd`; // Prints: /tmp
186
+
187
+ // Combine multiple options
188
+ const $custom = $({
189
+ stdin: 'test data',
190
+ mirror: false,
191
+ capture: true,
192
+ cwd: '/tmp'
193
+ });
194
+ await $custom`cat > output.txt`; // Writes to /tmp/output.txt silently
195
+
196
+ // Reusable configurations
197
+ const $prod = $({ env: { NODE_ENV: 'production' }, capture: true });
198
+ await $prod`npm start`;
199
+ await $prod`npm test`;
200
+ ```
201
+
143
202
  ### Execution Control (NEW!)
144
203
 
145
204
  ```javascript
@@ -242,6 +301,83 @@ syncCmd.on('end', result => {
242
301
  const syncResult = syncCmd.sync();
243
302
  ```
244
303
 
304
+ ### Streaming Interfaces
305
+
306
+ Advanced streaming interfaces for fine-grained process control:
307
+
308
+ ```javascript
309
+ import { $ } from 'command-stream';
310
+
311
+ // ๐ŸŽฏ STDIN CONTROL: Send data to interactive commands (real-time)
312
+ const grepCmd = $`grep "important"`;
313
+ const stdin = await grepCmd.streams.stdin; // Available immediately
314
+
315
+ stdin.write('ignore this line\n');
316
+ stdin.write('important message\n');
317
+ stdin.write('skip this too\n');
318
+ stdin.end();
319
+
320
+ const result = await grepCmd;
321
+ console.log(result.stdout); // "important message\n"
322
+
323
+ // ๐Ÿ”ง BINARY DATA: Access raw buffers (after command finishes)
324
+ const cmd = $`echo "Hello World"`;
325
+ const buffer = await cmd.buffers.stdout; // Complete snapshot
326
+ console.log(buffer.length); // 12
327
+
328
+ // ๐Ÿ“ TEXT DATA: Access as strings (after command finishes)
329
+ const textCmd = $`echo "Hello World"`;
330
+ const text = await textCmd.strings.stdout; // Complete snapshot
331
+ console.log(text.trim()); // "Hello World"
332
+
333
+ // โšก PROCESS CONTROL: Kill commands that ignore stdin
334
+ const pingCmd = $`ping google.com`;
335
+
336
+ // Some commands ignore stdin input
337
+ const pingStdin = await pingCmd.streams.stdin;
338
+ if (pingStdin) {
339
+ pingStdin.write('q\n'); // ping ignores this
340
+ }
341
+
342
+ // Use kill() for forceful termination
343
+ setTimeout(() => pingCmd.kill(), 2000);
344
+ const pingResult = await pingCmd;
345
+ console.log('Ping stopped with code:', pingResult.code); // 143 (SIGTERM)
346
+
347
+ // ๐Ÿ”„ MIXED STDOUT/STDERR: Handle both streams (complete snapshots)
348
+ const mixedCmd = $`sh -c 'echo "out" && echo "err" >&2'`;
349
+ const [stdout, stderr] = await Promise.all([
350
+ mixedCmd.strings.stdout, // Available after finish
351
+ mixedCmd.strings.stderr // Available after finish
352
+ ]);
353
+ console.log('Out:', stdout.trim()); // "out"
354
+ console.log('Err:', stderr.trim()); // "err"
355
+
356
+ // ๐Ÿƒโ€โ™‚๏ธ AUTO-START: Streams auto-start processes when accessed
357
+ const cmd = $`echo "test"`;
358
+ console.log('Started?', cmd.started); // false
359
+
360
+ const output = await cmd.streams.stdout; // Auto-starts, immediate access
361
+ console.log('Started?', cmd.started); // true
362
+
363
+ // ๐Ÿ”™ BACKWARD COMPATIBLE: Traditional await still works
364
+ const traditional = await $`echo "still works"`;
365
+ console.log(traditional.stdout); // "still works\n"
366
+ ```
367
+
368
+ **Key Features:**
369
+ - `command.streams.stdin/stdout/stderr` - Direct access to Node.js streams
370
+ - `command.buffers.stdin/stdout/stderr` - Binary data as Buffer objects
371
+ - `command.strings.stdin/stdout/stderr` - Text data as strings
372
+ - `command.kill()` - Forceful process termination
373
+ - **Auto-start behavior:** Process starts only when accessing stream properties
374
+ - **Perfect for:** Interactive commands (grep, sort, bc), data processing, real-time control
375
+ - **Network commands (ping, wget) ignore stdin** โ†’ Use `kill()` method instead
376
+
377
+ **๐Ÿš€ Streams vs Buffers/Strings:**
378
+ - **`streams.*`** - Available **immediately** when command starts, for real-time interaction
379
+ - **`buffers.*` & `strings.*`** - Complete **snapshots** available only **after** command finishes
380
+
245
381
  ### Shell Replacement (.sh โ†’ .mjs)
246
382
 
247
383
  Replace bash scripts with JavaScript while keeping shell semantics:
@@ -318,7 +454,7 @@ Create custom commands that work seamlessly alongside built-ins:
318
454
  import { $, register, unregister, listCommands } from 'command-stream';
319
455
 
320
456
  // Register a custom command
321
- register('greet', async (args, stdin) => {
457
+ register('greet', async ({ args, stdin }) => {
322
458
  const name = args[0] || 'World';
323
459
  return { stdout: `Hello, ${name}!\n`, code: 0 };
324
460
  });
@@ -328,7 +464,7 @@ await $`greet Alice`; // โ†’ "Hello, Alice!"
328
464
  await $`echo "Bob" | greet`; // โ†’ "Hello, Bob!"
329
465
 
330
466
  // Streaming virtual commands with async generators
331
- register('countdown', async function* (args) {
467
+ register('countdown', async function* ({ args }) {
332
468
  const start = parseInt(args[0] || 5);
333
469
  for (let i = start; i >= 0; i--) {
334
470
  yield `${i}\n`;
@@ -365,7 +501,7 @@ await execa('node', ['script.js']); // execa: separate processes
365
501
  await $`node script.js`; // zx: shell commands only
366
502
 
367
503
  // โœ… command-stream: JavaScript functions AS shell commands
368
- register('deploy', async (args) => {
504
+ register('deploy', async ({ args }) => {
369
505
  const env = args[0] || 'staging';
370
506
  await deployToEnvironment(env);
371
507
  return { stdout: `Deployed to ${env}!\n`, code: 0 };
@@ -401,11 +537,11 @@ await $`seq 1 5 | cat > numbers.txt`;
401
537
  await $`git log --oneline | head -n 5`;
402
538
 
403
539
  // ๐Ÿš€ UNIQUE: Virtual command piping
404
- register('uppercase', async (args, stdin) => {
540
+ register('uppercase', async ({ args, stdin }) => {
405
541
  return { stdout: stdin.toUpperCase(), code: 0 };
406
542
  });
407
543
 
408
- register('reverse', async (args, stdin) => {
544
+ register('reverse', async ({ args, stdin }) => {
409
545
  return { stdout: stdin.split('').reverse().join(''), code: 0 };
410
546
  });
411
547
 
@@ -433,12 +569,12 @@ import { $, register } from 'command-stream';
433
569
  const result = await $`echo "hello"`.pipe($`echo "World: $(cat)"`);
434
570
 
435
571
  // ๐ŸŒŸ Virtual command chaining
436
- register('add-prefix', async (args, stdin) => {
572
+ register('add-prefix', async ({ args, stdin }) => {
437
573
  const prefix = args[0] || 'PREFIX:';
438
574
  return { stdout: `${prefix} ${stdin.trim()}\n`, code: 0 };
439
575
  });
440
576
 
441
- register('add-suffix', async (args, stdin) => {
577
+ register('add-suffix', async ({ args, stdin }) => {
442
578
  const suffix = args[0] || 'SUFFIX';
443
579
  return { stdout: `${stdin.trim()} ${suffix}\n`, code: 0 };
444
580
  });
@@ -463,7 +599,7 @@ try {
463
599
  }
464
600
 
465
601
  // โœ… Complex data processing
466
- register('json-parse', async (args, stdin) => {
602
+ register('json-parse', async ({ args, stdin }) => {
467
603
  try {
468
604
  const data = JSON.parse(stdin);
469
605
  return { stdout: JSON.stringify(data, null, 2), code: 0 };
@@ -472,7 +608,7 @@ register('json-parse', async (args, stdin) => {
472
608
  }
473
609
  });
474
610
 
475
- register('extract-field', async (args, stdin) => {
611
+ register('extract-field', async ({ args, stdin }) => {
476
612
  const field = args[0];
477
613
  try {
478
614
  const data = JSON.parse(stdin);
@@ -660,7 +796,8 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event
660
796
  - `env: object` - Environment variables
661
797
 
662
798
  **Override defaults:**
663
- - Use `sh(command, options)` for one-off overrides
799
+ - Use `$({ options })` syntax for one-off configurations with template literals
800
+ - Use `sh(command, options)` for one-off overrides with string commands
664
801
  - Use `create(defaultOptions)` to create custom `$` with different defaults
665
802
 
666
803
  ### Shell Settings API
@@ -676,6 +813,38 @@ Control shell behavior like bash `set`/`unset` commands:
676
813
  - `unset(option)`: Disable shell option
677
814
  - `shell.settings()`: Get current settings object
678
815
 
816
+ #### Error Handling Modes
817
+
818
+ ```javascript
819
+ import { $, shell } from 'command-stream';
820
+
821
+ // โœ… Default behavior: Commands don't throw on non-zero exit
822
+ const result = await $`ls nonexistent-file`; // Won't throw
823
+ console.log(result.code); // โ†’ 2 (non-zero, but no exception)
824
+
825
+ // โœ… Enable errexit: Commands throw on non-zero exit
826
+ shell.errexit(true);
827
+ try {
828
+ await $`ls nonexistent-file`; // Throws error
829
+ } catch (error) {
830
+ console.log('Command failed:', error.code); // โ†’ 2
831
+ }
832
+
833
+ // โœ… Disable errexit: Back to non-throwing behavior
834
+ shell.errexit(false);
835
+ await $`ls nonexistent-file`; // Won't throw, returns result with code 2
836
+
837
+ // โœ… One-time override without changing global settings
838
+ try {
839
+ const result = await $`ls nonexistent-file`;
840
+ if (result.code !== 0) {
841
+ throw new Error(`Command failed with code ${result.code}`);
842
+ }
843
+ } catch (error) {
844
+ console.log('Manual error handling');
845
+ }
846
+ ```
847
+
679
848
  ### Virtual Commands API
680
849
 
681
850
  Control and extend the command system with custom JavaScript functions:
@@ -690,11 +859,79 @@ Control and extend the command system with custom JavaScript functions:
690
859
  - `enableVirtualCommands()`: Enable virtual command processing
691
860
  - `disableVirtualCommands()`: Disable virtual commands (use system commands only)
692
861
 
862
+ #### Advanced Virtual Command Features
863
+
864
+ ```javascript
865
+ import { $, register } from 'command-stream';
866
+
867
+ // โœ… Cancellation support with AbortController
868
+ register('cancellable', async function* ({ args, stdin, abortSignal }) {
869
+ for (let i = 0; i < 10; i++) {
870
+ if (abortSignal?.aborted) {
871
+ break; // Proper cancellation handling
872
+ }
873
+ yield `Count: ${i}\n`;
874
+ await new Promise(resolve => setTimeout(resolve, 1000));
875
+ }
876
+ });
877
+
878
+ // โœ… Access to all process options
879
+ // All original options (built-in + custom) are available in the 'options' object
880
+ // Common options like cwd, env are also available at top level for convenience
881
+ // Runtime additions: isCancelled function, abortSignal
882
+ register('debug-info', async ({ args, stdin, cwd, env, options, isCancelled }) => {
883
+ return {
884
+ stdout: JSON.stringify({
885
+ args,
886
+ cwd, // Available at top level for convenience
887
+ env: Object.keys(env || {}), // Available at top level for convenience
888
+ stdinLength: stdin?.length || 0,
889
+ allOptions: options, // All original options (built-in + custom)
890
+ mirror: options.mirror, // Built-in option from options object
891
+ capture: options.capture, // Built-in option from options object
892
+ customOption: options.customOption || 'not provided', // Custom option
893
+ isCancelledAvailable: typeof isCancelled === 'function'
894
+ }, null, 2),
895
+ code: 0
896
+ };
897
+ });
898
+
899
+ // โœ… Error handling and non-zero exit codes
900
+ register('maybe-fail', async ({ args }) => {
901
+ if (Math.random() > 0.5) {
902
+ return {
903
+ stdout: 'Success!\n',
904
+ code: 0
905
+ };
906
+ } else {
907
+ return {
908
+ stdout: '',
909
+ stderr: 'Random failure occurred\n',
910
+ code: 1
911
+ };
912
+ }
913
+ });
914
+
915
+ // โœ… Example: User options flow through to virtual commands
916
+ register('show-options', async ({ args, stdin, options, cwd }) => {
917
+ return {
918
+ stdout: `Custom: ${options.customValue || 'none'}, CWD: ${cwd || options.cwd || 'default'}\n`,
919
+ code: 0
920
+ };
921
+ });
922
+
923
+ // Usage example showing options passed to virtual command:
924
+ const result = await $({ customValue: 'hello world', cwd: '/tmp' })`show-options`;
925
+ console.log(result.stdout); // Output: Custom: hello world, CWD: /tmp
926
+ ```
927
+
693
928
  #### Handler Function Signature
694
929
 
695
930
  ```javascript
696
931
  // Regular async function
697
- async function handler(args, stdin, options) {
932
+ async function handler({ args, stdin, abortSignal, cwd, env, options, isCancelled }) {
933
+ // All original options available in 'options': options.mirror, options.capture, options.customValue, etc.
934
+ // Common options like cwd, env also available at top level for convenience
698
935
  return {
699
936
  code: 0, // Exit code (number)
700
937
  stdout: "output", // Standard output (string)
@@ -703,10 +940,13 @@ async function handler(args, stdin, options) {
703
940
  }
704
941
 
705
942
  // Async generator for streaming
706
- async function* streamingHandler(args, stdin, options) {
943
+ async function streamingHandler({ args, stdin, abortSignal, cwd, env, options, isCancelled }) {
944
+ // Access both built-in and custom options from 'options' object
945
+ if (options.customFlag) {
946
+ yield "custom behavior\n";
947
+ }
707
948
  yield "chunk1\n";
708
949
  yield "chunk2\n";
709
- // Each yield sends a chunk in real-time
710
950
  }
711
951
  ```
712
952
 
@@ -771,10 +1011,172 @@ const result4 = await $`echo "pipe test"`.pipe($`cat`);
771
1011
  const text4 = await result4.text(); // "pipe test\n"
772
1012
  ```
773
1013
 
1014
+ ## Signal Handling (CTRL+C Support)
1015
+
1016
+ The library provides **advanced CTRL+C handling** that properly manages signals across different scenarios:
1017
+
1018
+ ### How It Works
1019
+
1020
+ 1. **Smart Signal Forwarding**: CTRL+C is forwarded **only when child processes are active**
1021
+ 2. **User Handler Preservation**: When no children are running, your custom SIGINT handlers work normally
1022
+ 3. **Process Groups**: Child processes use detached spawning for proper signal isolation
1023
+ 4. **TTY Mode Support**: Raw TTY mode is properly managed and restored on interruption
1024
+ 5. **Graceful Termination**: Uses SIGTERM โ†’ SIGKILL escalation for robust process cleanup
1025
+ 6. **Exit Code Standards**: Proper signal exit codes (130 for SIGINT, 143 for SIGTERM)
1026
+
1027
+ ### Advanced Signal Behavior
1028
+
1029
+ ```javascript
1030
+ // โœ… Smart signal handling - only interferes when necessary
1031
+ import { $ } from 'command-stream';
1032
+
1033
+ // Case 1: No children active - your handlers work normally
1034
+ process.on('SIGINT', () => {
1035
+ console.log('My custom handler runs!');
1036
+ process.exit(42); // Custom exit code
1037
+ });
1038
+ // Press CTRL+C โ†’ Your handler runs, exits with code 42
1039
+
1040
+ // Case 2: Children active - automatic forwarding
1041
+ await $`ping 8.8.8.8`; // Press CTRL+C โ†’ Forwards to ping, exits with code 130
1042
+
1043
+ // Case 3: Multiple processes - all interrupted
1044
+ await Promise.all([
1045
+ $`sleep 100`,
1046
+ $`ping google.com`
1047
+ ]); // Press CTRL+C โ†’ All processes terminated, exits with code 130
1048
+ ```
1049
+
1050
+ ### Examples
1051
+
1052
+ ```javascript
1053
+ // Long-running command that can be interrupted with CTRL+C
1054
+ try {
1055
+ await $`ping 8.8.8.8`; // Press CTRL+C to stop
1056
+ } catch (error) {
1057
+ console.log('Command interrupted:', error.code); // Exit code 130 (SIGINT)
1058
+ }
1059
+
1060
+ // Multiple concurrent processes - CTRL+C stops all
1061
+ try {
1062
+ await Promise.all([
1063
+ $`sleep 100`,
1064
+ $`ping google.com`,
1065
+ $`tail -f /var/log/system.log`
1066
+ ]);
1067
+ } catch (error) {
1068
+ // All processes are terminated when you press CTRL+C
1069
+ }
1070
+
1071
+ // Works with streaming patterns too
1072
+ try {
1073
+ for await (const chunk of $`ping 8.8.8.8`.stream()) {
1074
+ console.log(chunk);
1075
+ // Press CTRL+C to break the loop and stop the process
1076
+ }
1077
+ } catch (error) {
1078
+ console.log('Streaming interrupted');
1079
+ }
1080
+ ```
1081
+
1082
+ ### Signal Handling Behavior
1083
+
1084
+ - **๐ŸŽฏ Smart Detection**: Only forwards CTRL+C when child processes are active
1085
+ - **๐Ÿ›ก๏ธ Non-Interference**: Preserves user SIGINT handlers when no children running
1086
+ - **โšก Interactive Commands**: Commands like `vim`, `less`, `top` work with their own signal handling
1087
+ - **๐Ÿ”„ Process Groups**: Detached spawning ensures proper signal isolation
1088
+ - **๐Ÿงน TTY Cleanup**: Raw terminal mode properly restored on interruption
1089
+ - **๐Ÿ“Š Standard Exit Codes**:
1090
+ - `130` - SIGINT interruption (CTRL+C)
1091
+ - `143` - SIGTERM termination (programmatic kill)
1092
+ - `137` - SIGKILL force termination
1093
+
1094
+ ### Command Resolution Priority
1095
+
1096
+ ```javascript
1097
+ // Understanding how commands are resolved:
1098
+
1099
+ // 1. Virtual Commands (highest priority)
1100
+ register('echo', () => ({ stdout: 'virtual!\n', code: 0 }));
1101
+ await $`echo test`; // โ†’ "virtual!"
1102
+
1103
+ // 2. Built-in Commands (if no virtual match)
1104
+ unregister('echo');
1105
+ await $`echo test`; // โ†’ Uses built-in echo
1106
+
1107
+ // 3. System Commands (if no built-in/virtual match)
1108
+ await $`unknown-command`; // โ†’ Uses system PATH lookup
1109
+
1110
+ // 4. Virtual Bypass (special case)
1111
+ await $({ stdin: 'data' })`sleep 1`; // Bypasses virtual sleep, uses system sleep
1112
+ ```
1113
+
1114
+ ## Execution Patterns Deep Dive
1115
+
1116
+ ### When to Use Different Patterns
1117
+
1118
+ ```javascript
1119
+ import { $ } from 'command-stream';
1120
+
1121
+ // โœ… Use await for simple command execution
1122
+ const result = await $`ls -la`;
1123
+
1124
+ // โœ… Use .sync() when you need blocking execution with events
1125
+ const syncCmd = $`build-script`
1126
+ .on('stdout', chunk => updateProgress(chunk))
1127
+ .sync(); // Events fire after completion
1128
+
1129
+ // โœ… Use .start() for non-blocking execution with real-time events
1130
+ const asyncCmd = $`long-running-server`
1131
+ .on('stdout', chunk => logOutput(chunk))
1132
+ .start(); // Events fire in real-time
1133
+
1134
+ // โœ… Use .stream() for processing large outputs efficiently
1135
+ for await (const chunk of $`generate-big-file`.stream()) {
1136
+ processChunkInRealTime(chunk);
1137
+ } // Memory efficient - processes chunks as they arrive
1138
+
1139
+ // โœ… Use EventEmitter pattern for complex workflows
1140
+ $`deployment-script`
1141
+ .on('stdout', chunk => {
1142
+ if (chunk.toString().includes('ERROR')) {
1143
+ handleError(chunk);
1144
+ }
1145
+ })
1146
+ .on('stderr', chunk => logError(chunk))
1147
+ .on('end', result => {
1148
+ if (result.code === 0) {
1149
+ notifySuccess();
1150
+ }
1151
+ })
1152
+ .start();
1153
+ ```
1154
+
1155
+ ### Performance Considerations
1156
+
1157
+ ```javascript
1158
+ // ๐Ÿš€ Memory Efficient: For large outputs, use streaming
1159
+ for await (const chunk of $`cat huge-file.log`.stream()) {
1160
+ processChunk(chunk); // Processes incrementally
1161
+ }
1162
+
1163
+ // ๐ŸŒ Memory Inefficient: Buffers entire output in memory
1164
+ const result = await $`cat huge-file.log`;
1165
+ processFile(result.stdout); // Loads everything into memory
1166
+
1167
+ // โšก Fastest: Sync execution for small, quick commands
1168
+ const quickResult = $`pwd`.sync();
1169
+
1170
+ // ๐Ÿ”„ Best for UX: Async with events for long-running commands
1171
+ $`npm install`
1172
+ .on('stdout', showProgress)
1173
+ .start();
1174
+ ```
1175
+
774
1176
  ## Testing
775
1177
 
776
1178
  ```bash
777
- # Run comprehensive test suite (266 tests)
1179
+ # Run comprehensive test suite (270+ tests)
778
1180
  bun test
779
1181
 
780
1182
  # Run tests with coverage report
@@ -782,9 +1184,10 @@ bun test --coverage
782
1184
 
783
1185
  # Run specific test categories
784
1186
  npm run test:features # Feature comparison tests
785
- npm run test:builtin # Built-in commands tests
1187
+ npm run test:builtin # Built-in commands tests
786
1188
  npm run test:pipe # .pipe() method tests
787
1189
  npm run test:sync # Synchronous execution tests
1190
+ npm run test:signal # CTRL+C signal handling tests
788
1191
  ```
789
1192
 
790
1193
  ## Requirements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "command-stream",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "Modern $ shell utility library with streaming, async iteration, and EventEmitter support, optimized for Bun runtime",
5
5
  "type": "module",
6
6
  "main": "src/$.mjs",