Skip to content
BLACKLAKE
blx shell▾ docs nav

blx — the shell capture path

BlackLake captures AI actions wherever they originate. blx is the shell-side capture path: a thin wrapper that classifies the command, governs it through your policies, runs it, and records the exit code into the same ledger as every other AI action.

The wrapper exists because shell commands are still where most consequential operations land — git push, terraform apply, gcloud run deploy, kubectl apply -f production.yaml. Whether a teammate is using AI to draft those commands or running them directly, the receipt should be the same shape as everything else BlackLake records.

Install#

blx ships inside the unified blacklake npm package as a bin alias. Install once and both binaries are on your PATH.

npm i -g blacklake
blx --version

If you only want it in a project:

npm i blacklake
npx blx git push

Supported commands today#

blx git push
blx terraform apply
blx gcloud run deploy
blx kubectl apply
blx aws s3 sync
blx pnpm publish

blx ships with classifiers for the most common deploy / IaC / package-publish commands. Anything else is passed through with a generic shell.exec action shape — the receipt still records what ran and what the exit code was.

How a call flows#

  1. blx git push parses the command and produces an action shape: { tool: 'git.push', args: { remote: 'origin', branch: 'main' } }.
  2. The wrapper calls govern() against your active workspace. Policies decide allow / deny / require approval.
  3. If allowed, the underlying git push runs. The exit code is recorded back to BlackLake.
  4. If approval is required, the wrapper waits — pings the approval URL on stderr, blocks until a human decides via the console / email / mobile push.
  5. If denied, the wrapper exits with a non-zero status and a clear message. No git push runs.

Adding a custom classifier#

Shipping classifiers for every command in every team's stack is a losing game. Instead, register a custom classifier in your workspace config.

// ~/.blacklake/blx.config.ts
import { classifier } from 'blacklake';

export default [
  classifier({
    match: /^pnpm db:migrate$/,
    action: () => ({ tool: 'database.migrate', args: { stage: process.env.STAGE ?? 'dev' } }),
    riskClass: 'high',
  }),
];

The classifier function returns the action shape govern() will see. A high riskClass lets your policies route it to the two-person approval flow without changing every call site.

Exit-code handling#

blx mirrors the exit code of the wrapped command for the allowed path. Denied calls exit with 127 and a BLACKLAKE_DENIED reason on stderr. Approval-required calls block until the decision lands; the eventual exit code matches the wrapped command on approve, or 127 on reject. CI scripts can branch cleanly on $?.

Cookbook — when to wrap#

A practical rule: wrap a command when its blast radius is bigger than the developer running it. Some examples:

  • Deploys. blx gcloud run deploy, blx kubectl apply, blx vercel deploy --prod. Production rollout should land a receipt.
  • Schema migrations. blx pnpm db:migrate, blx alembic upgrade head. Even at low risk, a receipt makes the rollback story easier.
  • Infrastructure-as-code. blx terraform apply, blx pulumi up. The plan / apply gap is a high-leverage place for two-person approval.
  • Production CLI ops. blx aws s3 cp, blx gcloud sql import. One-off ops that touch production deserve the same audit trail as governed AI tool calls.
  • Package publishing. blx pnpm publish, blx npm publish. Supply-chain compromise is a real risk; a receipt is one extra control.

Day-to-day commands (ls, git status, cat) do not need to be wrapped. The point is to wrap the consequential ones — the same boundary that already separates a code review from a typo fix.

  • MCP gateway — capture from CLI coding tools through MCP.
  • SDK govern() — capture from application code.
  • Policy guide — write rules that classify-and-route shell commands.