llms.txt
Maritime CLI: instructions for AI agents
Copy this file into your agent's context (CLAUDE.md, AGENTS.md, or a system prompt), or let the agent fetch it from https://maritime.sh/docs/cli/llms.txt. It teaches an LLM to operate Maritime (https://maritime.sh): every computer an agent needs (sandboxes, Linux and Windows desktops, Android phones). Agents deployed here run in persistent micro-VMs with storage at /data; they sleep when idle and wake in about a second on the next message or trigger. Billing: plans from $20 a month, and the plan fee becomes usage credit; machines are billed per second at hourly rates, with a 7-day free trial (card needed, usage capped at $20). Details: https://maritime.sh/pricing
The CLI describes itself offline: maritime guide prints its usage guide, and maritime guide --json emits the full command and flag manifest, introspected live from the installed CLI. Prefer that manifest, and maritime <command> --help, over guessing flags. --dry-run on any command prints the HTTP request without sending it.
Setup
- Install: curl -fsSL https://maritime.sh/install.sh | sh (no Node needed; it checks the download's sha512), or npm install -g maritime-cli (the npm package is maritime-cli; the command is maritime; needs Node 18+. If a flag below is unknown, run npm install -g maritime-cli@latest)
- Authenticate, first path that works wins:
- New user, or no plan yet: maritime onboard --json. It prints one JSON object whose step names what the user must do: sign_in (give them sign_in.url and sign_in.code), checkout (give them checkout_url: the 7-day free trial in Stripe Checkout, card required, nothing charged for 7 days) or billing_action (give them billing_url). After each step run maritime onboard --wait --json, until step is done. Do this before you build anything: no machine starts without a plan or the trial.
- MARITIME_TOKEN in the environment (an mk_ key or a login token); the CLI reads it before anything else.
- Any account, browser or not: maritime login --json prints one JSON line on stderr with user_code and verification_uri_complete; show both to the user and wait for their approval in any signed-in browser.
- Email/password account, no browser: maritime login --email
--password - No account, user present: maritime signup -e
-p -n --json (ask the user for the email and password; never invent them) - No account, do not stop: add --claim-later to maritime create. No login needed; the agent runs under a temporary account for 72 hours. The JSON response carries data.claimUrl (the user opens it to keep the agent) and data.token (a temporary key for that agent; export it as MARITIME_TOKEN to operate it). One agent only; template agents must bring their own model key (-e ANTHROPIC_API_KEY=...).
- Verify: maritime whoami --json prints {authenticated, source, method, expires_at} and exits 4 when no credential is set or a login token expired. It does not call the API, so a wrong mk_ key still reports authenticated; maritime doctor checks it.
- The email verification wall: until the user clicks the link Maritime emailed them, create, deploy, start, restart, keys create, and computer creation are refused with HTTP 403 and email_not_verified in the error (exit 4). Ask the user to click the link. To get it again they sign in at https://maritime.sh/login, which offers to resend it. Then retry the same command. List, status, logs, and templates work before verification.
- Long-running automation: once verified, mint a key that never expires: maritime keys create --name my-agent --json (data.raw_key is shown once), and export MARITIME_TOKEN=mk_...
Full setup procedure, step by step: https://maritime.sh/agents.md
MARITIME_API_URL overrides the API base (default https://api.maritime.sh).
The contract
- Pass --json on EVERY command. Results go to stdout: reads print {"meta": {...}, "results": ...}; writes print {"action", "resource", "path", "status", "success", "data"}; chat, status and whoami print their object directly. Errors print "Error: ..." on stderr; API errors include the HTTP status and the API's response body. A 409 also prints {"error": "...", "code": 5} on stdout. Branch on the exit code, never scrape human text.
- The global --agent flag sets --json --compact --no-input --no-color --yes. --compact keeps only key fields; leave it off when you need the full body.
- Exit codes: 0 success, 1 other error, 2 usage (unknown flag, missing argument, missing --yes), 3 not found (HTTP 404), 4 auth (no credential, expired, HTTP 401/403), 5 API error (other HTTP errors such as 402, 409, 422, 5xx), 6 partial failure, 7 rate limited (HTTP 429), 10 config.
- The top-level agent commands (chat, logs, status, env, delete and the rest) take the exact agent name or an ID prefix. Inside a directory linked with maritime init/link, the agent argument can be omitted. Commands whose help shows
(exec, files, agents ...) need the full ID. - In a linked directory, chat treats every argument as the message. To reach another agent from there, use maritime chat -a
"...".
Core tasks
Discover templates, create an agent, wait for it, talk to it:
maritime templates --json maritime create my-agent --template openclaw --json maritime status my-agent --json # wait for "active" maritime chat my-agent "introduce yourself" --json | jq -r '.response'
No account yet and nobody to ask: deploy now, claim later (72 h, 1 agent):
maritime create my-bot --repo https://github.com/you/agent --claim-later --json
Deploy your own code (any framework; Dockerfile at the repo root):
maritime create my-bot --json maritime deploy my-bot --source github --repo https://github.com/you/agent --branch main --wait --json
Serve a repo on a public no-login URL (sleeps idle, wakes on the next visit):
maritime create my-app --repo https://github.com/you/app --public --port 3000 --json
Encrypted env vars (secret by default; --reload pushes into the running agent):
maritime env set my-agent OPENAI_API_KEY=sk-... --reload --json maritime env import my-agent ./prod.env --reload --json maritime env list my-agent --json
Inspect and debug:
maritime list --json
maritime logs my-agent --level error --json
maritime info my-agent --json
maritime history my-agent --json # build/deploy attempts + buildLog
maritime exec
Message an agent AS one of your end users (sticky per-user routing through the project front door; same --user always reaches the same instance):
maritime message my-agent --user cust_42 "where is my order?" --json | jq -r '.data.reply'
Your end users (the people your product serves), each with their own agent:
(end-users, alias users; bodies go in --body-json, fields as in the REST API)
maritime users upsert user_42 --display-name "Ada" --tags pro --json # safe on every sign-in maritime users create user_42 --body-json '{"templateId":"openclaw"}' --json # their agent, exactly once maritime users set-policy user_42 --body-json '{"policy":{"llm_spend_cap_cents_per_month":500}}' --json maritime users policy user_42 --json # read; clear-policy removes it maritime users token user_42 --body-json '{"scopes":["chat"],"ttlSeconds":900}' --json # eu_ token maritime users list --json · get · agents · suspend · resume · delete maritime usage get --from 2026-09-01 --to 2026-10-01 --json # per agent, each row has externalUserId maritime usage daily --json # per day
Lifecycle (rarely needed; agents sleep and wake on their own):
maritime start my-agent --json maritime sleep my-agent --json maritime restart my-agent --json # also picks up new env vars maritime delete my-agent --yes --json # --yes is REQUIRED non-interactively
Scheduling
Timers inside a sleeping agent do not fire. Put scheduled work on platform triggers: maritime triggers list
maritime exec takes the full agent ID and a required --body-json: {"command": "..." or ["argv", ...], "detached": false, "timeout": 60}. Words after the agent ID are not sent. timeout defaults to 60 seconds attached or 3,600 seconds detached. A detached call returns executionId at once; fetch or cancel it through the REST API: https://maritime.sh/docs/api#exec On agents with detached support, disconnecting does not cancel execution. Do not repeat submission after a lost response without accounting for a command that may already be running. Older agents support attached exec with combined output and no execution ID. Detached submission on those agents returns HTTP 501 with unsupported: outdated vm. Please use a new agent. Use a new agent for detached execution. Restarting an older agent does not add this feature.
Gotchas
- create and delete never hang without a terminal: a missing name, or delete without --yes, exits 2.
- Env changes apply on the next boot unless you pass --reload (or run maritime env reload
). - If chat fails with agent_unavailable in the error, the agent is not running: maritime start
, wait a few seconds, retry. - A 404 or "not running" on file/exec operations usually means the agent is asleep or errored: start it and retry; do not recreate it.
- mk_ keys never expire unless created with --expires-in-days; login tokens do. Use mk_ for automation.
- LLM access: most templates work with no model key (Maritime's model proxy, charged at the provider's list price, no markup, from your AI credits). The claude_code template is the exception: it always needs your own ANTHROPIC_API_KEY as a secret env var.
Something wrong with these docs?
If a command, flag or example here does not match what the CLI does, or a call fails and the error does not tell you what to do, tell Maritime. Any mk_ key works. Put the X-Request-Id of the failing call in requestId when there is one and name yourself in tool. Never include keys, tokens or env values.
curl -X POST https://api.maritime.sh/api/v1/feedback
-H "Authorization: Bearer $MARITIME_API_KEY"
-H "Content-Type: application/json"
-d '{"kind":"cli","subject":"maritime env set","message":"
kind is one of docs, sdk, api, cli, computers, dashboard, other. Capped at 5 reports a minute and 20 a day per account. Reference: https://maritime.sh/docs/sdk/feedback