33This page owns the detailed public semantics of the current command surface. Run ` firstdraft --help ` or a command
44group's ` --help ` for concise executable syntax. See [ Errors and recovery] ( errors.md ) before retrying a failed mutation.
55
6- The current ` 0.8.x ` source line contains the auditable command shell, local Foundation Plan initialization, local
6+ The current ` 0.8.x ` source line contains the auditable command shell, browser and device login, local Foundation Plan initialization, local
77application-key and UUID generation, conditional whole-document push, whole-graph analysis status polling, direct
88Compile-and-materialize and private publish orchestration, and retained-Compilation inspection. CLI ` 0.8.x `
99requires the service's ` 0.7.x ` API contract. The
1010[ changelog] ( https://github.com/firstdraft/cli/blob/main/CHANGELOG.md ) describes each version.
1111
1212## Command map
1313
14- | Command | Network | Purpose |
15- | ------------------------------------- | ------- | ---------------------------------------------------------- |
16- | ` firstdraft plan init ` | No | Create an empty local Foundation Plan and Project identity |
17- | ` firstdraft generate application-key ` | No | Preview deterministic name-to-key derivation |
18- | ` firstdraft generate uuid ` | No | Generate one or more Foundation Plan subject identities |
19- | ` firstdraft plan push ` | Yes | Conditionally submit the exact whole Plan |
20- | ` firstdraft plan status ` | Yes | Read or wait for the current whole-graph analysis |
21- | ` firstdraft plan compile ` | Yes | Push and analyze, then materialize in the current folder |
22- | ` firstdraft plan compile --github ` | Yes | Push and analyze, then publish to private GitHub |
23- | ` firstdraft compilation status ` | Yes | Inspect a retained Compilation by ID |
24- | ` firstdraft compilation download ` | Yes | Verify and materialize a successful retained Compilation |
14+ | Command | Network | Purpose |
15+ | ------------------------------------- | ------- | ----------------------------------------------------------- |
16+ | ` firstdraft plan init ` | No | Create an empty local Foundation Plan and Project identity |
17+ | ` firstdraft generate application-key ` | No | Preview deterministic name-to-key derivation |
18+ | ` firstdraft generate uuid ` | No | Generate one or more Foundation Plan subject identities |
19+ | ` firstdraft login ` | Yes | Approve the CLI in a browser and save a token for an origin |
20+ | ` firstdraft logout ` | Yes | Revoke and remove the saved token for an origin |
21+ | ` firstdraft plan push ` | Yes | Conditionally submit the exact whole Plan |
22+ | ` firstdraft plan status ` | Yes | Read or wait for the current whole-graph analysis |
23+ | ` firstdraft plan compile ` | Yes | Push and analyze, then materialize in the current folder |
24+ | ` firstdraft plan compile --github ` | Yes | Push and analyze, then publish to private GitHub |
25+ | ` firstdraft compilation status ` | Yes | Inspect a retained Compilation by ID |
26+ | ` firstdraft compilation download ` | Yes | Verify and materialize a successful retained Compilation |
2527
2628## Select an environment and authenticate
2729
28- Production at ` https://firstdraft.com ` is the default. Create a token at
29- [ First Draft] ( https://firstdraft.com/api-tokens ) and provide it through ` FIRSTDRAFT_API_TOKEN ` when running a network
30- command. Keep token values out of shell history and command arguments.
30+ Production at ` https://firstdraft.com ` is the default. Log in once per environment:
3131
3232``` sh
33- firstdraft plan push
33+ firstdraft login
3434```
3535
36- For staging, create a separate token at [ First Draft staging] ( https://staging.firstdraft.com/api-tokens ) , provide it
37- through ` FIRSTDRAFT_STAGING_API_TOKEN ` , and select staging on the first remote command:
36+ The command prints an authorization URL on standard error. Open it in a browser on the same machine, sign in, and
37+ approve ** First Draft CLI** . The CLI then saves a token and prints ` Logged in to <origin> ` on standard output.
38+
39+ For staging, select it on the login and on the first remote command:
3840
3941``` sh
42+ firstdraft --staging login
4043firstdraft --staging plan push
4144firstdraft plan compile --staging
4245```
4346
44- ` --staging ` may precede the command group or appear among a remote command's options. It selects
45- ` https://staging.firstdraft.com ` . ` plan init ` and ` generate ` remain local; a global flag on a local command does not
46- save an environment selection. The first successful push, including the push within ` plan compile ` , pins the API
47- origin in ` .firstdraft/state.json ` .
47+ ### How login works
48+
49+ The default login uses the OAuth 2.0 authorization code flow with PKCE (` S256 ` ) and a loopback redirect
50+ (RFC 8252). The CLI listens on ` 127.0.0.1 ` at an ephemeral port for one ` GET /callback ` , sends a random ` state `
51+ and code challenge, and waits up to five minutes. Only a callback with the exact ` state ` is accepted. Requests
52+ for other paths, with a missing or different ` state ` , or with another ` Host ` get an error page and do not end
53+ the wait, so a stray local request can neither inject a code nor cancel the login. The first matching callback
54+ closes the listener and shows a page that says you can close the tab. The CLI then exchanges the single-use code
55+ and its PKCE verifier at ` POST /oauth/token ` . The token never appears in a URL.
56+
57+ On a machine without a browser, such as an SSH session or a container, use the device flow (RFC 8628):
58+
59+ ``` sh
60+ firstdraft login --interactive # also -i or --device
61+ ```
62+
63+ The CLI prints a verification URL, a short user code, and a URL that already includes the code. Open either URL on
64+ any device, confirm the code, and approve. The CLI polls at the server's interval, adds five seconds when asked to
65+ slow down, and stops when the code expires.
66+
67+ Both flows send this machine's hostname as the device name shown on the approval page and in the token's name on
68+ ` /api-tokens ` . A denied approval exits with ` authorization_denied ` . A login that is not approved in time exits with
69+ ` authorization_expired ` . Other failures exit with ` login_failed ` . No credential is saved unless the login succeeds.
70+ See [ Errors and recovery] ( errors.md#login-and-logout-errors ) .
71+
72+ ### Saved credentials
73+
74+ The token is saved in ` $XDG_CONFIG_HOME/firstdraft/credentials.json ` , or ` ~/.config/firstdraft/credentials.json `
75+ when ` XDG_CONFIG_HOME ` is unset or not absolute. The directory is created with mode ` 0700 ` . The file is written
76+ with mode ` 0600 ` through a temporary file and an atomic rename. Each update holds ` credentials.json.lock ` in the
77+ same directory from its read to its rename, so concurrent commands cannot drop each other's entries. An update
78+ waits up to 10 seconds for another command's lock, then fails with reason ` credentials_locked ` . The CLI never removes
79+ a lock by itself: if no other ` firstdraft ` command is running, the lock was left by one that exited mid-update, and
80+ you can delete it. The file stores one entry per exact origin, so production, staging, and local development logins
81+ can exist side by side. Logging in again to the same origin replaces its
82+ entry. Run ` firstdraft logout ` first if the old token should also be revoked. The CLI never prints a saved token or
83+ writes it to ` .firstdraft ` .
84+
85+ ` firstdraft logout ` (or ` firstdraft --staging logout ` ) asks First Draft to revoke the saved token for the selected
86+ origin through ` POST /oauth/revoke ` . It then removes the local entry even when revocation cannot be confirmed; in
87+ that case it says so on standard error, and you can revoke the token on ` /api-tokens ` . With nothing saved, it
88+ reports that and exits 0. If a newer login saved a different token for the origin while revocation was pending,
89+ logout keeps that token and says so on standard error. If the local update fails after revocation, logout exits with ` logout_failed ` and reports
90+ whether revocation was confirmed; see [ Errors and recovery] ( errors.md#login-and-logout-errors ) . Logout never changes
91+ token environment variables.
92+
93+ ### Environment tokens
94+
95+ Tokens from the environment still work and take precedence over a saved login. Create one on
96+ [ First Draft] ( https://firstdraft.com/api-tokens ) and provide it through ` FIRSTDRAFT_API_TOKEN ` . For staging, create a
97+ separate token at [ First Draft staging] ( https://staging.firstdraft.com/api-tokens ) and provide it through
98+ ` FIRSTDRAFT_STAGING_API_TOKEN ` . Keep token values out of shell history and command arguments. ` login ` and ` logout `
99+ note on standard error when an environment token will override the saved login.
100+
101+ ### Origins and projects
102+
103+ ` --staging ` may precede the command group or appear among a remote command's options, including ` login ` and
104+ ` logout ` . It selects ` https://staging.firstdraft.com ` . ` plan init ` and ` generate ` remain local; a global flag on a
105+ local command does not save an environment selection. The first successful push, including the push within
106+ ` plan compile ` , pins the API origin in ` .firstdraft/state.json ` .
48107
49108Existing Projects keep their pinned origin with or without the flag. A CLI upgrade does not migrate a Project or its
50109credentials. A staging flag that disagrees with a Project's pin stops before any request.
51110To work with another environment, initialize a separate project directory and submit the Plan there; do not edit
52111the existing Project's private state to redirect it.
53112
54- ` FIRSTDRAFT_API_URL ` remains available for an initial custom HTTPS origin or loopback HTTP development server.
55- ` --staging ` together with a different URL is an error; the equivalent normalized staging URL is allowed. Later
56- pushes and compilation reject an override that differs from the pin. Read-only status and retained download
57- commands use the pin and ignore ` FIRSTDRAFT_API_URL ` unless checking its conflict with an explicit ` --staging ` .
58-
59- Every remote command selects credentials from its effective origin: the exact ` https://staging.firstdraft.com `
60- origin requires ` FIRSTDRAFT_STAGING_API_TOKEN ` ; production and custom origins use ` FIRSTDRAFT_API_TOKEN ` . Neither
61- token is a fallback for the other. This includes existing staging Projects and retained status or artifact reads,
62- even when no flag is supplied.
113+ ` FIRSTDRAFT_API_URL ` remains available for an initial custom HTTPS origin or loopback HTTP development server, and
114+ it also selects the origin for ` login ` and ` logout ` , for example `FIRSTDRAFT_API_URL=http://127.0.0.1:3000
115+ firstdraft login` . ` --staging` together with a different URL is an error; the equivalent normalized staging URL is
116+ allowed. Later pushes and compilation reject an override that differs from the pin. Read-only status and retained
117+ download commands use the pin and ignore ` FIRSTDRAFT_API_URL ` unless checking its conflict with an explicit
118+ ` --staging ` .
119+
120+ Every remote command selects credentials from its effective origin. First it uses the environment: the exact
121+ ` https://staging.firstdraft.com ` origin uses ` FIRSTDRAFT_STAGING_API_TOKEN ` ; production and custom origins use
122+ ` FIRSTDRAFT_API_TOKEN ` . When that variable is unset or empty, it uses the token saved by ` firstdraft login ` for that
123+ exact origin. No token is a fallback for a different origin: neither environment variable substitutes for the other,
124+ and a saved login for one origin never authenticates another. This includes existing staging Projects and retained
125+ status or artifact reads, even when no flag is supplied. An unreadable or malformed credentials file counts as no
126+ saved login.
63127
64128The CLI sends the selected token as a Bearer credential on every API request. It does not save it in ` .firstdraft ` ,
65129print it, or require it for local commands. Revoke a token in the environment that issued it if it is exposed. A
@@ -402,8 +466,10 @@ contents, and digests without claiming POSIX mode bits. The declared and streame
402466
403467## Service endpoints
404468
405- The CLI calls these Service API routes at the origin pinned for the Project. Every request sends the selected token
406- as a Bearer credential, refuses redirects, and has a bounded timeout. Each request takes its method and path from one
469+ The CLI calls these Service API routes. The ` /v1 ` routes go to the origin pinned for the Project and send the
470+ selected token as a Bearer credential. The ` /oauth ` routes go to the origin selected for ` login ` or ` logout ` and
471+ send form-encoded OAuth parameters without a Bearer credential. Every request refuses redirects and has a bounded
472+ timeout. Each request takes its method and path from one
407473` SERVICE_ROUTES ` entry in ` src/api-response.js ` . ` test/service-endpoints.test.js ` fails when this table and
408474` SERVICE_ROUTES ` differ, or when no file in ` src/ ` uses a declared route.
409475
@@ -416,15 +482,21 @@ as a Bearer credential, refuses redirects, and has a bounded timeout. Each reque
416482| ` GET ` | ` /v1/projects/{project_id}/compilations/{compilation_id}/artifact ` | Download that Compilation's artifact |
417483| ` PUT ` | ` /v1/projects/{project_id}/github-publication ` | Start or rejoin the Publication |
418484| ` GET ` | ` /v1/projects/{project_id}/github-publication ` | Poll or reconcile the Publication |
485+ | ` POST ` | ` /oauth/token ` | Exchange a login grant for a token |
486+ | ` POST ` | ` /oauth/device_authorization ` | Start a device-flow login |
487+ | ` POST ` | ` /oauth/revoke ` | Revoke a saved token |
419488
420489` plan push ` sends the Plan ` PUT ` , and ` plan status ` reads the analysis. ` plan compile ` does both. It then starts a
421490Compilation, polls it, and downloads its artifact. With ` --github ` , it starts and polls the Publication instead.
422491` compilation status ` reads one retained Compilation. ` compilation download ` reads it and downloads its artifact.
492+ ` login ` exchanges its authorization code at ` /oauth/token ` . With ` --interactive ` , it starts at
493+ ` /oauth/device_authorization ` and then polls ` /oauth/token ` . ` logout ` calls ` /oauth/revoke ` . The browser, not the
494+ CLI, opens ` /oauth/authorize ` and ` /device ` , so they have no rows.
423495
424496The first push sends ` If-None-Match: * ` . Later pushes, the Compilation ` POST ` , and the Publication ` PUT ` send the
425497saved Head ETag in ` If-Match ` . The Service also has a Compilation cancel route, which the CLI does not call.
426498
427- These routes belong to the API-contract range that ` release/compatibility.json ` accepts. The CLI does not read the
499+ The ` /v1 ` routes belong to the API-contract range that ` release/compatibility.json ` accepts. The CLI does not read the
428500Service's ` FirstDraft-API-Contract ` response header. Instead, the Service's release compatibility check compares the
429501declared ranges before a release. The Service documents the routes in its Foundation Plan machine reference,
430502` docs/architecture/reference/README.md ` in the private ` firstdraft/firstdraft ` repository.
0 commit comments