This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@thaitype/shell is a lightweight, type-safe wrapper around execa for running shell commands with flexible output modes. It provides a simplified API with built-in support for dry-run mode, verbose logging, and multiple output handling strategies.
Key Dependencies:
execa(v9.6.0) - The underlying process execution librarystring-argv- For parsing command strings into argument arrays
Module System: This is an ESM-only package. All imports must use .js extensions (e.g., ./shell.js) even though source files are .ts.
The codebase is intentionally minimal with a single-class design:
src/
├── index.ts # Main entry point, re-exports from shell.ts
└── shell.ts # Shell class with all core logic
The Shell class (src/shell.ts) provides three public methods:
-
run()- Recommended for most use cases. Throws on error.- Returns
StrictResultwith stdout and stderr only - Command either succeeds or throws an exception
- Returns
-
safeRun()- Never throws. Returns error result instead.- Returns
SafeResultwith stdout, stderr, exitCode, and success flag - Always succeeds, check
result.successto determine outcome
- Returns
-
execute()- Low-level method with explicit throwOnError control- Pass
{ throwOnError: true }for run() behavior - Pass
{ throwOnError: false }for safeRun() behavior
- Pass
Implementation Details:
-
Output Modes - Three strategies for handling stdout/stderr:
capture: Pipes output for programmatic access (default)live: Inherits stdio, streams to console in real-time (returns null for stdout/stderr)all: Combines both - captures AND streams simultaneously
Implementation detail: Maps output modes to execa stdio configuration using a
stdioMapobject -
Command Parsing - Accepts commands as either:
- String: Parsed via
string-argvto handle quoting/escaping - Array: Passed directly as [program, ...args]
- String: Parsed via
-
Error Handling - Two throw modes:
simple(default): Throws clean error message with command, exit code, and stderrraw: Throws the fullExecaErrorobject from execa
-
Execution Modes:
- Normal: Executes commands via execa
- Dry-run: Logs commands but skips execution, returns mock success result
- Verbose: Logs every command before execution (works with or without dry-run)
ShellOptions: Constructor configuration (defaultOutputMode, dryRun, verbose, throwMode, logger)RunOptions: Per-command overrides, extendsExecaOptionsfrom execaStrictResult: Return type forrun()(stdout, stderr)SafeResult: Return type forsafeRun()(stdout, stderr, exitCode, success)
pnpm build # Format → Build ESM → Annotate pure calls
pnpm build-esm # TypeScript compilation onlyBuild outputs:
dist/esm/- Compiled JavaScript modulesdist/dts/- TypeScript declaration files
The build uses Babel's annotate-pure-calls plugin for better tree-shaking.
pnpm test # Run tests in watch mode (Vitest)
pnpm test:ci # Run tests once (for CI)
pnpm test:coverage # Generate coverage report
pnpm test:coverage:feedback # Run coverage with detailed feedback on uncovered linesTests are located in test/shell.test.ts and focus on Shell class logic (not execa features).
pnpm lint # Type check + ESLint + Prettier check
pnpm lint:fix # Auto-fix linting issues
pnpm check-types # TypeScript type checking only
pnpm format # Format src/ with Prettierpnpm dev # Watch mode with tsx
pnpm start # Run src/main.ts onceNote: src/main.ts is not in the repository - you'll need to create it for local testing.
pnpm changeset # Create a changeset
pnpm release # Lint → Build → Version → Publish (uses changesets)All imports in source files MUST use .js extensions (not .ts) due to ESM requirements:
export * from './shell.js'; // ✓ Correct
export * from './shell'; // ✗ WrongThe Shell class accepts an optional logger function that defaults to console.log. Use optional chaining when calling:
this.logger?.(`$ ${args.join(' ')}`);Three approaches to error handling:
run(): Always throws on error (usesreject: truein execa)safeRun(): Never throws, returns result withsuccess: false(usesreject: falsein execa)execute({ throwOnError }): Explicit control over throw behavior
When using safeRun() or execute({ throwOnError: false }), check result.success to determine if the command succeeded.
Output modes are implemented by mapping to execa's stdio arrays:
- Single values like
'pipe'or'inherit' - Arrays like
['pipe', 'inherit']for theallmode (both capture and stream)
- Package Manager: pnpm@10.11.0 (specified in packageManager field)
- Node Version: >=20 required
- Published Files: Only
dist/,src/,README.md,package.jsonare included in npm package - Uses
npm-run-all2(run-s) for sequential script execution in package.json
- Uses
"module": "nodenext"withverbatimModuleSyntax: true - Path alias:
@thaitype/shell→src/index.ts(for internal imports) - Strict mode enabled
noUnusedLocalsandnoUnusedParametersare disabled (allowing unused variables)