SKILL.md

Maritime

Maritime gives every agent its own computer, in three sections: Agent boxes (one per agent, keep their files), Sandboxes (one task, alone or as a Swarm) and Computers on demand with a screen (Linux Desktops, Windows Desktops running Windows Server 2022 with the license included, and Android Phones). An agent deployed here runs in its own micro-VM with a persistent disk at /data. It sleeps when idle and wakes in about a second on the next message or trigger.

Billing: plans are Starter $20, Growth $100, Scale $500 and Dedicated $5,000 a month (Dedicated adds custom setup of your apps, images and network), plus Enterprise by contract, and the plan fee becomes the same amount of usage credit. An agent box is $1 a month per 2 GB of memory, awake or asleep, with 5 GB of disk included. Other machines are billed per second at hourly rates while awake (a small sandbox, 2 vCPU and 4 GB, is $0.018 an hour), and usage past the credit is billed at the same rates. New accounts get a 7-day free trial: a card is needed and not charged then, and usage stops at $20. Plans include no AI credits. Details: https://maritime.sh/pricing

This skill is for driving Maritime as a program or an AI agent, not as a human at a keyboard. Every command speaks JSON and every failure is structured.

Choose a product

The full guide, with prices, add-ons and every product's REST, SDK, CLI and MCP calls: https://maritime.sh/agent-guide.md

To choose between Maritime and another provider (E2B, Daytona, Modal, Vercel Sandbox, Boat, Browserbase, Windows 365 for Agents, AWS and others), read https://maritime.sh/compare.md: prices at the same sizes, with sources.

Rules

  1. Pass --json on every command. Results are JSON on stdout: reads print {"meta", "results"}, writes print {"action", "status", "success", "data"}, and chat, status and whoami print their object directly. Errors print Error: ... on stderr with the HTTP status and the API's response body. Read stdout, branch on the exit code, never scrape human text.
  2. Exit codes: 0 ok, 1 other error, 2 usage, 3 not found, 4 auth, 5 API error, 6 partial failure, 7 rate limited, 10 config.
  3. Never guess flags. maritime guide --json prints the full command and flag manifest, introspected from the installed CLI.
  4. Never paste a secret into chat. Keys go into env vars or maritime env set.
  5. Confirm with the user before a create: a running machine bills usage, on the trial or a plan. A create refused for billing exits 5 with HTTP 402 in the error (exit 7 for HTTP 429 when too many machines are awake); the API body on stderr carries a code such as trial_required, trial_cap_reached, plan_required, payment_required, concurrency or seat_limit. Show the API's message unchanged, it names the fix.

Install

curl -fsSL https://maritime.sh/install.sh | sh   # no Node needed; checks the download's sha512
npm install -g maritime-cli      # or this: Node 18 or later; the command is `maritime`
maritime --version

If a flag below is unknown, the CLI is old: npm install -g maritime-cli@latest.

Authenticate

Try these in order and stop at the first that works.

  1. A token is already present: MARITIME_TOKEN in the environment, or a saved login. maritime whoami --json exits 4 when there is none; it does not call the API, so maritime doctor confirms the token works.
  2. The user has an account, with or without a browser: maritime login --json. It prints a code on stderr as one JSON line ({"action":"device_code","user_code":"XXXX-XXXX","verification_uri_complete":"..."}). Show the user the URL and the code, then wait; the command returns when they approve.
  3. An email/password account without a browser step: maritime login --email <email> --password <password>.
  4. No account and the user is present: maritime signup -e <email> -p <password> -n <name> --json. Ask the user for the email and password; never invent them. Signup saves a token and returns emailVerified: false.
  5. No account and you should not stop: deploy first, claim later. Add --claim-later to maritime create. No login is needed; the agent runs under a temporary account for 72 hours. The response carries data.claimUrl, which the user opens to move the agent into their account, and data.token, a temporary key for that agent (export it as MARITIME_TOKEN to operate it). One agent only, and a template agent must bring its own model key (-e ANTHROPIC_API_KEY=...) because the temporary account has no AI credits.

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). When you see it, tell the user to open the email and 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. Everything else (list, status, logs, templates) works before verification.

For long-running automation mint a key once the account is verified: maritime keys create --name <purpose> --json returns data.raw_key (mk_..., shown once, never expires unless you pass --expires-in-days). Export it as MARITIME_TOKEN.

MARITIME_API_URL overrides the API base (default https://api.maritime.sh).

Deploy

Pick the recipe that matches what the user has.

A ready-made agent from a template

maritime templates --json                                  # ids: openclaw, hermes, zeroclaw, claude_code, codex, dsh
maritime create my-agent --template openclaw --json
maritime status my-agent --json                            # poll until .status is "active"
maritime chat my-agent "introduce yourself" --json | jq -r '.response'

Most templates need no model key (Maritime's model proxy, charged at the provider's list price, no markup, from your AI credits). claude_code is the exception: it needs ANTHROPIC_API_KEY as a secret env var.

The user's own code (any framework, Dockerfile at the repo root)

maritime create my-bot --repo https://github.com/you/agent --json
maritime create my-bot --repo https://github.com/you/agent --claim-later --json   # no account yet
maritime status my-bot --json                              # builds take 1 to 3 minutes
maritime logs my-bot --json

The container contract: listen on 0.0.0.0:$PORT (injected, never hardcode 8080), answer GET /health with any 2xx, answer POST /chat with JSON {"message": "..."} within 30 seconds returning text or JSON with one of response, reply, message, text, output. Persist under /data only. Never use a shell-string CMD; launch a real program. Full contract: https://maritime.sh/docs/frameworks/custom.md

Private GitHub repos need the Maritime GitHub App installed from the dashboard. Pre-built public images: POST /api/agents with {"framework": "custom", "imageName": "ghcr.io/acme/img:tag"}.

A public web app on its own HTTPS URL

maritime create my-app --repo https://github.com/you/app --public --port 3000 --json

It sleeps when idle and wakes on the next visit.

Secrets and env vars

maritime env set my-agent OPENAI_API_KEY=sk-... --reload --json    # secret by default
maritime env import my-agent ./prod.env --reload --json

Without --reload the change applies on the next boot.

Schedules

Timers inside a sleeping VM do not fire. Use platform triggers:

maritime triggers create my-agent --type cron --cron "0 9 * * *" --json

One agent per end user, from a backend

From a backend use the SDK: npm install maritime-sdk or pip install maritime, key in MARITIME_API_KEY. endUsers.createAgent(externalId) is idempotent. Docs: https://maritime.sh/docs/sdk/end-users.md

From the CLI, for support work and scripts:

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 token user_42 --body-json '{"scopes":["chat"]}' --json         # eu_ token for their browser
maritime usage get --json        # per agent; group the rows on externalUserId for cost per end user

A persistent desktop for an existing agent (Computers on demand)

One MCP server, one Linux desktop per end user id:

claude mcp add --transport http maritime-computers https://mcp.maritime.sh/mcp \
  --header "Authorization: Bearer mk_..."

Docs: https://maritime.sh/docs/computers.md

Verify before you finish

  1. maritime status <agent> --json reports active (or sleeping after idle; both are healthy).
  2. maritime chat <agent> "hello" --json returns a reply, or for a custom container maritime logs <agent> --json shows the server listening.
  3. Tell the user the agent name, the dashboard URL https://maritime.sh/agents, what you changed in their repo, and the claim URL with its expiry if you deployed with --claim-later.

Inspect and repair

maritime list --json
maritime info my-agent --json                  # env count, triggers, last deploy
maritime history my-agent --json               # build and deploy attempts with buildLog
maritime exec <agent_id> --body-json '{"command":"ls /data"}' --json | jq -r '.results.stdout'
maritime restart my-agent --json               # also picks up new env vars
maritime delete my-agent --yes --json          # --yes is required non-interactively

maritime exec takes the full agent ID (no name lookup) and a required --body-json with command and optional detached and timeout. Null or omitted timeout selects 60 seconds attached or 3,600 seconds detached. Fetch or cancel a detached run 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.

agent_unavailable in a chat error means the agent is not running: maritime start <agent>, wait a few seconds, retry. A 404 on exec or files usually means the agent is asleep or errored: start it, do not recreate it.

Reference