Identity and config
login, logout, whoami and init, plus the three places the CLI looks for a credential.
Four commands carry everything the codespar binary knows about who you are. login writes a key to disk, whoami proves that key resolves to an org and a project, logout removes it, and init is the one command in this group that never reads a credential at all.
Measured 12/09/2026, against the repository build of the CLI (packages/cli/dist, compiled 11/09) pointed at an HTTP stub on 127.0.0.1. Every transcript below is captured output and no probe reached the production API. Where a line comes from reading the source instead of running it, the sentence says so.
Where a value comes from
Three sources, first match wins, resolved one value at a time rather than one source at a time (a key from the environment and a project from the file is a normal combination):
- Root flags:
--api-key,--base-url,--project - Environment:
CODESPAR_API_KEY,CODESPAR_BASE_URL,CODESPAR_PROJECT - The file at
~/.codespar/config.json
The base URL falls back to https://api.codespar.dev when no source carries one. The project has no fallback: without it the server applies the org default.
There is no codespar config subcommand. The top-level help lists 23 entries (22 commands plus Commander's own help) and none of them is config or reset. The file is written by login and logout, or by hand.
codespar login
Reads an API key from the terminal, spends one GET /v1/whoami call proving the key works, and only then writes it to ~/.codespar/config.json with mode 0600. A key that fails validation is never persisted.
Flags
| Flag | Type | Required | What it does |
|---|---|---|---|
--api-key <key> | string | no | Declared on the command and listed by codespar login --help, but the value never reaches the command. See the warning below. |
Root flags still apply. --base-url is the one that matters here: login resolves the host from --base-url, then CODESPAR_BASE_URL, then https://api.codespar.dev. Read from the source: login is the one command that does not consult baseUrl in the config file, so a bare codespar login after a login against a staging host rewrites the stored host back to production.
--api-key is listed but not read. The root program and the login subcommand declare the same --api-key long flag, and Commander 13.1.0 binds the value to the root command when that happens, while the login handler reads only its own options. Measured three ways on 12/09/2026: codespar login --api-key notakey prompts; codespar --api-key notakey login prompts; and a 12-line isolated Commander program reproduces the split, handing the subcommand {} and the root {"apiKey":"X"}. Until it is fixed, pipe the key on stdin or export CODESPAR_API_KEY and skip login entirely.
printf 'csk_test_probekey\n' | codespar loginℹ Get your API key at https://codespar.dev/dashboard/…
API key:
✓ Logged in as ana@loja.com.br
ℹ Organization: Loja ExemploThe dashboard URL is truncated above on purpose: the docs audit rejects any paragraph that pairs that path with key language. Mint keys at Dashboard → API Keys.
API key: is written to stdout, not stderr, so codespar login > log.txt swallows the prompt and leaves the terminal looking hung. The ℹ and ✓ lines go to stderr.
On a TTY the key is read without echo: the prompt prints once and the typed characters are swallowed, so a pasted key does not land in your scrollback.
What it writes
{
"apiKey": "csk_test_probekey",
"baseUrl": "http://127.0.0.1:8931"
}What it calls one GET /v1/whoami, the same route as codespar whoami.
Refusals
| What you see | Cause | What to do |
|---|---|---|
✗ That doesn't look like a CodeSpar key — they start with `csk_live_` or `csk_test_`., exit 1 | The value does not start with csk_. Checked before any network call, so nothing is written. | Copy the key again; the check is on the prefix only. |
✗ API key is required., exit 1 | An empty answer at the prompt. Measured by piping a bare newline. | Paste a key, or use CODESPAR_API_KEY. |
✗ GET /v1/whoami → 401: Invalid API key, exit 1 | The API rejected the key. Measured against a stub answering 401; the text after the colon is whatever message the API returned. Nothing is written: the config directory stayed empty. | Check the key and the environment its project belongs to. |
| Nothing at all, exit 0, nothing written | stdin is closed, which is what a CI step without a TTY looks like. readline reaches EOF, the question never settles and the process ends quietly. Measured with < /dev/null. | Pipe the key in, or set CODESPAR_API_KEY instead of calling login. |
codespar whoami
Spends one GET /v1/whoami with the resolved credential and prints the five lines the CLI derives from the response. It is the cheapest way to see which org, project and environment a key actually lands on.
https://api.codespar.dev/v1/whoamiFlags none of its own. codespar whoami --help lists only -h, --help; the root flags do the work.
Human output, line by line
| Line | Comes from | Printed when the field is absent |
|---|---|---|
User | user.email, else user.id | (unknown) |
Organization | organization.name | (none) |
Project | project.name, else project.id | (none) |
Key env | key.environment | (unknown) |
Scopes | key.scopes, joined with a comma | (all) |
Each placeholder was measured against a stub that answered {}. (all) is the CLI's word for "the response carried no key.scopes array", not a statement the API made about the key.
codespar whoamiUser ana@loja.com.br
Organization Loja Exemplo
Project checkout-prod
Key env test
Scopes sessions:write, charges:writeWith --json the response body is printed verbatim, with nothing on stderr:
codespar whoami --json | jq -r '.key.environment'The flag is declared on the root command but Commander accepts it in both positions, so codespar whoami --json and codespar --json whoami behave the same.
Refusals
| What you see | Cause | What to do |
|---|---|---|
✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY., exit 1 | No key in any of the three sources. Raised before any request. | Run login, or export CODESPAR_API_KEY. |
✗ GET /v1/whoami → 401: Invalid API key, exit 1 | The API rejected the key. Measured against a 401 stub. | Check that the key matches the environment of its project. |
✗ GET /v1/whoami → <status>: <message>, exit 1 | Any other non-2xx. The client pulls message, then error.message, then a string error, and falls back to the status line alone. Measured at 401 and 403. | Read the message; it is the API's, not the CLI's. |
✗ Network error calling GET /v1/whoami: fetch failed, exit 1 | The host did not answer. Measured against a closed port. | Check --base-url and the network. |
✗ Request to GET /v1/whoami timed out after 30000ms., exit 1 | The per-request timeout. 30s is the client default and this command does not raise it. | Retry, or point at a reachable host. |
codespar logout
Removes the apiKey key from ~/.codespar/config.json and leaves everything else in place. No network call, no credential needed.
Flags none. --json is accepted and ignored: measured, codespar logout --json exits 0 with zero bytes on stdout and ✓ Logged out. on stderr. A script that parses that output gets an empty string.
codespar logout
cat ~/.codespar/config.json✓ Logged out.
{
"baseUrl": "http://127.0.0.1:8931"
}Two measured edges worth knowing before you trust it:
- On a machine that never logged in,
logoutstill creates~/.codespar/config.jsoncontaining{}, with mode0600. - It clears the file, not the environment. With
CODESPAR_API_KEYexported, the next command is still authenticated. Measured:whoamisucceeded afterlogoutwith the key in the environment.
codespar init <name>
Copies one of four templates shipped inside the package into ./<name> and substitutes {{name}} in both file contents and file names. Zero network, and no credential: measured with an empty config file and no CODESPAR_* variable set.
Argument and flags
| Name | Type | Required | What it does |
|---|---|---|---|
<name> | string | yes | Directory to create, and the value substituted for {{name}}. Must match ^[a-z0-9][a-z0-9-_]*$, case-insensitive. |
-t, --template <slug> | string | no | One of the four slugs below. Given, it skips the prompt. |
-y, --yes | boolean | no | Skip the prompt and take the first template, pix-agent. |
Templates
| Slug | Label | Framework |
|---|---|---|
pix-agent | Pix Payment Agent | OpenAI |
ecommerce-checkout | E-Commerce Checkout | Claude |
streaming-chat | Streaming Chat | Next.js + Vercel AI |
multi-tenant | Multi-Tenant Agent | Next.js + OpenAI |
codespar init loja-pix -y
ls -a loja-pixℹ Creating loja-pix using the Pix Payment Agent template...
✓ Created loja-pix/
Next steps:
cd loja-pix
cp .env.example .env # then fill in your keys
npm install
npm run dev
.env.example .gitignore README.md package.json src tsconfig.jsonWithout -t and without -y the command prints the four templates on stdout and asks Pick a number [1-4] (default 1):. An empty answer takes the first one.
Refusals
| What you see | Cause | What to do |
|---|---|---|
✗ Project name must start with a letter/number and contain only letters, digits, dashes, and underscores., exit 1 | The name failed the pattern. Measured with codespar init "my agent". | Drop spaces and punctuation. |
✗ Unknown template "nope". Available: pix-agent, ecommerce-checkout, streaming-chat, multi-tenant, exit 1 | -t did not match a slug. | Use one of the four. |
✗ Directory loja-pix/ already exists and is not empty. Choose a different name or delete it first., exit 1 | The target exists with something in it. An existing empty directory is accepted. | Pick another name. |
✗ Invalid choice: 9, exit 1 | The interactive picker got something outside the range. Measured; nothing was created. | Answer with a number in range, or use -t. |
✗ Template not found at <path>. Is the package installation corrupted?, exit 1 | The templates/ directory is missing from the installed package. | Reinstall the CLI. |
The config file
One file, ~/.codespar/config.json, created by login and rewritten by logout. Three keys are read and nothing else:
| Key | What it is |
|---|---|
apiKey | The credential. Written by login, removed by logout. |
baseUrl | The API host. Written by login. Read by every command except login itself. |
project | The project every request is scoped to. Nothing in this group writes it; set it by hand or pass --project. |
Every write goes through the same helper, which merges into whatever is already on disk and then chmods the file to 0600, because the file holds a credential. Measured after login: -rw-------.
Environment variables
Three carry credential and scope, and win over the file:
| Variable | Effect |
|---|---|
CODESPAR_API_KEY | The credential. Beaten by --api-key. |
CODESPAR_BASE_URL | The API host. Beaten by --base-url. Honored by login too, so you can point a whole session at a different environment. |
CODESPAR_PROJECT | The project. Beaten by --project, measured on the wire. |
Three more shape the output rather than the request:
| Variable | Effect |
|---|---|
NO_COLOR | Set to anything, and ANSI color is off. Color is also off whenever stdout is not a TTY. |
FORCE_COLOR | 0 turns color off; any other value turns it on even when stdout is piped. |
NO_BANNER | 1 suppresses the ASCII banner. The banner already stays away from subcommands, from --json, and from any non-TTY stdout. |
Root flags
Declared once on the root command and valid for every subcommand:
| Flag | What it does |
|---|---|
--api-key <key> | The credential, beating environment and file. Not honored by login, per the warning above. |
--base-url <url> | The API host. |
--project <id> | Scopes requests to a project. |
--json | Machine-readable output instead of tables. Accepted before or after the subcommand. |
-v, --version | Prints the version. The short form is -v, not Commander's usual -V. |
Whatever resolves ends up on the wire: every request carries Authorization: Bearer <key>, Content-Type: application/json and User-Agent: codespar-cli/<version>, plus x-codespar-project when a project resolved. Measured with --project proj_from_flag alongside CODESPAR_PROJECT=proj_from_env: the header carried the flag's value.
codespar --version, the startup banner and the User-Agent on every request all report the version the installed package declares — they read the manifest, so they cannot disagree with it or with each other. If you are correlating CLI traffic in your logs, the header is codespar-cli/<version>.
Exit codes and streams
| Code | When |
|---|---|
0 | Success, and --help. Also the silent no-op when login reads EOF from a closed stdin. |
1 | An expected refusal, printed to stderr as ✗ <message>. Also Commander's own argument errors, which print as error: <message> without the ✗. |
2 | An unexpected error: the CLI prints ✗ internal error: and a stack trace. Reserved for a bug. Read from the source; no probe in this group produced one. |
Tables, key/value blocks and JSON go to stdout. The ℹ, ✓ and ⚠ lines go to stderr, so codespar whoami --json | jq sees only the document. The exception in this group is the API key: prompt and the init template picker, both of which write to stdout.