One $ for every environment | Xec
Skip to main content<br>1runtime dependencyssh2 — loaded only when an SSH target is used<br>4environments, one APIlocal, SSH, Docker, Kubernetes<br>3runtimesNode, Bun, Deno — byte-identical results, pinned by a parity test<br>4,900+testsacross the engine, CLI, loader and UI kit<br>~70msCLI startupagainst a ~28ms floor for an empty Node process
The seam this closes<br>Running a command somewhere other than your own machine means assembling four libraries with four APIs, four error shapes and four streaming models — then keeping them in step.Assembled by handexecalocal processesssh2remote hostsdockerodecontainers@kubernetes/client-nodepodsFour result types. Four ways a failure surfaces. Moving a service from a container to a host means rewriting the code that talks to it.<br>With xecconst result = await $.ssh(host)`systemctl status api`;
result.ok // exit 0 and not signalled<br>result.stdout // string<br>result.stdall // both streams, in arrival order<br>result.duration // ms
// same shape for local, docker and k8sOne result type, one error hierarchy, one streaming model. The target changes; the code does not.
The contract<br>Each of these is enforced by a test in this repository. They are written as promises about what will not happen to you, because that is what you need to know before running something against a production host.An option works or it fails loudly<br>.cd() on a container changes the directory in the container. .env() on a pod exports in the pod, and never leaks into your own process. Nothing is accepted and quietly dropped.<br>No silent data loss<br>Output past maxBuffer kills the producer and fails with the truncated head kept — never an empty result with exit code 0. A process killed by a signal is never ok, and reports 128 + signum.<br>Interpolation is safe by default<br>Interpolated values are quoted for the shell that will actually parse them, so a value can never change the structure of a command. $.raw exists for when you mean it.<br>Secrets stay out of logs<br>Tokens, API keys, URL credentials and PEM blocks are redacted in output, events, error messages and the verbose echo — with one rule set, including across stream chunk boundaries.<br>Killing a command kills its tree<br>sh -c "node server.js" is a process tree. Kill, abort, timeout and buffer overflow all signal the whole group, so nothing is orphaned holding a port.<br>A cached result belongs to its target<br>Cache keys carry the host, container, pod, namespace and cluster. One machine’s answer is never served for another, so a health check cannot report on the wrong box.
Two ways in<br>As a librarynpm i @xec-sh/coreconst staging = $.ssh('deploy@staging')<br>.cd('/srv/app')<br>.env({ NODE_ENV: 'staging' })<br>.timeout('60s')<br>.retry({ maxRetries: 3 });
await staging`pnpm migrate`;
for await (const line of staging`tail -f app.log`) {<br>if (line.includes('ERROR')) alert(line);<br>}Every environment takes the same chain. Output streams as it arrives, so a follow works the way you expect.<br>As a CLInpm i -g @xec-sh/clixec on deploy@prod-1 'systemctl restart api'<br>xec in postgres-main 'pg_dump mydb'<br>xec in production/api-7f9d 'cat app.log'
xec run deploy.ts # a script, with $ in scope<br>xec forward hosts.prod 8080:80Targets, defaults and tasks live in .xec/config.yaml. Scripts get the same API the library exposes — nothing is CLI-only.
What Xec is not<br>Knowing where a tool stops is worth as much as knowing what it does.<br>Not an Ansible replacement. No inventory graph, no declarative convergence. Xec is imperative TypeScript for the automation you would otherwise write in bash — with types, tests and one API instead of four.Not an SDK wrapper. Adapters speak the native tools — the ssh2 protocol, the docker and kubectl CLIs — so behaviour matches what you would get by hand, exit codes included.Not a dependency tree. The execution core declares one runtime dependency, ssh2, and loads it only when an SSH target is used. Running a command locally loads no third-party code at all.
Packages<br>@xec-sh/coreExecution engine — the $ API, adapters, pooling, streaming.→@xec-sh/cliCommand line — run tasks and commands against any target.→@xec-sh/loaderScript loading — TypeScript transform, REPL, watch mode.→@xec-sh/kitTerminal UI — prompts, spinners, tables, colour.→