iframe CLI · v0.2.1

Operate your iFrame estate from the terminal.

The iframe CLI puts your iFrame instances under your fingertips: lifecycle, status, customer-scoped SSH access, usage, and billing. Provider-agnostic by design — your scripts never see cloud-provider instance types or region codes.

Install

One command. Node 20+.

curl -fsSL https://cloud.iframe.ai/install.sh | sh

Other distributions

  • npmnpm install -g @iframeai/cli
  • brewbrew install iframeai/tap/iframe

The CLI is a single self-contained binary (iframe.cjs) bundled with esbuild. It uses the built-in fetch; no extra HTTP stack. Verify with iframe --version.

Quick start

Login, list, reboot — in under 60 seconds.

  1. Issue a key. Open the dashboard and create one under Sidebar → CLI Keys → New key. The plaintext is shown once.
  2. Save it. Run iframe login. The key lands at ~/.iframe/credentials with mode 0600.
  3. Confirm. iframe whoami verifies connectivity and prints your scopes.
  4. Use. iframe instances list shows your active instances. iframe instances reboot performs an in-OS soft reboot that preserves identity, IP, and storage.
# 1. Issue a key in the dashboard
#    cloud.iframe.ai → Sidebar → CLI Keys → New key
#    (copy the value once — it is only shown at creation)

# 2. Save it to ~/.iframe/credentials
iframe login --profile default --api-url https://cloud.iframe.ai

# 3. Confirm
iframe whoami

# 4. List your iFrame instances
iframe instances list

# 5. Soft reboot — preserves instance identity, public IP, and storage
iframe instances reboot inst-123e4567-e89b-12d3-a456-426614174000 --yes

Authentication

Keys, scopes, and storage.

Key shape

Keys are 12-character public IDs and 32-character secrets, prefixed by environment. Only a salted SHA-256 of the secret is stored on our side; the plaintext is shown once at creation.

ifk_live_<keyId>_<secret>
# or ifk_test_<keyId>_<secret> for non-prod

Override per invocation

IFRAME_API_URL=https://cloud.iframe.ai \
IFRAME_API_KEY=ifk_live_........ \
  iframe instances list --output json | jq '.[] | .id'

Scopes

Each key carries a coarse scope set. Mint narrow keys for CI; mint wider keys for on-call rotations.

instances:readlist, get, status
instances:lifecyclesoft reboot
instances:accessSSH grants on a node
instances:reprovisionrebuild root disk from launch image
instances:webhookconfigure per-instance notification webhook
usage:readcurrent-month accrued cost per resource
billing:readlist and inspect invoices

Revocation

Revocations are immediate. The next request from a revoked key receives HTTP 401 / exit 3. Revoke from Sidebar → CLI Keys.

Output formats

Table, JSON, YAML.

--output table is the default on a TTY. When stdout is not a TTY (e.g. piped), json is auto-selected so jq Just Works. Use --output yaml for hand-readable structured output.

# piping into jq → JSON auto-selected
iframe instances list | jq '.[] | select(.lifecycle=="live") | .id'

# explicit YAML
iframe instances get inst-...  --output yaml

# explicit table (forces TTY rendering even when piped)
iframe usage --output table

Lifecycle vocabulary

Four states. Provider-agnostic.

The CLI never exposes provider lifecycle words. Both the iFrame application status and the underlying cloud state are mapped to a small canonical vocabulary:

LifecycleMeaning
livethe instance is up and reachable
haltedthe instance is stopped, terminated, or decommissioned
transientthe instance is starting, stopping, or rebooting
unknownthe orchestrator could not derive the lifecycle

Mutating commands (instances reboot, instances reprovision, keys add) refuse with HTTP 409 / exit 5 when lifecycle is anything other than live. The reply includes the current lifecycle value so scripts can branch.

Command reference

Every command, every flag.

Auth & profile

iframe login

Save an API key for a profile to ~/.iframe/credentials (mode 0600). Prompts for the key on stdin.

iframe login [--profile <name>] [--api-url <url>]
  --profile  default  profile name  (also: IFRAME_PROFILE)
  --api-url           API base URL  (default: https://cloud.iframe.ai)

iframe logout

Remove a saved profile from the credentials file.

iframe logout [--profile <name>]

iframe whoami

Inspect the active credentials, the bound organization, and the scopes on the active key.

iframe whoami
# table:
# API URL : https://cloud.iframe.ai
# Key ID  : kmpkhm5qeprr
# Key name: ci pipeline
# Org     : Acme (org-uuid)
# Scopes  : instances:read, instances:lifecycle, usage:read

iframe doctor

Diagnose connectivity, key validity, and the bound org. Useful as a CI smoke check.

iframe doctor
# config:        OK (api_url=https://cloud.iframe.ai)
# auth:          OK (key=kmpkhm5qeprr, scopes=5)
# org binding:   Acme (org-uuid)
# api version:   v1

Instances

iframe instances list

List every iFrame instance in the active org. Lifecycle is always re-derived from the live cloud-provider state at the boundary; the value you see is current as of the request.

iframe instances list [--state live|halted|transient|unknown|all]
  --state  filter by canonical lifecycle  (default: shows everything)

Bad --state values are rejected with exit 2 and a list of valid values.

iframe instances get <id>

Show full details for one iFrame instance: lifecycle, IPs, attached volumes, hardware specs, rate, contract dates.

iframe instances get inst-123e4567-e89b-12d3-a456-426614174000
# Returns 404 / exit 4 if the id does not exist in your org.

iframe instances status <id>

Liveness probe: the canonical lifecycle, plus an out-of-band SSH reachability check with latency and a redacted reason on failure.

iframe instances status inst-...
# {
#   "lifecycle": "live",
#   "public_ip": "203.0.113.42",
#   "ssh": { "ssh_reachable": true, "latency_ms": 38 }
# }

iframe instances reboot <id>

Soft reboot via in-OS systemctl reboot --no-block. Preserves the instance's identity, public IP, private IP, and root volume. The cloud-provider lifecycle API is never called, so any underlying reservation remains intact.

iframe instances reboot inst-... [-y|--yes]
  -y, --yes  skip the interactive confirmation prompt

# Refuses with exit 5 if lifecycle != live; the reply includes the
# current lifecycle so scripts can branch:
# {"error":"Soft reboot unavailable: instance is halted — start it first",
#  "exit_code":5,"lifecycle":"halted"}

iframe instances reprovision <id>

Rebuild the instance from its original launch image — a "clean slate" reset that preserves every piece of identity that matters and wipes everything that doesn't. Three steps run in order:

  1. Root replacement. The root volume is replaced from the same launch image the instance was originally provisioned from. Instance ID, public IP, private IP, and the underlying reservation are preserved.
  2. Ephemeral wipe. Every non-root NVMe instance-store device is blkdiscard'd. Persistent data volumes are not touched.
  3. Soft reboot. Issued at the hypervisor level so the new root sees a clean kernel. Skipped automatically if step 2 wiped nothing.
iframe instances reprovision inst-... [-y|--yes]
  -y, --yes  skip the interactive confirmation prompt

# Returns immediately with a task_id; the orchestrator drives the
# rest in the background. Track progress with `iframe instances get`:
#   lifecycle             — `live` once back online
#   last_reprovision_state — pending → in-progress → succeeded
#   last_cleanup           — wipe_state and reboot_state
#
# Refuses with exit 5 if lifecycle != live or another reprovision
# is already in flight on the same instance.

SSH grants survive automatically. Active grants stored against the instance are replayed onto the new root: each cust_* user is recreated, its public key is reauthorized, and its --sudo flag is restored. Grants created before pubkey persistence (legacy) are marked pending_resync and must be re-issued by the customer.

Customer-scoped SSH grants

iframe keys list <id>

List active and revoked SSH grants on an iFrame instance. Each grant maps an SSH public key to a per-customer Linux user named cust_<keyId8>_<label>. The sudo field reflects whether the grant was created with --sudo.

iframe keys list inst-...
# [
#   {
#     "grant_id":"…",
#     "linux_user":"cust_kmpkhm5q_alice",
#     "pubkey_fingerprint":"SHA256:…",
#     "pubkey_type":"ed25519",
#     "status":"active",
#     "sudo": false,
#     "created_at":"2026-04-26T07:48:56Z"
#   }
# ]

iframe keys add <id>

Land a customer-scoped Linux user with a public key. The user is named cust_<keyId8>_<label> and never shares the default OS user (ubuntu). Linux users are created on the live node over the orchestrator's admin SSH channel — no provider-specific handoff is exposed to the caller.

iframe keys add <id> --user <label> --pubkey <path>
                       [--note <text>] [--sudo]
  --user    label segment, [a-z0-9-]{2,16}, becomes part of cust_…_…
  --pubkey  path to a public key file (or '-' to read stdin)
  --note    optional, free-form note saved with the grant
  --sudo    grant the cust_ user NOPASSWD:ALL via /etc/sudoers.d/.
            Off by default — opt in per grant.

# Example — no sudo (default)
iframe keys add inst-... --user alice --pubkey ~/.ssh/alice.pub
# {
#   "grant_id":"…",
#   "linux_user":"cust_kmpkhm5q_alice",
#   "pubkey_fingerprint":"SHA256:…",
#   "sudo": false,
#   "ssh_login_hint":"ssh cust_kmpkhm5q_alice@203.0.113.42"
# }

# Example — opt-in NOPASSWD root
iframe keys add inst-... --user alice --pubkey ~/.ssh/alice.pub --sudo

When --sudo is set, the orchestrator validates a single <linux_user> ALL=(ALL) NOPASSWD:ALL line with visudo -cf before installing it as /etc/sudoers.d/iframe-<linux_user> (mode 0440, owned by root). The flag is recorded on the grant and replayed automatically across instances reprovision. Refuses with exit 5 when lifecycle is not live; exit 2 on a malformed label or unparseable public key; exit 6 if the orchestrator can't reach the instance over its admin channel.

iframe keys remove <id> <grantId>

Revoke an SSH grant and remove the Linux user. If the instance is currently live the user is deleted in-OS over SSH; if not, the grant is marked revoked in the database and the user is removed automatically the next time the node is live.

iframe keys remove inst-... <grant-id>
# {
#   "revoked": true,
#   "ssh_cleanup": { "attempted": true, "ok": true },
#   "message": "Linux user removed and grant revoked"
# }

Usage

iframe usage

Per-resource accrued cost for a calendar month. Defaults to the current month. Same numbers your dashboard shows.

iframe usage [--month YYYY-MM]
  --month  defaults to the current calendar month

# Output (JSON)
# {
#   "period": "2026-04",
#   "total_accrued": 4321.56,
#   "currency": "USD",
#   "items": [
#     {
#       "iframe_id":"inst-…",
#       "lifecycle":"live",
#       "rate_per_hour":2.50,
#       "rate_per_month":1800.00,
#       "accrued_cost":1234.50,
#       "days_with_cost_records":21
#     }
#   ]
# }

A malformed --month is rejected with exit 2.

Billing

iframe billing invoices

List invoices for the active org. Optional --year filter.

iframe billing invoices [--year YYYY]

iframe billing invoice <period>

Show one invoice. <period> is YYYY-MM.

iframe billing invoice 2026-04
# Returns 404 / exit 4 if the invoice does not exist.
# Bad period strings are rejected with exit 2.

Notifications

iframe instances webhook *

Per-instance signed webhook for node-disruption events. Configure, rotate, test, list deliveries, and remove. Full reference, payload contract, signature recipe, and a drop-in receiver live on a dedicated page.

iframe instances webhook set       <id> --url <https-url> --events advisory,warning,offline
iframe instances webhook get       <id>
iframe instances webhook rotate    <id>
iframe instances webhook test      <id> [--event advisory|warning|offline]
iframe instances webhook deliveries <id> [--limit N]
iframe instances webhook remove    <id>

Requires scope instances:webhook. See Notifications for the wire format, the three event severities, the HMAC recipe, and a zero-dependency Node receiver.

Discoverability. iframe --help lists every command; iframe <command> --help drills in. Every subcommand also accepts --profile and --output.

Environment

Variables the CLI honors.

VariableEffect
IFRAME_API_KEYfull API key — overrides the credentials file
IFRAME_API_URLAPI base URL — overrides the credentials file
IFRAME_PROFILEprofile name (default: default)
IFRAME_CONFIGpath to the credentials file (default: ~/.iframe/credentials)
IFRAME_YESset to 1 to skip interactive confirmation prompts

Exit codes

Predictable signals for scripts.

CodeMeaning
0success
2misuse — bad arguments or validation error
3auth failure (401/403, missing key, bad scope, revoked key)
4not found (404)
5conflict (409 — e.g. lifecycle is not live)
6server error (5xx)
7network error (DNS, TLS, timeout)

Security

What the server sees. What your terminal sees.

Provider-agnostic surface

Every CLI response is redacted server-side: no provider names, no instance types, no region codes, no vol-… or i-… identifiers. Only your iFrame IDs cross the wire.

Hashed at rest

We store a salted SHA-256 of each key; the plaintext is never written to disk. Revocation is immediate — the next request from a revoked key receives exit 3.

Cross-tenant isolation

A key issued to org A cannot read or mutate org B. SSH grants land distinct cust_* Linux users; iFrame instances are single-tenant by policy, so root is gated behind the explicit --sudo opt-in per grant.

Ship faster from the terminal.

Your iFrame estate, scriptable, redacted, and one npm install away.

Issue an API key →