Skip to main content

Execution and operation

Run one tool call from a terminal, read the schema before you do, follow the session it ran in, and tail what it logged.

6 min read
View MarkdownEdit on GitHub

Seven leaf commands run a tool and account for what it did: execute, ship, tools show, sessions list, sessions show, sessions close and logs tail. sessions, tools and logs are groups with no action of their own. Running one alone prints its own usage to stderr and exits 1.

They are not one client. sessions *, tools * and logs tail speak HTTP directly from the CLI. execute and ship go through the SDK, which opens a session, runs the call inside it and deletes the session in a finally. That seam is not cosmetic: measured, the two halves answer the same rejected key with different exit codes and disagree about what --project accepts. Both are below.

Every flag, message and exit code on this page was run against @codespar/cli 0.10.0 — the version on npm — and the transcripts are the captured bytes of those runs.

What every command here shares

Credentials and scope

Resolution order, first match wins: the root flag, then the environment variable, then ~/.codespar/config.json.

SettingRoot flagEnvironmentConfig key
API key--api-key <key>CODESPAR_API_KEYapiKey
API host--base-url <url>CODESPAR_BASE_URLbaseUrl
Project--project <id>CODESPAR_PROJECTproject

With nothing set, the host is https://api.codespar.dev. A resolved project rides on every request as the x-codespar-project header; with no project resolved the header is absent.

With no key resolved, all seven refuse before opening a socket, with the same line:

stderr, exit 1
✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.

That check runs inside the command, so an argument Commander itself rejects is reported first: codespar execute with no credential and no --server answers error: required option '-s, --server <id>' not specified, not the credential line.

--project is validated on one half and not the other. codespar --project proj_abc sessions list sent x-codespar-project: proj_abc without complaint. The same --project proj_abc on execute or ship exits 2 with Error: CodeSpar projectId must match ^prj_[A-Za-z0-9]{16}$, because the SDK constructor checks the shape and the direct-HTTP client does not. A project id of the prj_ + 16 characters form works on both.

stdout, stderr and --json

Tables, key/value blocks and JSON go to stdout. Status lines go to stderr, prefixed , or , and an empty table prints (no results) there too. A pipe into jq receives the data alone.

--json is declared on the root command, but Commander accepts it on either side of the subcommand: codespar --json sessions list and codespar sessions list --json printed identical JSON with an empty stderr. Colour is dropped when stdout is not a TTY, when NO_COLOR is set, or when FORCE_COLOR=0.

Two commands on this page take --json and ignore it, because they have no JSON path at all: sessions close prints its confirmation to stderr and leaves stdout empty either way, and ship without --json writes nothing to stdout.

Exit codes

CodeWhenFirst line of stderr
0The command finished, including --helpnothing
1A refusal the CLI raises itself, or an argument Commander rejects✗ <message>, or error: <message> for Commander's own
2Any other exception. The stack trace follows✗ internal error:

There is no uniform exit code for "the tool call failed." Measured against one stub answering {"success": false, "error": "no_eligible_providers"} to the same route: codespar execute printed Tool call failed: no_eligible_providers to stderr, printed the envelope to stdout, and exited 0. codespar ship, on the identical response, exited 2 with ✗ internal error: and a stack trace reading Error: ship failed: no_eligible_providers. The typed SDK wrapper behind ship throws a plain Error, which the CLI does not recognise as its own refusal type. A script that branches on the exit code of ship is branching on something else.

The same seam shows on rejected credentials. A 401 on sessions list is one line and exit 1; a 401 on execute or ship, raised while creating the session, is exit 2 with a stack trace naming createSession failed: 401.

codespar execute <tool>

Opens a throwaway session against one server, sends one tool call into it, prints the result envelope, and deletes the session in a finally. Three requests per run: POST /v1/sessions, then the execute, then DELETE /v1/sessions/{id}.

POSThttps://api.codespar.dev/v1/sessions/{id}/execute
Can move money

Resolves `tool` as a CodeSpar meta-tool first, then as a catalog tool

NameTypeRequiredWhat it does
<tool>positionalyesThe tool name sent as tool in the request body
-s, --server <id>stringyesThe session is opened with servers: ["<id>"]. Declared as a required option, though --help does not mark it
-i, --input <json>stringnoParsed as the input object. Mutually exclusive with --input-file
-f, --input-file <path>stringnoSame, read from a file
-u, --user <id>stringnoSent as user_id when the session is created. Defaults to the literal cli-user, set in the command body and therefore absent from --help

With neither --input nor --input-file, the input is the empty object {}.

Command
codespar execute codespar_ship \
  --server melhor-envio \
  --input '{"action":"quote"}'
stdout
exit 0
{
  "success": true,
  "data": {
    "id": "shp_01J",
    "status": "quoted",
    "carrier": "Correios",
    "cost_minor": 2140,
    "estimated_delivery": "2026-09-17"
  },
  "duration": 734,
  "server": "melhor-envio",
  "tool": "codespar_ship",
  "tool_call_id": "tcl_01J"
}

Without --json, stdout carries the same envelope and stderr carries one line, ✓ codespar_ship succeeded in 734ms. With --json, stderr is empty and stdout is unchanged: the envelope is already the machine-readable form.

Calls POST /v1/sessions with {"servers": ["<--server>"], "user_id": "<--user>"}, then POST /v1/sessions/{id}/execute with {"tool": "<tool>", "input": {...}}, then DELETE /v1/sessions/{id}. All three measured on the wire. The SDK equivalent is session.execute.

WhenWhat you getExit
--server omittederror: required option '-s, --server <id>' not specified1
--input and --input-file both given✗ Pass either --input or --input-file, not both.1
--input is not an object✗ --input must be a JSON object. (an array or a scalar is refused)1
--input is not JSON✗ --input is not valid JSON: <the parser's own message>1
--input-file does not exist✗ internal error: and a stack trace reading ENOENT: no such file or directory2
Key rejected while opening the session✗ internal error: and a stack trace reading createSession failed: 401 <body>2
The tool ran and failedTool call failed: <message> on stderr, the envelope on stdout0

codespar ship

Opens a session attached to no server, runs the codespar_ship meta-tool inside it, and closes it. The meta-tool picks the carrier, so there is no --server: the whole operation arrives as one JSON object under --input.

POSThttps://api.codespar.dev/v1/sessions/{id}/execute
Can move money

Resolves `tool` as a CodeSpar meta-tool first, then as a catalog tool

NameTypeRequiredWhat it does
-i, --input <json>stringone of the twoThe codespar_ship arguments as a JSON object
-f, --input-file <path>stringone of the twoThe same object, read from a file
-u, --user <id>stringnoSent as user_id when the session is created. Defaults to cli-user

The CLI checks four things before it opens the session, and nothing else. These are the fields it names, read from the command's own validator:

FieldRequiredWhat the CLI enforces
actionyesOne of label, quote, track. Anything else is refused
tracking_codefor trackMust be present when action is track
origin, destinationfor label, quoteBoth must be present
itemsfor label, quoteMust be a non-empty array

That table is the CLI's check, not the tool's schema. The shapes of origin, destination and each item, the optional service_level and metadata, and every result field beyond the ones printed below are the server's contract, not this command's. Read them on codespar_ship before building a payload.

Command
codespar ship --input '{
  "action": "quote",
  "origin": { "postal_code": "01310100" },
  "destination": { "postal_code": "22041011" },
  "items": [{ "weight_g": 500 }]
}'
stderr
exit 0 — stdout is empty
✓ ship shp_01J → quoted
ℹ Carrier: Correios
ℹ ETA: 2026-09-17
ℹ Cost (minor units): 2140

The human form prints, when the result carries them and only then, the tracking code, the carrier, the label URL, the estimated delivery and the cost in minor units. Every one of those lines goes to stderr; without --json this command writes nothing at all to stdout. With --json, stdout carries the typed result object and stderr is empty.

Calls POST /v1/sessions with {"servers": [], "user_id": "<--user>"}, then POST /v1/sessions/{id}/execute with {"tool": "codespar_ship", "input": {...}}, then DELETE /v1/sessions/{id}. The SDK equivalent is session.ship.

WhenWhat you getExit
Neither --input nor --input-file✗ ship requires --input '<json>' or --input-file <path>. followed by a worked example1
Both given✗ Pass either --input or --input-file, not both.1
action missing or unknown✗ ship.action must be one of: label, quote, track.1
action: "track" with no code✗ ship.tracking_code is required when action=track.1
label or quote without both addresses✗ ship.origin and ship.destination are required when action=label|quote.1
label or quote with an empty items✗ ship.items must contain at least one item when action=label|quote.1
The tool ran and failed✗ internal error: and a stack trace reading Error: ship failed: <message>2

All six validations run before the session is opened, so a rejected payload costs no request.

codespar tools show <name>

Prints one tool's identity and its prose description, found inside its server's tool listing.

GEThttps://api.codespar.dev/v1/servers/{id}/tools
NameTypeRequiredWhat it does
<name>positionalyesThe tool to find in that server's listing
-s, --server <id>stringyesThe server that exposes it
--jsonbooleannoRoot flag. Prints the tool's object verbatim

Changed in 0.8.0. --server is now required, and the command no longer prints input and output schemas: the per-server listing carries a name and a description. For a meta-tool's full schema use codespar tools meta <name>, which reads the published definitions without a network call.

Command
codespar tools show codespar_ship
stdout
exit 0
Name    codespar_ship
Server  codespar

Quote, label or track a shipment.

Input schema:
{
  "type": "object",
  "properties": {
    "action": {
      "enum": [
        "label",
        "quote",
        "track"
      ]
    }
  },
  "required": [
    "action"
  ]
}

The description, the input schema and the output schema each print only when the route returns them; the Name and Server pair always prints. An Output schema: block follows the input one under the same rule.

Calls GET /v1/servers/{id}/tools and finds the named tool in the listing.

WhenWhat you getExit
<name> omittederror: missing required argument 'name'1
No --serverThe refusal above, naming codespar servers list. Nothing is sent1
Name not on that server✗ <server> exposes no tool called "<name>". Run `codespar tools list --server <server>` …1
Key rejected✗ GET /v1/servers/<id>/tools → 401: <the API's own message>1

codespar tools list

Lists tools across the catalog, or one server's, with each description cut to fit a terminal column. It takes -s, --server <id>, which is sent as the server query parameter, and the root --json. Its full block, including the truncation rule and the empty-result behaviour, is on Catalog and discovery, because it reads the catalog rather than a running session, which is why it lives there.

codespar sessions list

Lists the sessions the key can see as a six-column table, newest first as the route returns them. Both filters are forwarded to the route verbatim; the CLI parses neither and defaults neither.

GEThttps://api.codespar.dev/v1/sessions
NameTypeRequiredWhat it does
--status <s>stringnoSent as the status query parameter, unparsed
--limit <n>stringnoSent as the limit query parameter, unparsed
--jsonbooleannoRoot flag. Prints the array inside the envelope, not the envelope

--status is checked against the vocabulary the listing declares — active, closed, error — and --limit must be a positive whole number. Either one outside that fails before anything is sent:

exit 1
✗ --status expects one of active, closed, error, got "inventado".
✗ --limit expects a positive whole number, got "abc".

When a flag is omitted its query parameter is simply absent: codespar sessions list with no flags sends GET /v1/sessions with no query string, and the server applies its own default page size.

Command
codespar sessions list --status active --limit 5
stdout
exit 0
ID        USER      STATUS  SERVERS  TOOL CALLS  CREATED
ses_01J8  cli-user  active  asaas    3           2026-09-12 14:02:11
ses_01J7  agent-7   closed           12          2026-09-12 11:47:03

A missing field prints -; SERVERS is the list joined with commas, and CREATED is the timestamp cut to YYYY-MM-DD HH:MM:SS. An empty result prints (no results) on stderr with nothing on stdout, and still exits 0; --json prints [].

Calls GET /v1/sessions, reading the rows from a data array in the response.

WhenWhat you getExit
Key rejected✗ GET /v1/sessions → 401: <the API's own message>1
Scope rejected✗ GET /v1/sessions → 403: <the API's own message>1
No sessions match(no results) on stderr, empty stdout0
Request exceeds 30s✗ Request to GET /v1/sessions timed out after 30000ms.1

codespar sessions show <id>

Prints one session as a seven-line key/value block, and with --logs fetches that session's tool calls in a second request and prints them as a table underneath.

GEThttps://api.codespar.dev/v1/sessions/{id}
NameTypeRequiredWhat it does
<id>positionalyesPercent-encoded into the path
--logsbooleannoMakes a second request for the session's tool calls and prints them
--jsonbooleannoRoot flag. See the warning below: --logs changes what the JSON root is
Command
codespar sessions show ses_01J8 --logs
stdout
exit 0
ID          ses_01J8
User        cli-user
Status      active
Servers     asaas
Tool calls  2
Created     2026-09-12T14:02:11.000Z
Closed      -

Logs:
TOOL             SERVER    STATUS   MS   AT
codespar_ship    codespar  success  812  14:02:14
codespar_charge  codespar  error    190  14:03:01

Created and Closed print the raw timestamp, unlike the list, which trims it. The log table's AT column is the time of day only.

--logs changes the root of the JSON. Without it, --json prints the session object itself. With it, the root becomes an envelope, {"session": {...}, "logs": [...]}. A parser written against one form breaks on the other, and the flag that switches them is not a formatting flag.

Calls GET /v1/sessions/{id}, plus GET /v1/sessions/{id}/tool-calls when --logs is given.

WhenWhat you getExit
<id> omittederror: missing required argument 'id'1
Key rejected✗ GET /v1/sessions/<id> → 401: <the API's own message>1
Session not found✗ GET /v1/sessions/<id> → 404: <the API's own message>1

codespar sessions close <id>

Closes an active session by id and prints one confirmation line. It takes no flags of its own, and --json does nothing: stdout stays empty either way.

DELETEhttps://api.codespar.dev/v1/sessions/{id}
NameTypeRequiredWhat it does
<id>positionalyesPercent-encoded into the path
Command
codespar sessions close ses_01J8
stderr
exit 0 — stdout is empty
✓ Session ses_01J8 closed at 2026-09-14T16:41:02.118Z.
WhenWhat you getExit
<id> omittederror: missing required argument 'id'1
Key rejected✗ DELETE /v1/sessions/<id> → 401: <the API's own message>1
Session not found✗ DELETE /v1/sessions/<id> → 404: <the API's own message>1

codespar logs tail

Reads the project's recent tool calls and prints one fixed-width line each, oldest first. With --follow it keeps polling for new ones until you interrupt it.

GEThttps://api.codespar.dev/v1/tool-calls
NameTypeRequiredWhat it does
--limit <n>numbernoHow many recent calls to read. Default 20; a non-integer is refused before anything is sent
-f, --followbooleannoKeep polling every 3s, printing rows not seen yet
-s, --server <id>stringnoKeeps only this server — applied to the page fetched, not by the API
--status <s>stringnoSame, and same caveat
-t, --tool <name>stringnoSame, and same caveat

This is a poll, not a stream. The API has no push channel for the tool-call log, so --follow re-reads the most recent page every three seconds and prints the rows it has not seen.

The three filters are applied client-side, to the page that was fetched, because the route takes no such query parameters. Whenever one is set, the command prints how many of the fetched rows matched — so an empty result cannot be read as "nothing happened".

| --json | boolean | no | Root flag. One JSON document per entry (see below) |

Command
codespar logs tail --status error --tool codespar_charge
stdout
exit 0
14:02:14  SUCCESS  codespar_ship             melhor-envio      812ms
14:03:01  ERROR    codespar_charge           asaas             190ms no_eligible_providers

The columns are padded to fixed widths (status to 7, tool to 24, server to 16), with the status green, red or yellow for success, error and running. Tailing logs — press Ctrl-C to stop goes to stderr before the first frame. A frame the parser cannot read is skipped in silence, which is how keep-alives pass through.

--json here is not one document. It prints a separate JSON object per entry onto the same stdout, back to back, with no array around them and no delimiter between them. A plain | jq over the stream will not parse it; a line-delimited or streaming reader will.

Calls GET /v1/tool-calls, once without --follow and once per poll with it.

WhenWhat you getExit
The route answers 404The streaming-not-available message below, naming the one-shot alternative1
The route answers 401 or 403The auth message below, carrying the status it got1
Any other status✗ Log stream returned <status>. and an invitation to report it1
The connection cannot be opened✗ Failed to connect: <the network error's own message>1

The first two, verbatim:

stderr, exit 1
✗ Auth failed (401). Check CODESPAR_API_KEY or re-run `codespar login`.
  • codespar_ship: the schema, the carriers and the result shapes behind codespar ship
  • Sessions: the HTTP routes the session commands call, and the async-settlement flow a tool call starts
  • session.execute and session.ship: the SDK calls execute and ship wrap
  • Catalog and discovery: tools list, and the commands that find a tool worth executing
  • Debugging: what to do with a tool call once you have its id
Execution and operation | CodeSpar