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
  • 3 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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.