Struct CLI
Sign in to Struct from your terminal, connect your coding agent
(Claude Code, Cursor, Codex, or any MCP client) to Struct, and set up Auto Monitors. The CLI works the same for
people and for agents and scripts.
Full documentation: docs.struct.ai/reference/cli.
Install
Requires Node.js 18 or newer.
npm install -g @struct-ai/cli
Or run it without installing:
npx @struct-ai/cli <command>
Commands
struct login
Signs you in and saves a login for one workspace. The CLI prints the sign-in link first
and then waits until you approve, like gh auth login:
- On your own computer it opens the link in your browser. After you approve, the
browser returns to a temporary local address on this computer and the CLI finishes.
- Over SSH, on Linux without a display, or when no browser opens it shows a link and
a short code instead. Open the link on any device, check that the code matches, and
approve. Use
--device to choose this yourself.
Device login needs a Struct server that supports it. If yours does not, the CLI prints
the browser link instead (and --device fails): open it in a browser that can reach
this machine, because approving it returns to a temporary local address here.
--org <workspace> | The workspace to approve (name or id). Pick it on the approval page. |
--device | Sign in with a code on any device. |
--no-browser | Print the link without opening a browser. |
--with-token | Save a token read from stdin instead of signing in. |
--timeout <seconds> | How long to wait for approval (default 900, which is 15 minutes). |
--admin | Also ask for the admin permission (workspace admin commands). |
Access tokens last one hour; after 90 days without a renewal you sign in again. If you
are already logged in to that workspace, the CLI asks whether to sign in again; without a
terminal it does not ask (exit 2): run struct logout first. A browser login that can
only read is the exception: struct login (or struct login --org <workspace>) replaces it without asking, also without a
terminal (the new login is saved before the old one is revoked). That replacement must be
for the same workspace: approving another one saves nothing (workspace_mismatch); use
struct login --org <workspace> to add another workspace.
--with-token does not replace a browser login: if the workspace already has one saved,
it exits 1 (oauth_login_exists) and changes nothing. Keep using that login for its
own tokens, or run struct logout --org <workspace> first to revoke it, then retry with an
independently issued token (logout also ends tokens issued to that login).
With --org, if you approve a different workspace the CLI saves nothing, revokes the
new login and exits 1; the browser page says which workspace to choose instead. The
approval page does not preselect the workspace yet, so choose it there.
--admin asks for one more permission, shown as its own line on the approval page:
managing the workspace's ingest keys, deployment keys and GitHub deploy webhook
(struct keys, struct github deploy-webhook). It covers only credentials that let
something into Struct; everything else, including Fix agent secrets and other tools' keys,
works with an ordinary login. It works only while you are a workspace admin. Running struct login --admin on a login
without it signs in again and replaces that login.
No login, with or without --admin, can change the plan, buy credits, set up auto top-up
or read billing details. Do those in the Struct web app.
struct whoami
Shows the user, workspace and role of the current login.
struct orgs list and struct orgs switch <workspace>
You can be logged in to several workspaces. Each login belongs to one workspace on one
Struct server and is approved separately. orgs list shows the workspaces you have a
login for (the current one is marked *). orgs switch selects one; if you have no
login for it yet, it starts struct login --org <workspace>.
struct logout
Revokes the current workspace's login on Struct, then removes it from this computer.
Your other workspaces and coding agents that signed in themselves stay signed in.
Logout also revokes old struct connect grants saved on this computer; use
struct mcp install to switch those agents to their own sign-in.
--org <workspace> logs out of another saved workspace; --all logs out of every
workspace on this server (it cannot be combined with --org). A workspace with no
saved login, or a name that matches several, is a usage error (exit 2).
- If Struct cannot revoke the login (for example, it cannot be reached), the login is
still removed from this computer, but the command exits
1 and says so. Disconnect
the Struct CLI in Struct settings to end it.
- A token you added with
--with-token is only removed from this computer; revoke it
where you created it.
struct mcp install --client <claude|cursor|codex> and struct mcp url
Connects your coding agent to Struct's MCP server. The configuration contains only the
server URL: your agent signs in to Struct itself the first time it connects, so no token
is printed or stored in its settings.
claude | Runs claude mcp add --transport http --scope user struct <url> (Claude Code, for all your projects). |
codex | Adds a [mcp_servers.struct] table to ~/.codex/config.toml (or $CODEX_HOME/config.toml), keeping your settings. |
cursor | Adds a struct entry to ~/.cursor/mcp.json, keeping your other servers. |
If claude is not installed, the CLI prints the command to run instead. Afterwards, sign
in to Struct from your agent: in Claude Code, run /mcp; for Codex, run
codex mcp login struct (the --json result names this nextStep). Installing never
waits for a browser. Running the install again changes nothing when the entry is already
there, and a config file the CLI cannot read safely (for example, invalid TOML or JSON)
is left untouched. Add --print to
only print the command (or, for Cursor, an install link, and for Codex, the configuration
to add) without changing anything. struct mcp url prints the URL for any other MCP
client.
struct connect without a provider (or with --agent) from earlier versions still works
but is deprecated: it prints the same URL-only setup and the new command to use.
struct connect <provider>
Connects an observability tool with its API key: Datadog, Grafana, Loki, Prometheus,
Axiom, Better Stack, Mezmo, SumoLogic and Render, or AWS CloudWatch with an IAM role.
Struct checks the key or role with the tool, saves it, then tests the saved connection.
DATADOG_API_KEY=… DATADOG_APP_KEY=… struct connect datadog --site us5 --name "Prod Datadog"
GRAFANA_TOKEN=… struct connect grafana --url https://acme.grafana.net --org-id 1
LOKI_TOKEN=… struct connect loki --deployment grafana-cloud --url https://logs.grafana.net --instance-id 123
PROMETHEUS_PASSWORD=… struct connect prometheus --url https://prom.example --auth basic --username reader
BETTERSTACK_TOKEN=… BETTERSTACK_SQL_PASSWORD=… struct connect betterstack --sql-host eu-nbg-2-connect.betterstackdata.com --sql-user reader
- Each tool's flags and environment variables are listed by
struct connections catalog <provider>.
- Secrets come from their environment variable, from stdin (
--api-key-stdin and the
like, or --stdin for the only one missing) or from a hidden prompt in a terminal. A
secret given as a flag works, with a warning that it may be saved in your shell
history. Secrets are never printed.
- Without a terminal nothing is asked: a missing value exits
2 (missing_fields) and
names its flag and variable.
--name and --instructions (when agents should use it) are optional; by default the
connection is named after the tool.
- Connecting the same account again updates its connection instead of adding another
(
"created": false). URL-based accounts also include their organization, tenant or
authentication identity; key-based accounts include their site, region or organization.
- The result's
connection.health says whether the access Struct needs is there; the
exit code is 0 either way. Refusals keep a stable code, for example
credentials_rejected, invalid_fields, unsafe_provider_url or
connections_restricted_to_admins.
AWS. Struct assumes an IAM role in your account, trusted with an External ID Struct makes
for your workspace. Print the setup commands, run them in AWS CloudShell (the CLI never runs
anything in your AWS account), then connect with the command they end with:
struct connect aws --print-commands [--region eu-west-1]
struct connect aws --role-arn arn:aws:iam::123456789012:role/StructCloudWatchRole --region eu-west-1 --external-id struct-…
--region defaults to AWS_REGION when it is set, as for the connect.
New connections require an External ID Struct made for your workspace (external_id_not_issued
otherwise), and each connection needs its own. Reconnecting the same role may keep its saved
External ID. A new role that AWS still refuses is tried again for up to 90 seconds
(permissions_propagating after that).
Connecting needs permission to change connections (an admin, or any member while
connections are not restricted to admins).
struct automonitors
Sets up Auto Monitors. Changing settings needs the configure permission level (the
permission field of struct whoami --json shows yours); every member can read them.
settings get | Shows whether Auto Monitors is on, how deploys are reported, the alert channels and the providers. |
settings set --alert-channel <channel> | Sends alerts to a Slack channel (id, #name or link) and makes it the default channel. |
settings set --environment <name> | Sets the deployment environment treated as production. |
channels list | Lists the alert channels. |
channels add <channel> [--default] [--description <text>] | Sends alerts to another channel. Struct's bot joins public channels itself; invite it to private ones first. |
channels remove <channel> | Stops alerts to a channel. A channel outside the alert list changes nothing (exit 0); an unresolvable name or link fails. |
enable | Turns Auto Monitors on. Safe to repeat. The result lists anything still missing. |
disable --yes | Turns Auto Monitors off. Without a terminal, or with --json, --yes is required. |
providers enable|disable <provider> | Opts a connected provider in or out. Other providers' settings stay as they are. |
settings set aws --log-groups <g1,g2> | Sets the CloudWatch access-log groups AWS baselines read from (an empty value clears them). |
check <provider> | Checks the provider's access now: connection, configuration, reads and writes, with a grant command for what is missing. |
struct deploys
Chooses how deploys reach Struct, which is what Auto Monitors starts from.
setup github [--environment <name>] | Uses deployment events from the Struct GitHub App. |
setup workflow [--workflow <path>] [--environment <name>] | Counts successful runs of a GitHub Actions workflow as deploys. Defaults to the saved workflow, or Struct's recommendation. Struct creates the repository webhook when the GitHub App may. |
setup webhook [--environment <name>] | You report deploys with POST /api/deployments and a deploy key that an org admin creates. |
show | Shows the deploy signal and whether Struct is ready for it, or what is missing. |
workflows list | Lists the default repository's workflows, with Struct's recommendation and the saved one (selected). github_unavailable when GitHub cannot be read, github_not_connected without it. |
environments list [--query <text>] [--limit <n>] | Lists GitHub environment names. The limit defaults to 20 and accepts 1–50. |
The CLI never shows the webhook URL or signing secret. If Struct cannot create the
webhook itself, an org admin finishes the setup in the Struct web app, where they are
shown.
struct connections catalog|list|show|test
-
struct connections catalog [<provider>] lists what you can connect. With a provider
(for example datadog or postgres) it shows how to connect it: the fields, the
environment variable and flag for each, whether it needs a browser, the access it
needs in that tool and the commands that grant it.
-
struct connections list [--type <type>] lists your workspace's connections with
their health: ok; needs_reauth (Struct has no usable stored credential: it was
removed, or an OAuth refresh was refused; reconnect); missing_permissions (the
credential works but lacks access); error (the last test failed, including the tool
rejecting the key or password: check it; the detail gives the tool's reason); or
unknown (not tested since the connection or its credential last changed). --type takes a kind
(oauth_app, observability, data, custom_mcp) or a provider.
-
struct connections show <id> shows one connection's settings (for a database, its
saved host, database and user, never the secret), health and missing access. --grant-command prints only the command or steps that grant the missing
access, for piping.
-
struct connections update <id> [--name <name>] [--instructions <text>] [--role-arn <arn>] [--region <region>]
changes a connection; changed AWS settings are tested before they are saved.
-
struct connections remove <id> --yes removes an AWS connection; it is refused
(connection_in_use) while an SSH tunnel uses its role.
-
struct connections grant-command <id> prints the command that grants what the
connection needs (for what the last check found missing, or the full upgrade).
-
struct connections test <id> tests a saved connection with its stored credential
and records the result as its health. Without an id it tests every connection that
can be tested. --provider <provider> tests new values without saving anything; it
reads each field from the environment variable the catalog names, or from
--field key=value:
DATADOG_API_KEY=… DATADOG_APP_KEY=… struct connections test --provider datadog --json
The result's ok and health say whether the test passed; the exit code is 0
either way. Secrets passed with --field may be saved in your shell history, so the
CLI warns about them; they are never printed.
Testing new values needs permission to change connections (an admin, or any member
while connections are not restricted to admins).
struct connect github|slack|sentry|linear|jira|asana
Connects an app that you approve in a browser. The CLI prints the link (and opens it
when this machine has a browser), then waits. Open it in a browser signed in to Struct
as the same user this CLI is logged in as: the link works once, in that one browser,
for 10 minutes. Approve Struct on the app's page, and the terminal prints the result.
$ struct connect slack --json
{"status":"waiting_for_human","provider":"slack","url":"https://app.struct.ai/connect/session?token=...","expiresAt":"...","sessionId":"..."}
{"status":"connected","provider":"slack","connection":{"id":"...","provider":"slack","account":"Acme","accountId":"T0123"}}
- An app that is already connected says so; GitHub still applies
--repo if supplied.
--reauth reconnects it (for example to approve new permissions); a reconnect keeps
the same organization, site or workspace.
- When your login reaches several, choose one in the browser, or name it here:
--org (GitHub account or organization, Sentry organization), --site (Jira site
name or URL) or --workspace (Asana). With several and no flag, --json prints a
second waiting_for_human line with "step":"choose" and the options.
Cancelling the choice in the browser ends the command with cancelled.
struct connect github --repo owner/name also sets the default repository.
--no-browser only prints the link; --timeout <seconds> (default 600) limits the wait.
- Linear, Jira and Asana are part of the Pro plan (
plan_required otherwise).
- Connecting needs permission to change connections (an admin, or any member while
connections are not restricted to admins). The CLI never sees the app's tokens.
struct github, struct sentry and struct slack channels
struct github repos | Lists the repositories the Struct GitHub App can access; * marks the default. |
struct github set-repo <owner/name> | Sets the default repository. It must be one the app can access (repository_not_found otherwise). |
struct sentry orgs | Lists the organizations the connected Sentry user belongs to; * marks the one Struct reads. |
struct sentry set-org <slug> | Chooses the organization Struct reads (organization_not_found if the user is not a member). |
struct slack channels list [--query <text>] [--archived] [--limit n] [--cursor c] | Searches the workspace's channels and says whether Struct's bot is in each. |
struct slack channels join <channel> | Has Struct's bot join a public channel by id, #name or link. Safe to repeat. Invite it to private channels in Slack. |
Setting a repository, an organization or joining a channel needs permission to change
connections; listing is open to every member.
struct triage
Chooses which Slack channels Struct watches and when it investigates on its own. Changes
need the configure permission (struct whoami --json shows yours); every member can read.
struct triage get slack | Lists the triage channels with their Investigate (Ambient in Struct teammate channels) and Alerts state and keywords. |
struct triage slack add <channel> [--no-investigate] | Adds a channel by id, #name or Slack link. Struct's bot joins public channels itself; invite it to private channels first. New channels auto-investigate unless you pass --no-investigate. Safe to repeat. |
struct triage slack set <channel> [--investigate on|off] [--include k1,k2] [--exclude k1,k2] | Changes only the settings you pass. --include and --exclude replace that channel's keywords; "" clears them. |
struct triage slack remove <channel> | Takes the channel out of automatic triage: no alerts, no automatic investigations. The bot stays in the channel, so an @Struct mention can still start an investigation. Safe to repeat. |
Each change touches only the one channel, so changes from the CLI and the web app do not
overwrite each other.
struct build-agent settings show and struct build-agent settings set
Shows or changes the Fix agent: who writes the fix (struct for the Struct Build Agent,
cursor or devin), when Struct opens a pull request (manual: when asked, auto:
automatically), and the investigation confidence an automatic pull request needs (4:
Very Likely or higher, 5: Definite). show also lists the sources that open pull
requests automatically and whether Cursor and Devin keys are saved (never the keys).
struct build-agent settings set --provider struct --trigger auto --min-confidence 5
Any member can run show; set needs a workspace admin (forbidden otherwise).
Cursor needs a saved Cursor API key, and Devin a Devin API key and organization ID: until
they are saved, set --provider cursor (or devin) exits 1 with
cursor_api_key_required, devin_api_key_required or devin_org_id_required. With
--provider cursor or --provider devin, the key in CURSOR_API_KEY or DEVIN_API_KEY
is sent in the same change:
CURSOR_API_KEY=… struct build-agent settings set --provider cursor
DEVIN_API_KEY=… struct build-agent settings set --provider devin --devin-org-id org-…
struct build-agent keys set and struct build-agent keys remove
Save, replace or remove the Cursor or Devin API key without changing the provider
(workspace admins). keys set cursor reads the key from --stdin, else
CURSOR_API_KEY (DEVIN_API_KEY for Devin), else a hidden prompt in a terminal. Keys
are never printed; Struct only reports whether one is saved. Removing the key of the
selected provider switches Fix to the Struct Build Agent, and the CLI says so.
printf %s "$KEY" | struct build-agent keys set devin --stdin
struct build-agent keys remove cursor
struct build-agent repos, scripts and prompt
Set up the repositories the Struct Build Agent works on. Any member can list and show;
changes need a workspace admin. <repo> is owner/name, a configuration name or its id.
struct build-agent repos available --search api
struct build-agent repos add --repo acme/api --node 22
struct build-agent repos set acme/api --node 24
struct build-agent scripts set acme/api --pre --file setup.sh
struct build-agent scripts set acme/api --post --file check.sh
struct build-agent prompt set acme/api --file prompt.md
struct build-agent scripts show acme/api --pre > setup.sh
struct build-agent repos remove acme/api --yes
repos add needs a GitHub connection that can reach the repository
(github_not_connected, github_repository_not_accessible); a repository has one
configuration (repository_already_configured). Node.js can be 18, 20, 22 or 24 (default
24). scripts show and prompt show print nothing on stdout and exit 0 when none is set
("set": false with --json). Deletes ask first in a terminal; elsewhere pass --yes.
struct memory struct-md show, generate and edit
STRUCT.md is the business context every Struct agent reads. show prints its body on
stdout (title, version and generation status go to stderr). To edit it safely, take the
body and its version from the same show --json answer, so Struct refuses your edit if
someone saved in between instead of overwriting their change:
struct memory struct-md show --json > struct-md.json
jq -j .body struct-md.json > STRUCT.md
struct memory struct-md edit --file STRUCT.md \
--expected-version "$(jq .version struct-md.json)" --reason "Added the billing service"
generate has an agent write STRUCT.md while it is empty (or again after a failed
attempt). It needs a workspace admin. Add --wait to wait until it finishes
(--timeout <seconds>, default 900, which also bounds each check). Running it while a
generation is under way follows that generation.
edit changes the title (--title) and/or body (--file <path>, or --file - for
stdin; at most 4,000 tokens). --reason is saved with the new version. The CLI edits
the current version; pass --expected-version <n> (the version from
show --json) to refuse the edit if someone saved in between
(memory_version_conflict). Edits are refused while a generation is running
(struct_md_generating). Editing needs a workspace admin.
- If memories are not turned on for your workspace, these commands exit
1 with
memories_not_enabled.
struct artifacts list|show|versions|pdf
Reads the reports Struct saves, such as investigation write-ups and plans. You see the
same reports as in the Struct web app: your team's reports and your own private ones.
struct artifacts list | Lists reports, most recently changed first. |
struct artifacts show <id> | Prints the report's Markdown on stdout (title, kind, version and link go to stderr). With --json, prints the whole report. |
struct artifacts versions <id> | Lists the report's saved versions, newest first. --before <n> shows older ones; --limit <n> (1 to 100). |
struct artifacts pdf <id> | Saves the report as a PDF with its charts; diagrams are left out, with a note in their place. -o <file> (default <id>.pdf, or <id>-v<n>.pdf with --version); --force replaces the file. |
list takes --search <words> (words in the latest version; quotes match a phrase and
or matches either word), --kind <investigation|incident|postmortem|plan|build_summary>,
--since <when> (an age such as 7d or 12h, a date such as 2026-10-01, or a time with
a zone), --limit <n> (1 to 100, default 25) and --cursor <cursor> (the nextCursor of
the previous page, with the same filters; the next-page hint on stderr includes them).
show and pdf take --version <n> to read a saved version instead of the latest. pdf
checks the destination before exporting: a missing folder fails with output_dir_missing,
a folder as the destination with output_is_directory, and an existing file without
--force with file_exists. A report that does not exist and one you cannot see both
fail with the error code not_found.
struct skills list|search|show|create|edit|auto-manage|delete
Skills are the step-by-step guides Struct's agents follow, the same ones as on the Skills
page in the Struct web app. A skill has a title, an optional description (the symptom it
applies to, which search matches) and its steps in Markdown, one ## Step N: title
heading per step. Any member can read and change skills.
struct skills list | Lists skills, newest first: id, created, auto or people (who manages it), title and description. --page <n> (from 0), --limit <n> (1 to 200, default 50). |
struct skills search "<words>" | Finds skills whose title, description or steps match, with the best matching text. --limit <n> (1 to 20, default 10). |
struct skills show <id> | Prints the title, description and steps. --markdown prints only the steps exactly as stored. With --json, prints the whole skill. |
struct skills create | Creates a skill: --title, optional --description, and --file <path> with the steps (--file - reads stdin). Running it twice makes two skills. |
struct skills edit <id> | Changes --title, --description (or --clear-description to remove it) and/or the steps (--file, which replaces all of them). |
struct skills auto-manage <id> <on|off> | on lets Struct refine the skill from investigation feedback; off keeps it as people wrote it. |
struct skills delete <id> | Deletes the skill. Asks first in a terminal; elsewhere pass --yes. |
To change the steps safely, take them and the version from the same show --json answer,
so Struct refuses your edit if someone saved in between (skill_version_conflict) instead
of overwriting their change:
struct skills show <id> --json > skill.json
jq -j .markdownBody skill.json > skill.md
struct skills edit <id> --file skill.md --expected-version "$(jq .version skill.json)"
Without --expected-version, edit uses the version it reads just before saving. A
skill that does not exist fails with skill_not_found.
struct members list
Lists the workspace's members with their role (admin or member), admins first. Any
member can run it. With --json it prints {"members":[{userId,email,name,role,joinedAt}]}.
struct keys ingest|deploy list|create|revoke
Manages the workspace's ingest keys (pk-, for the Struct SDKs) and deployment keys
(sk-, for recording deploys). You must be a workspace admin and signed in with
struct login --admin. A login without the admin permission gets admin_login_required
(exit 4); with that permission, a non-admin gets admin_required.
struct keys <ingest|deploy> list | Lists keys: id, name, the first characters, created and expiry. Never the key itself. --all includes expired keys; --limit <n>, --page <n>. |
struct keys <ingest|deploy> create | Creates a key and prints it once. --name <name> (required), --description <text>, --expires-at <date or time with a zone>. |
struct keys <ingest|deploy> revoke <id> | Revokes a key; anything using it stops working. An unknown id fails with key_not_found. |
create prints the new key as the only line on stdout (details go to stderr), so
STRUCT_INGEST_KEY=$(struct keys ingest create --name sdk) works; with --json it is the
token field. Struct never shows it again, so store it right away.
struct build-agent secrets list|set|remove
Manages the environment variables the Struct Build Agent gets when it works on your
repositories (they apply to every repository configuration). They need a workspace admin;
an ordinary struct login is enough (a member gets admin_required). Values are
write-only: list shows names only, and the CLI never prints a value.
printf %s "$NPM_TOKEN" | struct build-agent secrets set NPM_TOKEN --stdin
struct build-agent secrets set NPM_TOKEN --from-env NPM_TOKEN
struct build-agent secrets list
struct build-agent secrets remove NPM_TOKEN
set reads the value from stdin (--stdin, one trailing line ending is dropped), from the
environment variable you name (--from-env), or from a hidden prompt in a terminal; it
never reads a variable you did not name. New names must be environment variable names (a
letter or underscore, then letters, digits or underscores; invalid_secret_name
otherwise), because the Build Agent can only use those. remove accepts any saved name,
including older ones that do not follow the rule. Removing a name that is not saved fails
with secret_not_found.
Cursor and Devin API keys are not secrets here: they belong to the Fix agent settings.
struct github deploy-webhook show|create|rotate
Manages the GitHub repository webhook that tells Struct when a deploy workflow run
finishes (workflow_run events on the workspace's default repository). It needs a
workspace admin signed in with struct login --admin, and a GitHub connection
(github_not_connected otherwise).
struct github deploy-webhook show | Shows the repository, whether the webhook is set up on GitHub, the payload URL, and a fingerprint of the secret. |
struct github deploy-webhook create | Creates or repairs the webhook. Struct adds it on GitHub itself when the GitHub App may manage webhooks. |
struct github deploy-webhook rotate | Replaces the secret; deliveries stop until GitHub has the new one. Asks first; --yes without a terminal. |
The payload URL names the workspace and is not a credential; GitHub signs every delivery
with the secret. create and rotate print the full secret only when Struct makes a new
one, and only once (credentialsRevealed with --json). Otherwise Struct shows only a
short fingerprint of a readable secret (secretFingerprint), which changes when the secret is rotated.
For unreadable webhook ciphertext, show reports error without a fingerprint;
create refuses with webhook_secret_unreadable, and rotate replaces the secret.
When Struct could not set them on GitHub (needs_permission, error), paste the URL and
secret into the repository's webhook settings right away. create and rotate wait up to three minutes and are never retried; if
one fails without an answer, run struct github deploy-webhook show to check whether new
credentials were saved, and rotate to get a fresh secret if they were.
Agents and scripts
-
Command data goes to stdout; progress and notices go to stderr.
-
--json prints JSON Lines on stdout. The last line is always the final document: the
command result, or {"error":{"code":"...","message":"..."}} on failure. Every
command prints exactly one line, except struct login (and struct orgs switch when
it starts a login) and struct connect <app>: before waiting, they print what the
person has to do, so an agent can pass the link on.
$ struct login --json
{"status":"waiting_for_human","method":"browser","url":"https://api.struct.ai/api/oauth/authorize?...","expiresAt":"..."}
{"status":"ok","loggedIn":true,"method":"browser","workspace":{"id":"...","name":"Acme"},"role":"admin",...}
A device login line also has userCode and verificationUri. The CLI prints a
waiting_for_human line only for the link it is actually waiting on: if it cannot
open a browser and switches to a device code, it prints only the device line.
-
Refusals keep a stable error code, for example forbidden (your role cannot make
the change), feature_not_enabled, not_found, channel_private or
workflow_required.
-
A login that can only read gets write_login_required (exit 4) when the route would
accept a write grant: run struct login --org <workspace> to allow changes (for a login saved with
--with-token, save a token that allows changes with struct login --with-token --org <workspace>, or run
struct logout --org <workspace> and then struct login --org <workspace>; for a browser
login whose permissions were not saved, run those two commands). With STRUCT_TOKEN, set it
to a token that allows changes, or unset it to use your saved login. Key management and the GitHub deploy webhook
instead require struct login --admin and get admin_login_required. The saved login is
kept and still works for reads. A login Struct no longer accepts (invalid, expired or
revoked) gets auth_required (exit 4).
-
Exit codes:
0 | Success |
1 | Error |
2 | Usage error |
4 | Authentication required |
-
The CLI only prompts when stdin and stdout are terminals; otherwise a command that
needs an answer fails with exit 2 and tells you what to run instead.
-
Set STRUCT_PROMPT_DISABLED=1 to also refuse browser approval: a command that would
need it exits 4 instead of waiting. struct login and struct connect <app>, whose
purpose is browser approval, still print the link and wait.
-
For unattended runs, set STRUCT_TOKEN. It is used only in memory, never saved, and
takes precedence over saved logins. To save a token as a login instead, pipe it to
struct login --with-token.
Environment variables
STRUCT_API_URL | https://api.struct.ai | The Struct server to use. |
STRUCT_TOKEN | unset | A token to use instead of saved logins (never saved). |
STRUCT_PROMPT_DISABLED | unset | Set to 1 to refuse prompts and browser approval. |
The CLI reads these only from your environment. It never loads .env files from the
current directory, and it prints a notice on stderr when a non-default server is set.
Workspace activation
New workspaces can sign in before a short setup call activates them. Until then, after
struct login (and struct whoami) the CLI prints a note on stderr:
Your workspace isn't activated yet. Book a short setup call to activate it:
<booking link>
Questions? Email team@struct.ai
or, if you already booked the call:
Your workspace isn't activated yet. We'll email you after your setup call.
Questions? Email team@struct.ai
Sign-in still succeeds. With --json, the result includes activated,
setupCallBooked (null when Struct cannot tell yet; no booking advice is shown then)
and, when a call should be booked, bookingUrl. A request that Struct
refuses because the workspace is not activated exits 1 with the error code
account_not_activated and the booking link; with --json, the error also has the link
as bookingUrl when Struct sent one.
Credentials
- Saved in
~/.struct/profiles/, one file per workspace and server, with permissions
0600 (only your user can read them). ~/.struct/state.json records the selected
workspace for each server.
- Each login is bound to the server that issued it (scheme, host and port) and to one
workspace. If you point
STRUCT_API_URL at another server, the CLI does not send your
saved credentials there; sign in again. Switching workspaces never moves a login to
another workspace.
- Renewals are coordinated between CLI processes running at the same time, and files
are replaced atomically. A network problem never deletes a login. If the answer to a
renewal is lost, the CLI keeps the renewal token it sent and sends it again on the next
command, until Struct returns the renewed login or rejects it (then it asks you to sign
in again, exit
4). struct logout still revokes such a login.
Security
- PKCE and a state check protect the sign-in code. The local callback listens only on
127.0.0.1, on a port chosen for each sign-in.
- Authenticated requests never follow redirects, so a token cannot be forwarded to
another host.
- Settings come only from your environment, never from a repository's
.env file.
- Tokens are never printed.
Troubleshooting
- "You are not logged in": run
struct login.
- "Struct no longer accepts the saved login" or "Struct rejected the saved login":
your access was revoked or expired; run
struct login again.
- "Could not renew the saved login": Struct could not be reached, had a temporary
problem, or the renewed login could not be saved. Your login was kept; run the command
again (no need to log in again): the next command picks up the renewal where it stopped.
- "The saved login is locked by a struct process" (
credential_locked):
another computer that shares your home directory is using the saved login, or a struct
command there was interrupted. The CLI never takes over that lock. If no struct command
is running there, delete the lock it names and try again.
- Browser doesn't open: the CLI switches to a code you can enter on any device, or
you can copy the printed link into your browser.
Help
Questions or problems: team@struct.ai or
docs.struct.ai.
License
Copyright Struct. All rights reserved.