npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thelacanians/chalupa-cli

v0.9.4

Published

Ephemeral Docker Compose environments on DigitalOcean, SSH-only

Readme

Chalupa 🛶

Chalupa turns a selected subset of a Docker Compose project into a disposable DigitalOcean environment. It derives services, TCP ports, named volumes and dependencies from Compose, forces every published service port onto 127.0.0.1, and exposes only SSH through an explicit cloud firewall.

The local Pulumi backend keeps compute and persistent data in separate stacks:

  • <name> owns disposable compute and its SSH-only firewall.
  • <name>-data owns an optional protected block volume.

Compute consumes the exact volumeId exported by the protected data stack through a Pulumi stack reference; it never discovers storage by a potentially ambiguous name. Destroying compute does not destroy data.

Every pulumi invocation in the Taskfile runs with PULUMI_CONFIG_PASSPHRASE="" (env: at the top of Taskfile.yml), which is what makes pulumi login --local usable without a passphrase prompt. The accepted tradeoff: an empty passphrase means Pulumi's own secret outputs are encrypted with a well-known key, so they are effectively plaintext-at-rest in the local state directory (.pulumi/). This is a deliberate, accepted local-backend choice, not an oversight — the DigitalOcean token itself never passes through Pulumi config or Pulumi secrets at all (it is read from tvault by the Taskfile and handed to the provider as a process environment variable), so the one credential this project cannot afford to leak was never exposed to that tradeoff in the first place. Treat .pulumi/ as sensitive local state anyway; anything Pulumi does mark secret (for example cloud-init user data) is only as protected as the passphrase, and here that is none.

Install

curl -fsSL https://chalupa.run/install.sh | bash
chalupa setup

The installer puts checksum-verified Bun 1.3.14+ and go-task 3.51.1+ on your PATH, then installs or upgrades the CLI (@thelacanians/chalupa-cli from the npm registry) with Bun. Every downloaded runtime artifact is pinned by release and SHA-256; nothing runs with sudo. If Bun and go-task are already installed, bun add -g @thelacanians/chalupa-cli installs or upgrades only the CLI.

chalupa setup then walks the onboarding: the DigitalOcean token (validated against the API, stored in tvault, never echoed), an SSH key (reuses ~/.ssh/id_ed25519 or creates a dedicated one and registers it in your DigitalOcean account), a control-plane account (signup + magic-link verification), and a starter chalupa.yml in the current directory. The console signup is open — email verification is the only gate.

chalupa doctor reports which of Task, Pulumi, tvault, ssh, and nc are on PATH without running or querying any of them.

Requirements

  • Bun
  • Task — required for every lifecycle action, not optional tooling. chalupa/the operator UI delegate to it for anything that touches a stack; without task on PATH, only offline inspection/preview keeps working. chalupa doctor checks for it.
  • Pulumi CLI
  • TinyVault v0.19.0 or newer
  • ssh and nc, the system OpenSSH client and netcat — used for tunnels, chalupa ssh, agent bootstrap, and the seed task's tunnel check
  • A DigitalOcean API token stored in tvault project chalupa

chalupa doctor reports which of Task, Pulumi, tvault, ssh, and nc are on PATH without running or querying any of them.

Only Bun is supported for JavaScript dependencies and scripts.

Try the offline demo

The repository ships a self-contained synthetic fixture: examples/demo-harbor/.

bun install --frozen-lockfile
task validate

Validation is free and offline. It runs TypeScript checks, Bun tests and the same Compose inference used by Pulumi, then prints the complete redacted remote Compose document. Only offline preview and validation default to the synthetic demo. Every task that can create or destroy provider resources refuses to run without an explicit CONFIG.

Preview redaction covers every value resolved from the operator shell, common secret-bearing keys, and URL userinfo. It is not a general DLP scanner: never place literal secrets under unusual field names in Compose.

Use the local operator interface

chalupa is the operator entrypoint. From a checkout, link it once so it is on your PATH (the public installer above already did this for you):

bun install --frozen-lockfile
bun link
chalupa --version

Without the link, every example also works as bun run chalupa -- <arguments> from the checkout:

bun run chalupa -- doctor
bun run chalupa -- inspect
bun run chalupa

With no explicit config, inspection and the interactive UI use the synthetic offline demo and disable provider actions.

Each allowlisted action is a direct command; namespaced Taskfile actions are typed as two words instead of with a colon:

chalupa up --config ./chalupa.yml
chalupa tunnel --config ./chalupa.yml
chalupa ssh --config ./chalupa.yml
chalupa ci arm --config ./chalupa.yml
chalupa teardown register --config ./chalupa.yml

A relative --config resolves against the current directory, so these run as written from the project that owns chalupa.yml. chalupa ssh resolves the droplet address from Pulumi output: operating an environment never requires reading or pasting its IP.

chalupa login --config ./chalupa.yml   # or --url https://chalupa.example
chalupa open
chalupa logout

chalupa login signs this terminal into the control-plane console. It prints a short code and the address where you approve it, waits until you confirm the same code in the browser, and stores the resulting credential in ~/.config/chalupa/session.json with owner-only permissions. chalupa open then opens the console already signed in, and chalupa logout revokes the terminal and removes the local copy. The stored credential is never the administrator token, never printed, expires after 30 days, and can be revoked from Terminals in the console. login, open, and logout are the only commands where the CLI itself makes a network call directly. Every other chalupa action stays offline until it delegates to Task — but delegated lifecycle actions (up, down, data-up, tunnel, agent-bootstrap, the ci:* actions, report, and more) absolutely do reach the network once Task runs them: they contact DigitalOcean through Pulumi, SSH into the droplet, and, when cloud.url is configured, call the control plane. "Only login/open/logout touch the network" describes the CLI process itself before it hands off, not the lifecycle it triggers. The DigitalOcean token still lives only in tvault and is never written to disk.

chalupa run <action> remains accepted as the compatibility spelling of the same allowlist. The CLI delegates to public Taskfile commands and passes only the canonical config path; other Taskfile inputs such as SSH_IDENTITY are read from the environment. data-nuke is intentionally unavailable. The CLI does not read credentials, providers, stacks, Docker, or SSH at startup. See the Local CLI and operator UI guide for exact confirmation phrases, distinct compute and protected-data status, TTY rules, and interrupted-operation recovery. The responsive TUI uses the website's accessible green/teal visual language, supports arrow, direct, paging, and Vim navigation, and preserves every status and risk cue when NO_COLOR is set. It can bind down to a fresh local provider ID; a direct chalupa down explicitly remains unbound.

Every action honours one verbosity control: -q/--quiet, -v/--verbose, or CHALUPA_VERBOSITY=quiet|verbose in the environment (a flag always wins over the environment). VERBOSITY=quiet|verbose works identically when the Taskfile is invoked directly with task. Verbose is exactly the historical firehose — Pulumi's own preview table and per-resource progress, chalupa ci follow's raw NDJSON. The default level captures Pulumi's output and prints a short summary (resource counts, duration, warnings, and ip/priceHourly/ providerId for compute) instead, and renders ci follow's journal as one line per step with a running tally instead of raw JSON. Quiet shortens a clean success further. None of this touches a failure: at every level, a failed pulumi up/down/data-up prints the complete transcript it always did, so a diagnostic is never harder to reach than before.

Use it with a project

Place chalupa.yml next to that project's Compose file:

name: my-environment
# instance: staging   # optional: run a second copy of this config concurrently
compose: ./docker-compose.yml
services: [api, postgres]

persist:
  sizeGb: 10

ssh:
  keys: [my-laptop]
  allowedCidrs: [203.0.113.10/32]

An optional instance suffix lets one config provision more than one environment at the same time: everything that names a stack, a DigitalOcean resource, or a control-plane environment composes name-instance (the confirmation phrases, stack names, droplet, firewall, and data volume all follow). Without it, the environment identity is exactly name, so existing configs are unchanged. Two instances of the same config in one DigitalOcean account still need the same SSH keys and CIDRs, but they no longer collide on resource names.

Then use the lifecycle:

chalupa data-up --config /absolute/path/to/chalupa.yml # optional, one time
chalupa up --config /absolute/path/to/chalupa.yml
chalupa tunnel --config /absolute/path/to/chalupa.yml
chalupa down --config /absolute/path/to/chalupa.yml

Those commands delegate to the Taskfile, which stays the owner of the lifecycle logic. Repository work keeps using task from this checkout: task setup, task typecheck, task test, task validate, task preview, task ci-preview, task report, task cloud:*, the teardown runner and schedule tasks, and task data-nuke.

up and data-up always depend on the offline validate gate. They never install dependencies. Every billable or destructive Taskfile lifecycle action requires the stack name restated as an explicit confirmation variable, checked inside the task itself, not only by whatever called it: task up requires CONFIRM_UP=<config.name>, task down requires CONFIRM_DOWN=<config.name>, task data-up requires CONFIRM_DATA_UP=<config.name>, and task data-nuke requires CONFIRM_DATA_NUKE=<config.name> — the same long-standing gate the other three now mirror. The chalupa CLI and its operator UI supply the matching CONFIRM_* variable automatically, but only after their own interactive (or --confirm) typed-phrase flow succeeds; calling task up/task down/task data-up directly means restating the variable yourself. task data-nuke is additionally deliberately separate from the CLI allowlist — it has no CLI command at all — because it unprotects and permanently destroys the data stack. When the control plane is configured, down captures the deployment identity before destroy and sends a signed, idempotent sunk event only after Pulumi succeeds; delivery failure is a warning. Provider credentials and the ingest key are never loaded into the same subprocess: a private, short-lived receipt connects the completed destroy to the reporting phase. Provider reconciliation may recover a deployment Chalupa already observed, but the provider invoice remains authoritative.

task down and task data-nuke delegate their signal-sensitive bodies to real Bash scripts. Their INT/TERM/HUP cleanup restores private receipts; data-nuke also re-protects the <name>-data stack if a signal arrives after unprotect and before a completed destroy. A hard kill or machine loss cannot run any cleanup, so if an interrupted nuke reports that protection could not be restored, repair it before doing anything else:

pulumi stack select <name>-data
pulumi state protect --all --yes

See Troubleshooting for the recovery procedure.

Supported Compose boundary

Chalupa supports pre-built images, explicit environments, commands, healthchecks, TCP port mappings, depends_on, resource overrides and named volumes. It rejects configurations that would make a disposable remote copy unsafe or non-reproducible, including:

  • bind mounts and container-engine sockets;
  • build, env_file, custom/external volume drivers and anonymous volumes;
  • UDP, port ranges and ports without a stable published host port;
  • host/custom networking, privileged containers, devices and host namespaces;
  • secrets/config files, elevated capabilities and other host-dependent runtime features.

A service may declare omitBindMountTargets in its Chalupa override when a source Compose bind is strictly local and its remote behavior is supplied by an env or command override. Targets are literal absolute container paths, must identify exactly one bind, and produce a preview warning. Named volumes cannot be omitted, and every unlisted bind remains an error, so this exception never places a host path in the remote Compose document.

Every accepted published port is rewritten to 127.0.0.1 and included in the SSH tunnel output.

Compose labels provide optional, project-owned classification:

services:
  postgres:
    image: postgres:17-alpine
    labels:
      run.chalupa.role: database
      run.chalupa.database.kind: postgresql
      run.chalupa.database.primary: true

  migrate:
    image: ghcr.io/example/demo-migrations:2026-07-24
    labels:
      run.chalupa.role: initializer
      run.chalupa.initializes: postgres
    depends_on:
      postgres:
        condition: service_healthy

  api:
    image: ghcr.io/example/demo-api:2026-07-24
    labels:
      run.chalupa.role: application
      run.chalupa.logs: include
    depends_on:
      migrate:
        condition: service_completed_successfully

preview-compose validates the initializer graph, shows inferred service roles and explicit log sources, and rejects unknown run.chalupa.* labels. Database image detection is advisory only. Chalupa never creates SQL, credentials, images, or initializer commands. Logs are excluded by default; included services use a deterministic local journald sink without granting the collector Docker-socket access.

Log selection may instead live with the environment in chalupa.yml. Values under session.logs.services override the Compose label for the same service; services mentioned by only one source keep that source's value:

session:
  purpose: test
  expiresAfterMinutes: 120
  logs:
    services:
      api: include
      noisy-support-service: exclude
    retentionDays: 7

Control plane

An optional cloud.url enables a small systemd heartbeat agent. The agent reports container state, memory/CPU, uptime and disk use with replay-resistant HMAC v1 requests. It receives no DigitalOcean credential and cannot create or delete infrastructure. Pulumi marks all cloud-init user data as secret, but the ingest key is not part of that user data or any Pulumi resource input.

cloud:
  url: https://your-control-plane.example
  heartbeatSeconds: 30

session:
  purpose: demo
  expiresAfterMinutes: 120
  logs:
    services:
      api: include
    retentionDays: 7
    maxTotalBytes: 67108864
    maxBytesPerSecond: 262144
    maxChunkBytes: 1048576
    maxLineBytes: 8192

The shared CHALUPA_INGEST_KEY lives in tvault and in the control plane's deployment environment. task up preflights its availability before creating compute. After provisioning, a separate ingest-only subprocess streams it over the same SSH trust path as task tunnel; it never places the value in a command argument. Initial accept-new host-key enrollment is trust on first use, not independent host identity verification. If delivery cannot authenticate, compute remains provisioned and may be billable. Fix SSH access and run task agent-bootstrap CONFIG=/absolute/path/to/chalupa.yml.

For account separation, pass a dedicated local key as an absolute Task input:

task up CONFIG=/absolute/path/to/chalupa.yml \
  SSH_IDENTITY="$HOME/.ssh/id_ed25519_chalupa"

Chalupa requires that file to be operator-owned, regular rather than a symlink, recognized by the system OpenSSH implementation, unencrypted for batch use, and mode 0400 or 0600. Every local SSH operation then adds IdentitiesOnly=yes, so OpenSSH offers only that key. Use the same input with agent-bootstrap, tunnel, and down; the latter uses SSH for final log and Monitor flushes. When SSH_IDENTITY is absent, normal OpenSSH agent/default identity selection remains unchanged. Only the public half belongs in DigitalOcean; never put the private key in chalupa.yml, tvault, Vercel, or the repository.

session gives a demo or test run a visible teardown deadline and an optional, bounded private-log budget. It does not place a DigitalOcean credential in Vercel or the droplet, so expiry cannot destroy local Pulumi state by itself. A trusted operator can explicitly register the exact local stack for the opt-in local expiry runner: task teardown:register CONFIG=/absolute/path/to/chalupa.yml. Its default run is dry (task teardown:run); the armed path requires a local TEARDOWN_ENABLED=1 and delegates only to task down after rechecking the config digest and provider ID. On macOS, review the dry run and then use tvault agent start --idle 0, followed in another terminal by task teardown:schedule:install plus task teardown:schedule:status, to enable the user-level launchd guard. Installation verifies the persistent same-user agent can read the exact direct chalupa provider credential without inheriting unlock secrets; the agent still has its documented broader same-user vault authority, and the plist contains no credential. A due run acquires the provider credential before any remote flush, then keeps it out of the log and Monitor subprocesses. Remove and reinstall the schedule when its interval or pinned paths change. task teardown:schedule:remove disables it first, refuses to unload a running job, and retains registrations and logs; stop the TinyVault agent after removal and a clean dry run. If the deployment uses a dedicated SSH identity, pass the same SSH_IDENTITY input during schedule installation; the private launchd file retains only its local path for final flushes. It never targets a -data stack, and Chalupa never forwards log bytes through a Vercel Function: the control plane authorizes direct private-object uploads owned by file.cheap and retains only indexed metadata and bounded excerpts in Neon.

The droplet collector reads journald directly and accepts only exact CONTAINER_TAG=chalupa.<service> records for services whose generated /etc/chalupa/services.json entry says logs: include. It has no Docker socket access. The policy in /etc/chalupa/log-policy.json bounds each UTF-8 line, the per-second capture rate, each zstd-compressed NDJSON chunk and the whole session. Cursor, session, sequence and pending chunks survive service restarts in /var/lib/chalupa-logs; invalid policy, state, journal metadata or upload grants fail closed. Excerpts are independently bounded and redact credential assignments, bearer values, URL credentials and private-key markers before leaving the host.

The collector source is not embedded in cloud-init. task agent-bootstrap streams it through the existing trusted SSH stdin channel beside the HMAC key, verifies a locally precomputed SHA-256 digest on the droplet, and installs it atomically. Plan, commit and finalize requests use HMAC v1. Chunk bytes use the returned one-object private PUT grant to go straight to file.cheap; Chalupa, Vercel and Neon never receive storage credentials or proxy the bytes. task down makes a bounded best-effort stop that flushes, uploads and finalizes the current session before destroying compute. Failure warns but never touches the protected data stack.

Monitor performance telemetry is a separate opt-in capability. Chalupa installs an exact checksum-pinned Linux release, runs monitor telemetry without the ingest key, and gives signing and bounded retries to a hardened local forwarder. The control plane retains seven days of source windows and indefinite hourly summaries, so deployment charts survive compute destruction.

observability:
  monitor:
    version: 1.15.0
    sha256:
      amd64: f8c729041cdd10681f3adc7ec40f9f4e9c1f0e25215b078a028e401a0e9048ef
      arm64: 1712d96f9803e297440b3baffbe9e6d09fb4bbe4afaf88be19aef3aa2bf77f4e
    sampleSeconds: 5
    windowSeconds: 30

Monitor v1.15.0 and the checksums above are published and independently verified. Telemetry remains opt-in. Its contract contains only fixed aggregate metrics and availability; it excludes host, process, path and application identity. CPU or heap profiles remain an explicit future artifact workflow, not part of automatic telemetry. See the integrated Performance telemetry and private-operations documentation before enabling it.

Monitor v1.15.0 also understands Chalupa's CHALUPA_CI_* run, step, suite, attempt, and environment identifiers for explicit local investigations. That diagnostic lane is separate from automatic telemetry: it creates grouped local issues and credential-free evidence references only when an operator or CI step invokes it.

task report accepts Cairn stats v1 generated with --include-runs, removes raw payloads, label maps and filesystem paths, and sends only the normalized allowlist (including the selected group value as a bounded variant). To attach evidence from a local vault, use file.cheap v0.31.0 or newer to generate an ArtifactRefV1 after the artifact pack is complete, place the complete envelope in a sidecar keyed by the raw Cairn run ID, and pass it explicitly:

fcheap artifact-ref <stash-id> \
  --kind cairntrace.run \
  --producer-tool cairntrace \
  --native-schema urn:cairntrace.dev:run:v1 \
  --native-id <run-id> \
  --entrypoint run.json \
  --json

task report \
  REPORT=/path/to/cairn-stats.json \
  ARTIFACTS=/path/to/artifact-sidecar.json

The local reader bounds the raw stats file at 4 MiB and the sidecar at 1 MiB; the signed normalized payload remains capped at 256 KiB.

task ci:report publishes the parent pipeline record for a CI run: it reads a bounded summary.json from the environment over the existing SSH boundary, resolves providerId from the stack so the run's spend is attributable, derives the stop reason from the summary itself, and sends it under the same ingest-only credential profile as task report. A finished run reaches the control plane without anyone remembering to do it — task ci:cancel publishes what it collected, and task down publishes before compute is destroyed, which is the last moment summary.json exists at all. Both are best effort and never block a destroy; publishing the same run twice is inert, because the control plane keys on sourceRunId. To show a suite's individual spec rows underneath their pipeline, hand task ci:report the same document you reported with task report:

task report REPORT=/path/to/cairn-stats.json
task ci:report CONFIG=/path/to/chalupa.yml SPEC_RUNS=/path/to/cairn-stats.json

SPEC_RUNS accepts exactly what reporting accepts — Cairn stats v1, a glyphrun run.v1 array, or a plain array of run IDs — so the IDs are guaranteed to match the suite_runs rows that were just created rather than being retyped.

Cairntrace v2.1.0 and Glyphrun v0.16.0 can separately publish bounded complete-run evidence to the private hosted file.cheap service and return a fcheap-cloud reference. Chalupa stores reference metadata only. It never uploads artifact bytes, fetches external links server-side, or tries to resolve a local file.cheap vault.

Lifecycle and control-plane commands use fixed credential profiles, so each subprocess receives only the tvault keys it needs plus a fixed operational environment allowlist. Arbitrary parent-shell variables, NODE_OPTIONS, and unrelated cloud credentials are not forwarded. Useful offline and private-staging checks are:

task cloud:verify
task cloud:staging-check CLOUD_ORIGIN=https://your-private-chalupa.example
task cloud:db:migrate

The integrated English documentation lives in cloud/content/docs and renders at /docs. Start with the quickstart, then use the tvault and private operations runbooks before connecting a real fleet.

Repository map

  • src/config.ts — runtime config schema and path resolution.
  • src/compose.ts — safe Compose inference and preview redaction.
  • src/cloud-init.ts — persistent, hardened host bootstrap and heartbeat.
  • src/cli/ — the chalupa CLI entrypoint, its operator TUI (src/cli/tui/), and the scoped helper scripts (with-tvault.ts, config-value.ts, pulumi-summary.ts, report-ci.ts, teardown-runner.ts, and the rest) that Taskfile.yml shells out to.
  • runtime/ci-engine/ — the droplet-resident chalupa-ci.py engine that runs an armed ci: plan, collects evidence, and publishes artifacts.
  • runtime/log-collector/ — digest-verified droplet log collector source.
  • src/report.ts — Cairn v1 normalization and request signing.
  • index.ts — thin Pulumi resource layer.
  • preview-compose.ts — offline entrypoint using the same Compose-inference engine as index.ts.
  • preview-ci.ts — offline entrypoint that renders a declared ci: plan without touching the cloud.
  • cloud/ — web control plane.
  • tests/glyph/ — TUI behavior fixtures exercised through the glyph runner.
  • AGENTS.md — operational and safety rules for contributors and agents.