froglet.
CLI

Overview

The froglet command-line tool. Every dashboard operation, plus file upload/download, PTY tickets, and scripted scope minting.

The CLI talks to the Froglet API at /v1/... over HTTPS using a Bearer key. Source lives at clients/cli/; distributed on npm as @kortix/froglet-cli.

Install

npm  install -g @kortix/froglet-cli
pnpm add  -g @kortix/froglet-cli

# or from source
cargo install --git https://github.com/kortix-ai/froglet froglet-cli

Supported: macOS arm64/x64, Linux arm64/x64 (glibc), Windows x64. See Installation for the auth flow.

Global flags

FlagEffect
--jsonPrint the raw API payload. Designed for piping.
--url <url>Override the API base URL for one invocation.

Every command supports --json and exits non-zero on failure. The JSON output is the unwrapped data field — no data/error container, just the payload.

Command groups

GroupCovers
authLog in, log out, show identity.
mePrincipal currently authenticating (workspace, role, scopes).
sbSandbox lifecycle, exec, files, PTY, snapshots, backups, restore.
templatesTemplate catalog + publish/unpublish/delete.
buildsBuild templates from Docker images.
portsPublic HTTPS routes per sandbox.
ssh-accessSSH bastion tokens per sandbox.
volPersistent volumes, attach/detach/search.
keysWorkspace API keys (issue, rotate, scope).
membersWorkspace member management.
quotasUsage + limits + raise-quota requests.
adminPlatform-admin ops. Workspace keys get 403 platform_admin_required.

Per-sandbox commands accept either the slug (demo) or the full UUID. Per-port / per-key / per-ssh-access commands need the full UUID — pull it from --json.

auth

froglet auth login [--token <fl_…>] [--url <url>]
froglet auth logout
froglet auth whoami

me

$ froglet me
via         api_key
workspace   acme (acme)
role        owner
scopes      workspace:read, members:read, sandboxes:read, …

For API-key auth, the CLI surfaces the issuer's role and the key's effective scopes (intersection of the key's stored patterns and the issuer's current scopes). Demoting the issuer auto-narrows every outstanding key on the next request.

quotas

froglet quotas                                # bar chart of all workspace limits
froglet quotas check sandboxes --delta 1

check is a non-mutating "would this fit?" preflight. Exit code is 1 on rejection. The resource arg accepts friendly shortnames:

ShortnameCanonical
sandboxessandboxes_concurrent
vcpuvcpu_concurrent
memorymemory_mb_concurrent
diskdisk_gb_total
volumesvolumes_total
snapshotssnapshots_total
keysapi_keys_total
membersmembers_total
templatestemplates_per_workspace

ports

froglet ports ls <sandbox>
froglet ports expose <sandbox> --internal <port> [--label x] [--public|--private] [--ttl <sec>]
froglet ports revoke <sandbox> <port-full-uuid>
froglet ports share  <sandbox> <port-full-uuid> [--ttl <sec>]

Routes follow the pattern https://<label>--<sandbox-slug>.froglet.sh. Reserved labels (api, admin) are rejected.

$ froglet ports expose demo --internal 8080 --label web
exposed :8080 → http://web--demo.froglet.sh

--ttl 600 auto-revokes after 10 minutes. --private requires an access token. Mint a one-time share with ports share:

$ pid=$(froglet ports ls demo --json | jq -r '.[0].id')
$ froglet ports share demo "$pid" --ttl 300
token (expires …): eyJhbGciOiJI…

ssh-access

froglet ssh-access ls  <sandbox>
froglet ssh-access new <sandbox> [--name <n>] [--ttl <hours>]
froglet ssh-access rm  <access-full-uuid>

Access goes through the bastion at ssh.froglet.sh:2222.

$ froglet ssh-access new demo --name laptop --ttl 4
created access c35d7262-…

    ssh -p 2222 eyJhbGciOiJI…@ssh.froglet.sh

expires 2026-05-17T18:00:00.000Z

Paste the ssh -p 2222 … line into your terminal.

keys

froglet keys ls
froglet keys scopes                                  # every valid scope, grouped
froglet keys new --name <n> --scopes a,b,c           # secret is shown ONCE
froglet keys rm <key-full-uuid>

A key's effective scopes are the intersection of what you request at creation and what the issuer currently has — you can't mint a key with more authority than your own.

The keys ls table is wide because the scopes column is huge. Prefer --json | jq in scripts.

members

froglet members ls
froglet members roles                                # built-in role presets
froglet members role    <user-id> <viewer|member|admin|owner>
froglet members scopes  <user-id> [--grant a,b] [--deny c,d]
froglet members remove  <user-id>

Assigning owner requires the workspace:transfer_ownership scope — only the owner preset carries it. The role gate is scope-based, so it works correctly under API-key auth.

admin

froglet admin hosts ls / drain <id> / undrain <id>
froglet admin orders ls / new
froglet admin cells ls

Platform-admin operations. A workspace key gets 403 platform_admin_required (by design). For break-glass DB ops, use apps/admin-cli (pnpm admin …) — that runs directly against the database.

Scripting

# find a slug, get the full UUID
froglet sb ls --json | jq -r '.[] | select(.state=="running") | .id'

# wait for state=running
until [ "$(froglet sb get demo --json | jq -r .sandbox.state)" = "running" ]; do
  sleep 0.5
done

# stream a build's log SSE
url=$(froglet builds log-url <build-id>)
curl -N -H "Authorization: Bearer $FROGLET_API_KEY" "$url"

Exit codes

CodeMeaning
0Success.
1API returned an error (4xx/5xx). --json prints the body.
Othersb exec propagates the guest command's exit code.

Gotchas

  • Quote the whole command to sb exec. clap eats -a as a flag if passed separately. Use sb exec demo 'uname -a'.
  • sb snapsb backup. snap is fast but not uploaded — lost if the host dies. backup writes to R2 and is restorable across hosts.
  • ports revoke/share, ssh-access rm, keys rm, vol rm need the full UUID, not the 8-char prefix. Get it from --json:
    pid=$(froglet ports ls demo --json | jq -r '.[0].id')
    froglet ports revoke demo "$pid"
    Sandboxes and volumes don't have this issue — slug resolution is built in.
  • exec and ls2 response bodies are snake_case (exit_code, modified_ms) while the rest of the API is camelCase. The CLI hides the difference; just be aware if you script against /v1/... directly.
  • The default API URL is https://api.froglet.sh — not https://froglet.sh. Old configs that hard-coded the dashboard host will 404; re-run auth login or set FROGLET_API_URL.

Troubleshooting

SymptomLikely causeFix
401 unauthorizedKey missing / wrong env / config not saved.froglet auth login again, or export FROGLET_API_KEY=….
403 forbidden_scopeToken doesn't carry the scope this route needs.froglet keys scopes to see them; mint a fresh key with keys new --scopes ….
403 platform_admin_requiredYou hit an admin/… route with a workspace key.Use apps/admin-cli for platform ops.
404 not_found on sb get <slug>Stale slug or wrong workspace.sb ls to see what's actually there.
400 invalid_enum_value on quotas checkResource name not recognised.Use a shortname (sandboxes, vcpu, …) or the full canonical enum.
500 reserved labelTried --label api / --label admin on ports expose.Pick a different label.
503 not_configured on vol searchAPI process is missing FROGLET_VOLUMED_URL / FROGLET_VOLUMED_AUTH_TOKEN.Operator-side fix.
429 quota_exceededYou're at the limit for the resource.froglet quotas to see what's red; quotas request to raise it.
decode response errorsCLI is older than the API and a field renamed.npm i -g @kortix/froglet-cli@latest.