@tarout/cli
v1.15.2
Published
Tarout CLI: the Saudi cloud platform for coding agents
Maintainers
Readme
Tarout CLI
The official command-line interface for Tarout - the Saudi cloud platform built for coding agents. Your agent writes code and defines infrastructure; Tarout provisions it instantly.
Installation
curl -fsSL https://tarout.sa/install.sh | shOr with other package managers:
# Using npm
npm install -g @tarout/cli
# Using yarn
yarn global add @tarout/cli
# Using pnpm
pnpm add -g @tarout/cli
# Using bun
bun add -g @tarout/cliQuick Start
# 1. Login via browser (opens authentication page)
tarout login
# 2. Inspect and deploy from your project root
tarout deploy --wait --source upload
# The first deploy prompts to create or link an app, and to create detected resources.Cloned a repo that already deploys to Tarout? Its .tarout/ folder arrives
with only a .gitignore, because your login is not committed. Run tarout login
(or straight tarout deploy, which signs you in and asks which app to deploy
to). On a machine with no browser, such as a server over SSH, run
tarout login --device and approve its one-time code from any browser. In CI,
set TAROUT_TOKEN to a key from
https://tarout.sa/dashboard/agent/keys. To commit the login instead, see
Sharing one login through git.
Call any API (tarout call)
Beyond the curated commands, tarout call reaches every platform procedure
directly - the same control surface exposed via REST and MCP:
tarout call --list # discover all callable procedures + type
tarout call application.create --input '{"name":"my-app"}' --json
tarout call deployment.all --input '{"applicationId":"app_123"}'MCP server
tarout-mcp is a local MCP server that gives coding agents (Claude Code,
Cursor, Claude Desktop) the CLI's capabilities as first-class tools:
deploy from the current directory, sync .env, run SQL against Postgres,
schedule cron tasks (job_*), run a command in an app's container
(app_exec), explain what a deploy would build (app_explain_build), deploy
a ready-made app from a template (template_*), check parked approval requests
(approvals_*), map a project in one read (agent_manifest), read the agent
activity timeline and credentials (agent_events, agent_sessions), switch
org/project/env, upgrade billing, and more, with a
call escape hatch covering the entire platform API.
Set up every agent at once: tarout agent setup
tarout agent setup # shows the plan, asks once
tarout agent setup --dry-run # only shows the plan and the changesFinds the coding agents on this machine (Claude Code, Codex, Cursor, VS Code /
GitHub Copilot, Devin Desktop / Windsurf, OpenCode, Gemini CLI), installs the
tarout-deploy and tarout-domains skills, and registers the hosted MCP
server below. It uses OAuth, so no credential is written to disk and no
sign-in is needed first; the command then prints each agent's sign-in step
(/mcp in Claude Code, codex mcp login tarout, and so on).
--localregisters the stdiotarout-mcpinstead of the hosted URL.--agents claude,cursor,--skills-onlyand--mcp-onlynarrow the run.--yesapplies without asking (required when stdin is not a TTY);--jsonreturns one result per agent.
Only agents already installed are touched, other MCP servers are left alone,
and an existing tarout entry with different settings is replaced only with
--yes. Re-running reports unchanged. tarout agent init is the per-project
counterpart (CLAUDE.md / AGENTS.md plus the Claude Code permission allowlist).
Map a project in one read: tarout agent manifest
tarout agent manifest # compact tree of the active project
tarout agent manifest --env-names # list env var names instead of a count
tarout agent manifest --project api --jsonOne call returns the whole project: every app with its status, URL, source,
custom domains, linked databases, env var names (never values) and
scheduled job count, plus the project's databases, buckets and domains.
--json prints the full manifest in the standard envelope. Unlike the rest of
tarout agent, it needs you signed in. The MCP server exposes the same read as
the agent_manifest tool ({ projectId? }).
Agent credentials and activity: tarout agent sessions / tarout agent events
tarout agent sessions # OAuth connections and API keys
tarout agent events # the 30 most recent agent actions
tarout agent events --since 1h # everything in the last hour (up to 500)
tarout agent events --follow # keep printing new ones (Ctrl+C stops)
tarout agent events --follow --json | jq .proceduresessions lists this account's agent credentials in the organization, in two
groups: OAuth connections (hosted MCP connectors: client, tier, status,
created, last used, expires) and API keys (name, prefix, tier, status, last
used). Status is active, paused (agent access switched off) or expired.
The key itself is never shown. Revoking or pausing is done by a human in the
dashboard (Agent > Keys, https://tarout.sa/dashboard/agent); this command
deliberately does not.
events is the timeline the dashboard's Agent page shows: every change an
agent made through the CLI or MCP (reads are not recorded), with the time, the
key or OAuth client that acted, the procedure, the surface (cli or mcp),
ok or error, and the error. Rows print oldest first. --limit takes 1 to 500
(default 30, or 500 with --since); --since takes 15m, 2h, 7d or
1h30m. --follow polls every 3 seconds, prints only rows it has not printed
before, and exits 0 on Ctrl+C. --json prints one envelope, or with --follow
one JSON object per event per line (NDJSON, "type": "agent_event").
Both are organization-level: they need you signed in but no project. When the
platform refuses a read (an organization agent-policy rule, for example), they
exit 5 with PERMISSION_DENIED and the dashboard link rather than a stack
trace. The MCP server exposes the same reads as agent_events
({ limit?, since? }, up to 100 rows) and agent_sessions. agent_sessions
exists because user.listApiKeys was already reachable over MCP through the
platform surface and call; it adds no new access.
No install: the hosted connector
If you just want an agent talking to Tarout, you do not need this package at all. The platform hosts an MCP server that authenticates with OAuth, so the URL is the whole setup:
https://tarout.sa/api/mcp- claude.ai / ChatGPT / Claude Desktop: Settings, Connectors, add a custom connector, paste the URL. Leave client ID and secret empty.
- Claude Code:
claude mcp add --transport http tarout https://tarout.sa/api/mcp - Codex:
codex mcp add tarout --url https://tarout.sa/api/mcpthencodex mcp login tarout - Cursor (
~/.cursor/mcp.json):"tarout": { "url": "https://tarout.sa/api/mcp" }
That path offers a curated tool set plus a tarout_call escape hatch. Install
the CLI below when you want the local toolset, deploys from the current folder,
and per-project credentials. Full guide:
https://tarout.sa/docs/getting-started/connect-your-ai
Setup (local stdio server)
Claude Code
npm i -g @tarout/cli
claude mcp add tarout -- tarout-mcpCursor (~/.cursor/mcp.json):
{
"mcpServers": {
"tarout": { "command": "tarout-mcp" }
}
}Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"tarout": { "command": "tarout-mcp" }
}
}Auth
The server reuses the credential tarout login wrote: the project's
.tarout/auth.json first, then the machine-wide profile. Launch it from the
project directory, or pass a path argument on the tools that accept one, so
it resolves the right project. If nothing resolves, tool calls return a
structured AUTH_ERROR.
Bootstrap URL
Point an agent at https://tarout.sa/agent-setup/prompt.md and it will
install the CLI + register the server in one shot.
Commands
Authentication
| Command | Description |
|---------|-------------|
| tarout login | Authenticate via browser; writes this project's .tarout/auth.json |
| tarout login --token <key> | Same, headless, with no browser |
| tarout login --device | Sign in from a host with no browser (SSH, a container): approve the printed one-time code in any browser |
| tarout login --commit-token | Commit this project's .tarout/auth.json with the repo (details); --no-commit-token undoes it |
| tarout register | Create a new account via browser |
| tarout token <key> | Alias for login --token |
| tarout token:create | Mint a new API key for the current account |
| tarout logout | Sign this project out (--global for the machine-wide login) |
| tarout whoami | Show current user, organization, project, and credential scope |
Credentials are per project - see Configuration.
# Login with a custom API URL (e.g. staging)
tarout login --api-url https://staging.tarout.sa
# Store the credential machine-wide instead of in this project
tarout login --global
# On a server over SSH or in a container: open the printed URL on any device,
# enter the code, and approve it (only approve a code you started yourself)
tarout login --device
# Ignore this project's credential for a single command
tarout whoami --global-authApplications
| Command | Description |
|---------|-------------|
| tarout apps list | List all applications |
| tarout apps create <name> | Create a new application |
| tarout apps delete <app> | Delete an application |
| tarout apps info <app> | Show application details |
| tarout apps open <app> | Open application URL in browser |
# List apps as JSON
tarout apps list --json
# Create an app
tarout apps create my-api
# Delete without confirmation
tarout apps delete my-api --yesDeployment
| Command | Description |
|---------|-------------|
| tarout deploy [app] | Deploy an application |
| tarout deploy:status <app> | Check deployment status |
| tarout deploy:cancel <app> | Cancel running deployment |
| tarout deploy:list <app> | List recent deployments |
| tarout deploy:retry <app> | Retry a failed deployment without rebuilding |
| tarout deploy:rollback <app> | Roll back to a previous deployment |
# Inspect and deploy the current folder
tarout deploy --wait --source upload
# Deploy and wait for completion
tarout deploy my-app --wait
# Check deployment status
tarout deploy:status my-appRetrying a failed deployment
A deployment can build a good image and then fail after the build - the host could not pull the image, a registry token went stale, the target server hiccuped. Deploying again from scratch re-resolves the commit, re-runs preflight and re-enters the builder just to arrive at the identical image.
tarout deploy:retry my-app --waitThis reuses the image the failed deployment already produced and re-runs only
the deploy step. It refuses deployments that failed during the build, because
there is no image to reuse - fix the code and run tarout deploy instead.
tarout deploy inspects the current folder for database, file storage, and Git signals before it asks questions. A folder with a github.com remote deploys from that repo, so every push redeploys: the CLI binds it when the organization's GitHub connection can read the repo. When it cannot, a terminal run offers to connect GitHub and waits for it; an agent or script run uploads the folder and its result names the fix, tarout providers github connect --wait --app <app>, which opens Tarout's GitHub setup page, waits until GitHub can read the repo, and binds the app. A folder with no GitHub remote is uploaded, so users without GitHub can still deploy.
Logs
| Command | Description |
|---------|-------------|
| tarout logs <app> | View application logs |
# Fetch a snapshot of recent logs
tarout logs my-app
# Filter by log level
tarout logs my-app --level error
# Logs from last hour
tarout logs my-app --since 1h
# Last 100 lines
tarout logs my-app --limit 100
# Follow continuously (Ctrl+C to stop)
tarout logs my-app --follow
--followpolls every 3s and prints only new lines - the platform exposes runtime (container) logs as a snapshot query, not a stream. Under--jsonit emits JSON Lines, one object per line, so agents can consume it incrementally.
Environment Variables
| Command | Description |
|---------|-------------|
| tarout env <app> list | List environment variables (masked) |
| tarout env <app> set <KEY=value> | Set an environment variable |
| tarout env <app> unset <KEY> | Remove an environment variable |
| tarout env <app> pull | Download variables as .env file |
| tarout env <app> push | Upload variables from .env file |
tarout env <app> list shows an AVAILABLE column: build + runtime or
runtime only.
This is not cosmetic. Only public-prefixed keys (NEXT_PUBLIC_*, VITE_*,
EXPO_PUBLIC_*, PUBLIC_*, GATSBY_*, NUXT_PUBLIC_*, REACT_APP_*) are
passed to the build - everything else, including DATABASE_URL and every
secret, exists only in the running container, so that secrets can never land in
build logs or image history. A build step that reads a runtime-only variable
sees nothing, however correctly you set it. If your build genuinely needs one,
move that step to a releaseCommand instead.
# Set a variable
tarout env my-app set DATABASE_URL=postgres://...
# Set multiple variables
tarout env my-app set API_KEY=xxx SECRET=yyy
# Download to .env file
tarout env my-app pull
# Upload from .env file
tarout env my-app pushRun a local command with the app's env: tarout run
tarout run -- npm test
tarout run --app api -- npx prisma migrate deployRuns the command on your machine with the linked app's environment variables
injected (--app picks another app by id, name or slug). Put the command after
--: it starts directly, without a shell, and every argument reaches it
exactly as typed, spaces and quotes included. Use sh -c "..." when you need
pipes or shell syntax.
- Managed database variables (
DATABASE_URL,PGHOST, ...) carry the database's external connection details. A database without external access is left unset, and stderr prints how to enable it (tarout db external-access <db> --enable); the platform's hidden-route placeholder is never injected. - Values are never printed.
- The command's output and exit code pass straight through, unmapped; a
command killed by a signal exits
128+n(130 for SIGINT). Ctrl+C and SIGTERM are forwarded to the command.--jsondoes not apply and is rejected.
tarout dev and tarout build resolve their variables the same way.
Run a command in the app's container: tarout exec
tarout exec -- ls -la "/app/my dir"
tarout exec -- "npm run migrate && echo ok"
tarout exec --app api --timeout 300 -- node scripts/backfill.jsRuns one command inside the app's running container (the linked app, or
--app) and prints its stdout and stderr. Put the command after --:
- One argument is sent as-is and runs as a shell line (
sh), so quote the whole line locally when it uses pipes,&&or$VARS. - Several arguments are each quoted for
sh(single quotes, with'written as'"'"'), so the container sees exactly the argv you typed.
It is one request, not a stream: output arrives when the command finishes, and
the platform keeps the first 4,000 and last 8,000 characters of each stream.
--timeout is 1 to 300 seconds (default 60). The command's exit code is
tarout exec's exit code (124 when it timed out). With --json you get one
{ success, data } envelope carrying exitCode, stdout, stderr,
truncated, timedOut, durationMs and ok (exit 0, no timeout), and the
exit code is 0 whenever the
command ran, whatever its own exit code (the same convention as
tarout jobs run); a non-zero exit then means the CLI or platform failed.
Owners and admins only. An operator-tier agent key gets NEEDS_APPROVAL with a
tarout approvals wait <id> next step; once a human approves, the command
runs but its output is not returned. There is no interactive shell in the CLI:
-it prints the link to the dashboard console (Application > Console).
Explain what a deploy would build: tarout build --explain
tarout build --explain
tarout build --explain --app api --wait 300 --jsonAsks Tarout what a deploy would build and whether it would get past the
pre-build checks, without building, deploying or changing anything. It reads
the app's configured source on Tarout (the tracked Git branch, or the last
uploaded archive), not your local files, so push (or upload) first to explain
local changes. Nothing runs locally. The report shows the source
(repo@branch and short commit), detected kind and build type, a summary,
providers and toolchain versions, build steps with their commands, the start
command, the port, and the names of build-time variables (never values),
then warnings and errors.
Exit status: 0 when the plan is ok, 12 (BUILD_FAILED) when a deploy would
fail, and 11 (EXPLAIN_PENDING) when Tarout is still inspecting after --wait
seconds (default 120, max 600; run it again to pick up the same inspection).
--json prints one envelope carrying the platform's full answer. The platform
allows 10 explanations a minute per user. The MCP tool app_explain_build
does the same.
Templates
Deploy a ready-made app (an image, its port, the environment variables it reads, and a managed PostgreSQL database when it needs one) into the active project with one command.
| Command | Description |
|---------|-------------|
| tarout template list | List the templates: code, name, category, and whether it creates a Postgres database |
| tarout template info <code> | Image, port, database, docs link, and every variable: required, secret, generated or defaulted |
| tarout template deploy <code> | Create the app, its database if any, fill the variables, and start the first deployment |
tarout template list
tarout template info <code>
tarout template deploy <code> --env [email protected] --yes --waitdeploy takes --name <name>, a repeatable --env KEY=VALUE, and --wait
(follow the first deployment and stream its logs, like tarout deploy --wait).
--env keys are checked against the template first: an unknown key exits 2
before anything is created. A required variable you leave out is asked for
(masked when secret); with --json or --non-interactive it is a
needs_input event naming the --env flag to add (exit 6). A template that
creates a managed PostgreSQL database asks before it does, since that can add
a charge to your plan; --yes skips the question. Variables the platform
generates are listed by name only and never printed: read one with
tarout env reveal <app> <KEY>. An operator-tier API key may get
NEEDS_APPROVAL (wait with tarout approvals wait <id>), and a plan limit
gets the usual upgrade options. The MCP tools template_list,
template_info and template_deploy do the same.
Databases
| Command | Description |
|---------|-------------|
| tarout db list | List all databases |
| tarout db create [name] | Create a new PostgreSQL database |
| tarout db delete <db> | Delete a database |
| tarout db info <db> | Show connection details |
| tarout db connect <db> | Open database shell |
| tarout db external-access <db> | Turn external access on or off (always TLS) |
# Create a PostgreSQL database (the only engine that can be created)
tarout db create mydb
# Get connection string
tarout db info mydb
# Allow external connections from anywhere (TLS is always required)
tarout db external-access mydb --enable --public
# Connect directly (opens psql)
tarout db connect mydbDomains
External (customer-owned) domains connect through one flow: add-external → DNS records → verify → link to app.
| Command | Description |
|---------|-------------|
| tarout domains list | List domains |
| tarout domains add-external <hostname> | Register an external hostname (creates the edge hostname) |
| tarout domains instructions <hostname> | Show the exact DNS records to create |
| tarout domains verify <hostname> | Check DNS configuration |
| tarout domains wait-verified <hostname> | Poll until verified (--timeout, --interval) |
| tarout domains app link-to-app --domain-id <id> --app-id <id> | Attach the verified domain to an app |
| tarout domains unlink <domain> | Unlink a domain from an application |
# 1. Register the hostname, 2. add the records it prints, 3. verify, 4. attach
tarout domains add-external www.example.com
tarout domains instructions www.example.com
tarout domains wait-verified www.example.com --timeout 1800
tarout domains app link-to-app --domain-id <domain-id> --app-id <app-id>Root domains (example.com) connect only when the domain's DNS is hosted on
Cloudflare: the CLI instructs a root CNAME that must be set to Proxied
(orange cloud). On other DNS providers, connect www.example.com and
redirect the root to it. Hostnames under a Tarout-registered domain use
tarout domains app link-registered instead (domains link is retired and
always rejected by the platform).
Scheduled Tasks (cron)
Two kinds of task: http fires a signed request at your app's own URL, command runs a shell command inside the app's running container (so the app must already be deployed).
| Command | Description |
|---------|-------------|
| tarout jobs list | List scheduled tasks (defaults to the linked app) |
| tarout jobs info <id> | Show a task, including its signing secret |
| tarout jobs create | Create a scheduled task |
| tarout jobs update <id> | Update a scheduled task |
| tarout jobs delete <id> | Delete a task and its run history |
| tarout jobs enable <id> / disable <id> | Pause or resume a task |
| tarout jobs run <id> | Run a task immediately |
| tarout jobs runs <id> | Show recent runs (exit code, duration, output) |
# Ping /cron/hourly on the linked app every hour
tarout jobs create --name hourly --schedule "0 * * * *" --path /cron/hourly
# Run a command inside the container every night at 02:00 Riyadh time
tarout jobs create --name cleanup --type command \
--command "bun run cleanup" --schedule "0 2 * * *" --timezone Asia/Riyadh
# Fire it now and wait for the result (command runs are queued in the background)
tarout jobs run <id> --wait
# Inspect the history
tarout jobs runs <id> --limit 5Command tasks may run for up to 15 minutes, so jobs run only queues them:
without --wait it reports queued and you read the outcome from
tarout jobs runs <id>. HTTP tasks run inline (60s cap) and print their result
immediately. Each HTTP fire carries x-tarout-cron-timestamp and
x-tarout-cron-signature headers - verify them with the task's signing secret
(tarout jobs info <id>).
Approvals
When an operator-tier API key calls a destructive action (deleting an app,
dropping a database), the platform parks it for a human and refuses the call
with NEEDS_APPROVAL:<id>. The action has not failed: a human approves or
denies it in the dashboard (Agent page, https://tarout.sa/dashboard/agent),
and approving runs it. Requests expire after 24 hours.
| Command | Description |
|---------|-------------|
| tarout approvals list | List requests, pending first (--status, --limit) |
| tarout approvals get <id> | Show one request: procedure, requesting key, status, expiry, result or error |
| tarout approvals wait <id> | Wait for the decision (--timeout 30m, --interval 5) |
# The id comes from the NEEDS_APPROVAL error (details.approvalId under --json)
tarout approvals wait pa_123abc --timeout 1h --jsonwait exits 0 only when the action was executed. A denied request exits 5,
an expired one or one that failed when it ran exits 1, and a request still
pending when --timeout passes (or on Ctrl+C) exits 11 with
APPROVAL_PENDING and a nextCommand to resume. There is no approve or deny
command: the platform refuses both to API keys, because an agent must never
approve its own request. The MCP server exposes the same reads as
approvals_list and approvals_get.
Organizations
| Command | Description |
|---------|-------------|
| tarout orgs list | List organizations |
| tarout orgs switch <org> | Switch active organization (local CLI default) |
| tarout orgs activate <org> | Set the active organization server-side |
# Switch organization
tarout orgs switch "Acme Corp"Tarout has no separate "environment" (production/staging) concept - apps are scoped to an organization and a project. Use
tarout projectsto separate workloads.
All commands
The sections above cover the everyday flows. The full command surface (run any
with --help for its subcommands and flags):
| Namespace | Description |
|-----------|-------------|
| tarout login / logout / whoami | Authenticate, sign out, show current context |
| tarout apps | Manage applications |
| tarout deploy / up | Deploy an application (up = inspect + deploy the current folder) |
| tarout logs | View application logs |
| tarout env | Manage application environment variables |
| tarout db | Manage databases |
| tarout domains | Manage domains and DNS |
| tarout storage | Manage cloud storage buckets |
| tarout template | Deploy ready-made apps from templates (list, info, deploy) |
| tarout servers | Manage cloud servers (VMs) |
| tarout backups | Manage database backup configurations |
| tarout destinations | Manage backup storage destinations |
| tarout billing | Manage subscription and billing |
| tarout wallet | Manage AI Gateway wallet balance |
| tarout referral | Your referral code, link and compute-credit earnings (history for per-payment credit) |
| tarout ai | Manage AI Gateway models and API keys |
| tarout monitor | Manage uptime monitors for applications |
| tarout jobs | Manage scheduled tasks (cron) for applications |
| tarout approvals | See and wait on approval requests for agent actions |
| tarout projects | Manage projects within the active organization |
| tarout orgs | Manage and switch the active organization |
| tarout providers | Manage Git providers (GitHub, GitLab) |
| tarout keys | Manage SSH keys for server access |
| tarout firewall | Manage firewall templates for cloud servers |
| tarout tickets | Manage support tickets |
| tarout notifications | Manage notification preferences |
| tarout inbox | Manage in-app notifications |
| tarout link | Link the local directory to a Tarout application |
| tarout dev | Run local dev server with cloud environment variables |
| tarout build | Build locally with cloud environment variables; --explain shows what a deploy would build from the app's configured source |
| tarout run | Run any local command with the app's environment variables (tarout run -- <command>) |
| tarout exec | Run one command inside the app's running container (tarout exec -- <command>) |
| tarout settings | Platform settings and information |
| tarout upgrade | Upgrade the CLI to the latest published version |
| tarout queues | Background job queues (platform operators only) |
| tarout call | Call any platform procedure directly (see above) |
| tarout agent | Set up coding agents: setup (skills + MCP, machine-wide), init (per project), connect; manifest maps the project in one read; sessions lists agent credentials; events shows (or --follows) what agents did |
Global Flags
These flags work with all commands:
| Flag | Description |
|------|-------------|
| --json | Output as JSON (machine-readable) |
| --yes, -y | Skip all confirmation prompts |
| --quiet, -q | Minimal output (errors only) |
| --verbose, -v | Extra debug information |
| --no-color | Disable colored output |
| --no-update-check | Skip the automatic CLI self-update for this command |
Self-update on every command
Before running any command, the CLI checks npm for a newer @tarout/cli; when
one exists it installs it globally and re-runs your exact command on the new
version - so the CLI (and any agent driving it) always runs the latest, with no
action needed. The network check is throttled to at most once every 3 hours
(so ordinary commands stay fast - the throttle window is a single local read);
tarout up / tarout deploy force an immediate check so a deploy is never on a
stale CLI. It fails open (offline or npm errors just continue on the current
version). Opt out per-invocation with --no-update-check or permanently with
TAROUT_NO_UPDATE_CHECK=1; tune the throttle with
TAROUT_UPDATE_CHECK_INTERVAL_SECONDS (set 0 to check on every command).
Under --json the update is announced as a
{ "type": "event", "event": "cli_update" } line on stderr.
To bypass the throttle and request an upgrade explicitly, run:
tarout upgradeThis command does not require authentication or a linked project. In JSON mode
it returns upgraded or up_to_date; registry and installation failures return
a structured error and a non-zero exit code.
AI & Automation Usage
The CLI is designed to be 100% AI-friendly and scriptable:
# Get JSON output for parsing
tarout apps list --json
# Non-interactive operations (no prompts)
tarout apps delete my-app --yes
# Quiet mode for scripts
tarout deploy my-app --quiet
# Pipe-friendly
APP_ID=$(tarout apps list --json | jq -r '.[0].id')Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments |
| 3 | Authentication error (not logged in) |
| 4 | Resource not found |
| 5 | Permission denied |
| 6 | Needs input - see needs_input event below |
| 11 | Still running or pending when the wait ended, not failed: resume with the envelope's resumeCommand (deploy) or nextCommand (approvals, build --explain) |
| 12 | Build failed: the local tarout build command failed, or tarout build --explain found errors that would stop a deploy |
JSON Output Format
All --json output follows a consistent structure:
// Success
{ "success": true, "data": { ... } }
// Error
{ "success": false, "error": { "code": "NOT_FOUND", "message": "..." } }
// List operations
{ "success": true, "data": [...], "meta": { "total": 10 } }Agent Input Relay (needs_input)
When tarout up --json hits a choice point and the value wasn't passed as a
flag, it emits a single needs_input line on stdout and exits with code 6
instead of crashing or silently defaulting. The external agent reads that
event, asks the human user in its chat UI, then re-invokes tarout up with
the same arguments plus the new flag.
{
"type": "needs_input",
"field": "name",
"kind": "input",
"question": "Application name:",
"default": "my-project",
"flag": "--name",
"sensitive": false,
"context": { "step": "app_name", "defaultName": "my-project" }
}Fields:
field- stable id (e.g.name,region,token,source).kind-"input" | "select" | "confirm" | "password".question- show verbatim to the user.choices- present whenkind: "select".default- pre-fill suggestion.flag- the CLI flag to pass on the next invocation.sensitive-truefor tokens / passwords (mask in UI, omit from logs).context- free-form metadata the agent can use to phrase a richer prompt.
Loop pattern for an agent:
1. invoke `tarout up --json [flags]`
2. read stdout line-by-line
3. if line.type === "needs_input":
- ask the user for `question`
- re-invoke with the same flags + `${flag} ${answer}`
4. otherwise treat the final JSON envelope as success / errorFlags currently supported by tarout up for skipping the relay:
--token, --name, --plan, --source, --repo, --branch, --region, --yes.
Configuration
Credentials are per project. tarout login writes ./.tarout/auth.json,
and the CLI finds it by walking up from the working directory - so it works
from any subfolder, and connecting a key in one project never re-points another
at a different account.
your-project/
.tarout/
auth.json # the credential: mode 0600, in a 0700 directory (git-ignored by default)
project.json # which Tarout app this directory deploys to (never committed)
config.json # your deploy contract: COMMIT THIS
.gitignore # written automatically: ignores everything but itself and config.json,
# and tells whoever clones the repo how to sign inauth.json and project.json are git-ignored on creation and always excluded
from deploy archives. You normally never edit those two by hand.
Sharing one login through git
By default every person who clones the repo signs in for themselves with
tarout login. If you would rather the whole team (or a private CI runner)
share one login that comes with the clone, commit the credential:
# Neither a dashboard key nor a browser-login token expires.
tarout login --token <key> --commit-token # key from https://tarout.sa/dashboard/agent/keys
git add .tarout/.gitignore .tarout/auth.json
# Already signed in? The flag works on its own, no new sign-in:
tarout login --commit-tokenThis adds !auth.json to .tarout/.gitignore. Only do it in a private
repository: anyone who can read the repo can deploy and manage resources as the
key's account, and so can anything that builds from it (a Git-connected app
build included). The CLI warns if a .gitignore higher up still hides
.tarout/, which would keep the file out of git anyway.
To go back, run tarout login --no-commit-token, then
git rm --cached .tarout/auth.json. A key that was ever pushed stays in the git
history, so revoke it in the dashboard as well.
.tarout/config.json - the deploy contract
Everything else Tarout needs used to be inferred: which database to provision
came from scanning your dependencies, and "working" meant / returned 200.
Heuristics are a good default and a bad contract - they cannot be reviewed, and
they can differ between the laptop that ran tarout up and the CI that runs it
next. This file is the declaration, and it is committed so it travels with the
repo.
{
// Container readiness probe (loopback, inside the container).
"healthCheck": { "path": "/", "expectedStatus": 200 },
// Routes fetched on your PUBLIC url after the container is healthy.
// If one of these does not serve, the deployment is NOT promoted.
"smokePaths": ["/", "/ar"],
// Runs once inside the new container, after it starts and before the
// deployment goes live. Where migrations belong.
"releaseCommand": "bunx prisma migrate deploy",
"releaseCommandTimeoutSec": 300,
// Overrides the framework detector.
"build": { "buildCommand": "bun run build" },
// What to provision. Beats dependency scanning in BOTH directions:
// "postgres": false means don't, even if you depend on `pg`.
"resources": { "postgres": true, "storage": false },
// Created once if missing, never rotated.
"generatedSecrets": ["SESSION_SECRET"]
}Precedence is manifest > what is already set on the app > detection. Every key is optional; an absent file means absent, not empty.
A malformed manifest fails the command and names the offending field, rather than falling back to detection - a contract that silently doesn't apply would deploy something other than what the file says.
Why smokePaths matters
The container health probe runs on the container's own loopback and asks "is a
process listening". That is not the same question as "does the app work": an app
whose / returns 200 while every real route 500s passes it. Listing the routes
that must actually work turns that into a gate - and until you list one, Tarout
only warns, because it cannot know which of your routes are supposed to serve.
Why releaseCommand matters
Without it, prisma migrate deploy has nowhere to run except your start
command, where it re-runs on every container restart and every replica, races
itself, and cannot fail a deployment. A release command runs exactly once per
deployment, and a non-zero exit stops the rollout with the current version still
serving.
Resolution order
| # | Layer | Set by |
|---|-------|--------|
| 1 | .tarout/auth.json at or above the cwd | tarout login (default) |
| 2 | Machine-wide profile | tarout login --global |
| 3 | TAROUT_TOKEN environment variable | your shell - lowest precedence, and ignored entirely whenever 1 or 2 exists |
tarout whoami --json reports which one is in effect as scope
(project / global / env / none) plus the credentialPath. Pass
--global-auth on any command to skip layer 1 for that invocation.
Running tarout login somewhere that is not a project (no .tarout, .git, or
package manifest above it) falls back to the machine-wide profile and tells you
so, rather than scattering a .tarout/ folder.
The machine-wide profile
Stored in the OS-standard config directory (via the conf package), not
~/.tarout/:
- macOS:
~/Library/Preferences/tarout-nodejs/config.json - Linux:
~/.config/tarout-nodejs/config.json(or$XDG_CONFIG_HOME) - Windows:
%APPDATA%\tarout-nodejs\Config\config.json
Both layers hold the same shape:
{
"currentProfile": "default",
"profiles": {
"default": {
"token": "cli_xxx...",
"apiUrl": "https://tarout.sa",
"organizationId": "...",
"organizationName": "My Org",
"projectId": "...",
"projectName": "My Project",
"projectSlug": "my-project",
"userId": "...",
"userEmail": "[email protected]"
}
}
}Requirements
- Node.js 18.0.0 or higher
- A Tarout account (sign up)
Support
- Documentation: tarout.sa/docs
- Issues: GitHub Issues
- Discord: Join our community
License
MIT - see LICENSE for details.
