New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@struct-ai/cli

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@struct-ai/cli

Struct AI CLI

latest
npmnpm
Version
2.1.1
Version published
Maintainers
1
Created
Source

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.

OptionWhat it does
--org <workspace>The workspace to approve (name or id). Pick it on the approval page.
--deviceSign in with a code on any device.
--no-browserPrint the link without opening a browser.
--with-tokenSave a token read from stdin instead of signing in.
--timeout <seconds>How long to wait for approval (default 900, which is 15 minutes).
--adminAlso 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.

ClientWhat struct mcp install does
claudeRuns claude mcp add --transport http --scope user struct <url> (Claude Code, for all your projects).
codexAdds a [mcp_servers.struct] table to ~/.codex/config.toml (or $CODEX_HOME/config.toml), keeping your settings.
cursorAdds 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.

CommandWhat it does
settings getShows 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 listLists 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.
enableTurns Auto Monitors on. Safe to repeat. The result lists anything still missing.
disable --yesTurns 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.

CommandWhat it does
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.
showShows the deploy signal and whether Struct is ready for it, or what is missing.
workflows listLists 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

CommandWhat it does
struct github reposLists 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 orgsLists 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.

CommandWhat it does
struct triage get slackLists 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      # repositories Struct can reach
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    # prepares the machine
struct build-agent scripts set acme/api --post --file check.sh   # checks the change
struct build-agent prompt set acme/api --file prompt.md          # at most 1,000 characters
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
# edit STRUCT.md, then:
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.

CommandWhat it does
struct artifacts listLists 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.

CommandWhat it does
struct skills listLists 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 createCreates 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
# edit skill.md, then:
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.

CommandWhat it does
struct keys <ingest|deploy> listLists 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> createCreates 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).

CommandWhat it does
struct github deploy-webhook showShows the repository, whether the webhook is set up on GitHub, the payload URL, and a fingerprint of the secret.
struct github deploy-webhook createCreates or repairs the webhook. Struct adds it on GitHub itself when the GitHub App may manage webhooks.
struct github deploy-webhook rotateReplaces 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:

    CodeMeaning
    0Success
    1Error
    2Usage error
    4Authentication 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

VariableDefaultPurpose
STRUCT_API_URLhttps://api.struct.aiThe Struct server to use.
STRUCT_TOKENunsetA token to use instead of saved logins (never saved).
STRUCT_PROMPT_DISABLEDunsetSet 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.

Keywords

struct

FAQs

Package last updated on 24 Oct 2026

Related posts