Guides
Using fw from AI agents and scripts
JSON output, exit codes, and a login a person approves
fw is built to be driven by AI coding agents and scripts as well as by
people. Commands that report information take --json, failures have
distinct exit codes, and the login keeps the token away from the agent.
A typical session
fw login --yes --json # a person approves the link
fw projects list --json
fw structures list --lang r --json # also tools, package-sets, and skills
fw new --from vaccine-study && cd vaccine-study
fw project bootstrap
fw project push-spec
fw publish outputs/notebooks/results.html --slug results --yes --json
fw publish outputs/notebooks/results.html --to github --yes --json
Logging in without a terminal
When fw has no terminal, fw login needs --yes, given after reading the
notice it prints. It then prints a code and a link on stderr and waits. A
person opens the link in a browser where they are signed in and approves
it. fw stores the token in the keyring; the agent never sees it.
fw login --yes --json
With --json, the code arrives on stderr as one line, ready to show the
person:
{"user_code": "QGMK-JE6Q", "verification_url": "https://framework.pub/device?code=QGMK-JE6Q", "expires_in": 600}
The code expires after 10 minutes. When it is approved, stdout gets the same
object as fw status --json.
Warning
Never paste a token into an agent's prompt or onto a command line. fw does not accept tokens as arguments, because arguments end up in shell history and process lists. In automation, pipe a token from a file:
fw login --with-token --yes < token.txt.
JSON output
These commands take --json: login, status, projects list,
project show, blueprints list, project status, publish, and
project push-spec. The output goes to stdout as one JSON document, and
messages for people go to stderr. Each command's reference page lists its
fields. Token values never appear in the output.
fw projects list --json | jq -r '.projects[].slug'
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Failure |
| 2 | Bad flags or arguments |
| 3 | Not logged in, or the server rejected the token |
| 4 | The server has no such project or resource |
| 5 | Browser login was denied, or its code expired |
| 6 | An upload has "private" in a path and needs --include-private |
With --json, a failure also prints one JSON line on stderr:
{"error": "You have no project named 'nope'. (HTTP 404)", "exit_code": 4}
Exit code 6 also carries "reason": "private_path". Show the person which
files have "private" in their path, and rerun with --include-private only
if they confirm the upload.
Questions fw asks
fw asks before it connects a machine (fw login) and before it uploads
(fw publish). Without a terminal it cannot ask, so these need --yes.
fw prints what it is about to do first, so an agent can show it to the
person. fw project push-spec asks only when the project's path contains
"private". fw new with cloud choices such as --blueprint never creates
the project on Framework Cloud without a terminal unless you pass --cloud.
fw new NAME --lang stata while logged in stops with exit 1 when the server
cannot be reached and no defaults are saved; pass --offline to use fw's
built-in defaults instead. Nothing else prompts when there is no terminal.
What never leaves the machine
fw never uploads anything on its own. Data files and project code go to the
cloud only when you run fw publish or fw project push-spec, and
fw publish sends only the path it is given. fw publish --to github
commits and pushes only published/SLUG/ in the project's repository, and
its JSON is {"url", "commit", "pushed", "pages_enabled"}. Tell the person
that a GitHub Pages site is public, even from a private repository. When a file's full path
contains "private", in any letter case, fw stops with exit code 6 unless you
pass --include-private. Sensitive projects keep their AI deny rules, which
stop agents from reading private folders and .env.