Start here
Local CLI and operator UI
Install and use the offline-first Chalupa CLI without bypassing its Taskfile, confirmation, or secret boundaries.
Reviewed 2026-07-24
The Chalupa CLI is a local operator surface for the repository's existing workflow. It can inspect a configuration, read bounded local state, and delegate approved actions to public Taskfile commands. It does not implement a second Pulumi lifecycle.
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.
Run from a checkout
Install the locked dependencies, then invoke the CLI through Bun:
cd "$HOME/projects/chalupa"
bun install --frozen-lockfile
bun run chalupa -- doctor
bun run chalupa -- inspect
The extra -- separates Bun's arguments from Chalupa's arguments. This is the
supported no-link workflow and always runs the source in the current checkout.
For a user-local chalupa command linked to the same checkout:
cd "$HOME/projects/chalupa"
bun link
chalupa --version
The link points at this checkout's declared binary. To update it, update the checkout and reinstall its locked dependencies:
cd "$HOME/projects/chalupa"
git pull --ff-only
bun install --frozen-lockfile
chalupa --version
Review local changes before pulling. bun link is optional; every example
below also works as bun run chalupa -- <arguments>.
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 execute those tools or query credentials, Docker, the
network, local stacks, or a cloud provider. A missing operator requirement
produces a non-zero exit while the report still identifies offline inspection
as available.
Inspect safely first
With no config, inspect uses the repository's examples/demo-harbor
fixture:
chalupa inspect
chalupa inspect --json
This is synthetic offline mode. 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.
Command reference
| Command | Behavior |
|---|---|
chalupa |
Opens the operator UI when both input and output are interactive terminals. Prints help when invoked with no arguments outside a TTY. |
chalupa doctor [--json] |
Reports separate offline and full-operator readiness without running external executables. |
chalupa inspect [--config PATH] [--json] |
Builds a local inspection. Without a config, it uses the synthetic fixture. |
chalupa status --config PATH [--json] |
Reads bounded compute and protected-data status from exact local Pulumi stacks. |
chalupa tui [--config PATH] |
Opens the interactive operator UI. An explicit invocation fails outside a TTY. |
chalupa run ACTION [--config PATH] |
Delegates one allowlisted public action to Task from the Chalupa repository root. |
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 exact confirmations
Only these public actions are accepted by chalupa run. The Actions screen
shows the same set plus the bounded local-status refresh:
| Action | Risk | Exact phrase |
|---|---|---|
preview |
offline | none |
validate |
offline | none |
data-up |
billable persistent data | persist <environment> |
up |
billable compute | launch <environment> |
agent-bootstrap |
changes the provisioned host | bootstrap <environment> |
tunnel |
read-only connection | none |
seed |
changes application data | seed <environment> |
teardown:register |
changes the local expiry registry | register <environment> |
down |
destroys disposable compute | sink <environment> |
<environment> is the exact name from chalupa.yml. Confirmation is
case-sensitive and must match the whole phrase. A mismatch cancels the action
before Task runs.
Before each typed 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 run up --config "$DEMO_CONFIG"
Every changing or long-running action requires an interactive terminal.
preview and validate are the only actions available in synthetic mode and
the only run actions accepted outside a TTY. status --json remains
available to non-interactive consumers when they supply an explicit config.
Provider-bound sinking in the UI
The operator UI adds a stronger identity check to down. 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. It shows
that ID in the target summary and passes 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.
Direct chalupa run down intentionally does not refresh status and is not
provider-ID-bound. Its target summary says that the binding is unavailable and
that only the configured local stack identity is known. It still requires the
exact sink <environment> phrase and uses the normal compute-only Taskfile
path. Prefer the UI when a fresh local provider identity is available.
data-nuke is intentionally unavailable in the CLI and operator UI. Permanent
data deletion remains an independent, explicitly confirmed Taskfile operation
documented in Lifecycle. Never reproduce it
with direct Pulumi commands.
Navigate the operator UI
The UI has five screens:
- Overview — environment identity, services, persistence, ports, diagnostics, and configured control-plane features.
- Preview — the complete redacted remote Compose document.
- Status — manually refreshed, independent compute and protected-data state from the local Pulumi backend.
- Actions — the fixed Taskfile allowlist and risk classification.
- Help — safety boundaries and keyboard reference.
Use these keys:
| Key | Effect |
|---|---|
1–5 |
Open a screen directly. |
| Left, Right, or Tab | Move between screens. |
| Up or Down | Move through actions or scroll the current screen. |
| Enter | Run the selected enabled action. |
r |
Refresh status from the local Pulumi backend for an explicit config. |
| Escape | Return to Overview. |
q or Ctrl-C |
Exit. |
The UI starts from local inspection only; it never refreshes status
automatically. In synthetic mode it displays SYNTHETIC OFFLINE DEMO,
disables status refresh, and disables every action except Preview and
Validate.
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.
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. - Run
task status CONFIG="$DEMO_CONFIG"for the Taskfile's human view and diagnostics. - 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.