command-stream 0.0.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.
Files changed (4) hide show
  1. package/$.mjs +1720 -0
  2. package/LICENSE +24 -0
  3. package/README.md +776 -0
  4. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,776 @@
1
+ # command-$tream
2
+
3
+ $treamable commands executor
4
+
5
+ A modern $ shell utility library with streaming, async iteration, and EventEmitter support, optimized for Bun runtime.
6
+
7
+ ## Features
8
+
9
+ - 🐚 **Shell-like by Default**: Commands behave exactly like running in terminal (stdoutβ†’stdout, stderrβ†’stderr, stdinβ†’stdin)
10
+ - πŸŽ›οΈ **Fully Controllable**: Override default behavior with options (`mirror`, `capture`, `stdin`)
11
+ - πŸš€ **Multiple Usage Patterns**: Classic await, async iteration, EventEmitter, .pipe() method, and mixed patterns
12
+ - πŸ“‘ **Real-time Streaming**: Process command output as it arrives, not after completion
13
+ - πŸ”„ **Bun Optimized**: Designed for Bun runtime with Node.js compatibility
14
+ - ⚑ **Performance**: Memory-efficient streaming prevents large buffer accumulation
15
+ - 🎯 **Backward Compatible**: Existing `await $` syntax continues to work
16
+ - πŸ›‘οΈ **Type Safe**: Full TypeScript support (coming soon)
17
+ - πŸ”§ **Built-in Commands**: 18 essential commands work identically across platforms
18
+
19
+ ## Built-in Commands (πŸš€ NEW!)
20
+
21
+ command-stream now includes **18 built-in commands** that work identically to their bash/sh counterparts, providing true cross-platform shell scripting without system dependencies:
22
+
23
+ ### πŸ“ **File System Commands**
24
+ - `cat` - Read and display file contents
25
+ - `ls` - List directory contents (supports `-l`, `-a`, `-A`)
26
+ - `mkdir` - Create directories (supports `-p` recursive)
27
+ - `rm` - Remove files/directories (supports `-r`, `-f`)
28
+ - `mv` - Move/rename files and directories
29
+ - `cp` - Copy files/directories (supports `-r` recursive)
30
+ - `touch` - Create files or update timestamps
31
+
32
+ ### πŸ”§ **Utility Commands**
33
+ - `basename` - Extract filename from path
34
+ - `dirname` - Extract directory from path
35
+ - `seq` - Generate number sequences
36
+ - `yes` - Output string repeatedly (streaming)
37
+
38
+ ### ⚑ **System Commands**
39
+ - `cd` - Change directory
40
+ - `pwd` - Print working directory
41
+ - `echo` - Print arguments (supports `-n`)
42
+ - `sleep` - Wait for specified time
43
+ - `true`/`false` - Success/failure commands
44
+ - `which` - Locate commands
45
+ - `exit` - Exit with code
46
+ - `env` - Print environment variables
47
+ - `test` - File condition testing
48
+
49
+ ### ✨ **Key Advantages**
50
+
51
+ - **🌍 Cross-Platform**: Works identically on Windows, macOS, and Linux
52
+ - **πŸš€ Performance**: No system calls - pure JavaScript execution
53
+ - **πŸ”„ Pipeline Support**: All commands work in pipelines and virtual command chains
54
+ - **βš™οΈ Option Aware**: Commands respect `cwd`, `env`, and other options
55
+ - **πŸ›‘οΈ Safe by Default**: Proper error handling and safety checks (e.g., `rm` requires `-r` for directories)
56
+ - **πŸ“ Bash Compatible**: Error messages and behavior match bash/sh exactly
57
+
58
+ ```javascript
59
+ import { $ } from 'command-stream';
60
+
61
+ // All these work without any system dependencies!
62
+ await $`mkdir -p project/src`;
63
+ await $`touch project/src/index.js`;
64
+ await $`echo "console.log('Hello!');" > project/src/index.js`;
65
+ await $`ls -la project/src`;
66
+ await $`cat project/src/index.js`;
67
+ await $`cp -r project project-backup`;
68
+ await $`rm -r project-backup`;
69
+
70
+ // Mix built-ins with pipelines and virtual commands
71
+ await $`seq 1 5 | cat > numbers.txt`;
72
+ await $`basename /path/to/file.txt .txt`; // β†’ "file"
73
+ ```
74
+
75
+ ## Comparison with Other Libraries
76
+
77
+ | 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) |
78
+ |---------|----------------|-------|-------|-----|
79
+ | **Runtime Support** | βœ… Bun + Node.js | 🟑 Bun only | βœ… Node.js | βœ… Node.js |
80
+ | **Template Literals** | βœ… `` $`cmd` `` | βœ… `` $`cmd` `` | βœ… `` $`cmd` `` | βœ… `` $`cmd` `` |
81
+ | **Real-time Streaming** | βœ… Live output | ❌ Buffer only | 🟑 Limited | ❌ Buffer only |
82
+ | **Synchronous Execution** | βœ… `.sync()` with events | ❌ No | βœ… `execaSync` | ❌ No |
83
+ | **Async Iteration** | βœ… `for await (chunk of $.stream())` | ❌ No | ❌ No | ❌ No |
84
+ | **EventEmitter Pattern** | βœ… `.on('data', ...)` | ❌ No | 🟑 Limited events | ❌ No |
85
+ | **Mixed Patterns** | βœ… Events + await/sync | ❌ No | ❌ No | ❌ No |
86
+ | **Shell Injection Protection** | βœ… Auto-quoting | βœ… Built-in | βœ… Safe by default | βœ… Safe by default |
87
+ | **Cross-platform** | βœ… macOS/Linux/Windows | βœ… Yes | βœ… Yes | βœ… Yes |
88
+ | **Performance** | ⚑ Fast (Bun optimized) | ⚑ Very fast | 🐌 Moderate | 🐌 Slow |
89
+ | **Memory Efficiency** | βœ… Streaming prevents buildup | 🟑 Buffers in memory | 🟑 Buffers in memory | 🟑 Buffers in memory |
90
+ | **Error Handling** | βœ… Configurable (`set -e`/`set +e`, non-zero OK by default) | βœ… Throws on error | βœ… Throws on error | βœ… Throws on error |
91
+ | **Shell Settings** | βœ… `set -e`/`set +e` equivalent | ❌ No | ❌ No | ❌ No |
92
+ | **Stdout Support** | βœ… Real-time streaming + events | βœ… Shell redirection + buffered | βœ… Node.js streams + interleaved | βœ… Readable streams + `.pipe.stdout` |
93
+ | **Stderr Support** | βœ… Real-time streaming + events | βœ… Redirection + `.quiet()` access | βœ… Streams + interleaved output | βœ… Readable streams + `.pipe.stderr` |
94
+ | **Stdin Support** | βœ… string/Buffer/inherit/ignore | βœ… Pipe operations | βœ… Input/output streams | βœ… Basic stdin |
95
+ | **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 |
96
+ | **Virtual Commands Engine** | βœ… **Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | ❌ No extensibility | ❌ No custom commands | ❌ No custom commands |
97
+ | **Pipeline/Piping Support** | βœ… **Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | βœ… Standard shell piping | βœ… Programmatic `.pipe()` + multi-destination | βœ… Shell piping + `.pipe()` method |
98
+ | **Bundle Size** | πŸ“¦ ~15KB | 🎯 0KB (built-in) | πŸ“¦ ~25KB | πŸ“¦ ~50KB |
99
+ | **TypeScript** | πŸ”„ Coming soon | βœ… Built-in | βœ… Full support | βœ… Full support |
100
+ | **License** | βœ… **Unlicense (Public Domain)** | 🟑 MIT (+ LGPL dependencies) | 🟑 MIT | 🟑 Apache 2.0 |
101
+
102
+ ### Why Choose command-stream?
103
+
104
+ - **πŸ†“ Truly Free**: **Unlicense (Public Domain)** - No restrictions, no attribution required, use however you want
105
+ - **πŸš€ Revolutionary Virtual Commands**: **World's first** fully customizable virtual commands engine - register JavaScript functions as shell commands!
106
+ - **πŸ”— Advanced Pipeline System**: **Only library** where virtual commands work seamlessly in pipelines with built-ins and system commands
107
+ - **πŸ”§ Built-in Commands**: **18 essential commands** work identically across all platforms - no system dependencies!
108
+ - **πŸ“‘ Real-time Processing**: Only library with true streaming and async iteration
109
+ - **πŸ”„ Flexible Patterns**: Multiple usage patterns (await, events, iteration, mixed)
110
+ - **🐚 Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
111
+ - **⚑ Bun Optimized**: Designed for Bun with Node.js fallback compatibility
112
+ - **πŸ’Ύ Memory Efficient**: Streaming prevents large buffer accumulation
113
+ - **πŸ›‘οΈ Production Ready**: 266+ tests with comprehensive coverage
114
+
115
+ ## Installation
116
+
117
+ ```bash
118
+ # Using npm
119
+ npm install command-stream
120
+
121
+ # Using bun
122
+ bun add command-stream
123
+ ```
124
+
125
+ ## Usage Patterns
126
+
127
+ ### Classic Await (Backward Compatible)
128
+
129
+ ```javascript
130
+ import { $ } from 'command-stream';
131
+
132
+ const result = await $`ls -la`;
133
+ console.log(result.stdout);
134
+ console.log(result.code); // exit code
135
+ ```
136
+
137
+ ### Synchronous Execution
138
+
139
+ ```javascript
140
+ import { $ } from 'command-stream';
141
+
142
+ // Use .sync() for blocking execution
143
+ const result = $`echo "hello"`.sync();
144
+ console.log(result.stdout); // "hello\n"
145
+
146
+ // Events still work but are batched after completion
147
+ $`echo "world"`
148
+ .on('end', result => console.log('Done:', result))
149
+ .sync();
150
+ ```
151
+
152
+ ### Async Iteration (Real-time Streaming)
153
+
154
+ ```javascript
155
+ import { $ } from 'command-stream';
156
+
157
+ for await (const chunk of $`long-running-command`.stream()) {
158
+ if (chunk.type === 'stdout') {
159
+ console.log('Real-time output:', chunk.data.toString());
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### EventEmitter Pattern (Event-driven)
165
+
166
+ ```javascript
167
+ import { $ } from 'command-stream';
168
+
169
+ $`command`
170
+ .on('data', chunk => {
171
+ if (chunk.type === 'stdout') {
172
+ console.log('Stdout:', chunk.data.toString());
173
+ }
174
+ })
175
+ .on('stderr', chunk => console.log('Stderr:', chunk))
176
+ .on('end', result => console.log('Done:', result))
177
+ .on('exit', code => console.log('Exit code:', code));
178
+ ```
179
+
180
+ ### Mixed Pattern (Best of Both Worlds)
181
+
182
+ ```javascript
183
+ import { $ } from 'command-stream';
184
+
185
+ // Async mode - events fire in real-time
186
+ const process = $`streaming-command`;
187
+ process.on('data', chunk => {
188
+ processRealTimeData(chunk);
189
+ });
190
+ const result = await process;
191
+ console.log('Final output:', result.stdout);
192
+
193
+ // Sync mode - events fire after completion (batched)
194
+ const syncCmd = $`another-command`;
195
+ syncCmd.on('end', result => {
196
+ console.log('Completed with:', result.stdout);
197
+ });
198
+ const syncResult = syncCmd.sync();
199
+ ```
200
+
201
+ ### Shell Replacement (.sh β†’ .mjs)
202
+
203
+ Replace bash scripts with JavaScript while keeping shell semantics:
204
+
205
+ ```javascript
206
+ import { $, shell, set, unset } from 'command-stream';
207
+
208
+ // set -e equivalent: exit on any error
209
+ shell.errexit(true);
210
+
211
+ await $`mkdir -p build`;
212
+ await $`npm run build`;
213
+
214
+ // set +e equivalent: allow errors (like bash)
215
+ shell.errexit(false);
216
+ const cleanup = await $`rm -rf temp`; // Won't throw if fails
217
+
218
+ // set -e again for critical operations
219
+ shell.errexit(true);
220
+ await $`cp -r build/* deploy/`;
221
+
222
+ // Other bash-like settings
223
+ shell.verbose(true); // set -v: print commands
224
+ shell.xtrace(true); // set -x: trace execution
225
+
226
+ // Or use the bash-style API
227
+ set('e'); // set -e
228
+ unset('e'); // set +e
229
+ set('x'); // set -x
230
+ set('verbose'); // Long form also supported
231
+ ```
232
+
233
+ ### Cross-Platform File Operations (Built-in Commands)
234
+
235
+ Replace system-dependent operations with built-in commands that work identically everywhere:
236
+
237
+ ```javascript
238
+ import { $ } from 'command-stream';
239
+
240
+ // File system operations work on Windows, macOS, and Linux identically
241
+ await $`mkdir -p project/src project/tests`;
242
+ await $`touch project/src/index.js project/tests/test.js`;
243
+
244
+ // List files with details
245
+ const files = await $`ls -la project/src`;
246
+ console.log(files.stdout);
247
+
248
+ // Copy and move operations
249
+ await $`cp project/src/index.js project/src/backup.js`;
250
+ await $`mv project/src/backup.js project/backup.js`;
251
+
252
+ // File content operations
253
+ await $`echo "export default 'Hello World';" > project/src/index.js`;
254
+ const content = await $`cat project/src/index.js`;
255
+ console.log(content.stdout);
256
+
257
+ // Path operations
258
+ const filename = await $`basename project/src/index.js .js`; // β†’ "index"
259
+ const directory = await $`dirname project/src/index.js`; // β†’ "project/src"
260
+
261
+ // Generate sequences and process them
262
+ await $`seq 1 10 | cat > numbers.txt`;
263
+ const numbers = await $`cat numbers.txt`;
264
+
265
+ // Cleanup
266
+ await $`rm -r project numbers.txt`;
267
+ ```
268
+
269
+ ### Virtual Commands (Extensible Shell)
270
+
271
+ Create custom commands that work seamlessly alongside built-ins:
272
+
273
+ ```javascript
274
+ import { $, register, unregister, listCommands } from 'command-stream';
275
+
276
+ // Register a custom command
277
+ register('greet', async (args, stdin) => {
278
+ const name = args[0] || 'World';
279
+ return { stdout: `Hello, ${name}!\n`, code: 0 };
280
+ });
281
+
282
+ // Use it like any other command
283
+ await $`greet Alice`; // β†’ "Hello, Alice!"
284
+ await $`echo "Bob" | greet`; // β†’ "Hello, Bob!"
285
+
286
+ // Streaming virtual commands with async generators
287
+ register('countdown', async function* (args) {
288
+ const start = parseInt(args[0] || 5);
289
+ for (let i = start; i >= 0; i--) {
290
+ yield `${i}\n`;
291
+ await new Promise(r => setTimeout(r, 1000));
292
+ }
293
+ });
294
+
295
+ // Use in pipelines with built-ins
296
+ await $`countdown 3 | cat > countdown.txt`;
297
+
298
+ // Virtual commands work in all patterns
299
+ for await (const chunk of $`countdown 3`.stream()) {
300
+ console.log('Countdown:', chunk.data.toString().trim());
301
+ }
302
+
303
+ // Management functions
304
+ console.log(listCommands()); // List all registered commands
305
+ unregister('greet'); // Remove custom commands
306
+ ```
307
+
308
+ #### πŸ”₯ **Why Virtual Commands Are Revolutionary**
309
+
310
+ **No other shell library offers this level of extensibility:**
311
+
312
+ - **🚫 Bun.$**: Fixed set of built-in commands, no extensibility API
313
+ - **🚫 execa**: Transform/pipeline system, but no custom commands
314
+ - **🚫 zx**: JavaScript functions only, no shell command integration
315
+
316
+ **command-stream breaks the barrier** between JavaScript functions and shell commands:
317
+
318
+ ```javascript
319
+ // ❌ Other libraries: Choose JavaScript OR shell
320
+ await execa('node', ['script.js']); // execa: separate processes
321
+ await $`node script.js`; // zx: shell commands only
322
+
323
+ // βœ… command-stream: JavaScript functions AS shell commands
324
+ register('deploy', async (args) => {
325
+ const env = args[0] || 'staging';
326
+ await deployToEnvironment(env);
327
+ return { stdout: `Deployed to ${env}!\n`, code: 0 };
328
+ });
329
+
330
+ await $`deploy production`; // JavaScript function as shell command!
331
+ await $`deploy staging | tee log.txt`; // Works in pipelines!
332
+ ```
333
+
334
+ **Unique capabilities:**
335
+ - **Seamless Integration**: Virtual commands work exactly like built-ins
336
+ - **Pipeline Support**: Full stdin/stdout passing between virtual and system commands
337
+ - **Streaming**: Async generators for real-time output
338
+ - **Dynamic Registration**: Add/remove commands at runtime
339
+ - **Option Awareness**: Virtual commands respect `cwd`, `env`, etc.
340
+
341
+ ### πŸ”— **Advanced Pipeline Support**
342
+
343
+ **command-stream offers the most advanced piping system in the JavaScript ecosystem:**
344
+
345
+ #### **Shell-Style Piping (Traditional)**
346
+
347
+ ```javascript
348
+ import { $, register } from 'command-stream';
349
+
350
+ // βœ… Standard shell piping (like all libraries)
351
+ await $`echo "hello world" | wc -w`; // β†’ "2"
352
+
353
+ // βœ… Built-in to built-in piping
354
+ await $`seq 1 5 | cat > numbers.txt`;
355
+
356
+ // βœ… System to built-in piping
357
+ await $`git log --oneline | head -n 5`;
358
+
359
+ // πŸš€ UNIQUE: Virtual command piping
360
+ register('uppercase', async (args, stdin) => {
361
+ return { stdout: stdin.toUpperCase(), code: 0 };
362
+ });
363
+
364
+ register('reverse', async (args, stdin) => {
365
+ return { stdout: stdin.split('').reverse().join(''), code: 0 };
366
+ });
367
+
368
+ // βœ… Built-in to virtual piping
369
+ await $`echo "hello" | uppercase`; // β†’ "HELLO"
370
+
371
+ // βœ… Virtual to virtual piping
372
+ await $`echo "hello" | uppercase | reverse`; // β†’ "OLLEH"
373
+
374
+ // βœ… Mixed pipelines (system + built-in + virtual)
375
+ await $`git log --oneline | head -n 3 | uppercase | cat > LOG.txt`;
376
+
377
+ // βœ… Complex multi-stage pipelines
378
+ await $`find . -name "*.js" | head -n 10 | basename | sort | uniq`;
379
+ ```
380
+
381
+ #### **πŸš€ Programmatic .pipe() Method (NEW!)**
382
+
383
+ **World's first shell library with full `.pipe()` method support for virtual commands:**
384
+
385
+ ```javascript
386
+ import { $, register } from 'command-stream';
387
+
388
+ // βœ… Basic programmatic piping
389
+ const result = await $`echo "hello"`.pipe($`echo "World: $(cat)"`);
390
+
391
+ // 🌟 Virtual command chaining
392
+ register('add-prefix', async (args, stdin) => {
393
+ const prefix = args[0] || 'PREFIX:';
394
+ return { stdout: `${prefix} ${stdin.trim()}\n`, code: 0 };
395
+ });
396
+
397
+ register('add-suffix', async (args, stdin) => {
398
+ const suffix = args[0] || 'SUFFIX';
399
+ return { stdout: `${stdin.trim()} ${suffix}\n`, code: 0 };
400
+ });
401
+
402
+ // βœ… Chain virtual commands with .pipe()
403
+ const result = await $`echo "Hello"`
404
+ .pipe($`add-prefix "[PROCESSED]"`)
405
+ .pipe($`add-suffix "!!!"`);
406
+ // β†’ "[PROCESSED] Hello !!!"
407
+
408
+ // βœ… Mix with built-in commands
409
+ const fileData = await $`cat large-file.txt`
410
+ .pipe($`head -n 100`)
411
+ .pipe($`add-prefix "Line:"`);
412
+
413
+ // βœ… Error handling in pipelines
414
+ try {
415
+ const result = await $`cat nonexistent.txt`.pipe($`add-prefix "Data:"`);
416
+ } catch (error) {
417
+ // Source error propagates - destination never executes
418
+ console.log('File not found, pipeline stopped');
419
+ }
420
+
421
+ // βœ… Complex data processing
422
+ register('json-parse', async (args, stdin) => {
423
+ try {
424
+ const data = JSON.parse(stdin);
425
+ return { stdout: JSON.stringify(data, null, 2), code: 0 };
426
+ } catch (error) {
427
+ return { stdout: '', stderr: `JSON Error: ${error.message}`, code: 1 };
428
+ }
429
+ });
430
+
431
+ register('extract-field', async (args, stdin) => {
432
+ const field = args[0];
433
+ try {
434
+ const data = JSON.parse(stdin);
435
+ const value = data[field] || 'null';
436
+ return { stdout: `${value}\n`, code: 0 };
437
+ } catch (error) {
438
+ return { stdout: '', stderr: `Extract Error: ${error.message}`, code: 1 };
439
+ }
440
+ });
441
+
442
+ // Real-world API processing pipeline
443
+ const userName = await $`curl -s https://api.github.com/users/octocat`
444
+ .pipe($`json-parse`)
445
+ .pipe($`extract-field name`);
446
+ // β†’ "The Octocat"
447
+
448
+ // Cleanup
449
+ unregister('add-prefix');
450
+ unregister('add-suffix');
451
+ unregister('json-parse');
452
+ unregister('extract-field');
453
+ ```
454
+
455
+ #### **πŸ†š How We Compare**
456
+
457
+ | Library | Pipeline Types | Custom Commands in Pipes | `.pipe()` Method | Real-time Streaming |
458
+ |---------|----------------|---------------------------|------------------|---------------------|
459
+ | **command-stream** | βœ… System + Built-ins + Virtual + Mixed | βœ… **Full support** | βœ… **Full virtual command support** | βœ… **Yes** |
460
+ | **Bun.$** | βœ… System + Built-ins | ❌ No custom commands | ❌ No `.pipe()` method | ❌ No |
461
+ | **execa** | βœ… Programmatic `.pipe()` | ❌ No shell integration | βœ… Basic process piping | 🟑 Limited |
462
+ | **zx** | βœ… Shell piping + `.pipe()` | ❌ No custom commands | βœ… Stream piping only | ❌ No |
463
+
464
+ **🎯 Unique Advantages:**
465
+ - **Virtual commands work seamlessly in both shell pipes AND `.pipe()` method** - no other library can do this
466
+ - **Mixed pipeline types** - combine system, built-in, and virtual commands freely in both syntaxes
467
+ - **Real-time streaming** through virtual command pipelines
468
+ - **Full stdin/stdout passing** between all command types
469
+ - **Dual piping syntax** - use shell `|` OR programmatic `.pipe()` interchangeably
470
+
471
+ ## Default Behavior: Shell-like with Programmatic Control
472
+
473
+ **command-stream behaves exactly like running commands in your shell by default:**
474
+
475
+ ```javascript
476
+ import { $ } from 'command-stream';
477
+
478
+ // This command will:
479
+ // 1. Print "Hello" to your terminal (stdout→stdout)
480
+ // 2. Print "Error!" to your terminal (stderr→stderr)
481
+ // 3. Capture both outputs for programmatic access
482
+ const result = await $`sh -c "echo 'Hello'; echo 'Error!' >&2"`;
483
+
484
+ console.log('Captured stdout:', result.stdout); // "Hello\n"
485
+ console.log('Captured stderr:', result.stderr); // "Error!\n"
486
+ console.log('Exit code:', result.code); // 0
487
+ ```
488
+
489
+ **Key Default Options:**
490
+ - `mirror: true` - Live output to terminal (like shell)
491
+ - `capture: true` - Capture output for later use (unlike shell)
492
+ - `stdin: 'inherit'` - Inherit stdin from parent process
493
+
494
+ **Fully Controllable:**
495
+ ```javascript
496
+ import { $, create, sh } from 'command-stream';
497
+
498
+ // Disable terminal output but still capture
499
+ const result = await sh('echo "silent"', { mirror: false });
500
+
501
+ // Custom stdin input
502
+ const custom = await sh('cat', { stdin: "custom input" });
503
+
504
+ // Create custom $ with different defaults
505
+ const quiet$ = create({ mirror: false });
506
+ await quiet$`echo "silent"`; // Won't print to terminal
507
+
508
+ // Disable both mirroring and capturing for performance
509
+ await sh('make build', { mirror: false, capture: false });
510
+ ```
511
+
512
+ **This gives you the best of both worlds:** shell-like behavior by default, but with full programmatic control and real-time streaming capabilities.
513
+
514
+ ## Real-world Examples
515
+
516
+ ### Log File Streaming with Session ID Extraction
517
+
518
+ ```javascript
519
+ import { $ } from 'command-stream';
520
+ import { appendFileSync, writeFileSync } from 'fs';
521
+
522
+ let sessionId = null;
523
+ let logFile = null;
524
+
525
+ for await (const chunk of $`your-streaming-command`.stream()) {
526
+ if (chunk.type === 'stdout') {
527
+ const data = chunk.data.toString();
528
+
529
+ // Extract session ID from output
530
+ if (!sessionId && data.includes('session_id')) {
531
+ try {
532
+ const parsed = JSON.parse(data);
533
+ sessionId = parsed.session_id;
534
+ logFile = `${sessionId}.log`;
535
+ console.log(`Session ID: ${sessionId}`);
536
+ } catch (e) {
537
+ // Handle JSON parse errors
538
+ }
539
+ }
540
+
541
+ // Write to log file in real-time
542
+ if (logFile) {
543
+ appendFileSync(logFile, data);
544
+ }
545
+ }
546
+ }
547
+ ```
548
+
549
+ ### Progress Monitoring
550
+
551
+ ```javascript
552
+ import { $ } from 'command-stream';
553
+
554
+ let progress = 0;
555
+
556
+ $`download-large-file`
557
+ .on('stdout', (chunk) => {
558
+ const output = chunk.toString();
559
+ if (output.includes('Progress:')) {
560
+ progress = parseProgress(output);
561
+ updateProgressBar(progress);
562
+ }
563
+ })
564
+ .on('end', (result) => {
565
+ console.log('Download completed!');
566
+ });
567
+ ```
568
+
569
+ ## API Reference
570
+
571
+ ### ProcessRunner Class
572
+
573
+ The enhanced `$` function returns a `ProcessRunner` instance that extends `EventEmitter`.
574
+
575
+ #### Events
576
+
577
+ - `data`: Emitted for each chunk with `{type: 'stdout'|'stderr', data: Buffer}`
578
+ - `stdout`: Emitted for stdout chunks (Buffer)
579
+ - `stderr`: Emitted for stderr chunks (Buffer)
580
+ - `end`: Emitted when process completes with final result object
581
+ - `exit`: Emitted with exit code
582
+
583
+ #### Methods
584
+
585
+ - `stream()`: Returns an async iterator for real-time chunk processing
586
+ - `sync()`: Execute command synchronously (blocks until completion, events batched)
587
+ - `pipe(destination)`: Programmatically pipe output to another command (returns new ProcessRunner)
588
+ - `then()`, `catch()`, `finally()`: Promise interface for await support
589
+
590
+ #### Properties
591
+
592
+ - `stdout`: Direct access to child process stdout stream
593
+ - `stderr`: Direct access to child process stderr stream
594
+ - `stdin`: Direct access to child process stdin stream
595
+
596
+ ### Default Options
597
+
598
+ **By default, command-stream behaves like running commands in the shell:**
599
+
600
+ ```javascript
601
+ {
602
+ mirror: true, // Live output to terminal (stdout→stdout, stderr→stderr)
603
+ capture: true, // Capture output for programmatic access
604
+ stdin: 'inherit' // Inherit stdin from parent process
605
+ }
606
+ ```
607
+
608
+ **Option Details:**
609
+ - `mirror: boolean` - Whether to pipe output to terminal in real-time
610
+ - `capture: boolean` - Whether to capture output in result object
611
+ - `stdin: 'inherit' | 'ignore' | string | Buffer` - How to handle stdin
612
+ - `cwd: string` - Working directory for command
613
+ - `env: object` - Environment variables
614
+
615
+ **Override defaults:**
616
+ - Use `sh(command, options)` for one-off overrides
617
+ - Use `create(defaultOptions)` to create custom `$` with different defaults
618
+
619
+ ### Shell Settings API
620
+
621
+ Control shell behavior like bash `set`/`unset` commands:
622
+
623
+ #### Functions
624
+
625
+ - `shell.errexit(boolean)`: Enable/disable exit-on-error (like `set Β±e`)
626
+ - `shell.verbose(boolean)`: Enable/disable command printing (like `set Β±v`)
627
+ - `shell.xtrace(boolean)`: Enable/disable execution tracing (like `set Β±x`)
628
+ - `set(option)`: Enable shell option (`'e'`, `'v'`, `'x'`, or long names)
629
+ - `unset(option)`: Disable shell option
630
+ - `shell.settings()`: Get current settings object
631
+
632
+ ### Virtual Commands API
633
+
634
+ Control and extend the command system with custom JavaScript functions:
635
+
636
+ #### Functions
637
+
638
+ - `register(name, handler)`: Register a virtual command
639
+ - `name`: Command name (string)
640
+ - `handler`: Function or async generator `(args, stdin, options) => result`
641
+ - `unregister(name)`: Remove a virtual command
642
+ - `listCommands()`: Get array of all registered command names
643
+ - `enableVirtualCommands()`: Enable virtual command processing
644
+ - `disableVirtualCommands()`: Disable virtual commands (use system commands only)
645
+
646
+ #### Handler Function Signature
647
+
648
+ ```javascript
649
+ // Regular async function
650
+ async function handler(args, stdin, options) {
651
+ return {
652
+ code: 0, // Exit code (number)
653
+ stdout: "output", // Standard output (string)
654
+ stderr: "", // Standard error (string)
655
+ };
656
+ }
657
+
658
+ // Async generator for streaming
659
+ async function* streamingHandler(args, stdin, options) {
660
+ yield "chunk1\n";
661
+ yield "chunk2\n";
662
+ // Each yield sends a chunk in real-time
663
+ }
664
+ ```
665
+
666
+ ### Built-in Commands
667
+
668
+ 18 cross-platform commands that work identically everywhere:
669
+
670
+ **File System**: `cat`, `ls`, `mkdir`, `rm`, `mv`, `cp`, `touch`
671
+ **Utilities**: `basename`, `dirname`, `seq`, `yes`
672
+ **System**: `cd`, `pwd`, `echo`, `sleep`, `true`, `false`, `which`, `exit`, `env`, `test`
673
+
674
+ All built-in commands support:
675
+ - Standard flags (e.g., `ls -la`, `mkdir -p`, `rm -rf`)
676
+ - Pipeline operations
677
+ - Option awareness (`cwd`, `env`, etc.)
678
+ - Bash-compatible error messages and exit codes
679
+
680
+ #### Supported Options
681
+
682
+ - `'e'` / `'errexit'`: Exit on command failure
683
+ - `'v'` / `'verbose'`: Print commands before execution
684
+ - `'x'` / `'xtrace'`: Trace command execution with `+` prefix
685
+ - `'u'` / `'nounset'`: Error on undefined variables (planned)
686
+ - `'pipefail'`: Pipe failure detection (planned)
687
+
688
+ ### Result Object
689
+
690
+ ```javascript
691
+ {
692
+ code: number, // Exit code
693
+ stdout: string, // Complete stdout output
694
+ stderr: string, // Complete stderr output
695
+ stdin: string, // Input sent to process
696
+ child: ChildProcess // Original child process object
697
+ }
698
+ ```
699
+
700
+ ## Testing
701
+
702
+ ```bash
703
+ # Run comprehensive test suite (266 tests)
704
+ bun test
705
+
706
+ # Run tests with coverage report
707
+ bun test --coverage
708
+
709
+ # Run specific test categories
710
+ npm run test:features # Feature comparison tests
711
+ npm run test:builtin # Built-in commands tests
712
+ npm run test:pipe # .pipe() method tests
713
+ npm run test:sync # Synchronous execution tests
714
+ ```
715
+
716
+ ## Requirements
717
+
718
+ - **Bun**: >= 1.0.0 (primary runtime)
719
+ - **Node.js**: >= 20.0.0 (compatibility support)
720
+
721
+ ## Roadmap
722
+
723
+ ### πŸ”„ **Coming Soon**
724
+ - **TypeScript Support**: Full .d.ts definitions and type safety
725
+ - **Enhanced Shell Options**: `set -u` (nounset) and `set -o pipefail` support
726
+ - **More Built-in Commands**: Additional cross-platform utilities
727
+
728
+ ### πŸ’‘ **Planned Features**
729
+ - **Performance Optimizations**: Further Bun runtime optimizations
730
+ - **Advanced Error Handling**: Enhanced error context and debugging
731
+ - **Plugin System**: Extensible architecture for custom integrations
732
+
733
+ ## Contributing
734
+
735
+ We welcome contributions! Since command-stream is **public domain software**, your contributions will also be released into the public domain.
736
+
737
+ ### πŸš€ **Getting Started**
738
+ ```bash
739
+ git clone https://github.com/link-foundation/command-stream.git
740
+ cd command-stream
741
+ bun install
742
+ bun test # Run the full test suite
743
+ ```
744
+
745
+ ### πŸ“‹ **Development Guidelines**
746
+ - All features must have comprehensive tests
747
+ - Built-in commands should match bash/sh behavior exactly
748
+ - Maintain cross-platform compatibility (Windows, macOS, Linux)
749
+ - Follow the existing code style and patterns
750
+
751
+ ### πŸ§ͺ **Running Tests**
752
+ ```bash
753
+ bun test # All 266 tests
754
+ bun test tests/pipe.test.mjs # Specific test file
755
+ npm run test:builtin # Built-in commands only
756
+ ```
757
+
758
+ ## License - Our Biggest Advantage
759
+
760
+ **The Unlicense (Public Domain)**
761
+
762
+ Unlike other shell utilities that require attribution (MIT, Apache 2.0), command-stream is released into the **public domain**. This means:
763
+
764
+ - βœ… **No attribution required** - Use it without crediting anyone
765
+ - βœ… **No license files to include** - Simplify your distribution
766
+ - βœ… **No restrictions** - Modify, sell, embed, whatever you want
767
+ - βœ… **No legal concerns** - It's as free as code can be
768
+ - βœ… **Corporate friendly** - No license compliance overhead
769
+
770
+ This makes command-stream ideal for:
771
+ - **Commercial products** where license attribution is inconvenient
772
+ - **Embedded systems** where every byte counts
773
+ - **Educational materials** that can be freely shared
774
+ - **Internal tools** without legal review requirements
775
+
776
+ > "This is free and unencumbered software released into the public domain."