CLI
martha is the command-line surface for the platform — manage agents, functions, workflows, documents, tasks, integrations, and more. It's also the surface external agent runtimes (Claude Code, CrewAI, Pydantic AI, ork) operate the platform through.
Every command supports JSON output (--json) and predictable exit codes. The full command list shows up under martha --help; this page is the operational reference grouped by what you're trying to do.
Install
The CLI is published on npm as @aiaiai-pt/martha-cli.
# Run on demand (no install)
npx -y --package=@aiaiai-pt/martha-cli@latest martha --help
# Install globally
npm i -g @aiaiai-pt/martha-cli
martha --versionPin a version when calling from CI or agent runtimes:
npx -y --package=@aiaiai-pt/[email protected] martha ...Requires Node.js 22+.
Source build (contributors only)
git clone https://github.com/westeuropeco/martha
cd martha/martha-cli
bun install && bun run build
node dist/index.js --helpSet up
martha init — first-run wizard
Install does not write global configuration. martha init writes an explicit profile to ~/.martha/config.yaml so you don't have to learn the YAML schema. Two presets:
martha init # interactive, defaults to local
martha init --preset local # http://localhost:8080 + 8180
martha init --preset cloud # martha.nomadriver.co + keycloak.frank.nomadriver.co
martha init --no-interactive --name acme \
--api-url https://martha.acme.example \
--keycloak-url https://auth.acme.example \
--keycloak-realm acme # scripted customer profileFor customer, staging, or private-cloud deployments, prefer the scripted form with the exact URLs from your tenant admin. The hosted cloud preset is explicit and uses the verified Frank Keycloak issuer at keycloak.frank.nomadriver.co.
Bootstrap a grant-bearing principal
By default a fresh caller resolves to the tenant's shared default client, which has no grants — every function and workflow call is denied until someone hand-grants it. martha init closes that gap: after writing the profile it can provision a dedicated, intent-scoped principal so subsequent calls just work. Run interactively and it asks; or drive it with flags:
# Your own machine — adopt-or-create *your* client and grant the rag read set
martha init --bootstrap --mode human --intent rag --yes
# An integration / CI — mint a Keycloak service account (admin only)
martha init --bootstrap --mode integration --intent rag --label ci-rag --yes
# Grant several buckets at once, or name specific workflows / functions
martha init --bootstrap --mode human --intent workflows --workflow nightly_report --yes
martha init --bootstrap --mode human --intent custom --function recall --function wiki_search --yes| Flag | Meaning |
|---|---|
--bootstrap | Provision a principal after saving the profile (offered automatically when interactive). |
--mode human|integration | human = adopt-or-create your own client. integration = mint a service account + print MARTHA_CLIENT_ID/SECRET (requires the admin role). |
--intent rag|documents|workflows|custom | Usage bucket(s); repeatable / comma-separated. Determines the grant set. |
--workflow <name> | Workflow(s) to grant (with workflows or custom). Repeatable. |
--function <name> | Extra function(s) to grant (with custom). Repeatable. |
--label <name> | Integration mode: label for the service-account client id (martha-cli-<label>). |
--yes | Required to bootstrap non-interactively (CI safety). |
Intent → starter grant set (all read-only, least-privilege; the named set is granted to the client):
| Intent | Functions granted |
|---|---|
rag | query_collection, search_docs, read_doc, list_docs, list_collections, visual_search |
documents | list_collections, list_docs, read_doc, download_document, get_document_url |
workflows | the named workflow(s) + the rag read set (a workflow's function nodes run as the client, so they need the read grants too) |
custom | exactly the --function / --workflow names you pass |
The bootstrap is deterministic (no LLM), idempotent (re-running adopts the existing principal — it never duplicates), and reports exactly what it granted (granted X, Y to client Z). Unknown names are skipped and reported, not fatal.
For the human mode the resolved client is saved to your profile as default_client, and martha workflows execute automatically runs as it (passing it as the run-as principal) so the grants apply — without this you'd silently fall back to the grant-less tenant default. For integration mode nothing is written to your profile; the printed credentials are for your CI environment, which authenticates with martha auth login --service-account and resolves to the same client via its token.
Chat copilot (--agent)
On top of the run-as bootstrap you can provision a personal chat copilot — an agent you talk to with martha chat that holds your granted tools and calls them for you. Unlike the run-as client (whose grants fire on workflow nodes), the copilot's tools are granted to the agent, the one surface where agent grants take effect in chat.
# Provision your client + grants AND a copilot, in one step
martha init --bootstrap --mode human --intent rag --agent --yes
# Name it / pick a model (model defaults to the platform default — Sonnet)
martha init --bootstrap --intent documents --agent --agent-name research-bot --model claude-sonnet-4-6 --yesThis creates a cloud agent (no extra credentials), links it to your bootstrapped client, grants it the intent's tools, and writes default_agent to your profile. martha chat then uses it automatically:
martha chat "what's in my compliance collection?" # uses the default copilot
martha chat --agent research-bot "summarize the latest revision"martha chat (no flags) runs as your bootstrapped client + copilot — so its granted tools are available out of the box, no extra setup.
Task operator — grant the tasks intent and the copilot can create and track Martha tasks that run on the task pipeline:
martha init --bootstrap --intent rag,tasks --agent --yes
martha chat "create a task to audit the Q3 proposal against the spec"The copilot's task tools (create_task / list_tasks / get_task) are scoped to its own tasks (least-privilege); a created task executes as the copilot agent. Onboarding broader external tools (MCP) and self-provisioning are planned follow-ups — see issue #540.
martha doctor — diagnostic
Checks API reachability, Keycloak reachability, token validity, and CLI/API version skew. Run this first when something feels off.
martha doctorExits non-zero only on hard failures. Soft warnings (no token stored, etc.) don't fail the check.
martha skill — print agent skill bundle
Prints the bundled SKILL.md reference to stdout for agent runtimes. Pipe a head into the agent's prompt context:
npx -y --package=@aiaiai-pt/martha-cli@latest martha skill | head -200martha status — quick health snapshot
martha statusProfile name, API URL, auth state, and a live API health ping in one place.
martha update — self-update the CLI
martha update # update to the latest published version (confirms first)
martha update --check # report if a newer version exists; exit non-zero if behind
martha update --yes # skip the prompt (CI / scripts)
martha update --manager bun # force npm | bun (default: auto-detect)Checks npmjs.org for the latest @aiaiai-pt/martha-cli and reinstalls globally. Public package — no auth needed.
Authenticate
# Browser PKCE flow (humans, local dev)
martha auth login
# Headless password grant (scripted human login)
martha auth login --username [email protected] --password '...'
# Service account (CI / agent runtimes)
export MARTHA_CLIENT_ID="martha-tenant-acme-client"
export MARTHA_CLIENT_SECRET="..."
martha auth login --service-account
# Bypass auth entirely with a pre-issued token
export MARTHA_TOKEN="eyJhbGc..."
# Inspect, get, or clear
martha auth status
martha auth token # raw JWT to stdout (for piping into curl)
martha auth logoutService-account tokens auto-refresh ~30 s before expiry. Human tokens expire after ~5 min and require re-login (or the MARTHA_TOKEN env var for long-running scripts).
Definitions: agents, functions, workflows
The three definition types share the same CRUD surface.
CRUD
# List
martha agents list
martha functions list --inactive
martha workflows list --json
# Get details
martha agents get support-bot
martha functions get search_documents
# Create from a YAML/JSON file
martha agents create -f support-bot.yaml
# Update
martha functions update search_documents -f search.yaml
# Delete (soft by default; --hard for permanent)
martha agents delete old-bot
martha workflows delete old-pipeline --hard --yes
# Version history + rollback
martha functions versions search_documents
martha functions rollback search_documents 2Declarative apply
definitions apply reads YAML/JSON files, compares against remote state, and creates or updates. It never deletes — making it safe to run from CI on every push.
martha definitions apply -f support-bot.yaml
martha definitions apply -f definitions/ # whole directory
martha definitions apply -f all.yaml # multi-doc YAML
martha definitions apply -f definitions/ --dry-run # preview only
martha definitions apply -f definitions/ --yes --json # CI modeFile format
Each definition needs kind and name. Other fields are kind-specific.
kind: Function
name: search_documents
description: Search across document collections
endpoint: https://api.example.com/search
http_method: POST
parameters:
- { name: query, type: string, required: true }
---
kind: Agent
name: support-bot
description: Customer support agent
system_prompt: You are a helpful assistant.
llm_config:
provider: anthropic
model: claude-sonnet-4-6
loop_config:
max_iterations: 10Valid kinds: Function, Workflow, Agent.
For agents:
system_promptis the durable instruction used by the agent loop.- Grant an agent to a chat client with
martha clients grant <client> agent <agent>to make the agent callable as a tool from that client. runs_as_chatis an advanced top-level chat-takeover override, not normal provisioning. New customer onboarding should not use it unless intentionally replacing the chat client's own prompt/model/tools with exactly one agent.loop_config.enabledis legacy compatibility for that takeover path. New YAML should not use it.- Workflow
llmnodes still useconfig.promptfor a one-step user/task prompt; that is separate from an agent'ssystem_prompt.
Behavior
| Local vs remote | Action |
|---|---|
| Doesn't exist remotely | Create |
| Content differs | Update (new version) |
| Content matches | Skip |
| Exists remotely but not locally | Untouched (never deletes) |
Agent self-provisioning
Let an agent request its own capabilities (a human approves). Off by default; configure the per-agent policy here, approve requests via Approvals. See Agent self-provisioning for the full model.
# Enable + bound what's requestable (allow-list is fail-closed)
martha agents self-grant support-bot --enable --scope read_only \
--allow function:read_document --allow mcp:acme/research
# Add a collection data-scope; inspect current policy
martha agents self-grant support-bot --collection-root <collection-id>
martha agents self-grant support-bot --jsonExport
martha definitions export -o ./definitions/
martha definitions export --kind Agent -o ./agents/
martha definitions export --json --kind Function > functions.jsonWorkflow execution
martha workflows execute my-pipeline --inputs '{"key": "value"}'
martha workflows execute my-pipeline --follow # stream output
martha workflows execution <execution-id>
martha workflows execution <execution-id> --follow
martha workflows executions --status running
martha workflows cancel <execution-id>
martha workflows inputs my-pipeline # show expected schemaWorkflow introspection
martha workflows nodes my-pipeline # list nodes + connections
martha workflows node-types # all available types
martha workflows node-type llm # config schema for a typeChat
# Interactive chat REPL
martha chat
martha chat --client my-bot
# Drive a specific copilot agent (defaults to profile.default_agent)
martha chat --agent research-bot
# One-shot
martha chat --message "Summarise my open tasks."
martha chat --client my-bot --message "Hello" --jsonIf you provisioned a copilot (martha init --agent), martha chat with no flags runs as your bootstrapped client and that copilot — its granted tools work out of the box. Otherwise, without --client the tenant's default client is used; --agent <name> selects a specific copilot from the client's agents.
Sessions
martha sessions list # recent chat sessions
martha sessions list --client my-bot --status active
martha sessions get <session-id> # session detail + messages
martha sessions messages <session-id> --limit 50 # message history
martha sessions delete <session-id> --yesDocuments
martha documents collections # list collections
martha documents collections --tree # ASCII hierarchy view
martha documents create-collection --name kb --description "..."
martha documents create-collection --name q1-2026 --parent reports # nest under a parent (UUID, slug, or name)
martha documents move-collection q1-2026 --parent archives # reorganize the tree
martha documents move-collection 2026-reports --root # back to top-level
martha documents list --collection kb
martha documents upload ./report.pdf --collection kb
martha documents upload ./policies/ --collection kb --recursive # whole folder
martha documents get <document-id>
martha documents revisions <document-id> # version history
martha documents reingest <document-id> # rerun the pipeline (one doc)
martha documents retry --collection kb --status error --yes # re-drive ALL failed docs in a collection (+subtree)
martha documents delete <document-id> --yesdocuments retry is bounded and idempotent: it flips every failed document in the collection subtree back to pending and lets the ingestion reconciler re-drive them at a controlled rate (no thundering herd, safe on thousands of docs). Non-interactive use requires --yes. Pass --status pending to re-drive stuck-pending docs instead. See Document ingestion → Retrying failed documents.
Collections form a hierarchy via parent_collection_id (#372). Subtree listing is the default for collection-aware tools; permission grants cascade down (most-specific ancestor wins — see grant flags below). Cycles, cross-tenant parents, and hard-deletes of parents with children are rejected server-side.
Uploads kick off the parse → chunk → embed pipeline automatically. See Document ingestion for what each stage does.
Document sync sources
Durable connectors that keep a collection in sync with an external store (Google Drive, an S3-compatible bucket). See R2 / folder sync.
martha document-sync sources list # list configured sources
martha document-sync sources create --provider s3_compatible_folder \
--name customer-bucket --collection kb --connection <conn-id> \
--profile custom_s3 --bucket their-bucket --endpoint-url https://… --mode polling
martha document-sync sources run <source-id> # one-off sync
martha document-sync sources reconcile <source-id> # full diff (ingest new/changed, soft-delete gone)
martha document-sync sources status <source-id> # ingestion progress: counts, drain rate, ETA, errors
martha document-sync sources status <source-id> --window-minutes 60
martha document-sync sources retry <source-id> --status error --yes # re-drive this source's failed docs (herd-free)sources status reports the same aggregate ingestion picture as the admin source panel — counts by status, recent drain rate, a naive ETA, and the top error reasons. Add --json (global flag) for machine-readable output.
Wiki
The wiki is the tenant-level compiled knowledge base distilled from your documents.
martha wiki list # list wiki pages
martha wiki get topics/installation/chimney # show one page (markdown)
martha wiki search "antenna mounting" # full-text + semantic search
martha wiki settings get # current caps + policies
martha wiki settings set --max-pages-per-source 5
martha wiki schema get # tenant-defined schema markdown
martha wiki schema set -f schema.md # update schema
martha wiki recompile # re-run compile from current docsTasks
Async work queue for agents to claim and complete.
martha tasks list # all open tasks
martha tasks list --status open --priority high
martha tasks list --tracker linear # filter by tracker
martha tasks create --goal "Investigate latency regression" --priority high
martha tasks get <task-id>
martha tasks claim <task-id> # mark claimed
martha tasks heartbeat <task-id> # stay alive
martha tasks complete <task-id> --result "Root cause: …"
martha tasks fail <task-id> --error "Couldn't reproduce"
martha tasks cancel <task-id>Tasks can carry an external_ref linking them to Linear/GitHub/GitLab issues for bidirectional sync.
Teams
martha teams list
martha teams create --name code-review --strategy round_robin
martha teams add-agent code-review reviewer-1
martha teams add-agent code-review reviewer-2
martha teams remove-agent code-review reviewer-1
martha teams delete code-review --yesStrategies: round_robin, manual, external.
Approvals
martha approvals list # all pending
martha approvals list --status pending --requester support-bot
martha approvals get <approval-id>
martha approvals approve <approval-id> --reason "Verified with customer"
martha approvals reject <approval-id> --reason "Out of scope"Models
martha models list # available models
martha models list --provider anthropic
martha models default # current default
martha models default --set claude-sonnet-4-6 # set tenant defaultClients
A Client is a consumer of your agents (chat web app, SMS sender, voice line, embedded widget). Each has its own credentials and per-feature allowlists.
martha clients list
martha clients create --name my-bot
martha clients update my-bot --default # set as tenant default
martha clients grant my-bot function search_documents
martha clients grant my-bot function list_docs --collection reports # scope to /Reports + descendants (#372 PR2)
martha clients grant my-bot workflow data_pipeline
martha clients grant my-bot agent support-bot
martha clients revoke my-bot function search_documents
martha clients revoke my-bot function list_docs --collection reports # revoke just that scope
martha clients access my-bot # show all grants
martha clients delete my-bot --yes--collection is only valid for function grants (workflow and agent grants don't have a collection scope; passing it is a clean Validation error). Omit the flag to grant tenant-wide ("root grant", collection_id IS NULL); pass it on revoke to remove just that scope without touching others. Same shape works for martha agents add-function|remove-function --collection <slug-or-id>.
Embed key management
For embeddable chat clients, manage the credential the front-end uses to mint short-lived tokens server-side:
martha clients embed urls # current hosted widget URLs
martha clients embed key rotate my-bot # rotate the embed token signing key
martha clients embed key clear my-bot # disable embed for this clientMCP tools
Connect external MCP-server tools to your copilot. mcp add is one command: it creates-or-adopts the connection, discovers its tools, and grants access — no UUID juggling.
# Add a known server BY NAME from the catalog — no URL, no credential, no
# --auth-type (the catalog knows the auth mode; OAuth servers drive consent).
martha mcp catalog # browse known servers
martha mcp add github --agent copilot # resolves URL + OAuth from the catalog
# Adopt an existing connection (by id or unique name) and grant it
martha mcp add <connection-id> --agent copilot
# Add a custom (non-catalog) server from a direct URL — secret prompted in a TTY,
# or pass --credential-value with '-' for stdin / '@path' for a file
martha mcp add --url https://mcp.example.com/sse --name acme --agent copilot
# Custom OAuth server: drives browser consent from the terminal — opens the
# consent page, waits for approval, then grants. No credential to paste; tokens
# are stored in Vault and auto-refresh on expiry. (Catalog OAuth servers above
# need no --auth-type; this is for servers not in the catalog.)
martha mcp add --url https://mcp.acme.test/sse --name acme --auth-type oauth2 --agent copilot
# Headless / SSH / CI: print the consent URL instead of auto-opening, then poll
martha mcp add --url https://mcp.acme.test/sse --name acme --auth-type oauth2 --no-browser --agent copilot
# Narrow which tools the agent sees
martha mcp add github --agent copilot --tools search_issues,create_issue
# Also grant the client/workflow surface (so workflow mcp_client nodes can use it)
martha mcp add github --agent copilot --client 7
# Inspect, disable, revoke
martha mcp ls --agent copilot # what does my copilot have
martha mcp grant <conn> --agent copilot # raw grant
martha mcp disable <conn> --agent copilot # keep the row, enabled=false
martha mcp revoke <conn> --agent copilot
# Low-level (probe a server directly)
martha mcp discover --connection <id>
martha mcp call <tool> --connection <id> --input '{}'--agent defaults to profile.default_agent. Two surfaces (see ADR 0002): an agent grant fires in chat; a client grant fires on workflow mcp_client nodes (which run as the triggering client). Credentials resolve server-side and never appear in the grant.
For --auth-type oauth2, mcp add drives the full OAuth dance: it creates the connection, opens the consent page (the provider redirects back to Martha's server callback, which stores the tokens in Vault), polls until the connection is authorized, then discovers and grants. The token never touches the CLI, your shell history, or --json output. In a non-TTY without --no-browser the command fails fast with the consent URL rather than hanging — open the URL and re-run to adopt, or pre-create the connection in the admin UI.
Integrations
# Overview
martha integrations list # core / plugin / connected / custom
martha integrations specs # OpenAPI specs in the tenant
# Import an external API
martha integrations import-openapi \
--source https://api.example.com/openapi.json \
--name my-api
martha integrations sync <spec-id> # re-sync a previously imported spec
# Plugins (first-party services with manifests)
martha integrations plugins
martha integrations plugin martha-scoring # full detail
martha integrations proxy martha-scoring GET /health
martha integrations proxy martha-scoring POST /score --data '{"item_id":"123"}'
martha integrations proxy martha-scoring GET /rubrics --query "limit=10"The proxy injects tenant context automatically — no auth wiring required.
Notifications
Pre-configured notification channels (email, Slack, HTTP webhooks) callable from workflow nodes.
martha notifications channels # list available channels
martha notifications connections list # tenant connections
martha notifications connections create \
--provider slack \
--name eng-alerts \
--config '{"webhook_url":"https://hooks.slack.com/..."}'
martha notifications connections delete eng-alerts --yes
martha notifications send --channel email --to "..." --subject "..." --body "..."See Notification channels for full schema and routing.
Messaging
Manage messaging-channel adapters (SMS / WhatsApp via your provider).
martha messaging providers # configured providers
martha messaging numbers # active numbers
martha messaging numbers add --number +351... --provider infobip
martha messaging numbers test +351... --message "Test"Configuration
martha config init # initial setup wizard
martha config show # current config
martha config set api_url https://api.example.com
martha config profiles # list profiles
martha config use staging # switch active profileGlobal flags
| Flag | What it does |
|---|---|
--profile <name> | Use a named profile from ~/.martha/config.yaml |
--json | Machine-readable JSON on stdout. Always set when piping to jq or parsing in scripts. |
--api-url <url> | Override the API URL for this invocation |
--verbose | DEBUG-level logging on stderr (HTTP requests, retry attempts, token refresh) |
--quiet | Suppress informational output. Errors still print. |
--no-color | Disable colored output |
--yes | Skip confirmation prompts. Required in non-TTY contexts (CI, agents) — the CLI does not silently cancel without it. |
--version | Print the CLI version |
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error |
| 2 | Auth failure (token missing, expired, or rejected) |
| 3 | Not found (404) |
| 4 | Validation error (400 / 422) |
| 5 | Conflict (409) |
More
- Agent integration — using the CLI from external LLM-agent harnesses
- Composable workflows — what to put inside a workflow YAML
- OpenAPI integrations — wrapping external APIs as agent tools