Skip to content
Back to skills

Fused Cli

BSecurity

Reference for the fused CLI — environment management, file storage, secrets, code execution, and infrastructure commands. Use when writing or explaining shell commands that invoke `fused`, or when helping users set up, switch between, or provision environments. If the commands are part of building or running a project, load `fused-projects` first for the end-to-end model.

  • 5 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 26, 2026
developmentpythongoshellbashsqlnoderailsdockerawsgit

Works with

  • cli
  • api
  • mcp

Security analysis

B85/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned September 25, 2026

npx -y skills add fusedio/skills --skill fused-cli --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fused Cli?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fused Cli
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/fusedio-fused-cli/badge)](https://www.skillsdirectory.com/skills/fusedio-fused-cli)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fused-cli
description: Reference for the fused CLI — environment management, file storage, secrets, code execution, and infrastructure commands. Use when writing or explaining shell commands that invoke `fused`, or when helping users set up, switch between, or provision environments. If the commands are part of building or running a project, load `fused-projects` first for the end-to-end model.
---

# fused CLI reference

> **Part of the Fused skill set — don't work from it alone.** This is the command
> reference. For the workflow that decides *which* commands to run, load
> **`fused-projects`** (project lifecycle) or **`fused-execute`** (running code).
> See **`fused-guide`** for the full set.

Check the installed version with `fused --version` (useful for confirming an install before configuring anything).

## Environment selection

Every command targets a specific backend. Two ways to select it:

**Named environment (recommended)** — stored config in `~/.openfused/envs.json`:
```sh
fused --env prod files list
fused --env staging secrets list
```
`--env` can be omitted when the environment is resolved by project manifest or sole-env auto-selection (see resolution rules below).

**Legacy inline selection** — reads config from environment variables:
```sh
fused --backend aws files list        # reads OPENFUSED_* env vars
fused --backend local files list      # host venvs (uv/pip)
fused --backend fused files list      # Fused's managed fused
```

`--env` always wins over `--backend`. `OPENFUSED_ENV` is the env-var form of `--env`.

**Environment resolution order** (first match wins):
1. `--env` flag or `OPENFUSED_ENV` → explicit override (beats everything)
2. Inside a project with `[project].default_env` in `openfused.toml` → manifest pin
3. The global default environment (`fused env default NAME` sets it; `fused env default` prints it)
4. Exactly one named environment exists → sole-env auto-select
5. Multiple environments, no pin and no default → most commands pick deterministically (`local` if it exists, else the first name alphabetically); `infra` and deploy commands refuse and ask you to set `default_env` (`fused project set <project> --env <name>`), set a default, or pass `--env`
6. No environments → error: run `fused env create`

### Logging

Host logs (`openfused.*` loggers) go to stderr with a timestamp + level + logger-name
format. Set verbosity with `OPENFUSED_LOG_LEVEL` (default `INFO`; accepts `DEBUG`,
`INFO`, `WARNING`, `ERROR`, `CRITICAL`). Use `DEBUG` to surface Lambda cache hits and
cold-start/digest-resolution details when troubleshooting.

```sh
OPENFUSED_LOG_LEVEL=DEBUG fused --env prod code run --file job.py
```

---

## Environment management (`env`)

Named environments bundle all backend config into a named entry in `~/.openfused/envs.json`.

### Create

```sh
# AWS — provisions IAM role + Lambda automatically
fused env create prod --backend aws --prefix myapp- --region us-east-1

# AWS — skip provisioning (config only)
fused env create staging --backend aws --prefix myapp-staging- --no-provision

# Local — bare stdlib venv; scaffolds ~/.openfused/envs/dev/data automatically
fused env create dev --backend local
```

AWS `env create` runs `infra apply` automatically unless `--no-provision` is given. Pass `--no-provision` when the IAM role / Lambda already exist or when you want to review the plan first.

### List and inspect

```sh
fused env list            # all envs with their backend
fused env show prod       # JSON dump of config
fused env show            # config for the resolved environment
```

To pin an environment to a project (the recommended way to avoid per-command `--env`):
```sh
fused project set my-project --env prod   # validates env exists, writes default_env
fused project set my-project --clear-env  # remove the pin
```

### Update fields

```sh
fused env update prod --region us-east-1
fused env update prod --prefix newprefix- --lambda-timeout 600
fused env update prod --audit-bucket my-audit-bucket   # add/change audit bucket
fused env update prod --no-audit-bucket                # remove audit bucket
fused env update prod -p pandas -p duckdb   # set packages (AWS only: baked into the container image)
```

`env update` accepts all the same flags as `env create` (patch semantics — only specified fields change). Use `--no-cache-bucket` / `--no-audit-bucket` to clear a bucket field.

### Delete

```sh
fused env delete staging --yes   # removes config only; does NOT teardown AWS resources
```

### Full option reference for `env create`

| Option | Default | Notes |
|---|---|---|
| `--backend` | `aws` | `aws` (Lambda), `local` (host bare venv), or `fused` (Fused cloud). |
| `--region` | `us-west-2` | AWS region |
| `--prefix` | `openfused-` | Lambda function name prefix |
| `--role-arn` | — | Use an existing IAM role instead of creating one |
| `--role-name` | derived | Override the managed IAM role name |
| `--lambda-timeout` | `300` | Execution timeout in seconds |
| `--lambda-memory-mb` | `1024` | Lambda memory (MB). **AWS only.** |
| `--lambda-tmp-storage-mb` | `512` | Lambda `/tmp` ephemeral storage (MB). **AWS only.** |
| `--lambda-architecture` | `x86_64` | Lambda CPU architecture (`x86_64` or `arm64`). **AWS only.** |
| `--lambda-externally-managed` | off | Don't auto-manage the execution Lambda. **AWS only.** Skips the `GetFunction` existence check + `CreateFunction` at execute time (invokes it by name) and skips planning/applying it in `infra plan`/`apply`. Use when the Lambda lifecycle is managed separately (e.g. external IaC). Orthogonal to `--role-arn`, which only short-circuits the IAM role. On `env update`, toggle with `--lambda-externally-managed` / `--no-lambda-externally-managed`. |
| `--docker-image` | — | ECR image URI for the Lambda function. Normally set automatically by `infra build-image`; pass it only to register a pre-built image. |
| `--cache-bucket` | auto-derived | S3 bucket for `--input-file` staging, published share artifacts, and CodeBuild sources. Auto-named `<prefix>-cache` by default |
| `--no-cache-bucket` | off | Disable the cache bucket for this env |
| `--audit-bucket` | — | S3 bucket for WORM audit logs. Must have Object Lock enabled; `infra apply` creates it. |
| `--require-spec` | off | Still accepted and stored, but has **no effect**: executions are not verified (see "Verify settings" below). |
| `-p / --package` | — | Pip package to pre-install (repeatable). **AWS only** — baked into the Lambda container image (`image_build.packages`). Errors if passed to a local env. |
| `--system-dep` | — | System package via `dnf` (repeatable). **AWS only** — errors if passed to a local env. |
| `--python-version` | `3.12` | Python version for the container image. **AWS only** — errors if passed to a local env. |
| `--image-platform` | `linux/amd64` | Docker build platform for the container image. **AWS only** — errors if passed to a local env. |
| `--image-repo` | derived from prefix | ECR repository name. **AWS only** — errors if passed to a local env. |
| `--image-tag` | `latest` | Tag for the container image. **AWS only** — errors if passed to a local env. |
| `--builder` | `codebuild` | Image builder. **AWS only** — `codebuild` (default; remote AWS CodeBuild, no local Docker; uses the cache bucket) or `local` (docker build on host). Errors if passed to a local env. |
| `--dockerfile` | — | Path to a user Dockerfile within `--context-dir` (requires `--context-dir`). **AWS only.** |
| `--context-dir` | — | User build-context directory to build instead of the generated Dockerfile. **AWS only.** |
| `--local-path` | `~/.openfused/envs/<name>/data` | Local data directory (**local only**) |
| `--secrets-file` | `~/.openfused/envs/<name>/secrets.json` | Keychain account key identifying the per-env secrets store (**local only**; no file is written) |
| `--no-provision` | off | Skip `infra apply` on AWS |

### Fused (managed-openfused) backend — `--backend fused`

It runs code on **Fused's hosted, managed** environment over its data-plane endpoint. The local side provisions nothing and runs no code itself. `infra` commands are not supported on Fused (Fused operates the runtime). Create an env with `--backend fused`:

| Option | Default | Purpose |
|---|---|---|
| `--tier` | `prod` | Service tier selecting the base URL (`prod`/`staging`/`unstable`). |
| `--mcp-base-url` | — | Explicit data-plane base URL override (dev/self-host). |
| `--fused-org` / `--fused-env-id` | — | Org + environment (UUID or slug) for the scoped URL; set **together**. Omit both to use the bare endpoint with an env-bound key. |
| `--api-key-secret` | — | Name in fused's local secrets store holding the `ofs_` API key. |

```bash
fused env create fused-prod --backend fused --tier prod \
  --fused-org acme --fused-env-id default --api-key-secret fused/prod-key
```

The API key is resolved (first hit wins) from `--api-key-secret` (local secrets store) → `FUSED_API_KEY` → `FUSED_JWT` (scoped URL only). Storage is read + presign only (`files list` / `files get`); writes and secrets are not exposed by the managed surface and raise a clear error.

#### Guided onboarding — the `fused cloud` group

Instead of hand-building the env above, the `fused cloud` group runs the control-plane flow (login → find org/env → wait ready → mint an API key → store it → create the env). Auth0 config defaults to Fused's tenant + the `openfused-server-api` audience; override with `FUSED_CLOUD_AUTH0_DOMAIN` / `FUSED_CLOUD_AUTH0_CLIENT_ID` / `FUSED_CLOUD_AUTH0_AUDIENCE`.

```bash
fused cloud login [--no-browser]            # Auth0 PKCE; caches a control-plane JWT
fused cloud redeem [CODE] [--tier prod]     # redeem a beta invite: admit + create your org + env
                                            #   omit CODE and it prompts (keeps it out of shell history)
fused cloud orgs [--tier prod]              # list your orgs + envs and their provision_state
fused cloud setup [--tier prod] \           # the one-shot guided flow:
  [--beta-code CODE] \                          #   (optional) redeem a beta invite first, then
  [--org O --env E] [--env-name NAME]           #   pick org/env (auto if you have one), wait ready,
                                                #   mint a key, store it, create the `fused` env
fused cloud key create --org O --env E      # mint + store a key for an existing managed env
fused cloud key revoke --org O --id K       # revoke a data-plane key by id
fused cloud logout [--no-browser]           # delete the cached control-plane JWT
fused cloud logout --env NAME               # ALSO delete that env's stored data-plane key (full logout)
```

`setup` stores the minted key in the local secrets store (e.g. `fused/<env-name>-key`) and writes a `FusedCloudEnvironmentConfig` referencing it — never the raw key in `envs.json`. The fused env name defaults to `fused` for the canonical `default` managed env (else `fused-<env>`).

Token resolution at request time (first hit wins): **`FUSED_API_KEY`** env var (an explicit override) → the stored **`api_key_secret`** → **`FUSED_JWT`** (scoped-URL only). A configured-but-absent secret falls through rather than failing. **Logging out:** `fused cloud logout` clears the control-plane JWT; add `--env NAME` to also delete that environment's stored data-plane key (then `fused key revoke` to revoke it server-side).

Tune a managed env after creation with `env update <name>` — the managed-fused fields `--tier`, `--mcp-base-url`, `--fused-org`, `--fused-env-id`, and `--api-key-secret` are accepted (mirroring `env create --backend fused`). `infra` commands are not applicable (Fused operates the runtime) and report that posture.

If you have a **beta invite code**, redeem it during the beta gate either as a standalone step (`fused cloud redeem`, after `login`) or folded into setup (`fused cloud setup --beta-code CODE`). Redeeming admits your account and creates a personal org with a `default` environment, which setup then waits on and wires up. The code is single-use; an invalid or already-redeemed code raises a clear error. Prefer the standalone form with the code omitted — it prompts without echo, so the code stays out of `ps` and your shell history; the same applies to `fused cloud accept [TOKEN]`, the sibling command for joining an org you were invited to.

### Verify settings (`--verify`, `--require-spec`, `--audit-bucket`) — not enforced

`env create` / `env update` still accept `--verify JSON`, `--require-spec` and
`--audit-bucket` and store them in the environment, but **no command runs the
verify pipeline automatically**: `code run` and `code test` execute with no pre-
or post-execution scan, no type check, no spec requirement and no output
firewall. `verify.enabled`, `typecheck`, `typecheck_docker`, `block_on_warn`,
`expectations` and `require_spec` have no effect on execution.

The only verify fields still read are by the explicit `fused code verify` command:
`rules` (a list of `{"rule_id", "severity", "enabled"}` overrides) and the OSV
endpoint used by the dependency scan. See the **fused-verify** skill.

---

## Projects (`project`)

Projects are versioned, deployable collections of UDFs. The on-disk model is: **workspace ⊃ project ⊃ UDF**. All project commands implicitly target the `default` workspace at `~/.openfused/workspaces/default/` (override with `OPENFUSED_WORKSPACES_DIR`).

The first `project new` call auto-creates the default workspace (git init + installs the openfused-managed v6 pre-commit hook). The hook blocks manual commits that touch a UDF's `spec.md` without its `main.py` or vice versa. Use `git commit --no-verify` to bypass it when needed.

### Create a project

```sh
fused project new taxi-pipeline
# Created project 'taxi-pipeline' at ~/.openfused/workspaces/default/taxi-pipeline
```

| Argument | Notes |
|---|---|
| `NAME` | Project name; must match `^[a-z][a-z0-9]*([-_][a-z0-9]+)*$`, max 64 chars |

### List projects

```sh
fused project list
```

Prints all project names in the default workspace, sorted. Prints a help message when none exist yet.

### Add dependencies to a project

```sh
fused project add-dep taxi-pipeline duckdb pandas        # runtime deps
fused project add-dep taxi-pipeline pytest coverage --dev # dev deps (for `code test`)
```

Runs `uv add [--dev] <packages>` then `uv sync` inside the project's `scripts/`
dir in one step, so the lockfile and the installed venv stay in step — avoiding
the stale-venv warning (and silent cache-disable) a bare `uv add` would leave
behind. Never hard-fails on tooling problems (missing `uv`, `OPENFUSED_LOCAL_INSTALLER=pip`,
non-zero exit) — it prints a guided `Warning:` instead. Errors only on an unknown project.

### Show a project

```sh
fused project show taxi-pipeline
```

With `NAME` omitted, uses the resolved project (`OPENFUSED_PROJECT` → `openfused.toml` walk-up from cwd). Prints the project's **context packet** as JSON:

- `project` — `name`, `root`, `description`, `registered`, `source`
- `contract` — the project's `SKILL.md`
- `references` — the notes under `references/`
- `udf_scripts` — the UDFs under `scripts/`
- `environment` — `default_env`, `resolved_env`, `resolved_from`, `backend`, `error`, `warnings`

`project show` is **read-only**: it does not re-sync `openfused.toml`. Exits with an error when the project does not exist.

### Migrate / repair a project's layout (`project migrate`)

```sh
fused project migrate taxi-pipeline
```

Moves legacy root-level UDF folders into `scripts/<name>/` (committing the move),
then **re-syncs the manifest from disk**: discovers UDF folders under `scripts/`,
drops `[udfs.*]` entries with no folder, and preserves user-set fields
(`description`/`auth`/`cache_max_age`, unknown keys, TOML comments). It is the only
CLI command that re-syncs the manifest. Use it to repair a manifest that no longer
validates — for example a leftover `kind = "json"` UDF entry, which `doctor`
reports as `manifest-unreadable`. When nothing was moved, the re-synced
`openfused.toml` is left uncommitted; commit it yourself.

### Delete a project

```sh
fused project delete taxi-pipeline
```

Removes a project from the default workspace: `git rm -rf -- <name>` followed by
a `--no-verify` commit, then cleans gitignored/untracked residue (`scripts/.venv`,
`__pycache__`). An underscore-prefixed name fails slug validation ("Invalid
slug"). Prints JSON `{name, deleted, root}` on success.

### Naming rules

Project and UDF names are **lowercase slugs**: `^[a-z][a-z0-9]*([-_][a-z0-9]+)*$`, max 64 chars. Both `-` and `_` are accepted as segment separators, so snake_case UDF names like `list_comments` are valid. Lowercase-only prevents case-collision bugs on case-insensitive filesystems and in S3 key segments.

### Workspace layout

```
~/.openfused/workspaces/default/    # the default workspace (one git repo)
├── .git/                           # openfused-managed pre-commit hook installed here
├── taxi-pipeline/                  # a project = one folder
│   ├── openfused.toml              # manifest (re-synced from disk by `project migrate`)
│   ├── SKILL.md                    # project contract (agents read this for context)
│   ├── assets/                     # static project assets
│   ├── references/                 # dataset notes + findings (one file per topic)
│   └── scripts/
│       ├── pyproject.toml          # uv-managed Python deps (note: uv's [project] table, not fused's)
│       ├── tests/                  # project-level pytest suites
│       ├── taxi-analysis/          # a UDF, kind: py
│       │   ├── main.py
│       │   ├── spec.md
│       │   └── test_main.py
│       └── clean-trips/            # another UDF
│           ├── main.py
│           └── spec.md
└── sales-app/
```

A UDF is a folder under `scripts/` that contains `main.py` (kind `py`, the only
kind). `main.json` has no special meaning — it is an ordinary file, and a folder
holding only `main.json` is not a UDF. Dot-prefixed and underscore-prefixed
directories are skipped.

### Authoring UDFs (agent-authored)

There is no `udf generate` or `project regenerate` command. UDFs are authored by the driving agent:

1. Write `scripts/<name>/spec.md` and get it approved.
2. Write the entrypoint `scripts/<name>/main.py`.
3. Optionally scan it with `fused code verify <file>` before committing (nothing runs verify for you).
4. Commit `spec.md` + entrypoint together — the pre-commit hook enforces that spec and entrypoint are always paired in the same commit.

See the **fused-projects** skill for the full spec-first, agent-authored flow (env → project → UDF → run → deploy).

### Deploy a project (`project deploy`)

Batch-deploys all UDFs in a project to a channel. The workspace must be clean (all changes committed) unless `--force` is used.

```sh
fused project deploy taxi-pipeline                     # deploy all UDFs to preview
fused project deploy taxi-pipeline --channel release   # deploy all UDFs to release
fused project deploy taxi-pipeline --force             # bypass dirty-tree check
```

| Option | Default | Notes |
|---|---|---|
| `NAME` | required | Project name |
| `--channel` | `preview` | `preview` or `release` |
| `--force` | false | Deploy even with uncommitted changes |

> `--channel release` bypasses the preview gate and breaks the rollback
> invariant (rollback targets must be prior release events). Use it only for
> bootstrapping the very first release URL — never for a routine production
> release, which goes `deploy` (preview) → `promote`. See the `fused-projects`
> guardrails.

Requires AWS env + `cache_bucket` + provisioned serving plane (`fused infra serve`). Echoes the resolved env name; prints one URL per UDF. Exits 1 if any UDF fails to deploy.

### Promote a project (`project promote`)

Batch-promotes all UDFs in a project from preview to release.

```sh
fused project promote taxi-pipeline
```

### Show project deploy status (`project status`)

Shows the live cloud deploy snapshot for a project. Marks UDFs that are in the cloud snapshot but absent on disk as **orphaned** (prompt to restore or retire).

```sh
fused project status taxi-pipeline
```

Output columns: `UDF`, `CHANNEL`, `COMMIT`, `ORPHANED`, `URL`.

### Deploy a single UDF (`udf deploy`)

```sh
fused udf deploy analysis --project taxi-pipeline
fused udf deploy analysis --project taxi-pipeline --channel release
fused udf deploy analysis --project taxi-pipeline --force
```

| Option | Default | Notes |
|---|---|---|
| `NAME` | required | UDF name |
| `--project WF` | required | Project that owns the UDF |
| `--channel` | `preview` | `preview` or `release` |
| `--force` | false | Deploy even with uncommitted changes |

Requires a clean git tree (or `--force`), AWS env + `cache_bucket`, and a provisioned serving plane. Echoes the resolved env on stderr and prints the channel URL on stdout.

### Promote a single UDF (`udf promote`)

Repoints the release channel to whatever commit preview is currently running.

```sh
fused udf promote analysis --project taxi-pipeline
```

| Option | Default | Notes |
|---|---|---|
| `NAME` | required | UDF name |
| `--project WF` | required | Project that owns the UDF |

### Roll back a single UDF (`udf rollback`)

Rolls back the release channel to a prior commit. Defaults to the previous release commit when `--to` is omitted.

```sh
fused udf rollback analysis --project taxi-pipeline
fused udf rollback analysis --project taxi-pipeline --to abc123def
```

| Option | Default | Notes |
|---|---|---|
| `NAME` | required | UDF name |
| `--project WF` | required | Project that owns the UDF |
| `--to COMMIT` | previous release | Target commit SHA |

### Retire a UDF (`udf retire`)

Revokes the UDF's preview + release mounts, appends a retire event, and drops the UDF from the deploy snapshot. **This cannot be undone via this command.** Prompts for confirmation unless `--yes` is passed.

Retire enforces the same workspace-id conflict gate as deploy/promote/rollback: if the live snapshot was written by a different workspace it refuses unless `--force` is passed (an intentional takeover).

```sh
fused udf retire analysis --project taxi-pipeline
fused udf retire analysis --project taxi-pipeline --yes
fused udf retire analysis --project taxi-pipeline --yes --force   # take over a foreign-owned UDF
```

| Option | Default | Notes |
|---|---|---|
| `NAME` | required | UDF name |
| `--project WF` | required | Project that owns the UDF |
| `--yes` | false | Skip the confirmation prompt |
| `--force` | false | Take over a UDF deployed from a different workspace |

### Pre-commit hook (v6)

The workspace `.git/hooks/pre-commit` is installed/upgraded by `project new` (and by any `bootstrap_workspace` call). Current version: `v6`.

The hook blocks manual commits that touch a UDF's `spec.md` without its `main.py`, or vice versa — including one-sided deletions. The pairing is enforced at depth 4: `<project>/scripts/<udf>/<file>`. Tests, resource files, `main.json` and other files commit freely. fused's own auto-commits are always paired and pass through without `--no-verify`.

Use `git commit --no-verify` to bypass the hook when needed (e.g. fixing a typo in spec.md alone), but note this bypasses the spec↔entrypoint pairing check.

The hook body embeds `# openfused-managed pre-commit hook v6`. On re-install, older managed versions are upgraded; unmanaged hooks (no marker) are warned about and never overwritten.

---

## File storage (`files`)

### List

```sh
fused files list                        # list all buckets
fused files list --bucket my-bucket     # list all keys in bucket
fused files list --bucket my-bucket --prefix data/2024/
```

### Count

```sh
fused files count --bucket my-bucket
fused files count --bucket my-bucket --prefix logs/ --ext .parquet --ext .csv
```

### Get presigned URL

```sh
fused files get --bucket my-bucket --key data/report.parquet
fused files get --bucket my-bucket --key data/report.parquet --expires-in 7200
```

### Schema inspection

Prints column names, types, row count, and file metadata for Parquet, Arrow IPC, or CSV files.

```sh
fused files schema --bucket my-bucket --key data/report.parquet
```

### Upload

```sh
fused files upload data.parquet --bucket my-bucket --key uploads/data.parquet
cat data.csv | fused files upload - --bucket my-bucket --key uploads/data.csv
```

`SRC` defaults to stdin when omitted; `-` also reads from stdin.

---

## Health check (`doctor`)

`fused doctor` surveys **every** project in every workspace under
`~/.openfused/workspaces/` and reports per-project health findings — turning
latent layout/venv drift (the kind that otherwise surfaces as a confusing
`` `.venv` not found `` error at use time) into one up-front, actionable report.

```sh
fused doctor          # read-only survey (default)
fused doctor --fix    # remediate the fixable findings, then re-diagnose
```

**Read-only by default** — diagnosis runs the existing resolvers as pure probes
(never `uv sync`, never rewrites a manifest). It prints one block per
`<workspace>/<project>`; a project with no issues prints `OK`. With no projects
at all it prints `No projects found.`

Findings carry a severity — `BLOCK` (broken / won't run) or `WARN` (degraded):

| `rule_id` | Severity | Meaning | Fixable by `--fix`? |
|---|---|---|---|
| `invalid-name` | BLOCK | Project dir name is not a valid slug | no (rename manually) |
| `legacy-layout` | BLOCK | Legacy v1 UDF folder (`main.py`) at project root, no `scripts/` dir | yes (migrate) |
| `venv-missing` | BLOCK | `scripts/.venv` absent/incomplete | yes (`uv sync`) |
| `venv-stale` | WARN | venv older than `pyproject.toml`/`uv.lock` | yes (rebuild) |
| `stray-root-venv` | WARN | Stale root `.venv` beside a valid `scripts/.venv` | yes (remove) |
| `manifest-legacy` | WARN | `openfused.toml` uses `[workflow]` not `[project]` | yes (migrate) |
| `manifest-unreadable` | BLOCK | `openfused.toml` missing/unparseable or fails validation (e.g. a leftover `kind = "json"` UDF entry) | no (repair manually, or `fused project migrate` to re-sync from disk) |
| `env-unresolved` | BLOCK | Project env doesn't resolve | no (create/pin an env) |

**`--fix`** applies the fixable findings — migrate first (so `scripts/` exists),
then build/refresh the venv and remove stray root venvs — then re-diagnoses and
prints the residual. `invalid-name`, `env-unresolved`, and `manifest-unreadable`
always need a human and are never auto-fixed.

**Exit code:** `doctor` exits `1` when any `BLOCK` finding remains (after
remediation, under `--fix`); `WARN`-only or clean exits `0` — so it works as a CI
gate.

---

## Workspace projects (`project`)

A **project** is a directory rooted at an `openfused.toml` manifest — the unit of
scope and memory. Projects are discovered by directory listing
under `~/.openfused/workspaces/default/` (no registry file). Resolution is
git-style: explicit name → `OPENFUSED_PROJECT` → manifest walk-up from cwd →
global scope (pre-project behavior unchanged).

```sh
fused project create taxi --description "NYC taxi analysis"   # scaffold under workspaces/default/taxi
fused project create taxi --env prod-aws                      # scaffold and pin default_env (validates env exists)
fused project list                       # projects in the default workspace
fused project show [NAME]                # the context packet (see "Show a project")
fused project set NAME --description "new words" --env prod   # update the manifest's [project] keys
fused project set NAME --clear-env       # remove default_env from the manifest
```

> **`project use` was removed.** Select a project with one of:
> - `export OPENFUSED_PROJECT=NAME` — persists across commands in the shell
> - `cd` into the project directory — cwd walk-up resolves automatically
> - `--project NAME` per individual CLI call

`set` updates the `[project]` table of a project's `openfused.toml`
(at least one of `--description` / `--env` / `--clear-env` required; `--env`
and `--clear-env` are mutually exclusive). Edits are style-preserving —
comments, formatting, and unknown keys/tables in the manifest survive — and it
prints the updated `{name, description, default_env}` as JSON.

`create` (like `new`) scaffolds the skill-folder layout: `openfused.toml`, a
`SKILL.md` contract, `scripts/pyproject.toml`, and the `scripts/`, `references/`,
`assets/` convention directories. Each UDF lives as a folder `scripts/<name>/`
with `main.py` as the entrypoint and `spec.md` as its contract — UDFs anywhere
else are not listed or served. A project scopes:

- **environment** — the manifest's `default_env` is used when no `--env` /
  `OPENFUSED_ENV` override is given (explicit override always wins);
- **audit** — events are stamped with the project name; filter with
  `audit log --project NAME`.

Start agent work in a project with `fused project show` — one call returns
identity, the SKILL.md contract, reference notes, UDF scripts, and the resolved
environment.

To expose tools to an MCP host, see `fused app serve` below — there is no
per-project MCP server.

---

## Pipeline graph (`pipeline`)

The project's UDFs wired into a persisted, versioned graph — nodes,
edges, and a `canvas.toml` home. Both commands resolve the
project the same way as `project show` (explicit `--project` → `OPENFUSED_PROJECT`
→ `openfused.toml` walk-up) and emit the `Pipeline` JSON
(`{name, path, version, nodes, edges, viewport}`) to stdout. They are the seam an
app UI reads/writes the canvas through; the graph is a **design-time lens** and
never affects execution.

```sh
fused pipeline graph                        # read the derived/persisted graph as JSON
fused pipeline graph --project taxi          # explicit project
fused pipeline graph --canvas pipelines/reporting/canvas.toml  # a named canvas
fused pipeline derive                        # "create canvas": write canvas.toml + emit reloaded graph
fused pipeline derive --project taxi          # explicit project
```

- **`graph`** reads the graph: it unions the **implicit** UDF→UDF edges (from a
  static `fused.load(...)` scan of each `main.py`) with any **explicit**
  `[canvas].edges` authored in `canvas.toml`. A **missing**
  `canvas.toml` still yields the derivable graph (the implicit-scan floor, `version`
  null); a **corrupt** `canvas.toml` surfaces as a CLI error (non-zero exit), never
  a crash.
- **`derive`** is the derive-and-persist write path: it runs
  the implicit scan, lays nodes out left-to-right by stage depth, and writes a
  `canvas.toml` at the project root (or `--canvas PATH`) capturing the derived nodes
  + edges as **explicit lineage** (whole-document atomic write). It then emits the
  **reloaded** graph, so `version` is the `sha256:<hex>` content hash of the file
  just written. The Python core is the only `canvas.toml` writer (the mutation
  boundary).

---

## Audit log (`audit`)

```sh
fused audit log                          # last 50 events
fused audit log --limit 100
fused audit log --status blocked         # blocked events only
fused audit log --event-type cache_clear
fused audit log --project taxi           # events recorded under one project
```

Events are read from the local SQLite audit store (`~/.openfused/audit.db`).
Flags: `--limit`, `--event-type`, `--status allowed|blocked|warned`, `--project`;
there is no date-range filter and no merge of S3-stored events. What is still
recorded: `cache_clear` (from `share cache-clear`) and the share-mount lifecycle
events. `code run` / `code test` / `code verify` write no audit events, so
`--event-type execute_code` or `verify_code` only returns events from older
installs.

---

## Secrets (`secrets`)

```sh
fused secrets put db-password                # create or update (prompts for the value)
fused secrets get db-password                # print value
fused secrets list                           # all secrets
fused secrets list --prefix db-             # filter by prefix
fused secrets delete db-password             # delete (prompts; --yes to skip)
```

`secrets delete` errors on a missing secret (`Secret '<name>' not found`). On
AWS the secret is **scheduled** for deletion with the default 30-day recovery
window, not force-deleted; on the local backend the name is removed from the
OS keychain map immediately.

**Keep the value off the command line.** `put` takes the value from a no-echo
prompt when you omit it, so the secret never lands in argv — where any other
user on the host can read it (`ps`, `/proc/<pid>/cmdline`) — or in your shell
history. For scripts and CI, pipe it or point at a file:

```sh
printf %s "$DB_PASSWORD" | fused secrets put db-password   # piped stdin
fused secrets put db-password --value-file ./db-password   # from a file ('-' = stdin)
fused secrets put db-password "s3cr3t"                     # inline: works, but exposed
```

One trailing newline is stripped from piped/file input, and an empty read is an
error rather than an empty secret.

**Naming requirement for Lambda access**: the Lambda execution role can only read secrets whose name starts with the environment's function prefix (e.g. `openfused-`). Always prefix secret names with the function prefix when they need to be read from `fused code run`:

```sh
fused secrets put openfused-db-password   # readable from Lambda
fused secrets put db-password             # NOT readable from Lambda
```

Never pass secret values through `code run` inline code strings — retrieve them inside the execution using `openfused.get_secret("openfused-...")` (works on AWS and the local backend).

---

## Code execution (`code run`)

Runs Python code on the resolved environment's backend (Lambda for AWS, a host-venv subprocess for local, Fused's hosted runtime for `fused`). Assign `result` to return a value.

```sh
# Inline code — requires -c/--code
fused code run -c "result = 1 + 1"

# From a file (auto-detected; --file flag is optional)
fused code run myanalysis.py

# From stdin
cat myanalysis.py | fused code run

# Pass local files into the execution context
fused code run myanalysis.py --input-file data.parquet --input-file config.json

# Call a function in the file instead of reading `result`
fused code run myanalysis.py --entrypoint main
```

pip requirements are configured per-environment via `env update -p`, not per-call. Set them once:

```sh
fused env update prod -p pandas -p duckdb
```

Output format (in this order):
- the code's `stdout` is printed as-is
- its `stderr` goes to stderr
- `warning: …` lines, if any
- `result: <value>` when `result` is set, or `result (<media type>, status N):` followed by the body for a non-JSON result
- on failure the error and traceback go to stderr and the command exits non-zero

**Keep the package set stable across calls.** On AWS, packages are baked into the container image — changing them means rerunning `fused infra build-image` (build + ECR push). On local, venvs are cached by a hash of the package set — changing it rebuilds the venv (seconds with uv).

**Caching:** `code run` caches results for **1 hour**, keyed on the code, inputs and environment. There is no flag to disable, refresh or clear this cache. To force a fresh run, change the code or inputs (for example a comment), or wait for the hour to pass. (`code test` does not cache.)

`--monitor-interval` (CloudWatch poll seconds during a run, AWS only) defaults to **10**; the snapshot is printed to stderr while the run is in flight. There is no flag to turn monitoring off.

**Local backend — project venv (`--project` or `--project-dir`).** On a local environment, pass one of:

- `--project <name>` — workspace-registered project; venv must already exist (`uv sync`).
- `--project-dir <path>` — ad-hoc path to any directory containing `openfused.toml`; venv is materialised in place on first run via `uv sync` in `<dir>/scripts/`. Use this for skill-folder bundles (e.g. `~/.claude/skills/<project>`) without registering them in the workspace.

The two flags are **mutually exclusive**. Both are **local-only** — rejected with a clear error on AWS and Fused backends. Without either flag, local execution uses a bare stdlib-only venv (third-party imports fail).

```sh
# Workspace-registered project (venv must already exist)
fused code run myanalysis.py --project taxi-pipeline

# Ad-hoc path (venv materialised on first run via uv sync)
fused code run myanalysis.py --project-dir ~/.claude/skills/taxi-pipeline
```

---

## Security scan without execution (`code verify`)

Scans code and input files for security issues without running it. Packages configured in the resolved environment are scanned for CVEs. Exits 1 if any BLOCK-severity finding is produced.

```sh
# Scan a file
fused code verify myanalysis.py

# Inline scan
fused code verify -c "import subprocess; result = 1"

# Scan code + input files (path traversal in the filename, zip bombs)
fused code verify myanalysis.py --input-file data.zip

# Spec check — Claude reviews whether code matches description (requires an Anthropic
# API key: ANTHROPIC_API_KEY env var, or `fused secrets put anthropic-api-key`, which
# prompts for the key)
fused code verify myanalysis.py --spec "compute the mean of column A"

# Scan using a workspace project's deps
fused code verify myanalysis.py --project taxi-pipeline

# Scan using an ad-hoc project dir's deps (local-only; no backend execute)
fused code verify myanalysis.py --project-dir ~/.claude/skills/taxi-pipeline
```

| Option | Default | Notes |
|---|---|---|
| `-c / --code CODE` | — | Inline code string to scan |
| `--file` | off | Force-treat SRC as a file path (auto-detected when SRC exists on disk) |
| `--input-file PATH` | — | Input file to check for filename path traversal and zip bombs (repeatable). No PII scan runs, despite what `--help` says |
| `--spec TEXT` | — | Natural language description; triggers LLM spec-vs-code check |
| `--project NAME` | — | Scan this project's `pyproject.toml` deps for CVEs (local). Without it, the dep scan uses the AWS env image packages, or nothing on local. Mutually exclusive with `--project-dir`. |
| `--project-dir PATH` | — | Scan using `<dir>/scripts/pyproject.toml` deps (local-only). Mutually exclusive with `--project`. |

The flat `<stem>.spec.md` sidecar auto-discovery and `--no-spec` flag are **removed**. Each UDF now carries one `spec.md` in its own folder. Pass `--spec` explicitly when verifying outside a UDF context.

See the **fused-projects** skill for the spec-first, agent-authored UDF flow.

---

## Test code in Lambda (`code test`)

Runs pytest tests against user code inside the same Lambda environment it will execute in. Returns per-test outcomes, line coverage, and branch coverage. Packages configured in the resolved environment are pre-installed. Exits 1 if any tests fail.

```sh
# Basic usage
fused code test mymodule.py --test-file test_mymodule.py

# With input files available on disk during test run
fused code test mymodule.py --test-file test_mymodule.py --input-file data.csv
```

The test file must import from `user_code`:
```python
from user_code import my_function

def test_basic():
    assert my_function(1) == 2
```

pytest and coverage are auto-installed to `/tmp` on first use when they are not baked into the container image (~30s; warm containers reuse `/tmp/_test_deps`).

**Local backend — `--project` or `--project-dir` is required.** On a local environment, `code test` requires one of these flags. pytest and coverage must be declared as dev dependencies in the project's pyproject.toml; they are not auto-installed in the project venv. Add them in one step with `fused project add-dep <project> pytest coverage --dev` (runs `uv add --dev` + `uv sync`, so the venv isn't left stale). A stale project venv is auto-reconciled on `code run`/`code test` before executing (set `OPENFUSED_NO_VENV_SYNC=1` to opt out).

```sh
# Workspace-registered project
fused code test mymodule.py --test-file test_mymodule.py --project taxi-pipeline

# Ad-hoc path (venv materialised in place on first run)
fused code test mymodule.py --test-file test_mymodule.py \
    --project-dir ~/.claude/skills/taxi-pipeline
```

| Option | Default | Notes |
|---|---|---|
| `-c / --code CODE` | — | Inline code string to test |
| `--file` | off | Force-treat SRC as a file path (auto-detected when SRC exists on disk) |
| `--test-file PATH` | required | Pytest file to run (must import from `user_code`) |
| `--input-file PATH` | — | File extracted to Lambda working directory (repeatable) |
| `--project NAME` | — | Project venv to use (required on local backend; rejected on AWS/Fused). Mutually exclusive with `--project-dir`. |
| `--project-dir PATH` | — | Ad-hoc project dir venv (local-only; materialised on first run). Mutually exclusive with `--project`. |

---

## HTTP serving (`code serve`)

Serves Python code as a live HTTP endpoint (GET and POST) backed by the active compute backend (Lambda or a local host venv). Same source interface as `code run`, plus `--port` and `--host`.

```sh
# From a file → GET+POST /<stem>
fused code serve myudf.py --port 8000

# Inline code — requires -c/--code → GET+POST /run
fused code serve -c "result = 1 + 1"

# From stdin with custom route name → GET+POST /tiles
cat myudf.py | fused code serve --name tiles

# Override route name for a file → GET+POST /api
fused code serve myudf.py --name api --port 8000

# With static files available in every request's execution context
fused code serve myudf.py --input-file model.pkl --input-file config.json

# Bind publicly
fused code serve myudf.py --host 0.0.0.0 --port 8080

# Serve a project directory (multi-entrypoint).
# Folder-per-UDF layout (scripts/ present): each scripts/<name>/main.py → route /<name>.
# Flat layout (no scripts/): each top-level .py file → route /<stem>.
#   Excludes test_*.py / *_test.py / conftest.py / _*.py.
# In both modes, project-root _*.py files are shipped as shared resources.
# (A legacy root-level UDF-folder project must be migrated to scripts/ first —
#  `fused project migrate` — it is not served as-is.)
fused code serve ./my_project   # → GET+POST /daily, /stars, …
```

`--name` is rejected with a directory (each file is its own route); the reserved
route `health` is refused. A single file, `-c`, or stdin is one route.

`code serve` is the **local dev server only** — there is no `--deploy`: deployed
serving is share-only (`infra serve` provisions the plane, `share create` mints
each URL; see those sections).

**GET** — query params become `_params.json`:
```sh
curl "http://localhost:8000/myudf?lat=37.7&lon=-122.4"
```

**POST** — JSON body becomes `_params.json`:
```sh
curl -X POST http://localhost:8000/myudf -H "Content-Type: application/json" \
     -d '{"lat": 37.7, "lon": -122.4}'
```

Access params inside the execution context:
```python
import json
params = json.load(open("_params.json"))
lat = float(params.get("lat", 37.7))
result = {"lat": lat}
```

`result` is serialized as the JSON response body. Unhandled exceptions return `{"error": "<traceback>"}` with status 500. `GET /health` is always registered.

| Option | Default | Notes |
|---|---|---|
| `-c / --code CODE` | — | Inline code string to serve |
| `--file` | off | Force-treat SRC as a file path (auto-detected when SRC exists on disk) |
| `--name` | file stem or `run` | Override the route name |
| `--input-file PATH` | — | File included in every request's execution context (repeatable) |
| `--port` | `8000` | Port to listen on |
| `--host` | `127.0.0.1` | Host to bind (use `0.0.0.0` for public) |
| `--cache-max-age TTL` | `0s` | Cache route results for `TTL` (`s`/`m`/`h`/`d`); `0s` disables. On a hit the compute backend is not invoked; the body returns inline with `X-Openfused-Cache: hit; age=<s>` / `miss` |
| `--cache-allow-bypass` | off | Honour `Cache-Control: no-cache` to force a fresh execution and rewrite the entry |

### Deployed serving moved (`infra serve` + `share`)

`code serve --deploy`, `code serve --teardown`, and `code serve-list` **no longer
exist** (on any backend — the local background-process deploy is gone too).
Deployed serving is share-only:

- **`fused infra serve`** provisions the environment's serving plane (one
  HTTP API + one dispatcher Lambda) — see *Infrastructure* below;
- **`fused share create`** publishes an app and mints its URL with the access
  control you choose — see the `share` section below;
- **`fused share revoke`** / **`infra serve --rate-limit 0`** /
  **`infra serve --teardown`** take things down at the URL / plane level.

---

## Served URLs / share links (`share`)

The share-only URL model: `share create` is the **only**
URL-minting operation — it publishes an app (a `.py` file or a project directory)
as a content-addressed artifact and writes the mount record binding a token to it
with the access control you choose. Publishing never builds infrastructure.

It dispatches on the resolved environment's backend:

- **AWS** — publishes to the env's cache bucket and serves through the plane
  `infra serve` provisions. Requires a `cache_bucket`. All verbs work.
- **Fused** (managed) — uploads the source and mints the mount through the
  control-plane mounts API (needs `fused cloud login`). `create`, `list`,
  `repoint`, `recreate`, `revoke`, `update`, `errors` and `cache-clear` work;
  `show` is AWS-only. There is no `infra serve` — Fused operates the plane.

Every lifecycle op is audited (`share_create`/`share_revoke`/`share_recreate`/
`share_repoint` in `audit log`).
```sh
# Authed mount (default: Login with Fused) — token auto-derived from the stem
# (my_file.py → /my-file); audience from --jwt-audience or the env's
# serve_auth.default_audience (required — errors without one)
fused share create my_file.py

# Public share link — mints a crypto-random opaque token (the token IS the
# credential; a guessable public URL is never produced by accident)
fused share create --public my_file.py

# Named public mount — deliberately guessable; requires the explicit --token
# (prints a warning)
fused share create --public --token acme-dash my_file.py

# Custom issuer (must be on the env's serve_auth.issuer_allowlist) / ACL
fused share create --jwt-issuer https://idp.acme.io/ --jwt-audience proj my_file.py
fused share create --acl-subject alice@acme.io --acl-group analysts my_file.py

# Allow a browser origin to call the mount (repeatable for multiple origins)
fused share create --public --cors-origin https://app.acme.io my_file.py

# Whole project directory (entrypoints discovered like `code serve <dir>`),
# or a single entrypoint of it; --not-after sets a hard expiry
fused share create ./project
fused share create ./project --entrypoint daily --not-after 2026-12-31T00:00:00Z

# Inspect (list shows YOUR mounts; --all for everyone's)
fused share list
fused share list --all
fused share show my-file

# Revoke — the record becomes a tombstone: the token stays reserved (no other
# principal can ever claim it) and the mount goes dark within the plane's
# mount-cache TTL; --confirm forces the strong flush (dispatcher recycle —
# no new invocation sees the old record)
fused share revoke my-file
fused share revoke my-file --confirm

# Recreate: a FRESH opaque token for the same target (default — right for a
# leaked link), or revive the SAME token in place (owner-only)
fused share recreate my-file
fused share recreate my-file --same-token

# Repoint: update an ACTIVE mount's target in place — URL/token UNCHANGED.
# Publishes new-code.py, bumps the mount's version, and emits share_repoint
# in the audit log. Auth flags update the gate; omit to keep the existing gate.
fused share repoint my-file new-code.py
fused share repoint my-file new-code.py --entrypoint daily
fused share repoint my-file new-code.py --public
fused share repoint my-file new-code.py --jwt-audience new-proj
# --confirm forces the strong flush so the change is visible immediately
# (without it the update is visible within the mount-cache TTL, typically ≤5 min)
fused share repoint my-file new-code.py --confirm

# Result caching for the mount, and letting viewers download the page source
fused share create my_page_dir --cache-max-age 5m
fused share create my_page_dir --allow-clone

# Change posture in place (no SRC, no republish, same URL)
fused share update my-file --no-allow-clone

# Bust every cached result under a mount (the only cache-clear command)
fused share cache-clear my-file

# Owner-side detail behind an opaque deployed 500 {"error": "internal error", "id": …}
fused share errors my-file                 # newest-first records for one mount
fused share errors my-file <err-id>        # one full record
fused share errors --env --since 2h        # sweep the whole environment
```

Key rules: token auto-generation is gate-aware (authed → stem-derived name,
public → crypto-random opaque; `--random-token` forces opaque on an authed
mount). Creation is **owner-bound** — only the principal that published an app
(the normalized STS caller ARN, or `OPENFUSED_CALLER_NAME` without AWS
credentials) may mint mounts of it, and revoke/recreate are owner-guarded the
same way. Re-running `share create` with identical content is an idempotent
republish. `recreate` requires the mount to be revoked first. `repoint`
requires the mount to be **active** — revive a tombstone first with
`recreate --same-token` before repointing it.

`--cache-max-age TTL` (e.g. `5m`, `1h`, `0s`): on AWS it applies to fused-render
page bundles only and overrides the bundle manifest's own `cache_max_age`; on
Fused it applies to any mount. `--allow-clone` (fused-render pages only, off by
default) lets anyone who can reach the URL **download the page's source
bundle** — with `--public` that publishes the page's Python source to anyone
holding the URL. Turn it off again with `share update TOKEN --no-allow-clone`.

---

## Infrastructure (`infra`)

The `infra` commands work for the AWS and local backends; each command
dispatches by the resolved environment's backend (they are not supported on the
Fused backend).

### Plan / apply / teardown

```sh
fused infra plan        # dry run — exits 1 if changes needed
fused infra apply       # reconcile IAM role, Lambda functions to desired state
fused infra teardown    # delete all fused Lambdas + IAM role (prompts for confirmation)
fused infra teardown --yes   # skip prompt
```

`infra plan` is useful in CI: a non-zero exit code signals drift.

`infra teardown` does **not** delete S3 buckets or Secrets Manager secrets — it removes Lambda functions, the managed IAM role, and optionally the ECR repository.

### Local backend

For a `backend: "local"` environment, "infra" is the data/secrets/venvs
directories and the cached venv holding the env's `packages` (no cloud
resources):

```sh
fused infra plan          # reports missing dirs (exits 1 on drift)
fused infra apply         # create dirs (idempotent; bare venv is created lazily on first execute)
fused infra teardown      # remove the venvs dir + data dir (prompts); serve endpoints survive
fused infra lambda-reset  # clear the in-process venv ready-cache (nothing deleted from disk)
```

- `infra build-image` **errors** for local envs — there is no image; packages go
  into a cached venv, provisioned by `infra apply` (or lazily on first execute).

### The serving plane (`infra serve`)

Deployed serving's compute, managed like every other resource family. Provisions
one HTTP API (v2) + one dispatcher Lambda per environment; **mints no URLs** —
URLs come only from `share create`. Requires an AWS env with `cache_bucket`, and
Docker to build the dispatcher image (the fused package on the Lambda Python
base; `--image-uri` registers a pre-built image instead). Idempotent.

```sh
# Provision (or reconcile) the plane
fused infra serve

# Plane-wide rate limit; 0 = kill-switch (every mount answers 429, URLs stay
# stable; re-apply with N>0 to re-enable). Omitted = leave unchanged.
fused infra serve --rate-limit 50
fused infra serve --rate-limit 0

# Custom domain (every mount is hosted under it by path)
fused infra serve --domain api.example.com --cert-arn arn:aws:acm:...

# Remove the plane: every mount goes dark; mount records + published apps
# persist until `infra teardown`. Prompts unless --yes; takes no other options.
fused infra serve --teardown --yes
```

`infra teardown` also sweeps the plane (the `{prefix}serve` API, the dispatcher
Lambda, the serve ECR repo) along with everything else.

### Container image

AWS Lambda execution is **container-only**: the ECR image built here is the function's code (one `{prefix}container` function per env). There is no fallback — until an image is configured, `fused code run` fails with an error telling you to run `infra build-image`. Bake the packages you need into the image; per-call pip installs do not happen.

**Step 1 — configure the image in the environment** (one-time or when packages change):

```sh
fused env update prod \
  -p pandas -p pyarrow -p duckdb \
  --python-version 3.12
```

**Step 2 — build and push** (run again whenever you want to rebuild):

```sh
fused infra build-image
```

| Option | Default | Notes |
|---|---|---|
| `--image-uri` | — | Skip build; register a pre-built image |
| `--push / --no-push` | push | Push to ECR after build |
| `--builder` | env's `builder` (`codebuild`) | `codebuild` (default; remote AWS CodeBuild, no local Docker) or `local` (docker build on host) |

Build parameters (`-p/--package`, `--system-dep`, `--python-version`, `--image-platform`, `--image-repo`, `--image-tag`, `--builder`, `--dockerfile`, `--context-dir`) live in the environment config and are set via `env create` or `env update`. `infra build-image` reads them automatically.

After a successful build and push, the image is resolved to its digest URI (`…@sha256:…`) and stored in the resolved environment's `docker_image` field. Using the digest rather than the mutable tag (`:latest`) means `infra plan` can detect when a new image has been built and flag the Lambda function for update.

**CodeBuild is the default (no local Docker).** `infra build-image` builds remotely in AWS CodeBuild by default — no Docker daemon needed on your machine — using the env's cache bucket (the build source is uploaded there). Pass `--builder local` to build with the host Docker daemon instead. CodeBuild also accepts a user-supplied build context:

```sh
fused infra build-image                               # builds in CodeBuild (default); streams logs
fused env update prod --context-dir ./img --dockerfile Prod.Dockerfile  # your own Dockerfile + context
fused infra build-image --builder local -p duckdb    # opt into a host docker build
```

CodeBuild builds in your own AWS account and pushes via a service role whose ECR push is scoped to just the env's repo. `infra apply` also uses CodeBuild when the env's `builder` is `codebuild` (the default). `infra teardown` removes the CodeBuild project and its role. With no cache bucket configured (a deliberate `--no-cache-bucket`), the CodeBuild build fails fast with guidance to set one or pass `--builder local`. Limitation: concurrent builds sharing the same image tag aren't supported (the digest is resolved by the mutable tag).

---

## Serving an app's tools to an MCP host (`app serve`)

```sh
fused app serve /abs/path/to/app
```

`app serve APP_DIR` reads the `[[tool]]` tables from `APP_DIR/mcp.toml` and
publishes exactly those as tools on a **stdio MCP server**. Each call runs through
the **local** compute backend — an app's entrypoints read the author's own token
files and localhost services — so `--env` / the resolved environment are never
consulted. `mcp.toml` is authored by fused-render's MCP panel; a missing or
invalid one fails at startup, before the MCP handshake, so the host reports a
startup error rather than connecting to an empty tool list.

Point an MCP host at it with:

```json
{"command": "fused", "args": ["app", "serve", "/abs/path/to/app"]}
```

This is the only MCP surface `fused` ships. There is no general-purpose fused MCP
server (bare `fused` prints help), no `project serve --mcp` / `code serve --mcp`,
and no `widget`, `dev serve` or JSON-UI commands.

---

## Host extras (`fused[...]`)

Optional features need extras installed **on the host** where you run `fused`
(they are not packages inside the execution sandbox — use `env update -p` or
`project add-dep` for those):

| Extra | Installs | Needed for |
|---|---|---|
| `aws` | boto3, pyjwt[crypto], pyarrow | any AWS environment (Lambda, S3, Secrets Manager, `infra`, `share` on AWS) |
| `arrow` | pyarrow | `files schema`, and `fused.run()` returning a DataFrame (included in `aws`) |
| `ai` | anthropic | `code verify --spec` (the LLM spec check) |
| `verify` | ty | the type checker; no current CLI command runs it |
| `local` | keyrings.alt | local-backend secrets where there is no OS keychain (Linux/WSL; stores unencrypted) |
| `batch`, `raster`, `vector` | geo stacks + pyarrow | the legacy workbench geo SDK |
| `all` | geo stacks, selenium, fastmcp, pyarrow | everything above except `aws`, `ai` and `verify` |

Combine them as needed, e.g. `uv tool install 'fused[aws,ai]'`. `fused app serve`
needs no extra.

---

## Common patterns

### First-time AWS setup

```sh
fused env create prod --backend aws --prefix myapp- -p pandas
# Provisions IAM role + cache bucket
fused infra build-image   # build + push the Lambda container image (required before execution)
fused infra apply         # creates the {prefix}container Lambda from the image
fused infra plan          # verify no further drift
```

### Targeting a specific environment for a single command

```sh
fused --env staging secrets list   # uses staging for this command only
```

### CI drift check

```sh
fused infra plan || echo "Infrastructure out of sync — run apply"
```

### Inspecting a dataset before running expensive code

```sh
fused files schema --bucket my-bucket --key data/large.parquet
fused files count --bucket my-bucket --prefix data/ --ext .parquet
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…