@indx-workspace/cli
v0.6.9
Published
The Agent Workspace project CLI: create-agent-workspace generates a deployment project from one validated profile, and agent-workspace starts and validates it.
Readme
Agent Workspace CLI
@indx-workspace/cli on npm, released in lock-step with @indx-workspace/contracts, sdk, aw
and create (ADR-0082). npm create @indx-workspace@latest <directory> runs its generator through
@indx-workspace/create; npx -p @indx-workspace/cli agent-workspace --help prints the operator
usage. The package exposes two commands:
create-agent-workspace <directory> [--yes]writes the v2 projectaw devruns (S1-16). See Creating a project.create-agent-workspace <directory> --cloud aws|gcp|azure|local [--validate]writes the deployment profile and renders every artefact from it. It resolves every release and third-party image tag to an immutable registry digest before writing the project, so generation fails when artifacts cannot be verified.agent-workspace startvalidates those digests and starts the generated Compose topology, then waits for the web URL to answer and exits non-zero, naming the URL, when it does not. It prints the configured and effective runtime after Compose is healthy.agent-workspace stop [--volumes]takes the generated Compose project down, with the runtime sandboxes its runner started, and fails unless nothing of the project is left. See Stopping a project.agent-workspace start --embeddedis refused. The development-only process-local mode imports the server itself, which is not published, so it runs from a clone of the repository:pnpm start:embeddedat its root (packages/embedded).agent-workspace validate [directory]re-checksagent-workspace.yamland probes the stores it names.agent-workspace render [directory] [--check]re-rendersdeploy/,compose.yamland the image lines of.envfrom an editedagent-workspace.yaml;--checkexits non-zero on drift and writes nothing. See Re-rendering a project.agent-workspace deploy [directory] [--yes] [--target helm|terraform] [--infra <dir>]renders, prints the plan of the real tool for the profile's target (Docker Compose, Helm or Terraform), and applies it only with--yes. See Deploying a project.agent-workspace doctor [directory] [--json]reports, in one run, whether the project can start: the profile, its stores, the tools its target needs onPATH, and the server's contracts version. See Doctor.agent-workspace --versionprints the CLI version and the contracts version it was built against, the wayaw --versiondoes.
Creating a project
Without --cloud, npm create @indx-workspace <directory> writes a new directory holding the v2
project aw dev runs: package.json (exact pins of @indx-workspace/aw,
@indx-workspace/embedded and @indx-workspace/sdk at this CLI's version, and a dev script
running aw dev), pnpm-workspace.yaml, .gitignore, agent-workspace.yaml (kind: Project),
agents/<agentName>.ts and surfaces/main.ts. The last three are the files aw init writes,
rendered from the same setup answers.
- Without a directory, create asks
Project namefirst (typed; Enter takesmy-agent; a name that exists or is not a valid name is refused and asked again). With no terminal it usesmy-agentand says so on stderr. - On a terminal, create asks the setup questions in order, showing each one's choices or
grammar and its recommended answer; an empty line takes the recommendation, and an answer that
does not fit is asked again. It then lists the files and asks
Write these 6 files into <directory>?as a Yes/No menu (Yes is the default); a No writes nothing. - After writing, on a terminal, it asks
Install dependencies now?and thenStart it now?(Yes is the default for both). The install runs the package manager that rannpm create(npm_config_user_agent: npm, pnpm, yarn or bun) in the project directory, and starting runs<manager> run devthere, which isaw dev.--install/--no-installand--start/--no-startanswer the questions (--startimplies--install). --yestakes every recommended answer, writes without asking and installs the dependencies; it never starts the dev server, so a coding agent or CI does not block. npm reads a--yesof its own, so pass it after--:npm create @indx-workspace <directory> -- --yes.- When no person is at a terminal (stdin or stdout is not a TTY, or
CIis set) and--yesis not given, create takes the recommended answers (and your flags) and says so on stderr:no terminal: used the recommended answers (change them later with: npx aw init). It installs or starts only when--installor--startis given. It never reads answers from stdin. --answers <file>(YAML or JSON) answers the setup questions from a file; flags win over it. A provider that needs an endpoint without--model-base-urlexits 2 withfix: add --model-base-url <url>and writes nothing.- What was not done is printed as
Next steps:in order:cd, the install, the model key (export NAME=<your key>, or on Windows both a PowerShell and a cmd line), then<manager> run dev. - Each setup question has a flag that answers it:
--provider,--agent,--colorand the rest;create --helplists them all with their format and default. A flag's value is checked like the interview's answer, and a question a flag answered is not asked again. - An existing directory is refused, and no file is ever overwritten. No
.envis written and no secret is generated:aw devreadsANTHROPIC_API_KEYfrom its environment and never writes it to a file. - It ends with what to run next (
Next steps:thencd <directory>,npm install,npm run dev, and anexportline when the provider key is not set), thento share a workspace later: aw login --url <url> (reads workspaces; pushing with it is not supported yet).
--cloud local|aws|gcp|azure writes the deployment project instead, as before: the Compose
project for local (whose four answers are --database, --storage, --runtime and --auth,
or the defaults with --yes), or the profile and the Helm and Terraform artefacts for a cloud.
--yes and --answers are refused with a cloud other than local, and --validate without one.
render, validate, deploy and doctor read a deployment profile. Until P-2 they refuse a v2
project's agent-workspace.yaml by name, at the line and column of its kind key:
kind: Project is read by aw dev; render and deploy arrive with P-2The deployment profile
agent-workspace.yaml states where one deployment keeps its state, bytes, secrets, sandboxes and
identity. It is the only place those five decisions are written down, and every other artefact is
rendered from it — nothing is configured twice.
apiVersion: agentworkspace.dev/v1
kind: Deployment
cloud: gcp
storage:
relational: cloud-sql # a postgres:// URL, or that cloud's store by name
# rds | cloud-sql | azure-postgres, or compose-postgres on cloud: local. A name keeps the
# connection string — a credential — out of the file, in .env where the deployment reads it.
objects: s3://acme-ai-files # s3://, GCP's gcs://, Azure's azblob://, or local-filesystem
secrets: local-key # AWS also accepts kms, GCP secret-manager, and Azure key-vault
runtime:
default: cloud-run-service
identity:
oidc: https://accounts.google.com # optional
bootstrapAdmin: [email protected]The file is read by a YAML 1.2 parser (the core schema), so any valid YAML spelling of these fields
works. Some valid YAML is still refused, because a reader could take it more than one way: anchors
and aliases, tags other than the core schema's (!!str, !!int and so on), %TAG and %YAML
directives, duplicate keys, and a second document. Every refusal, including an unknown or
misspelt field, names the line and the column, as in
agent-workspace.yaml line 6, column 3: storage.kms is not a field of the deployment profile; ….
apiVersion and kind say which format the file is in; this release reads
agentworkspace.dev/v1 and Deployment and refuses any other value. A profile written before they
existed is still read as v1 for one minor release: agent-workspace validate accepts it and prints
a warning on stderr naming the two lines to add at the top of the file. A later release will refuse
it.
Editor validation and completion
A generated profile starts with a line that points the
YAML extension for VS Code,
and any other editor that uses yaml-language-server, at the profile's JSON Schema:
# yaml-language-server: $schema=https://unpkg.com/@indx-workspace/[email protected]/schema/agent-workspace.schema.jsonWith the extension installed, the editor completes each block's keys and the values the profile's
cloud accepts, and underlines an unknown key, a cross-cloud store or an unbuilt runtime driver as
you type. This package ships the schema as schema/agent-workspace.schema.json, and the URL names
that file on unpkg for the CLI release that wrote the profile, so it does not change under a
committed profile; for a profile written by an earlier release, add the line with the version of
the CLI you run.
0.2.1 was published before the package shipped the schema, so the URL a 0.2.1 profile names does not resolve and the editor silently stops validating; it resolves from the first release after 0.2.1. To validate without the network, point the first line at the installed copy:
# yaml-language-server: $schema=./node_modules/@indx-workspace/cli/schema/agent-workspace.schema.jsonThe schema is generated from the same Zod definition validate checks with
(src/profile-schema.ts), into packages/schemas (@agent-workspace/schemas) and schema/.
validate stays the authority: it also checks that a postgres:// connection string and the OIDC
issuer parse as URLs, which JSON Schema cannot express. The decision and the URL are recorded in
docs/adr/0091-the-deployment-profile-json-schema-is-generated-from-the-cli-zod-schema.md.
What --cloud writes:
| --cloud | Rendered, besides agent-workspace.yaml, .env and .gitignore | Default runtime |
| --------- | ----------------------------------------------------------------- | ------------------- |
| local | the Compose project | docker |
| aws | deploy/helm/values.yaml (layered on values-eks) | kubernetes |
| gcp | the same, plus deploy/terraform/gcp-cloudrun.auto.tfvars.json | cloud-run-service |
| azure | deploy/terraform/azure-container-apps.auto.tfvars.json only | aca-sessions |
The local Compose project is named after its directory: .env sets COMPOSE_PROJECT_NAME to
the directory name lowercased, with every character Compose does not accept replaced by -, and
the project's runtime-control and provider-egress networks take that name as their prefix. Two
generated projects, or one beside the repository's own development stack, therefore never adopt or
remove each other's containers. Two directories with the same name (in different parents) get the
same project name: set COMPOSE_PROJECT_NAME in one project's .env to run two projects of the
same name side by side. PORT in .env is the host port the web service is published on
(3000 by default): change it when that port is taken or to run a second project beside the
first. agent-workspace start answers on http://127.0.0.1:<PORT>, or on the host a tcp:// or
ssh:// Docker endpoint names: DOCKER_HOST from the shell or .env, else the current
docker context. It fails when the URL does not answer, or when something other than this
project's web service answers on it (another process holding the port). Before it pulls or starts
anything, start checks that PORT is free: it asks the daemon which containers publish it (a
container of this project is the project already running, and is fine), and on a local daemon it
also binds the port on this machine. A taken port stops the start with nothing started, saying to
free the port or change PORT. A process outside Docker on a remote daemon's host is found only
when Docker cannot bind PORT there: start then stops the services Compose had already started
with docker compose down (the data volumes are kept) and gives the same advice. The web service
is the only one on a non-internal network, which also gives it outbound access.
The runner reaches the daemon through its mounted /var/run/docker.sock and joins the socket's
group, DOCKER_GID. The generator writes this machine's value (0 outside Linux); start asks the
daemon it talks to, with a short-lived container of the pinned PostgreSQL image, and uses the
daemon's answer for that start when it differs, printing the value to record in .env. That is
what makes a remote, VM or docker-in-docker daemon work. If the daemon cannot answer and the runner
is denied the socket, start fails, naming DOCKER_GID. The session cookie is Secure, which
browsers keep only over HTTPS or on localhost: for a web URL on a remote Docker host, start names
an SSH port forward to localhost, or serve the project over HTTPS.
Browsers share cookies across ports, so the generated project passes COMPOSE_PROJECT_NAME to the
web service as AW_INSTANCE_ID, and its sign-in cookie is __Host-aw_session_<project>: two
projects on localhost:3000 and localhost:3002 in one browser keep separate sessions. A
deployment without AW_INSTANCE_ID keeps the name __Host-aw_session.
Re-rendering a project
Edit agent-workspace.yaml, then run agent-workspace render in the project directory (or
agent-workspace render <directory>). It reads the profile with the same reader as validate, so
a refused profile names its line, and then nothing is written. It re-renders, with the renderers
create-agent-workspace uses, the files it owns:
deploy/helm/values.yamlanddeploy/terraform/<module>.auto.tfvars.json, where the profile has that target;compose.yaml, forcloud: local, restored to the template the generator copies;- the image lines of
.env:WEB_IMAGE,GATEWAY_IMAGE,RUNNER_IMAGE,AGENT_WORKSPACE_RUNTIME_IMAGE_CLAUDE_AGENT_SDK, and forlocalPOSTGRES_IMAGEandSEAWEEDFS_IMAGE.
Every other line of .env (the secrets, the profile-derived settings, and anything you added) keeps
its value, its place and its line ending; a missing image line is added after the others. Each owned
file is listed as created, updated or unchanged, by path only: no value from .env is ever
printed, and a malformed .env line is refused by its number. A second run with the same profile
changes nothing. render never deletes a file, including one an earlier profile rendered (a
deploy/terraform/ file for the previous cloud, say), and never writes a file it does not own.
What it leaves in place is listed under Left in place, by path or .env key name only: a
compose.yaml or deploy/ file the current profile no longer renders, an image key it no longer
writes (POSTGRES_IMAGE and SEAWEEDFS_IMAGE off local), and the image keys of an older
generator (RUNTIME_IMAGE, MINIO_IMAGE). Remove them yourself once nothing uses them; the list
does not change the exit code of --check.
.env lines are read as Compose reads them: export WEB_IMAGE=… is the key WEB_IMAGE, and in an
unquoted value a # after whitespace starts a comment. An image line is rewritten in place and
keeps its export and its comment.
Each changed file is written to a temporary file next to it, flushed to disk and renamed over the
old one, so a crash cannot leave .env half-written; the new file keeps the old one's mode. A
replaced file is a new file, so a hard link to it no longer follows it and it is owned by whoever
ran render. render writes only regular files inside the project: if an owned path, or a
directory on the way to it (deploy, deploy/helm), is a symbolic link, it refuses before writing
anything.
Each image is taken from an AW_<NAME>_IMAGE override when one is set, else from the digest the
project's .env already pins, else from this CLI's release default. So a plain render is
offline and never upgrades an image; AW_WEB_IMAGE=…@sha256:… agent-workspace render is how a
patched image is picked up. A missing .env is refused: render never generates secrets.
agent-workspace render --check lists what would change (would create, would update) and exits
1 when anything would, writing nothing — run it in CI to fail a pull request whose generated files
were not re-rendered.
render rewrites only the image keys of .env. A profile edit that changes another .env value —
storage.objects (the OBJECT_STORAGE_* keys), identity.bootstrapAdmin (BOOTSTRAP_ADMIN), the
runtime or the secret backend — still needs that line edited by hand (ADR-0090).
Deploying a project
agent-workspace deploy takes an edited profile to the running deployment (ADR-0094). It runs
render first, in-process, so a refused profile is reported with its line and no tool runs. It
then drives the real tool for the profile's target, prints that tool's plan, and changes the
deployment only with --yes:
| cloud | Target | Plan | --yes |
| ------------------------------------------ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| local | Docker Compose | docker compose config --quiet, then each service as create, recreate, start or unchanged | docker compose up --detach |
| gcp, azure | Terraform (gcp-cloudrun, azure-container-apps) | terraform plan -detailed-exitcode -var-file=<rendered tfvars> -out=<temporary plan file> | terraform apply <that plan file> |
| aws; gcp, azure with --target helm | the chart infra/helm/agent-workspace | helm diff upgrade with the helm-diff plugin, else helm template and a Kind/name list of resources | helm upgrade --install … --create-namespace --wait |
- Exit codes. Without
--yesnothing is deployed:deployexits 0 when the deployment matches the profile and 2 when there are changes, asterraform plan -detailed-exitcodedoes. A refusal exits 1. Without helm-diff the cluster cannot be compared, so a chart plan always counts as a change. - Apply what was printed. For Terraform,
--yesapplies the plan file the printed plan wrote, never a second plan. The file holds variable values in plain text, so it lives in a0700temporary directory and is removed when the command ends, including on Ctrl-C orSIGTERM. --infra <dir>. The Terraform modules and the chart are not part of a generated project. A cloud target needs--infranaming theinfra/directory of a checkout of this repository;localdoes not. Terraform runs asterraform -chdir=<infra>/terraform/<module>with the rendereddeploy/terraform/<module>.auto.tfvars.jsonpassed as-var-file, so nothing has to be copied into the module. Runterraform -chdir=<infra>/terraform/<module> initwith your backend once first;deployrefuses an uninitialised module rather than configure a backend for you. Helm installs the releaseagent-workspacein the namespaceagent-workspace, with the cloud's flavour file and the rendereddeploy/helm/values.yaml, asdocs/deploy/kubernetes.mddoes, and creates that namespace on the first install.- The Compose project. Every
docker composecall runs for theCOMPOSE_PROJECT_NAMEin the project's.env, even when your shell sets another one. --target.gcpandazuredeploy with Terraform by default;--target helmselects the chart. A target the profile has no artefact for (--target terraformonaws,--target helmon an Azure profile with anazblob://store) is refused by name.- What is printed. Every tool runs without a shell, with its arguments as an array, in the
project directory. Each command line is echoed, and everything a tool prints passes through a
redactor first: it masks every
.envvalue except the public settings the generator writes (image references, the project name, bucket, region and service addresses) and the password of anyscheme://user:password@URL.docker compose configitself is never printed, because it interpolates.env.render's own report passes through the same redactor. A missing tool fails with advice to runagent-workspace doctor.
deploy writes the files render owns before it plans, because Terraform and Helm read them from
disk; that is the only thing it changes without --yes. So even a plan-only deploy rewrites
deploy/, compose.yaml (local) and the image lines of .env to match the profile. Run
agent-workspace render --check first to see what that would change without writing anything.
From a v2 project. @indx-workspace/cli/deploy exports deployProjectEnvironment, which
aw deploy production loads (ADR-0103, #621). It bridges the project's environments.production
to a profile with projectToDeploymentProfile, keeps that profile's deployment at
~/.config/agent-workspace/deployments/<project>/production/ (created 0700 with a 0600
.env on the first run, never regenerated), and runs this deploy over it. single-vm deploys
with Compose (docs/deploy/single-vm.md); gcp-cloudrun deploys with Terraform and needs
infraDirectory (docs/deploy/gcp-cloudrun.md). Each page lists the prerequisites it refuses by
name.
Stopping a project
Run pnpm stop (agent-workspace stop) in the project directory. It runs
docker compose down --remove-orphans, then removes what Compose does not know about: the runtime
sandboxes the runner started through the Docker socket, and the project's networks. A sandbox is a
container labelled agent-workspace.runtime=docker and either agent-workspace.compose-project=<project>
or attached to <project>-runtime-control (sandboxes from 0.1.2 and earlier carry no project
label); nothing else is removed. A sandbox's anonymous workspace volume goes with it, as when the
runner ends a session. The project's named volumes (the database, the object store, the files) are
kept; pnpm stop --volumes removes them too. The command exits non-zero, naming each container
or network still there, unless the project is gone.
The runner also removes its own sandboxes when it is stopped (SIGTERM, within the 30-second stop
grace period the template gives it), so a plain docker compose down normally leaves nothing
either. agent-workspace stop is what catches a runner that was killed, or sandboxes an older
release left.
Every cloud renders the .gitignore, which excludes .env: that file is where the generated
administrator password and every store credential live, and the profile beside it is the one the
deploy guides tell you to read, edit and commit.
agent-workspace deploy --infra <checkout>/infra applies a rendered
deploy/terraform/<module>.auto.tfvars.json where it lies (see
Deploying a project). To run Terraform yourself, copy it into
infra/terraform/<module>/ and run Terraform there — that file is auto-loaded only from the
directory Terraform runs in, and the generated deploy/terraform/ holds no .tf file of its own:
cp acme-ai/deploy/terraform/gcp-cloudrun.auto.tfvars.json infra/terraform/gcp-cloudrun/
terraform -chdir=infra/terraform/gcp-cloudrun applydocs/deploy/azure-container-apps.md shows the same copy for azure-container-apps. The
generator's own Next: line prints these steps for the artefacts it actually rendered.
There is no Terraform module for AWS; an EKS cluster you already run is installed with the chart.
And --cloud azure renders no chart overlay: its default storage.objects is an azblob://
container, while the chart wires only the S3 environment contract. Container Apps supplies the
Blob endpoint, managed identity and container. Name an s3:// bucket in agent-workspace.yaml to
deploy an Azure profile with the chart; the command says so when it withholds the overlay.
"Validated" means two things. Always, before any file is written: the document is schema-valid,
every URL scheme is one this release serves, and the cross-field rules hold — a refusal names the
offending dotted path. With --validate, and only for a store whose credential is present: a
connectivity probe (select 1, a bucket head, an OIDC discovery fetch). A failed probe names the
store and never prints the driver's own message, which routinely quotes a connection string.
Validation never writes. A bucket that is not there yet is reported as its own outcome —
missing objects: bucket <name> does not exist yet — the platform creates it on first boot —
rather than created: the platform creates it when it first boots, and a bucket the command created
itself would hide the mistake this outcome exists to show, a typo in storage.objects. It is not
a refusal, so the command still exits 0.
AWS profiles accept s3:// with kms; GCP profiles accept gcs:// with secret-manager;
Azure profiles accept azblob:// with key-vault. Cross-cloud use is refused rather than
accepted-and-ignored. See
docs/adr/0055-the-deployment-profile-is-one-validated-document-the-installer-renders.md.
Doctor
agent-workspace doctor [directory] [--json] runs four checks and prints one line for each result,
pass, warn or fail, with one line of advice under every warning and failure. It fixes
nothing.
| Check | Pass | Warn | Fail |
| --------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------- |
| profile | agent-workspace.yaml validates | it has no apiVersion | it is missing or refused; the refusal names the line |
| stores | the store answered the validate probe | the store was not probed, or its bucket is not created yet | the store did not answer, or .env is malformed |
| prerequisites | the tool is on PATH; its version is shown | an optional tool is missing, or a tool printed no version | a tool the target needs is missing |
| version-skew | the server serves this CLI's contracts | AGENT_WORKSPACE_URL is unset, the server is unreachable, or it states no version | the server is outside the range this CLI supports |
A store is not probed when .env holds no credential for it, or when it is on the Compose
project's internal network; the detail says how to check it. A bucket the platform creates on
first boot is a warning until it exists. The store probes are the ones validate runs, and they
stop at the first store that does not answer: that store fails and the others are reported as not
checked. The version check is the one validate and start run (ADR-0078). When the profile is
refused, the store and tool checks are reported as not checked; the version check still runs.
The tools checked depend on the profile:
| Profile | Tools |
| ------------------------------------------- | ----------------------------------------------------------------------------------- |
| cloud: local | docker and Docker Compose v2 |
| cloud: aws (the chart) | kubectl and helm |
| cloud: gcp, cloud: azure (Terraform) | terraform; with a gcs:// or s3:// bucket, also kubectl and helm, optional |
| runtime.default: sandbox-runtime on Linux | bwrap (bubblewrap) |
| runtime.default: docker / kubernetes | docker / kubectl |
A tool the profile does not need is neither run nor reported. Each tool is run once with its
version flag (terraform version, docker compose version --short, …); only the version number
is printed, never the tool's own output. A missing required tool fails with the address of its
install page.
The command exits 1 when any check fails and 0 when there are only warnings. The report is written
in full first; a failure then adds agent-workspace doctor: N check(s) failed on stderr.
--json writes the same results to stdout as one object, for a pipeline to read:
{
"ok": false,
"checks": [
{
"check": "prerequisites",
"subject": "terraform",
"status": "fail",
"detail": "terraform could not be run (not on PATH, or it did not answer); it is needed to apply the gcp module",
"advice": "Install Terraform: https://developer.hashicorp.com/terraform/install"
}
]
}No secret is printed, in either form. The userinfo of every URL is shown as ***, and so is any
value from the profile, .env or the environment that is a URL password or belongs to a variable
named like a credential (…PASSWORD, …SECRET, …TOKEN, …KEY).
Version skew
agent-workspace start and agent-workspace validate refuse a server whose contracts version this
CLI cannot operate. The check is one unauthenticated GET /api/me; a 401 is fine, because only the
x-agent-workspace-contracts and x-agent-workspace-contracts-minimum headers matter (ADR-0078).
startchecks once the server answers; a web URL that does not answer within two minutes is a failed start. A refused Compose deployment is left running: Compose started it and answered healthy before the check, and the CLI does not tear down what an operator may be about to inspect.agent-workspace stopin the project stops it.validatechecks whenAGENT_WORKSPACE_URLis set, in the shell or the project.env; without itvalidatestays offline.- Outside the served range the command exits non-zero. The message names the CLI's contracts
version, the range the server serves, which side to upgrade, and the fix: when the CLI is older,
Upgrade the CLI: npm install --global @indx-workspace/cli@<server minor>; when the server is older, upgrade the server,or downgrade the CLI: npm install --global @indx-workspace/cli@<server minor>. validatebounds the check by the sametimeoutMsas its store probes.- An answer without the headers, or an unreachable server:
validatewarns on stderr and proceeds, because ADR-0078 accepts a missing header.startfails in both cases. No headers means another process is holding the port: stop it or setPORTin.env. Unreachable means the start failed: checkdocker compose logs web. - In range: no change in behaviour or output.
The package is @indx-workspace/cli (CLI_PUBLISHED_PACKAGE, pinned to this manifest's name by a
test). The @agent-workspace scope on npm belongs to a third party, so nothing published names it.
A local project depends on exactly @indx-workspace/cli at the generator's own version (the
generator writes that dependency; the template's specifier is a placeholder), so pnpm install
inside it keeps the executable available after the ephemeral generator exits. A
--cloud aws|gcp|azure project deploys through Helm or Terraform and ships no package.json, so
its operator runs npx -p @indx-workspace/cli agent-workspace validate acme-ai instead. Do not
redirect users to the unscoped create-agent-workspace package name, which is not ours.
Release engineering can override source tags with AW_WEB_IMAGE, AW_GATEWAY_IMAGE,
AW_RUNNER_IMAGE, AW_RUNTIME_IMAGE, AW_POSTGRES_IMAGE, and AW_SEAWEEDFS_IMAGE. Overrides must
already be digest references. Mutable tags never reach the generated .env file.
AW_RUNTIME_IMAGE is the claude-agent-sdk engine's runtime image; the generated .env pins it
as AGENT_WORKSPACE_RUNTIME_IMAGE_CLAUDE_AGENT_SDK (ADR-0073, docs/deploy/engine-images.md). The MinIO-era
AW_MINIO_IMAGE is refused by name rather than ignored: the bundled object store is SeaweedFS
(ADR-0067).
Picking up patched images in an existing project
Platform images 0.1.2 patch the next/og remote code execution (GHSA-vcvr-r3jv-pc5j) and
@grpc/grpc-js CVE-2026-101916; projects generated with CLI 0.1.0 to 0.1.2 pin the vulnerable 0.1.0
or 0.1.1 images. Regenerate projects generated with CLI 0.1.0: their compose.yaml predates the
fixes in #379, so editing the image pins is not enough. The generator refuses an existing directory
and mints new secrets, so it cannot be re-run in place. Three ways work today:
- Generate a new project with
@indx-workspace/[email protected](or later) and move your data and settings across. - Edit the existing project's
.env: replace the digest inWEB_IMAGE,GATEWAY_IMAGE,RUNNER_IMAGEandAGENT_WORKSPACE_RUNTIME_IMAGE_CLAUDE_AGENT_SDKwith the digest ofghcr.io/indxdev/agent-workspace/<name>:0.1.2(web,gateway,runner,runtime-claude-agent-sdk), for example fromdocker buildx imagetools inspect. Keep each value a digest reference (...@sha256:...); the Compose file refuses a tag. Then runagent-workspace startagain. - With CLI 0.2.1 or later in the project, run
AW_WEB_IMAGE=… AW_GATEWAY_IMAGE=… AW_RUNNER_IMAGE=… AW_RUNTIME_IMAGE=… agent-workspace renderwith those digest references, which rewrites the same four lines of.env(and the rendereddeploy/files) and nothing else. See Re-rendering a project.
