@flop-labs/cli
FLOP Network CLI — interact with FLOP Network nodes from the command line.
Installation
The full CLI (key, miner, validator and model commands) needs Node.js 22+. Run a pinned release from public npm without installing it globally:
npx --yes --package @flop-labs/[email protected] flop --version
Or run the promoted container image, which needs only Docker. For a reproducible digest pin,
use releases.json. The image starts in /app, so mount
your working directory and pass absolute paths under it:
docker run --rm --network host -v "$PWD:/work" \
ghcr.io/flop-labs/flop-core/cli:stable \
--version
npm install --global @flop-labs/cli also works, but it puts a second flop on PATH: the
public Hermes launcher's installer below refuses to install next to it, and the two commands
shadow each other.
For the recommended public native Hermes provider path, install the standalone launcher without repository or package-registry access:
curl -fsSL https://flop.network/releases/cli/0.4.11/install.sh | sh -s -- --version 0.4.11
export PATH="$HOME/.local/bin:$PATH"
flop --network devnet hermes desktop --doctor
This release needs Node.js 22+, curl and the qualified Hermes 0.22.2 release. It contains the hosted
Hermes commands and the native public provider 0.1.6 and preserves an existing full CLI. See the
public launcher guide (docs/integrations/hermes-public-launcher.md in the FLOP repository) for
the native setup sequence, checksums, the 24-hour trial and acceptance limits.
The alternate hosted launcher needs explicit preparation of its generated Hermes
home; it is not qualified as a one-step launch on a fresh managed installation. The full CLI
source build remains:
pnpm install --frozen-lockfile
pnpm --filter @flop-labs/gateway-core build
pnpm --filter @flop-labs/sdk exec tsc
pnpm --filter @flop-labs/sdk run build:a2a-cjs
pnpm --filter @flop-labs/agent-sdk exec tsc
pnpm --filter @flop-labs/cli build
node sdk/cli/dist/cli.js --version # from the repository root
Usage
flop [options] [command]
Global Options
| Option | Description | Default |
|---|---|---|
--network <name> | local, devnet, testnet, or mainnet | local |
--rpc <url> | Explicit RPC override | selected profile |
--signer <uri> | Local signer URI | //Alice on local only |
--keystore <path> | Encrypted Polkadot JSON keystore | — |
--password-env <name> | Environment variable containing its password | — |
--output <mode> | human, pretty json, or compact agent | human |
--json | Output as JSON | false |
Commands
Unified role onboarding
The role commands provide one entry point for onboarding, diagnostics, status,
and lifecycle operations. init and doctor are read-only. Commands such as
register, calibrate-start, commission, unjail, rejoin, and exit
are explicit chain mutations.
# Agents: the devnet tailnet MCP is read-only; local mode is signer-capable.
flop --network devnet agent init --client codex
flop --network devnet agent doctor
flop agent launch codex
flop agent proxy
flop agent hermes desktop
flop keys generate --out agent-key.json --password-env FLOP_KEY_PASSWORD
flop agent wallet create <agent-address>
flop agent session create --delegate <address>
# Miners: sim first, measured TDX for verified production, cloud is non-attested.
flop miner init --mode sim
flop miner doctor --mode sim
# A serving miner: chain state plus the public proof gateway admission checks.
# SESSION_AUTH_TOKEN comes from the environment, never the command line.
flop miner doctor --running --session-url http://127.0.0.1:8645/session/info --endpoint https://<miner-host>
flop miner status [address]
flop --signer <MINER-URI> miner register --commission 10
flop --signer <MINER-URI> miner calibrate-start
# Validators: live stake minimum and PoUI-gated committee state are queried.
flop validator init --mode local
flop validator doctor
flop validator status [address]
flop --signer <VALIDATOR-URI> validator register
flop --signer <VALIDATOR-URI> validator commission 10
flop --signer <VALIDATOR-URI> validator unjail
flop --signer <VALIDATOR-URI> validator rejoin
flop --signer <VALIDATOR-URI> validator exit
All role planning and diagnostic commands support --json for automation.
Global network and custody options work at any nesting depth.
Networks and faucet
flop --network devnet network status
flop --network devnet faucet request <address>
flop --network testnet faucet request <address> --amount 25
flop --network mainnet network status # planned; no faucet
Faucet requests compare the RPC and faucet /chain genesis hashes before
requesting funds. A recognized RPC conflicting with explicit --network is
rejected.
Agent-efficient output
Use --output agent for a stable, single-line JSON result with no banners,
emoji, indentation, or successful doctor checks:
flop --output agent miner doctor --mode tdx
flop --output agent validator status <address>
flop --output agent agent init --client codex
Successes include ok:true. Failures go to stderr with a nonzero exit and a
compact code, message, retryable, and optional next action. --json
remains the backward-compatible pretty JSON format. Interactive processes such
as launch, proxy, and Hermes itself retain their application output after
startup; use Hermes --dry-run for a one-shot agent-readable launch plan.
Health
flop health # Human-readable
flop health --json # JSON output
Balance
flop balance 5Grwva8T1BQxqNJJM... # Check account balance
flop balance 5Grwva8... --json # JSON output
GPU Detection
flop gpu # Detect local GPUs
flop gpu --json # JSON output
Declared GPU inventory
nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits > gpus.csv
flop --keystore miner.json --password-env FLOP_KEY_PASSWORD miner declare-gpu --nvidia-smi gpus.csv
flop miner declare-gpu --detect --dry-run # on the GPU host itself; prints the remark only
flop --keystore miner.json --password-env FLOP_KEY_PASSWORD miner declare-gpu --retract # withdraw it
A registered miner publishes the GPUs it runs as a signed flop.gpu-inventory.v1 remark (format:
docs/design/gpu-inventory-remark.md in the FLOP repository), so explorers can show its hardware. It is
self-reported and display-only: calibration caps, SKU ceilings and rewards never read it. The CLI
image cannot run the host's nvidia-smi, so pass its output with --nvidia-smi (- reads stdin).
Capacity reservation rate
flop miner capacity-rate show [address] # rate, cap, reserved and free G_n/s at the finalized head
flop --keystore miner.json --password-env FLOP_KEY_PASSWORD miner capacity-rate set 25 --dry-run # encoded call only
flop --keystore miner.json --password-env FLOP_KEY_PASSWORD miner capacity-rate set 25
flop --keystore miner.json --password-env FLOP_KEY_PASSWORD miner capacity-rate set --whole-cap # clear it
Each channel opened against a miner reserves this rate from its accepted cap until the channel
lapses; unset, it reserves the whole cap, so only one channel fits at a time. miner-worker keeps
it at floor(cap / MINER_MAX_CONCURRENT_SESSIONS) unless you set a lower one. The call exists only
on a runtime with capacity reservations; elsewhere the command refuses.
Stake Info
flop info 5Grwva8... # Get miner stake info
Session Keys
flop session create --delegate <address> --daily-cap 50 --tx-limit 5
flop session list --owner <address>
flop --signer <DELEGATE-URI> chat --session-owner <OWNER> "hello"
flop --signer <DELEGATE-URI> proxy --session-owner <OWNER>
Session inference checks activity, expiry, and per-tx/daily/lifetime budgets,
then submits the HTLC through executeViaSession. chat persists conversation
IDs and supports --conversation, --history, and --delete.
Named-miner inference
flop --network devnet --keystore agent.json --password-env FLOP_KEY_PASSWORD \
session run --miner MINER_SS58 --model qwen/qwen2.5-0.5b-instruct \
--endpoint MINER_HTTPS_URL --prompt 'Reply OK.' --max-flop 0.01 --max-tokens 8 --json
The completion limit defaults to 256 tokens. --max-tokens must be a positive safe
integer; --max-flop bounds escrow separately. JSON includes finish_reason and
completion_tokens; length means the response reached its token limit. The turn
deadline remains 60 seconds, so large token limits or slow inference can still time
out and follow normal channel recovery. Settlement separately waits for the miner's
on-chain calibrated allowance; faster inference does not bypass that limit. Legacy miners may require
--auth-token-env MINER_SESSION_BEARER.
Models
flop model list
flop model get <hash-or-name> --precision FP16
flop model fetch <hash-or-name> --output-dir ./weights
flop model fetch <hash> --source hf:org/repo --precision INT8
flop model fetch <hash> --revision <40-hex sha> --output-dir ./weights
flop --signer <PUBLISHER> model upload --model-hash <hash> --path ./weights
flop --signer <PUBLISHER> model mirrors set --model-hash <hash> --append --source da:<table> --dry-run
BASE=http://miner:8000 DECODE_POLICY_HASH=0x<hash> \
flop --network local model onboard --source org/model --precision BF16
# Existing-commitment recovery/advanced mode:
flop --signer <PUBLISHER> model upload \
--model-hash <hash> --commitment <0x-hash> --size <bytes>
Fetch accepts hf:, modelscope:, HTTPS, and da: sources and writes only
after dm-verity matches the registered root. A Hugging Face mirror streams at one
commit: --revision <sha> pins it, and without it main is used only when its
sha256 table reproduces a content-v1 root (upload --source hf: likewise; see
docs/onboarding/model.md). Full upload builds the canonical
image, runs da-validator submit, registers its leased blob, and binds it.
Onboard additionally registers the model, mirrors, per-precision measured root,
and decode policy, but only after the serving endpoint passes capture replay,
binding rejection, and output-coherence probes. Re-running it submits no writes.
--hf-repo owner/model gives a local --source a Hugging Face mirror, but only
after that repo reproduces the measured root. Re-running with it adds the mirror
to an already registered model.
Gateway configuration
export MINER_GATEWAY_TOKEN='from-a-secret-manager'
flop gateway allowlist \
--miner https://miner.example \
--network devnet \
--auth-token-env MINER_GATEWAY_TOKEN \
--attestation ./gateway-attestation.json \
--json
The command emits ready-to-use GATEWAY_SESSION_ALLOWLIST and
GATEWAY_MODEL_CATALOG string values. The selected network supplies the RPC
default. It anonymously probes /session/info first to require bearer auth,
then checks every advertised identity field against model-registry, including
the measured root and decode-policy allowlist, and checks the miner's calibration
against the finalized hw-calibration snapshot.
The attestation file uses schema_version: "flop.gateway-attestation.v1" and
contains broker limits, deployment evidence, and catalog evidence. The
catalog block is a CanonicalModelRecord without the generated identity fields
or prices; it carries one price snapshot instead. Deployment
observedAt/expiresAt, catalog price validity, and feature conformance are
used unchanged. Expired evidence is refused rather than refreshed.
# GATEWAY_SESSION_ALLOWLIST / GATEWAY_MODEL_CATALOG hold the gateway's own values.
export GPUAI_API_KEY='from-a-secret-manager'
export GATEWAY_RENEWER_SIGNER_URI='dedicated funded account, not the broker signer'
flop --network devnet gateway renew \
--allowlist-env GATEWAY_SESSION_ALLOWLIST \
--catalog-env GATEWAY_MODEL_CATALOG --catalog-pricing chain \
--provider-key-env GPUAI_API_KEY \
--probe-signer-env GATEWAY_RENEWER_SIGNER_URI --probe-escrow 1 \
--out /opt/flop/gateway-evidence/gateway-evidence.json
gateway renew re-makes that evidence instead of re-stamping it, and writes
the file the gateway hot-reloads as GATEWAY_EVIDENCE_FILE. A miner's
deployment is renewed only when GPU.ai still reports the instance named in
deployment.source (operator-observed:gpu.ai/<instance> <region> <gpu>,
optionally followed by a (label)) as running in that region on that GPU. The
GPU is GPU.ai's identifier (rtx_a6000) or its name in GPU.ai's GPU type table
(RTX A6000, with or without NVIDIA); any other name is not renewed. A
tailnet-hosted miner (operator-observed:tailscale/<host> egress <ipv4> AS<n>,
optionally followed by the network's name and a (label)) is renewed only while the
host's tailscaled (--tailscale-socket) sees exactly one node of that name online,
tagged tag:miner, on a direct path from that egress address; the country stamped
from that address therefore still holds. Its authenticated /session/info must also still advertise the pinned address,
enclave key, and identity that the
finalized chain still admits. A record's tools conformance is renewed only by
a real compute-channel turn, paid from the probe signer's escrow, whose signed
frames carry a lookup tool call with a JSON query. Each renewal keeps its
declared window length. Every other claimed feature has no probe yet and lapses.
Whatever is not renewed is left out of the file and lapses at its declared
expiry. The command exits 1 when anything lapses and never prints the allowlist.
Before each probe it requires one --probe-escrow free on the probe signer at the
finalized head. When the signer holds less, no further probe runs, and stderr says
gateway renew: PROBE SIGNER UNDERFUNDED: ….
The GPU.ai single-instance response shape (status, region, gpu_type) is
assumed from the pricing and create APIs, so watch the first run.
An operator can instead publish an already settled owner-requested tools qualification without creating another probe or using the direct renewer's signer/provider credentials:
flop --network devnet gateway renew \
--catalog-env GATEWAY_MODEL_CATALOG --catalog-pricing chain \
--qualification-receipt ./settled-tools-qualification.json \
--qualification-receipt-sha256 "$QUALIFICATION_RECEIPT_SHA256" \
--out /opt/flop/gateway-evidence/gateway-evidence.json
The hash must identify the exact immutable receipt bytes. Receipt-only mode rejects the
direct-probe options. It verifies the original owner/resident/purpose envelope, signed
turn/prefix/ACK and canonical finalized positive settlement before appending one pass to
the existing evidence file. It preserves unrelated deployments and conformance entries,
keeps the proof-bound original probe time and existing declared window, and refuses a
conflicting re-publication. An identical receipt is a no-op. Concurrent gateway renew
commands using this version take an exclusive publication lock; a conflicting writer
fails rather than replacing its contents. Older or unrelated writers must be coordinated
separately. Receipt-only publication retains only this producer's latest unexpired pass
per exact model/feature identity, preserving its original times and all unrelated evidence.
A process killed while writing can leave OUT.lock; confirm that writer has stopped before
manually recovering that lock, and never remove another active writer's lock.
No fresh inference, escrow, signature submission or automatic publication by
the qualification endpoint occurs. Ordinary chat continues to require fresh tools
admission; a qualification request is not permission to run arbitrary stale-tools chat.
Keys, hardware, multisig, and air-gapped signing
export FLOP_KEY_PASSWORD='from-a-secret-manager'
flop keys generate --name treasury --out treasury.json \
--password-env FLOP_KEY_PASSWORD
export FLOP_IMPORTED_SECRET='mnemonic from a secret manager'
flop keys import --secret-env FLOP_IMPORTED_SECRET --out imported.json \
--password-env FLOP_KEY_PASSWORD
flop keys export treasury.json --source-password-env FLOP_KEY_PASSWORD \
--out treasury-backup.json --password-env FLOP_BACKUP_PASSWORD
flop keys inspect treasury.json
flop keys hardware
flop multisig address --threshold 2 --signatory <A> <B> <C>
flop --keystore treasury.json --password-env FLOP_KEY_PASSWORD \
multisig propose --threshold 2 --other <B> <C> --call <0x-call>
flop multisig list --account <MULTISIG>
flop tx build --address <ADDRESS> --call <0x-call> \
--out unsigned.json --qr
flop tx broadcast unsigned.json --signature <0x-multisignature>
tx build pins genesis, runtime, transaction version, and nonce. Broadcast
rejects drift. Terminal QR uses optional qrencode; the JSON envelope works
with Polkadot Vault, hardware wallets, HSM/MPC custody, or other detached
signers.
Wallets
flop wallet agent create <agent-address> --daily-cap 50 --tx-limit 5
Shared devnet agent credential
Provision one key for any supported agent client without launching Hermes:
flop --network devnet agent credential # path, live model, quota; no bearer
flop --network devnet agent credential --json # non-secret metadata
flop --network devnet agent credential --print-key # secret on stdout for local auth helpers
flop --network devnet agent credential --tailnet # private lane for approved Tailscale users
The command requires explicit --network devnet. By default it enrolls at the
public gateway https://api.flop.network with a wallet signature. On first use
it creates a local sr25519 account in ~/.flop/devnet-enroll-wallet.json
(unencrypted, devnet only, mode 0600; its secret is never printed). If that
account holds less than 1 FLOP, it requests a grant from
https://faucet.flop.network/fund and waits for the grant to finalize on
wss://rpc.devnet.flop.network (--rpc overrides). It then signs the
gateway's single-use challenge. The key is cached in
~/.flop/devnet-wallet-agent-key.json. The operator must explicitly fund and configure the allowance; the recommended
casual-use grant is 20,000 devnet FLOP, scoped to the exact model returned by
enrollment, and expires within 24 hours. Re-enrolling
rotates the bearer but never renews quota or expiry.
--tailnet keeps the private lane for approved Tailscale users. It enrolls at
http://gateway.ts.flop.network:3201/enroll and gives a seven-day allowance,
cached in ~/.flop/devnet-agent-key.json and shared with the Hermes provider.
Both caches are mode 0600 in a mode-0700 directory on POSIX systems. A .lock
directory per cache serializes enrollment across local clients. The
--print-key form is deliberately explicit; redirect or consume it privately
rather than recording terminal output. A stale .lock directory after a crash
can be removed only after confirming no enrollment is running.
flop --network devnet agent init and agent doctor use the public
https://mcp.flop.network/mcp read-only service by default; pass --mcp-url
to target another verified endpoint.
Hermes Desktop
The public native provider (sdk/hermes_public) is bundled with the
standalone launcher 0.4.5. Use flop hermes plugin install in an isolated
HERMES_HOME after hermes pm repair, then hermes auth add flop-public, hermes model and
hermes desktop. The separate private provider (sdk/hermes_native)
requires an approved Tailscale identity and FLOP repository access.
Follow the public native setup guide (docs/integrations/hermes-public-launcher.md,
"Native public provider") for the recommended public route. The full CLI also retains the private tailnet lane:
flop --network devnet hermes desktop --dry-run --json # inspect without provisioning
flop --network devnet hermes desktop --gateway http://gateway.ts.flop.network # tailnet lane
From a source checkout, replace flop with node sdk/cli/dist/cli.js after
the build above.
The alternate public hosted launcher requires managed-home preparation described
in the public guide; an immediate Desktop launch is not qualified. With the
tailnet --gateway, your Tailscale user ID must be approved first; request it with an
access request. The CLI then obtains a seven-day, 25 FLOP model-scoped allowance and
stores the key in ~/.flop/devnet-agent-key.json (mode 0600). Later launches
and other local agent clients check and reuse the cached key.
If the local key is lost or rejected, enrollment rotates the bearer while preserving
its spent amount and expiry. Exhausted, expired, and revoked allowances require an
operator to renew or reapprove them. Normal CLI output does not print the key.
Without --network devnet, the command starts the embedded signer proxy as before:
flop hermes desktop # embedded proxy + launch Hermes
flop --signer //Bob hermes desktop # settle under //Bob
flop hermes desktop --model llama-3-8b-instruct --max-flop 10 --max-tokens 4096
flop hermes desktop --proxy-port 1234 # embedded proxy on a custom port
flop --network devnet --signer "$FLOP_SIGNER" hermes desktop --proxy # funded signer
flop hermes desktop --configure # also persist via `hermes config set`
flop hermes desktop --no-proxy \
--gateway http://gateway.ts.flop.network --model "$FLOP_MODEL"
flop hermes desktop --dry-run --json # print resolved env/launch plan, don't launch
It wires Hermes to the embedded proxy via the env vars Hermes reads for a custom
OpenAI-compatible endpoint — OPENAI_BASE_URL (→ proxy /v1), OPENAI_API_KEY,
and HERMES_INFERENCE_MODEL (api_mode auto-detects chat_completions) — plus
FLOP_WS_URL/FLOP_SIGNER for the @flop-labs/hermes-plugin. --configure
additionally persists the wiring through the real hermes config set CLI
(model.provider=custom, model.base_url, model.api_mode, model.default).
Requires the Hermes binary (hermes-desktop or hermes) on PATH; use
--dry-run to inspect without it. A custom hosted --gateway requires
FLOP_API_KEY supplied privately and an explicit --model from the live
catalog. The CLI checks gateway health, model availability, and key validity
before launching. The private devnet gateway is reachable only from the FLOP
tailnet.
Local OpenAI Proxy
Expose a local OpenAI-compatible API (like LM Studio / Ollama's local server) where every request is submitted and settled on-chain under your signer. The proxy estimates G_n, submits the PoI task, waits for a TEE miner to settle it, then returns the result — all behind the OpenAI REST schema:
flop proxy # http://127.0.0.1:3201/v1
flop --signer //Bob proxy --max-flop 10
flop proxy --port 1234 --host 0.0.0.0 # expose beyond localhost
| Endpoint | Purpose |
|---|---|
POST /v1/chat/completions | Chat inference (OpenAI-compatible), stream: true supported |
POST /v1/completions | Text completion, stream: true supported |
GET /v1/models | List models (live from the payload gateway, falls back to defaults) |
GET /v1/models/{id} | Retrieve a single model |
POST /v1/embeddings | Embeddings, computed locally (no chain) — see below |
GET /health | Liveness |
OPTIONS * | CORS preflight (permissive, for browser/Electron clients) |
Embeddings are a cheap, high-frequency RAG primitive and don't need on-chain
settlement, so the proxy computes them locally with a small embedding model
(default Xenova/all-MiniLM-L6-v2, L2-normalized output like Ollama's
/api/embed). This needs the optional runtime dependency — install once:
pnpm add @huggingface/transformers
Without it, /v1/embeddings returns 501 with this hint (chat/completions are
unaffected). Override the model with --embedding-model <hf-id>.
Streaming: stream: true returns OpenAI SSE chunks (data: {…} … data: [DONE]),
incl. an optional usage chunk via stream_options.include_usage. FLOP settles a
task atomically, so this is settle-then-stream: the result is awaited, then
emitted as chunks (latency-to-first-token = settlement time), not incremental
miner generation.
Models: /v1/models reflects the payload gateway's registry
(<payload-gateway>/v1/models), falling back to the built-in defaults if it's
unreachable.
Options: --port (3201), --host (127.0.0.1), --payload-gateway
(off-chain prompt/result data plane, default http://localhost:3200),
--model, --max-tokens (default 2048), --max-flop. Uses global
--rpc/--signer. Point any OpenAI client at it via
OPENAI_BASE_URL=http://127.0.0.1:3201/v1.
Architecture
- Built with Commander.js
- Uses local SDK via
"@flop-labs/sdk": "file:../typescript" - TypeScript with ES modules, compiled via
tsc - All commands use
FlopClient.create()from the SDK with--rpcand--signerglobal options - Role groups reuse existing launch, proxy, key, wallet, session, staking, TDX, and provider workflows rather than introducing parallel implementations.
Operator images: use ghcr.io/flop-labs/flop-core/cli:stable (minimum 0.1.172),
ghcr.io/flop-labs/flop-core/miner-worker:stable (minimum 0.1.172), and
ghcr.io/flop-labs/flop-core/miner-vllm-toploc:stable (minimum 0.1.172).
For a reproducible pin, use the version and digest in releases.json.
just miner-pack /tmp/flop-miner-pack checks that manifest against the minimums, copies it, and sets the images to :stable.
The pack is for the public path, without the tailnet: public RPC, mailbox and sponsor, and
docker-compose.tls.yml for the session endpoint.