Run and test
Local CLI and operator UI
Install and use the offline-first Chalupa CLI without bypassing its Taskfile, confirmation, or secret boundaries.
Reviewed 2026-10-16
The Chalupa CLI is the operator entrypoint for your environments. It can
inspect a configuration, read bounded local state, and delegate approved
actions to the public Taskfile actions bundled with it. It does not implement a
second Pulumi lifecycle. With provider: chalupa, GPU commands instead call
the console queue using the local account session; no provider token is needed.
Everything an operator does to a specific environment is a chalupa command:
chalupa up, chalupa tunnel, chalupa seed, chalupa down, and the rest of
the allowlist. The CLI never replaces the Taskfile; it selects one of its
public actions and passes a canonical config path for BYOC.
Managed GPU uses the console API and a pinned local SSH record.
Opening the interface does not contact DigitalOcean, read tvault, select a Pulumi stack, start Docker, or open SSH. An external process runs only after you explicitly request an action or refresh local status.
The other operator pages are:
- CLI command reference: the command table, with each command's flags and behavior.
- Copies and variants: several copies of one
chalupa.ymlside by side. - URLs, routes and shares: how a forwarded service is reached, and remote shares.
- Logs and diagnose: a box's logs, a bounded diagnosis, and a failed test run's evidence.
Install
curl -fsSL https://chalupa.run/install.sh | bash
chalupa --version
chalupa -V and chalupa version print the same thing.
The installer puts checksum-verified Bun and go-task on your PATH and installs
or upgrades @thelacanians/chalupa-cli from the npm registry, with no sudo.
For your own cloud account, add --with-byoc (bash -s -- --with-byoc) and it
also installs checksum-verified Pulumi and TinyVault when they are missing or
too old, then warns about any missing ssh, nc or Python 3. If Bun and
go-task are already installed, bun add -g @thelacanians/chalupa-cli installs
or upgrades only the CLI. After that, operator commands work from your
own project directory. go-task is required for any action that reaches the
cloud, because the CLI delegates to it; chalupa doctor reports that
separately.
Use the project in your current directory
Interactive commands find chalupa.yml in the current directory or an ancestor,
stopping at the Git root. Explicit --config and the existing configuration
environment variables take precedence. No billable operation falls back to the
bundled demo. Scripts using --confirm still need --config on the same command
line so the target and authorization remain explicit.
chalupa up
chalupa exec -- bun run dev
chalupa down
up checks local port availability before provisioning, then opens temporary
tunnels to check the configured health endpoint. Without compute.health,
it reports tunnel availability and leaves application readiness unverified. GPU inference also checks installed model metadata; a real
turn is needed to observe loaded GPU placement. Readiness failures can leave
compute running.
exec runs the supplied command locally, in your working directory, using
the existing environment. It opens the Taskfile-owned tunnels and closes them
on exit. It never creates or destroys compute, never rewrites .env, and never
infers database passwords or URLs. Existing app connection settings should use
the local ports shown in the connection output. The child also receives
CHALUPA_ENVIRONMENT and CHALUPA_PORT_<remote-port> values.
For inference, chalupa launch (using inference.agents.default) configures the endpoint for your
agent's process. See Agent harnesses for supported tools
and chalupa exec -- my-agent for other commands. The command's stdout stays available for output
or JSON receipts; Chalupa's connection messages go to stderr. Ctrl-C cancels
the session, and a lost tunnel stops the local process. Compute remains running.
For a remote shell, use chalupa ssh. Image builds and live file synchronization
are not provided by exec; selected Compose services require pre-built images.
Check the workstation
Run the executable check before operating an environment:
chalupa doctor
chalupa doctor --json
doctor reports offlineReady separately from operatorReady. The offline
inspection path is ready when the CLI itself is running. Full operation also
requires Task, Pulumi, tvault, SSH, and nc to be discoverable on PATH.
doctor does not run those tools or query credentials, Docker, the
network, local stacks, or a cloud provider; the one local program it does run
is Python, to check validation readiness. Add --verbose to print a footnote
that says exactly what was and was not queried. A missing operator requirement
produces a non-zero exit while the report still identifies offline inspection
as available.
Inspect safely first
With no chalupa.yml here or in a parent directory, inspect and preview
refuse (exit 64) instead of describing a bundled fixture. Pass --config PATH
for your project, or --demo to describe the repository's examples/demo-harbor
fixture on purpose:
chalupa inspect --demo
chalupa inspect --demo --json
With --demo it describes the bundled example offline. It runs the same Compose inference as the
Pulumi program and reports services, inferred roles, tunnel ports, named
volumes, persistence, diagnostics, and the complete redacted remote Compose
document. It does not run Task or any external process.
Redacted output is still operator information. Absolute paths, service names, topology, port numbers, images, and non-secret metadata can remain visible. Do not publish the output or treat it as a general data-loss-prevention scan.
Select a real configuration explicitly:
export DEMO_CONFIG="/absolute/path/to/project/chalupa.yml"
chalupa inspect --config "$DEMO_CONFIG"
chalupa tui --config "$DEMO_CONFIG"
Prefer --config in scripts and operator notes so the selected environment is
visible at the call site.
Commands find ./chalupa.yml, then CHALUPA_CONFIG, then parent configs.
--config PATH is an optional override. Legacy Taskfile config variables retain
precedence. Managed up and down ask yes/no; use --yes for scripts.
Help and exit codes
chalupa --help lists the commands in groups (Start, Work on a box, Sessions
and cost, CI and tests, Images and cache, Managed GPU, account and agents). chalupa help <command> prints the flags of one command, for example chalupa help data
or chalupa help session extend, and chalupa help --all prints every form of
every command. chalupa <command> --help works too.
A usage error exits 64: an unknown command, an unknown flag, a missing
argument, or a missing config. The message names the problem and points back to
chalupa --help. A Taskfile action keeps the exit code of the action itself.
Managed environments are chosen with --name ENV; --env is an alias of it.
Verbosity
Every action honours one verbosity control, spelled the same way on the CLI and on the bundled Taskfile:
| Spelling | Where |
|---|---|
-q, --quiet |
chalupa ACTION -q / chalupa run ACTION -q |
-v, --verbose |
chalupa ACTION -v / chalupa run ACTION -v |
CHALUPA_VERBOSITY=quiet|verbose |
environment, read when no flag is passed |
VERBOSITY=quiet|verbose |
the same control, for the bundled Taskfile run directly |
A flag always wins over the environment; omitting both leaves the readable
default. Verbose is exactly the historical firehose: Pulumi's own preview
table and per-resource progress on data-up/up/down, and ci follow's
raw chalupa-ci {...} NDJSON line per engine event. The default level
captures Pulumi's transcript and prints resource counts, duration, warnings,
and (for up/data-up) the ip/priceHourly/providerId or
volumeName/volumeId outputs, instead of the whole thing; it renders ci follow's journal as one line per step — verdict, duration, and a running
tally — instead of raw JSON. Quiet shortens a clean success further (a
single line for Pulumi; passing steps go unprinted on ci follow, though
the tally still counts them).
None of this changes what a failure shows: at every level, a failed
pulumi up/down prints the complete transcript it always printed, in
full, before the task exits non-zero. A diagnostic is never harder to reach
than it was before this control existed.
status reports what the local Pulumi backend knows about two exact stack
names. Compute is absent, empty, or present. Protected data is
not-configured, absent, present, or unknown; configured states include
the expected size from chalupa.yml, while a present state also includes the
bounded local volume name and ID. The expected size is not a live provider
capacity reading.
The read path does not select a stack, load a provider credential, or query
DigitalOcean. An unexpected data stack, invalid outputs, inconsistent config,
or failed read becomes unknown instead of being treated as absent. Local
state can still be stale; the provider console and invoice remain
authoritative.
Actions and confirmations
These are the actions the Taskfile bridge accepts, whether they are typed
directly or through chalupa run (a test keeps this table in step with the
action list). chalupa explain --json is the authoritative, machine-readable
list of every action with its risk and phrase. The Actions screen shows the
same set plus the bounded local-status and CI-status refreshes:
| Command | Taskfile action | Risk | Exact phrase |
|---|---|---|---|
chalupa preview |
preview |
offline | none |
chalupa validate |
validate |
offline | none |
chalupa data-up |
data-up |
billable persistent data | persist <environment> |
chalupa data-snapshot --rate <usd> |
data-snapshot |
billable: prints the reviewed plan and its id; --plan <id> creates the snapshot |
the plan id, via --plan |
chalupa data load <archive> --dataset <name> |
data:load |
uploads a checksummed archive to the box's protected volume as a named dataset | load <environment> |
chalupa cache-up |
cache-up |
billable image-cache volume | cache <environment> |
chalupa cache-snapshot |
cache-snapshot |
billable snapshot of the image cache (refused while a box has it attached) | snapshot cache <environment> |
chalupa cache-nuke |
cache-nuke |
destroys the image-cache volume | drop cache <environment> |
chalupa up |
up |
billable compute; with session.reuse: auto a matching released box is reused with no phrase and re-leased up to session.expiresAfterMinutes (tunnel.onReuse: none keeps the reuse from re-opening the supervised tunnel, tunnel.afterUp: none closes the readiness tunnel at the end of a cold up --wait); without a terminal the phrase is still required to reuse, unless you pass --reuse-only: it reuses a live unchanged box or refuses with a reason code, never launches, and needs no phrase or terminal |
launch <environment> |
chalupa agent-bootstrap |
agent-bootstrap |
changes the provisioned host | bootstrap <environment> |
chalupa runtime deliver (also chalupa box refresh) |
runtime:deliver |
changes Chalupa's own files on the host; not billable, idempotent | none |
chalupa tunnel |
tunnel |
read-only connection | none |
chalupa ssh |
ssh |
read-only interactive session | none |
chalupa seed |
seed |
changes application data | seed <environment> |
chalupa teardown register |
teardown:register |
changes the local expiry registry | register <environment> |
chalupa session extend --hours N |
session:extend |
billable: moves a live session's deadline | extend <environment> |
chalupa session renew |
session:renew |
billable: re-leases an expired session whose host is still running | renew <environment> |
chalupa session release |
session:release |
shortens the lease to the warm window, then stops the tunnel; never adds spend; refuses unless the local teardown schedule is healthy | none |
chalupa ci arm |
ci:arm |
billable suite time | test <environment> |
chalupa ci follow |
ci:follow |
read-only log stream (--json: NDJSON events of the run armed last, no terminal needed; exits 0 settled, 1 otherwise) |
none |
chalupa ci cancel |
ci:cancel |
stops the active run | cancel <environment> |
chalupa ci report |
ci:report |
publishes the settled run; idempotent | none |
chalupa ci artifacts |
ci:artifacts |
read-only artifact index | none |
chalupa ci secrets |
ci:secrets |
delivers the declared ci.secrets values (stdin) to the box; nothing created, no spend |
none |
chalupa ci collect |
ci:collect |
reads a settled run's evidence into ~/.local/state/chalupa/ci-runs/<runId>/ |
none |
chalupa data-snapshot --rate USD [--plan ID] |
data-snapshot |
billable: snapshot storage of the protected data volume; without --plan it is the read-only review |
none: the reviewed plan id confirms it |
chalupa data load <archive> --dataset NAME |
data:load |
replaces a dataset of the same name on the box's protected volume | load <environment> |
chalupa cache-up |
cache-up |
billable: the disposable image and build cache volume | cache <environment> |
chalupa cache-nuke |
cache-nuke |
deletes the cache volume only; never the data volume | drop cache <environment> |
chalupa cache-snapshot |
cache-snapshot |
billable: snapshot storage of the cache, taken while the environment is down | snapshot cache <environment> |
chalupa share <service> |
share:arm |
creates a remote URL for one declared service until its TTL; auth: none needs --confirm "share <environment> <service> publicly" |
none |
chalupa share revoke <service>|--all |
share:disarm |
revokes the share at the console and stops the agent on the box | none |
chalupa tunnel keep |
tunnel:keep |
starts the supervised background tunnel; idempotent | none |
chalupa tunnel stop |
tunnel:stop |
stops it; compute is not touched | none |
chalupa tunnel status |
tunnel:status |
read-only report of the supervised tunnel | none |
chalupa tunnel watch |
tunnel:watch |
runs the tunnel supervisor in the foreground | none |
chalupa wait |
wait-ready |
read-only wait for every compute.readiness probe |
none |
chalupa monitor VERB |
monitor |
read-only Monitor verb on the box over SSH; mutating verbs are refused | none |
chalupa teardown schedule:install |
teardown:schedule:install |
installs the macOS operator teardown scheduler | none |
chalupa teardown schedule:doctor |
teardown:schedule:doctor |
read-only check of the scheduler and credentials | none |
chalupa resize |
resize |
edits compute.size in the previewed config (shows a diff first); chalupa up applies it under its own phrase. Set SIZE=<provider size slug> |
resize <environment> <size> in the CONFIRM_RESIZE environment variable |
chalupa allow-me |
allow-me |
adds this machine's egress /32 to ssh.allowedCidrs in the previewed config (shows a diff first); the firewall changes at the next up |
none |
chalupa apps swap SERVICE |
apps:swap |
replaces one running service with a derived image and restarts it | swap <environment> |
chalupa experiment plan |
experiment:plan |
offline: the matrix, worst-case time and destructive steps | none |
chalupa experiment run |
experiment:run |
runs the declared flow, reset recipes and onFinish teardown included |
experiment <environment> |
chalupa experiment compare |
experiment:compare |
read-only: rebuilds the verdict from a run directory | none |
chalupa experiment report |
experiment:report |
publishes the result; idempotent | none |
chalupa report-obsidian |
report-obsidian |
writes one Markdown note file from a result | none |
chalupa report-cortex |
report-cortex |
writes one Cortex case file (refs only) from a result | none |
chalupa reset --dataset NAME |
reset |
runs the experiment's reset recipe on the box | cleanup <recipe> <environment> in the CONFIRM_RESET environment variable |
chalupa cleanup |
cleanup |
runs one named, reviewed cleanup recipe on the box; set RECIPE=<recipe name> |
cleanup <recipe> <environment> in the CONFIRM_CLEANUP environment variable |
chalupa report |
report |
publishes a test run's normalized stats; idempotent | none |
chalupa down |
down |
destroys disposable compute | sink <environment> |
<environment> is the exact resolved environment name. Interactive up
and down use a yes/no prompt, defaulting to No; select Yes or press y to
continue. Escape, Ctrl-C, a closed terminal or No cancels. Other changing
actions retain their exact typed phrase. For non-interactive use, the table
lists the case-sensitive --confirm phrase; a mismatch refuses before Task.
Before each confirmation, Chalupa prints a target summary with the
redacted canonical config path, exact compute stack, region, and size.
data-up also shows the exact <environment>-data stack and configured
expected GiB. down shows the protected-data stack and states that it
survives.
For example:
chalupa up --config "$DEMO_CONFIG"
Changing actions need an interactive terminal unless you pass --config and
the exact --confirm phrase (see below); preview, validate and the
read-only verbs never need one. Synthetic mode (no config) offers only
preview and validate. status --json remains available to non-interactive
consumers when they supply an explicit config.
chalupa validate runs the full gate (typecheck, the test suite, preview and
the CI plan). chalupa up and chalupa data-up run a lighter offline launch
gate first: the config's Compose preview and CI plan. They skip Chalupa's own
test suite. Set CHALUPA_STRICT_GATE=1 to make them run the full gate as well.
What --confirm actually is
chalupa ACTION --config PATH --confirm "<phrase>" supplies exact
environment-bound authorization as a literal command-line argument — so a script, cron job, or another
tool can run a billable or destructive action without a terminal attached.
Be honest about what this buys you: it is friction against a human running
the wrong command against the wrong environment, not authentication. The
phrase is not a secret — it is deterministically derived from the resolved
config's own name field ("launch " + config.name, "sink " + config.name,
and so on), so anyone who can read chalupa.yml can compute it, and
src/cli/chalupa.ts prints the exact expected phrase back on a mismatch
(The supplied --confirm phrase does not match; expected exactly "...") —
deliberately, because hiding it would only turn a typo into a support
question instead of a stopped command. What --confirm actually enforces is
that the target and the authorization are stated together, explicitly, in
the one invocation that uses them (--confirm requires --config on the
same command line; an inherited CONFIG does not count), so an automation's
own config carries a phrase written once on purpose rather than a credential
that could leak or be replayed against a different environment. See chalupa docs non-interactive for the complete rule set.
Sign in to the console
chalupa setup, chalupa login, chalupa open, and chalupa logout are the
commands in this flow that reach the network; every other command stays
offline until it delegates to Task. They link this workstation to the hosted
control plane rather than to any specific environment.
For an ordinary customer account, use setup. It signs in or creates the tenant account used by billing and organization views:
chalupa setup
chalupa account --json # offline, with session tokens redacted
chalupa open
chalupa open billing
chalupa logout
The web sign-in form takes your email. Add your password if you have one; without a password, it emails a single-use code and asks you to enter it on the same form.
chalupa login is not the customer sign-in command. It is the separate,
legacy operator-only device flow and requires an administrator to approve the
displayed code on the console's Terminals page:
chalupa login --config ./chalupa.yml # or --url https://chalupa.example
setup writes the verified tenant-account session to
~/.config/chalupa/account.json; this is the account used by chalupa account
and chalupa open. login is a separate, legacy operator-only device flow.
It writes a revocable terminal credential to session.json only after an
administrator approves the displayed code. A tenant session in account.json
never auto-approves that device flow or grants operator access. Both files have
owner-only (0600) permissions. When the tenant account session is still
valid, open exchanges it directly for a short-lived, one-use browser grant.
open billing requests the billing destination from the same allowlisted
endpoint. A stored credential is never
sent to an origin different from the one that issued it.
account and the TUI's Hosted screen merge those files into a local,
redacted status view without contacting Chalupa. The view can include
cached plan and managed-hour fields, always marked fresh or stale; the hosted
billing view is authoritative. Session tokens are deliberately absent from
both text and JSON output. logout attempts to revoke both sessions and
removes the local copies even when the control plane cannot be reached.
The stored credentials are scoped to hosted access, not to Chalupa's provider
access: neither is the DigitalOcean token, neither is printed, both expire,
and the terminal credential can be revoked early from Terminals in the
console. --url
selects the control-plane origin directly; without it, Chalupa falls back in
order to CHALUPA_CLOUD_URL, then the selected config's cloud.url, then the
origin from a valid setup account or a previous chalupa login on this
machine (even if that terminal credential has expired — the origin it named is
still meaningful), and finally to https://chalupa.run, the one production
control plane, so a bare chalupa login on a fresh machine has somewhere to
go. chalupa login prints
which of the last two it used, since both are a guess rather than something
you told it. Nothing about login changes what the Taskfile allowlist can
do — it only lets open skip a manual sign-in.
Provider-bound sinking
Both surfaces bind down to the provider ID of the compute they most
recently observed, and pass it to the Taskfile as a typed
EXPECTED_PROVIDER_ID argument. The provider-scoped destroy task compares
the current stack output with that value immediately before destruction and
refuses a mismatch. Where they differ is what happens without a fresh
binding.
The operator UI requires the operator to refresh status first: press r
immediately before selecting Sink compute. The UI accepts only a
present compute status observed within the last two minutes with a valid provider ID,
and shows that ID in the target summary. Without that fresh observation,
sinking stays disabled — the UI refuses rather than proceeding unbound.
Chalupa also revalidates the same two-minute observation immediately after
the confirmation; a prompt left open past the deadline is cancelled before
Task starts and requires another status review.
Direct chalupa down captures its own fresh status immediately before
running, with no separate refresh step required, and binds to the reported
provider ID exactly as the UI does. When that capture fails, or the reported
compute carries no provider ID, chalupa down proceeds anyway, bound only by
the configured local stack identity, and prints a warning
(Provider binding unavailable; down stays bound to the stack name only.)
before it runs. It still requires interactive yes/no confirmation or the exact
--confirm "sink <environment>" phrase and
uses the normal compute-only Taskfile path. Prefer the UI when you want a
missing binding to block the destroy instead of just being warned about it.
data-nuke is intentionally unavailable in the CLI and operator UI. Permanent
data deletion remains an independent, explicitly confirmed Taskfile operation
documented in Lifecycle. The one automated route
is chalupa storage sweep for an environment that opted in with
persist.onDemand, which destroys the data volume only after a snapshot is
verified at the provider. Never reproduce it with direct Pulumi commands.
Navigate the operator UI
The interface follows the same deep-green and teal visual language as
chalupa.run. It adapts to light and dark terminals, uses a bounded 256-color
fallback, and returns to terminal-default styling when theme or color support
cannot be established safely. Set NO_COLOR=1 to disable styling. Status,
risk, selection, and availability always retain text markers, so color is
never the only signal.
The full-screen interface requires a capable interactive terminal. When
TERM=dumb, use the linear chalupa inspect and chalupa status commands and
the direct actions instead; Chalupa will not force an alternate-screen UI onto
that terminal.
The landing screen is Home, and it shows a session rather than a parsed configuration. A session is four separate observations, in order:
| State | What proves it |
|---|---|
| Configured | A chalupa.yml was read offline. |
| Provisioned | The local Pulumi backend reports live compute. |
| Reachable | The forwarded Ollama answered on 127.0.0.1. |
| Model ready | The declared model is installed or loaded, with its GPU placement. |
Each state carries its own observation and its own age. A state this laptop has
not observed reads UNKNOWN; it is never inferred from a neighbouring one, and
an observation older than two minutes reads STALE rather than as fact. Home
also names exactly one next action — "Launch GPU to start a session", "Open
agent", "Launch an agent", "Sink to stop billing" — so the interface has an
answer to "what do I press now".
Which screens exist depends on what the configuration declares. An inference environment shows Home, Model, Actions, and Help. A Compose or CI environment shows Home, Preview, Status, CI, Actions, Hosted, and Help.
- Home — session state, cost, expiry, the endpoint an agent uses, and the primary actions.
- Model — the tunnelled endpoint: daemon version against the pin, installed or loaded state, digest, quantization, declared and loaded context, model ceiling, GPU placement, weight size, and the endpoint URLs an agent needs.
- Preview — the complete redacted remote Compose document.
- Status — independent compute and protected-data state from the local Pulumi backend.
- CI — the declared suite, bounded run state, connection state, and budget.
- Actions — the Taskfile allowlist, narrowed to the blocks this configuration declares, with risk classification.
- Hosted — the redacted local account/session cache and usage freshness; opening it never makes a network request.
- Help — safety boundaries, what each session state means, and the keyboard reference.
Home's primary actions are Launch GPU (up), Open agent, and Sink
(down), plus Install expiry and Register expiry when the config
declares a session: block. Open agent asks which agent to run and then runs exactly what chalupa launch runs: the
UI suspends, the managed tunnel opens, the agent takes the terminal, and the
agent's own settings are never modified. When the agent exits, the UI says what
is true: the GPU is still running and still billed.
Every screen of an inference session carries the same chrome line: elapsed
time, the hourly rate, the estimated cost so far, and how much of the declared
session budget is left. Elapsed time is measured from the first moment this
window observed the box — status:json carries no creation time — and it is
labelled as an observation, not as uptime. Whether the local teardown scheduler
actually holds this deployment is not something the UI reads, so it says
armed state unknown and keeps a banner up: "Expiry is not armed. This GPU
runs until you sink it."
Use these keys:
| Key | Effect |
|---|---|
1–8 or o, m, p, s, c, a, u, ? |
Open a visible screen directly. |
Left, Right, h, l, or Tab |
Move between screens; Shift-Tab moves backward. |
Up, Down, j, or k |
Move through actions or scroll the current screen. |
| Page Up or Page Down | Scroll by one visible page. |
| Home or End | Jump to the beginning or end of the current screen. |
| Enter | Run the selected enabled action. |
r |
Refresh what the current screen observes. |
x or Backspace |
Dismiss the current notice, or close the agent chooser. |
| Escape | Return to Home. |
q |
Exit while the operator UI owns the terminal. |
| Ctrl-C | Interrupt the active confirmation or foreground Task command. |
Long prose wraps at word boundaries, while long Compose values wrap by
terminal cell width so the complete redacted plan remains inspectable. A ↳
marker identifies a visual Compose continuation and is not part of the source
document. Scroll is bounded to real content. Narrow terminals use short tab
labels and a focused action detail view; Page Up and Page Down reveal any
remaining details. Terminals smaller than 40 columns by 12 rows show only a
resize instruction instead of hiding controls or presenting a partial action.
The UI starts from local inspection only, so it opens offline and instantly.
For a real configuration it then re-reads the session about every twenty
seconds on its own: the bounded status:json read, and — when compute is present and the
config declares inference: — one bounded loopback read of the forwarded
Ollama. Automatic reads are quiet: they never move the screen, never claim the
UI is busy, and a read that fails never replaces what is on screen, because a
failed read is not evidence that anything changed. r remains available and
refreshes immediately. In synthetic mode the UI displays SYNTHETIC OFFLINE DEMO, performs no automatic reads, disables status refresh, and disables every
action except Preview and Validate.
The Actions screen lists only what the configuration can actually run:
data-up without a persist: block, seed without a seed: command, ci:
actions without a ci: block, and expiry registration without a session:
block are hidden rather than shown as refusals. For everything still listed it
explains why an operation is disabled before anything is delegated.
Control-plane bootstrap and tunnels are available only when their corresponding
declarative config exists. If a loaded local status proves compute is absent, bootstrap, tunnel,
shell, seed, and expiry registration remain disabled; without a status observation,
the UI labels them config-eligible and leaves runtime verification to the
Taskfile. Provider actions require an explicit chalupa.yml; down
additionally requires a fresh provider-bound status as described above.
Compact mode keeps the selected action, risk, availability, and exact
confirmation phrase in the focused view.
For an explicit config, the Status screen presents compute and protected data
separately. An unknown data state remains visible with its bounded reason;
the UI never collapses it into absent. A fresh present compute status with a
provider ID also enables provider-bound sinking for two minutes.
For confirmations and foreground Task output, the UI temporarily returns
terminal ownership to the command. A tunnel keeps ownership until its Task or
SSH process exits. After a completed Task, press Enter to return to the UI.
Starting a status refresh clears the previous observation immediately, and a
failed refresh leaves it unknown. Running data-up, up, or down also
invalidates the displayed observation, even when the Task fails, so a stale
provider identity cannot keep a lifecycle action enabled. The status view
shows the safe-down freshness deadline and repaints when it expires.
Recover from unknown state
If data-up, up, or down fails or is interrupted, do not immediately
repeat it or assume that rollback succeeded. Resource state and billing may be
unknown.
- Run
chalupa status --config "$DEMO_CONFIG"to inspect bounded local compute and protected-data state. - Verify the exact resource in the DigitalOcean console and check billing.
- Re-run
chalupa inspect --config "$DEMO_CONFIG"before choosing the next lifecycle action.
An absent local stack is not proof that the provider has no resource. A
present protected-data state identifies bounded local outputs and the
configured expected size, not live provider capacity or billing. Do not use
data-nuke as recovery.
Taskfile and secret boundaries
The CLI passes a canonical config path to a fixed public Taskfile action. The Taskfile continues to own validation, the local Pulumi backend, tvault profiles, SSH, reporting, and separation of compute from persistent data. Do not call underscore-prefixed tasks or Pulumi lifecycle commands directly.
The CLI does not cache config history, persist secret values, or create a credential store. Tasks resolve their existing least-privilege tvault profiles only when the selected action needs them. The redacted preview and local Pulumi state can still contain private operational metadata; protect the workstation and state directory.
Continue with Quickstart, Lifecycle, Secrets with tvault, and Troubleshooting.
Remote inference
See Remote GPU inference for the experimental host-Ollama workflow.
chalupa exec --format env --config PATH prints endpoint exports without changing files.
See Agent harnesses for launch commands and configuration.
setup --inference --provider digitalocean links the hosted account and writes a GPU starter with console
heartbeats, then offers to arm the organization's console reaper so it can stop
reported GPU deployments even when the laptop sleeps. --no-reaper-token skips
arming. --no-console skips linking and omits cloud:, warning that expiry
depends on this machine staying awake and unlocked.
Context per inference session
Use chalupa launch opencode --context auto to negotiate context on
the existing remote GPU. --context 64k, 128k or native request a
specific size or the model ceiling. See Inference for
fit checks and limitations. The YAML and teardown identity are unchanged.
Managed telemetry bootstrap
chalupa up arms console telemetry after the managed host is ready. The success
line is Telemetry armed: heartbeats, GPU samples and inference runs will appear in the console.
Use chalupa session bootstrap [--config PATH] [--json] to retry delivery or
rotate the environment's ingest key. tunnel and launch retry automatically
when the managed record has bootstrapped: false (including older records without
that field). JSON reports bootstrap state and the public key ID, never key material.
See Managed GPU for signals and receipt limitations.