Metadata-Version: 2.5
Name: fruxon
Version: 0.10.10
Summary: The Fruxon SDK is a lightweight Python client for integrating with the Fruxon platform.
Project-URL: bugs, https://github.com/fruxon-ai/fruxon-sdk/issues
Project-URL: changelog, https://github.com/fruxon-ai/fruxon-sdk/blob/main/HISTORY.md
Project-URL: homepage, https://github.com/fruxon-ai/fruxon-sdk
Author-email: Hagai Cohen <hagai@fruxon.com>
Maintainer-email: Hagai Cohen <hagai@fruxon.com>
License: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: certifi>=2024.2.2
Requires-Dist: click>=8.2
Requires-Dist: keyring>=24.0
Requires-Dist: typer>=0.13
Provides-Extra: test
Requires-Dist: coverage; extra == 'test'
Requires-Dist: ipdb; extra == 'test'
Requires-Dist: pre-commit; extra == 'test'
Requires-Dist: pytest; extra == 'test'
Requires-Dist: ruff; extra == 'test'
Requires-Dist: ty; extra == 'test'
Description-Content-Type: text/markdown

# fruxon

[![PyPI version](https://img.shields.io/pypi/v/fruxon.svg)](https://pypi.org/project/fruxon/)
[![Python](https://img.shields.io/pypi/pyversions/fruxon.svg)](https://pypi.org/project/fruxon/)
[![License](https://img.shields.io/pypi/l/fruxon.svg)](https://github.com/fruxon-ai/fruxon-sdk/blob/develop/LICENSE)

Run, build, and orchestrate AI agents from your terminal — the official Python SDK and CLI for the [Fruxon](https://fruxon.com) platform.

## Install

```bash
pip install fruxon
```

Requires Python 3.10+.

## 30-second quickstart

```bash
fruxon login                                       # opens a browser; stores the token in your OS keychain
fruxon agents list                                 # see what's deployed in your workspace
fruxon agents schema my-agent                      # learn what parameters my-agent expects
fruxon agents draft pull my-agent                  # fetch the working copy locally
fruxon agents draft validate my-agent              # lint the definition before pushing
fruxon agents draft run my-agent -p question="hi"  # run the draft to validate it
```

Production invocation of a *deployed* agent isn't a CLI concern — call it
from your app via the [Python client](#the-python-client). The CLI is for
building and maintaining agents.

`fruxon doctor` will tell you if anything's misconfigured.

## The CLI

Top-level discovery commands — useful whether you're a human or an AI agent driving the CLI:

| Command | What it does |
| --- | --- |
| `fruxon describe` | Dump the entire CLI surface as one JSON document (paths, args, options, types, defaults, choices, examples). The right entry point for an LLM driver — read it once, fluent. |
| `fruxon examples [topic]` | Curated, pasteable invocations grouped by topic (`draft`, `agents`, `executions`, …). |
| `fruxon completion {bash\|zsh\|fish}` | Print a shell-completion script. `eval "$(fruxon completion zsh)"` to install. |
| `fruxon guides list / show <id>` | Bundled CLI playbooks. Start with `fruxon-meet`; AI-agent drivers should load `fruxon-agent-mode` first. |
| `fruxon doctor` | Diagnose local setup (interpreter, SDK version, credentials, API reachability, auth). |

Auth:

| Command | What it does |
| --- | --- |
| `fruxon login` | Browser-based sign-in. token goes to your OS keychain (Keychain on macOS, Secret Service on Linux, Credential Manager on Windows). `--token $KEY` for headless. `--preset observer\|operator\|developer\|maintainer\|admin` declares the access level the key is minted with (default: developer); the approval page shows it for the user to approve or deny. |
| `fruxon whoami` | Show the active key/workspace and where each value came from (flag, env, keychain, file). |
| `fruxon logout` | Forget stored credentials. |
| `fruxon config list / get / set / unset` | Read/edit the persistent CLI config. |

Execution:

| Command | What it does |
| --- | --- |
| `fruxon agents draft run <agent> -p k=v` | Run the agent's **draft** revision (Origin=TEST — owner-scoped, no prod spend). The CLI's one execution surface. Streams text by default; pass `--output json` or `--no-stream` for the full result envelope. Under agent mode, emits NDJSON on stdout — one JSON record per SSE event. |
| `fruxon agents executions list <agent>` | List past executions, newest-first. The discovery step — find a record id. Narrow **by time** (`--status`, `--origin` (PRODUCTION default / TEST), `--revision`, `--since`/`--until`, `--limit`) or **by conversation** (`--participant`, `--session`, `--tool`, `--with-tool-errors`, `--has-tool-calls`, `--trigger-type`, `--run-type`, `--min-duration-ms`). Rows carry `subject` and `deliveryStatus` — `COMPLETED` says the agent finished, not that anyone heard it. |
| `fruxon agents executions get <agent> <record-id>` | Record summary for one execution — status, duration, cost, tokens. |
| `fruxon agents executions trace <agent> <record-id>` | Step-by-step trace of one execution — LLM/tool calls, durations. `--show-io` also prints each tool step's parameters and result (the payload lives at `steps[].result`, a sibling of `toolTrace` — whose `resultDelivery` carries sizes only). |
| `fruxon agents executions result <agent> <record-id>` | The final output one execution produced. |
| `fruxon agents approvals list/get/respond/cancel <agent>` | Operate a step's human-in-the-loop gate — see what a run is blocked on and answer it. `respond`/`cancel` confirm first and refuse without `--yes` in agent mode (an LLM must not silently auto-approve). |
| `fruxon agents memory list/subjects/get/forget-subject <agent>` | Inspect and prune what an agent remembers. `list` filters server-side by `--subject`/`--search`/`--scope`; `forget-subject` is a GDPR-style delete (confirms; refuses without `--yes` in agent mode). |
| `fruxon agents topics list/search/get/messages <agent>` + `fruxon agents inbox <agent>` | Read the conversation spine — the agent's topics (threads), one topic's transcript, and its inbox (what it's focused on). `list`/`search` filter server-side by `--state`/`--participant`/`--query`. Read-only. Each message carries `author` — a human colleague's turn is stored as `ASSISTANT` on purpose, so `role` alone cannot tell their reply from the agent's; `HUMAN_OPERATOR` can. A message the runtime *withheld* is in no transcript at all — see `fruxon messages list`. |
| `fruxon escalations list/history/unhandled/policy/ack` | What happened when an agent handed a conversation to a person. `list` is the queue (live takeovers + degraded escalations nobody was reachable for); `unhandled` is the append-only ledger under it. **`history` is the record of closed console-routed takeovers** — those leave the queue, so `list --status ALL` answers `[]` about a conversation a human worked for half an hour. Read `humanObservedAt` before believing a `RESOLVED`: null means nobody was ever seen picking it up. `policy <agent>` resolves who the *next* escalation would reach — an empty ladder under `NATIVE_HANDOFF` is correct, the console supplies the human. |
| `fruxon messages list` | The channel ledger — including inbound an agent was never shown. A **withheld** message (a human owns the chat in the source console, or admission refused the sender) never becomes a conversation turn, so it is in no transcript and the thread reads as a customer being ignored. `--delivery WITHHELD_PENDING` lists the ones nothing has ever acted on; nothing drains them. |
| `fruxon agents sandbox open/turn/fire/resolve-input/stream/answer/close <session>` | Drive an agent-network sandbox end-to-end without real channels — send turns as participants, test-fire triggers, and watch the run (`stream`, NDJSON in agent mode). Sandbox **captures the reply and hard-blocks every real send**; `--await` makes consult/approval gates suspend so they can be resolved (`answer` for consults, `approvals respond` for approvals). |
| `fruxon agents sandbox test <file-or-dir>` | Run scenario-based e2e tests of an agent's network behavior. A scenario (YAML/JSON) declares `steps` (turns / trigger fires), `responders` (scripted consult/approval answers), and `expect` assertions (reply / routing / no-leak / outcome); the runner drives a sandbox session and returns one verdict — exit 0 (pass) / 1 (fail) / 12 (bad file), with `--junit` for CI. An optional `setup` block provisions real resources first — an `asset` (uploaded + ingestion-waited), `participant`, or `agent` — templated as `${asset.catalog.id}` and **torn down afterwards even on failure** (`--keep` to inspect). Assertions are deterministic by default; `reply_judge: {metric, min_score}` adds an opt-in **semantic** check that scores the reply against a tenant eval metric. The thing an agent runs to prove the agent it built works. |

Invoking a *deployed* agent in production goes through the [Python client](#the-python-client) (`FruxonClient.stream` / `execute`), not the CLI.

Agent authoring + management:

| Command | What it does |
| --- | --- |
| `fruxon agents list` | Browse every agent in your workspace. `--output id` for shell pipes; `--include-disabled` (`-a`) to include disabled ones. |
| `fruxon agents get <id>` | Inspect one agent — display name, deployed revision, tags, expected parameters. |
| `fruxon agents schema <id>` | Typed parameter metadata — names, types, required, options. The shape a run will accept. |
| `fruxon agents validate <id> -p k=v` | Pre-flight a payload against the schema. Catches missing-required / wrong-type / invalid-option client-side, surfacing every finding in one pass. |
| `fruxon agents create --file <body.json> [--application <id>]` | Provision a new agent shell. Every agent is owned by an Application — `--application` fills `networkId`, and the workspace default is used when you omit it. Pair with `--schema` to print the JSON schema for the body first. |
| `fruxon agents draft schema` | Print the JSON Schema for a draft body (`AgentDraftPayload`) — the file you author: one `definition` plus its `parametersMetadata`, full capability closure inlined. |
| `fruxon agents draft validate <id> [--online]` | Lint a draft body locally (no network): a missing `definition`, slot ids, tool→slot wiring, provider config, misplaced `parametersMetadata`. `--online` also resolves references against the live catalog (unknown/unpublished config, unreal model, bad tool id). `{valid, errors, warnings}` envelope (the same `errors` as `agents validate`); exits 12 on any error. `warnings[]` are advisory wiring gaps (`consult_unwired`, `approver_slot_undeclared`) that never flip `valid` or the exit code. |
| `fruxon agents draft run <id> --file <def.json>` | Run a draft definition against an existing agent without publishing it. A CI gate for agent changes. |
| `fruxon agents revisions create/get/deploy` | Mint and deploy immutable revisions. `create --deploy` does both in one step. `get --as-draft` converts a stored revision back into a postable body. |
| `fruxon agents slots list/bind/unbind <id>` | An agent's contacts — the humans `notify` / `ask` / `escalate` reach. The definition declares them; the binding says who receives (`--participant`, `--role`, or `--queue`). Declared but unbound means the reach-out dead-ends at run time, so `list` calls that out and `unbind` is `--yes`-gated. `bind --role` warns first when nobody holds the role — advisory, since binding ahead of staffing is legitimate. |
| `fruxon agents check <id>` | Audit whether a **deployed** agent is wired to run end-to-end, beyond the definition: a deployed revision, an inbound path (bound trigger / channel), a consult roster if the agent enables consult, declared **and bound** contacts, every bound trigger populating the agent's required params, and that the revision's references resolve. `{overall, checks[]}` (doctor-style); exits non-zero only on a hard fail (no deployed revision) — advisory `warn` rows stay exit 0. |
| `fruxon agents draft pull/push/status/undo/redo/reset/discard` | Local working-copy authoring loop — same draft an open studio tab edits. |
| `fruxon agents draft evaluate <id> --dataset <uuid>` | Score the draft against a golden dataset (expensive — every sample is a full agent run). |
| `fruxon agents tests list/show/watch/cost/delete` | Browse + tail the test-chat sessions you've run on an agent (owner-scoped). |
| `fruxon agents budget list/get/set/delete` | Per-origin (PRODUCTION / TEST) cost caps and spend visibility. |

Integrations + tools:

| Command | What it does |
| --- | --- |
| `fruxon integrations list/get/create/update/verify/open` | Manage external integrations — the connections agents draw tools from. `create`/`update`/`verify` take a `--file` JSON body; `open` opens the dashboard. |
| `fruxon integrations configs list/get` | Inspect the per-integration auth/config records. |
| `fruxon integrations authorize <integration>` | Mint an application-level OAuth authorization URL to **connect** an integration — auto-detects its OAuth2 method (`--auth-method` to pick, `--scope`/`--config-param` to tune). OAuth needs a browser consent step, so the CLI hands you the link; a human clicks it, and the connection is saved as a tenant config a slot can pin. |
| `fruxon integrations triggers <integration>` | List the event types an integration can fire an agent on — each descriptor's `id` is the `eventType` a trigger listens for, plus the `payloadFields` (dotted paths) a binding's `parameterMappings` can read. Discovery for wiring an inbound-event trigger without guessing. |
| `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
| `fruxon tools list/get/create/update/delete/run` | Manage the tools inside an integration. Integration-scoped. `run` executes one tool outside any agent — real credentials, real result — via `--tool` plus `-p key=value`, with `-c` naming the config that supplies credentials. |
| `fruxon keys list/mint/revoke/delete/history/scopes` | Audit and revoke scoped tokens. Minting opens the dashboard so the secret never enters the CLI process. |
| `fruxon llm-providers list/get/models` | Browse LLM providers and models supported at the tenant level. |
| `fruxon assets create/list/get/wait/operations/delete` | Manage knowledge-base (RAG) assets a step can query — upload local files, wait for async ingestion, inspect operations, and use the ids for `assetConfig.assetIds`. `delete` confirms + refuses without `--yes` in agent mode. |
| `fruxon assets search/documents/chunks` | Reproduce and inspect a retrieval. `search` runs the same query an agent's `search_assets` does (`--mode HYBRID/VECTOR/FULL_TEXT`, `--top-k`, fusion weights) — note `score` is on a different scale per mode, and in hybrid the top hit is always 1.0. `documents` lists what the index contains (a `chunkCount` of 0 means that file yielded no indexable text); `chunks` reads one document's chunks in order. |
| `fruxon metrics list` | Browse the evaluation-metric catalog — the ids a step's LLM-judge config (`judge.metrics[]`) binds, with a default weight each. |
| `fruxon triggers list/get/create/update/delete/fire/bind/unbind` | Manage triggers — the schedule/event sources that fire agents. `create`/`update` take `--file` (+`--schema`); `create` also takes `--application`, the Application that will own the trigger and must own everything it fires. `bind`/`unbind` wire which agents fire; `fire` runs it now. `fire`/`delete`/`unbind` confirm + refuse without `--yes` in agent mode. The control plane for autonomy. `get` returns the full stored `workShape` and every `scheduleTimes` slot, so `get -o json | jq .workShape` produces a body `update --file` can post straight back — a PATCH replaces the whole shape, so edit what you read. |
| `fruxon triggers preview-shape/test-source` | Dry-run a work shape before saving or activating it; both write nothing. `preview-shape` renders, per stage, the resolved instruction, the whole `user_query` the agent is handed (item payload included), and the JSON schema the stage's declared `produces` keys become — against a real backlog item when you name a trigger. `test-source` runs the door's list tool once and says, per record, whether admission would take it, plus the exact arguments the tool was called with (for Gmail, your query with the day-granular `after:` folded in). `--file` tests a shape being edited. |
| `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--item-key` (client-side; the endpoint has no such filter), and each item's accumulated `data`, which is where a stage's declared `produces` keys land. `cursor` is the watermark and its freshness. `transitions` is one item's attempt history; `effects` is what it did to the outside world — an `UNKNOWN` row parks the item until someone settles it. `batches` is what an ASK stage is waiting on a human for. |
| `fruxon triggers ledger redrive/ignore/settle/cancel-batch` | The operator verbs; all confirm and refuse without `--yes` in agent mode. `redrive` returns a DEAD or FAILED item to the queue with its attempts reset — settle any `UNKNOWN` effect first or the next attempt hits the same refusal. `ignore` excludes an item for a **required** reason, which is the only thing separating IGNORED from DEAD later. `settle` answers an effect the platform could not confirm (`--landed` / `--did-not-land` + `--reason`); wrong in the second direction duplicates the write. `cancel-batch` calls off a question — the items go back to READY and a later pass re-batches them. |
| `fruxon triggers set-stages <id> --file` | Replace an automation's stages without rewriting the rest of its work shape (a PATCH replaces the whole shape, so this reads it, splices, and posts). Takes a bare stages array or any document carrying one. Pre-flights the two refusals the server only reports after the write: live items at a stage your list drops, and a `door` that will re-expand `source` on save. `--dry-run` prints the merged shape and the warnings. |
| `fruxon triggers revisions list/get/restore` | A trigger's audit history — every mutation writes a snapshot, and `changeReason` separates a config edit from a binding reconcile. `get -o json | jq .workShape` is the shape as it stood, which is how you undo an edit by hand. `restore` replays one and is **not** a universal undo: it runs the same validation a save does, so a snapshot that drops a stage still holding live items is refused. |
| `fruxon secrets list/get/grants` | Discover tenant secrets a step can reference — the ids for `allowedSecretIds` and the `{{secret.KEY}}` names, with publish-state and per-agent grants. Metadata only; values are never returned. |
| `fruxon participants list/get/create/update/delete/enable/disable` | Manage agent-network participants (people / groups / agents the network routes to). `list` takes `--search` (matches names **and channel addresses and account ids**, so a phone number or handle resolves to an id, and searches both directory tiers by default), `--kind`, `--tier`, `--agent`, `--environment-id`, and pages up to `--limit` / `--all`. `create`/`update` take `--file` (+`--schema`); `enable`/`disable` toggle routing; `delete` confirms + refuses without `--yes` in agent mode. |
| `fruxon applications roles <id>` | The Application's roster roles — who holds each, who defers to it, and how many holders are `consultable`. The answer to what a `--role` contact binding leaves open: the server takes any role string, so a role with references and no holders refuses at delivery time. `--unheld-only` lists just those. Trust the consultable count — every role path, delivery and consult alike, resolves through the consult-allowed member edges, so a holder without one is tagged but unreachable. |
| `fruxon applications list/get` | Inspect Applications — the container that owns agents, workflows and people. Every agent belongs to exactly one, and `agents create` needs its id, so this is where you find it. Creating and re-homing an Application itself stays in the dashboard. |
| `fruxon applications entry-points list/get/connect/attach/detach/move/update` | How inbound actually reaches an agent: an Application's claim on an external address. `list` with no Application answers "is this address already taken?". `connect` mints a new doorway (and prints the webhook URL **once**); `attach` adopts an existing one; `move` re-homes a claimed address; `detach` returns it to the Default Network. Writes — `detach`/`move` confirm first. |
| `fruxon environments list/get/create/update/archive` | Manage end-customer environments — the slugs connector bindings and `execute(environmentSlug=…)` attribute runs to (per-customer cost tracking, quotas, analytics). `list --search` filters; `archive` confirms + refuses without `--yes` in agent mode. |
| `fruxon pipelines list/get` | Read collection pipelines — one Agent or Workflow run per item of a source. An agent's `pipelines.bindings` allowlist names them **by id** and `run_pipeline` refuses any id it does not name, so this is where those ids come from (`get` also lists the reusable `sourceId`s `allowedSourceIds` takes). Read-only. |
| `fruxon capabilities list/get/create/update/delete` | Manage the consult-routing vocabulary (capabilities = name/area/description) that roster bindings and pins reference. `create`/`update` take `--file` (+`--schema`); `delete` --yes-gated in agent mode. |
| `fruxon participants bind/unbind/roster` + `fruxon agents roster` | Wire a participant onto an agent's consult roster and set its policy (roles / urgency / response policy); `agents roster` reads who advises an agent. `unbind` --yes-gated. |
| `fruxon consult-pins list/get/create/delete` | Deterministic capability→participant routing overrides (skip the network's scoring). `create` takes `--file` (+`--schema`); `delete` --yes-gated. |
| `fruxon triggers list/get` | Discover tenant triggers — the scheduled / event sources that fire agents, invisible to draft authoring otherwise. |
| `fruxon agents channels list / agents endpoints` | Inspect a networked agent's channel bindings and resolved provider endpoints (with bot identity). Superseded by `applications entry-points list`, which names the Application answering on each address; these read endpoints outside the published API. |
| `fruxon skills list/show` | Browse the tenant's product-skill catalog (resources that attach to agents at runtime). |

### `fruxon agents draft run` — examples

```bash
fruxon agents draft pull my-agent                                  # fetch the working copy first
fruxon agents draft run my-agent -p question="Hello" -p lang=en
fruxon agents draft run my-agent -p temp:=0.7 -p tags:='["a","b"]' # ':=' for typed JSON
fruxon agents draft run my-agent -p prompt=@./prompt.md            # '@file' reads from disk
fruxon agents draft run my-agent --params ./params.json            # whole-object input
cat params.json | fruxon agents draft run my-agent --stdin
fruxon agents draft run my-agent --output json                     # full result envelope
fruxon agents draft run my-agent --file my-agent.draft.json        # push local edits, then run
```

### Agent mode (CI, Claude Code, custom orchestrators)

When `CLAUDECODE=1`, `CI=1`, or `FRUXON_AGENT_MODE=1` is set, the CLI flips to a contract designed for parseability:

- **JSON by default.** Every `--output` flag defaults to `json`. Every read command emits a stable shape; every write echoes the server's response.
- **NDJSON streaming.** `fruxon agents draft run` and `fruxon agents tests watch` emit one JSON record per line on stdout (`{"type":"text","delta":"..."}`, `{"type":"tool_call",...}`, `{"type":"done",...}`).
- **Typed exit codes.** `10` = auth_required, `11` = not_found, `12` = validation, `13` = conflict, `14` = server_error, `15` = network_error, `16` = interactive_required. Match on the number, not on prose.
- **Structured errors.** Every failure emits a one-line JSON envelope on stderr: `{"error":{"code","message","exit_code","hint"}}`.
- **Interactive guards.** Any path that would block on stdin (browser login, `--edit`, missing `--yes`) fails fast with `EXIT_INTERACTIVE_REQUIRED` and a hint naming the bypass flag.
- **Cold-start manifest.** Bare `fruxon` invocation emits a one-line JSON manifest with next-step commands so an LLM driver learns the surface without `--help` walking.

Full contract: `fruxon guides show fruxon-agent-mode`.

## The Python client

```python
from fruxon import FruxonClient

client = FruxonClient(token="...", workspace="acme-corp")

# One-shot
result = client.execute(
    "support-agent",
    parameters={"question": "How do I reset my password?"},
)
print(result.response)
print(f"{result.trace.duration}ms · ${result.trace.total_cost:.4f}")

# Multi-turn — thread the session ID into subsequent calls
followup = client.execute(
    "support-agent",
    parameters={"question": "Tell me more"},
    session_id=result.session_id,
)

# Streaming
for chunk in client.stream_text("support-agent", parameters={"question": "Hi"}):
    print(chunk, end="", flush=True)

# Lower-level: typed SSE events (text, tool_call, tool_result, done, …)
for event in client.stream("support-agent", parameters={"question": "Hi"}):
    ...

# Discovery
for agent in client.list_agents():
    print(agent.id, agent.current_revision)

# Typed parameter metadata — what `execute` / `stream` will accept
schema = client.get_agent_parameter_metadata("support-agent", revision=1)
for p in schema["metadata"]:
    print(p["name"], p["type"], "required" if p.get("required") else "optional")

# Test a draft flow without publishing it (same result shape as execute)
result = client.test("support-agent", {"flow": {...}, "baseRevision": 3, "parameters": {...}})
for event in client.stream_test("support-agent", {"flow": {...}}):
    ...

# Integrations & tools — the connections + capabilities agents are built from
for integ in client.list_integrations(types=["CUSTOM"]):
    print(integ.id, integ.type)
client.create_integration({"id": "github", "displayName": "GitHub", "configMetadata": {...}})
for tool in client.list_tools("github"):
    print(tool.id, tool.tool_type)
client.create_tool("github", {"id": "list_commits", "integrationId": "github", "descriptor": {...}})
client.run_tool("github", {"toolId": "list_commits", "parameters": {"repo": "fruxon-sdk"}})

# Assets — local files become RAG knowledge sources after async ingestion
created = client.create_asset_from_file("./handbook.pdf", name="Support handbook")
asset_id = created["asset"]["id"]
client.wait_for_asset(asset_id, operation=created["longOperation"]["id"])
for asset in client.list_assets():
    print(asset.id, asset.vectorized)
```

The client picks up `FRUXON_TOKEN`, `FRUXON_WORKSPACE`, and `FRUXON_BASE_URL` only if you read them yourself — the constructor takes explicit values. The CLI resolves them automatically (flags → env → stored config).

## Credentials & storage

The CLI resolves auth in this order (first non-empty wins):

1. Explicit flags — `--token` / `--workspace` / `--base-url`
2. Environment — `FRUXON_TOKEN` / `FRUXON_WORKSPACE` / `FRUXON_BASE_URL`
3. Stored credentials (managed by `fruxon login`)

The stored layer is split:

- **token** → OS keychain via [`keyring`](https://pypi.org/project/keyring/). Set `FRUXON_NO_KEYRING=1` or let the keyring be unavailable to fall back to a `0600` JSON file.
- **Non-secrets** (`workspace`, `base_url`) → plain JSON under `~/.fruxon/credentials`.

`fruxon config list` shows both sources side by side.

## Environment variables

| Var | Effect |
| --- | --- |
| `FRUXON_TOKEN` | Default token. |
| `FRUXON_WORKSPACE` | Default workspace. |
| `FRUXON_BASE_URL` | Override the API base URL (staging / self-hosted). |
| `FRUXON_CONFIG_DIR` | Override the credentials directory (default `~/.fruxon`). |
| `FRUXON_DASHBOARD_URL` | Override where `fruxon login` points the browser. |
| `FRUXON_AGENT_MODE=1` | Opt into the agent-mode contract (JSON outputs, NDJSON streams, typed exits, structured errors). Also auto-detected from `CLAUDECODE=1` / `CI=1`. |
| `FRUXON_NO_KEYRING=1` | Force the JSON-file fallback for the token. |
| `FRUXON_CA_BUNDLE` | Path to a PEM file of extra trusted CAs (corporate TLS proxies). |
| `FRUXON_INSECURE=1` | Disable TLS verification (dev/staging only — never production). |
| `FRUXON_NO_BANNER=1` | Suppress all branding chrome. |
| `FRUXON_NO_UPDATE_CHECK=1` | Opt out of the "newer version available" notifier. |
| `NO_COLOR=1` | Standard convention — disables color output. |

## Docs

- [Installation](docs/installation.md)
- [CLI reference](docs/cli.md)
- [Python client](docs/python-client.md)
- [Auth & configuration](docs/auth-and-config.md)

In-CLI:
- `fruxon describe` — the whole command tree as JSON
- `fruxon guides list` — bundled procedural playbooks (orientation, build-agent, agent-mode contract, debug-trace, …)

## License

MIT — see [LICENSE](LICENSE).
