plori CLI
Install the CLI, complete browser login, run an agent, attach to a live session from the terminal, use JSON output in scripts, and keep API-key auth for CI.
Plori runs AI agents in cloud Workspaces. The plori CLI creates and runs them from a terminal or a script. It uses the same account, agents, Workspaces and files as the web app and the MCP server.
Install and sign in
curl -fsSL https://plori.ai/install.sh | sh
plori login
The installer puts one static binary in ~/.local/bin. It needs no sudo and no Node,
and it checks the download against the sha512 the npm registry publishes for that build.
Re-run it to upgrade. It leaves your shell config alone, so if ~/.local/bin is not on
your PATH yet, run the line the script prints (on macOS zsh,
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && exec zsh) before
plori login. On Windows, irm https://plori.ai/install.ps1 | iex installs to
%LOCALAPPDATA%\plori\bin and adds it to your user PATH for you; open a new terminal
afterwards. If you would rather go through npm, npm i -g @plori/cli installs the same
binary.
plori login opens the email-OTP page in your browser. A new email creates an account;
an existing email signs back in. The CLI returns once the browser completes the
sign-in.
The browser flow requests the openid offline_access mcp scopes and writes the
credentials to ~/.config/plori/config.json (or $XDG_CONFIG_HOME/plori/config.json),
file mode 0600. Stored fields: the public OAuth client id, a short-lived access token,
a rotating refresh token, their expiry, and the granted scopes. The CLI refreshes the
credentials automatically. plori logout clears the stored credentials.
For CI or headless environments, create an API key at Dashboard → Settings → API keys:
plori login --key - < /path/to/api-key
# or, without writing a config file
export PLORI_API_KEY=plori_sk_…
With PLORI_API_KEY set, plori login stores that key.
Secrets can come from environment variables or stdin:
| Flag | Environment variable |
|---|---|
--api-key and login --key |
PLORI_API_KEY |
--tenant-key |
PLORI_TENANT_KEY |
--callback-secret |
PLORI_WORKER_CALLBACK_SECRET |
Use - as the flag value to read a secret from stdin. Only one secret can use
stdin per command. Use an environment variable if the command also reads input
from stdin. Literal secret values in these flags produce a warning on stderr
because process listings and shell history can expose them.
The CLI sends credentials over HTTPS. HTTP is allowed for loopback hosts,
including localhost, *.localhost and loopback IP addresses.
Stored credentials are bound to the base URL used at login. To use another server,
run plori login --base-url URL for that server. Older config files use their
configured default as the credential's server.
Which sign-in can approve actions?
Both sign-in methods give the CLI the authority of the account owner:
- Browser login (
plori login) stores an OAuth session. - API key (
plori login --keyorPLORI_API_KEY) uses a key that you create in the web app.
With either sign-in, the CLI can do these things in the runs that you start:
- approve an action
- give a saved approval (
--always-allow) - answer a secret question
To answer with a secret from stdin, use plori input answer <run-id> <tool-call-id> --value -.
Saved approvals apply to these runs. With either sign-in, plori workflows run
can also run a workflow that is not active, and plori schedules create
activates a schedule immediately.
A run that an MCP client started has stricter rules. In that run, the CLI can deny an approval request or answer a question. It cannot approve an action or give a saved approval. Approve the action in the web app. The refusal for an approval includes the web address to open.
First Workspace and first run
plori workspace create mate
plori workspace run mate "Research this topic and save the result to work/report.md"
plori workspace create mate creates a Workspace and its agent. plori workspace run mate
sends that agent a task on the Workspace files. Use a Workspace name or its ID.
If names are duplicated, use the ID from plori workspace list.
Creation prints an idempotency key on stderr. Reuse it with the same request to retry
without creating another Workspace. workspace run waits for the reply by default.
The creation response includes default_manager_agent_id. Use it as AGENT_ID
for commands that take an agent, such as runs status and conversation attach.
The Workspace name is not an agent name.
For longer work:
plori workspace run mate "Run the full test suite" --no-wait
plori runs status "$AGENT_ID" <run-id> --wait
plori workspace run mate "Build it and stream progress" --follow
Long-running work
plori account watch streams every run's ending, or its pause for an approval or a question,
as one JSON line, until you stop it. Start a run in the background, then watch it end instead of
polling:
plori workspace run mate "Run the full test suite" --no-wait
plori account watch --agent "$AGENT_ID"
plori account inbox gives the same information without a long-running process: the runs
that ended since your last acknowledgement, plus anything paused on you.
plori workspace run --jsonl replaces the rendered output with a streamed,
machine-readable event log, and plori runs status --wait accepts --wait-seconds
to bound how long it blocks.
Exit codes
plori workspace run, plori runs status --wait, and plori account watch report a run's outcome as
an exit code.
| code | meaning |
|---|---|
| 0 | succeeded |
| 1 | API or runtime failure |
| 2 | usage error |
| 3 | missing, rejected, or expired credentials |
| 4 | could not reach the API server |
| 10 | the run is waiting for an approval or a question |
| 20 | the run ended in error |
| 30 | the run was cancelled |
| 40 | a wait ran out with the run still going |
Attach to a live conversation
plori conversation attach "$AGENT_ID"
attach opens the agent's current conversation in your terminal: recent history, a prompt,
and output streaming as it arrives. It is the same conversation
the web app shows, so a turn sent from the terminal appears in an open browser tab live.
The argument is an agent name, an agent id, or a conversation id, so a conversation id copied out
of the web app works on its own. --session <id> picks a specific conversation of an agent,
--history <n> replays a different number of turns (20 by default, 0 for none, all
for everything one request can carry, which stops at the newest 20,000 stored events),
--read-only tails without a prompt, and --verbose also prints reasoning and tool
results.
While attached, a line of text sends a turn, or steers the turn already running.
Ctrl-C cancels that turn and a second press within two seconds leaves. Ctrl-D on an
empty line, or /exit, detaches without stopping anything: the run keeps going on the
server and you can attach again later. The slash commands are /sessions (list this
agent's conversations), /new (start one), /model <slug> (change the model), /web (print
this conversation's web address), /exit, and /help.
Approval requests take a single key: y or n, or a to say yes and stop being
asked for that tool when the request carries a consent tool. A request for a value takes
a line. In a run that an MCP client started, the server refuses y and a, because the
owner approves the actions of that run in the web app. The refusal for y includes the web
address of the request. See Which sign-in can approve actions?.
Attaching gives the agent nothing from your machine. The shell and the Workspace files are in the agent's own cloud environment, so nothing local is uploaded and nothing the agent writes appears in the directory you ran the command from.
Secret requests are read without echo and answered over the same connection, so a secret you type never appears on screen or in your shell history. If the terminal cannot hide the typing, attach refuses the secret and prints the web address to answer it at instead.
Scripts and automation
Every command accepts --json. Piping stdout switches to JSON automatically, so
plori workspace list | jq . works without the flag. Progress and errors stay on stderr.
plori conversation attach is the exception. It is a live stream rather than one result, so it always
writes plain text, and it needs a terminal on both ends to show a prompt: redirect stdin
or stdout and it becomes the same read-only tail --read-only asks for. That is why
plori conversation attach "$AGENT_ID" | tee session.log records a clean transcript instead of a prompt
nobody can answer.
plori workspace list --json
plori account balance --json
plori workflow list --json
Schedules take a Workspace name or UUID. plori workflow create requires --workspace.
plori schedules create mate "Write a report" --in 3600
plori schedules list mate
plori schedules cancel mate <schedule-id>
plori workflow list --workspace mate
plori workflow create digest --workspace mate
plori workspace run mate "Build the steps for workflow digest"
plori workflow run digest --workspace mate
plori workflow execution digest <exec-id> --workspace mate
Use a schedule for one future agent turn and a workflow for a recurring automation.
Run plori help for the complete command list.
Workspace coordination
Use these commands to run workers in a Workspace and merge their changes. A worker is an agent that works on its own copy of the Workspace files. Workspace commands and worker start/list/status/stop accept a unique Workspace name or ID. Advanced storage commands take the exact IDs that earlier commands return.
| Command group | Operations |
|---|---|
workspace |
Create, list, get or update a Workspace. Manage members, costs and deletion. |
advanced independent-agent |
Create an agent that has no files of its own, to add to Workspaces. List the agents of the account. |
advanced copy |
List, get or delete copies of the files. clone makes a writable copy from a saved version. checkpoint saves a copy as a new saved version. |
advanced revision |
List, get or delete saved versions of the files. |
advanced workspace-files |
List, stat or read the files of a copy or a saved version. Write to a copy with an ETag check. |
worker |
Start a worker with its own copy. List workers, check status or stop a running worker. |
advanced task-group |
Create, list, get or stop a group of workers. Read its costs. |
changeset |
Inspect worker merges, submit saved revisions, resolve conflicts, and accept or reject changesets. |
advanced operation |
Get or wait for a file operation, such as advanced copy clone or advanced copy checkpoint. |
advanced agent stop, advanced agent retire |
Stop an agent, or stop new work for it, and keep the Workspace history. |
Start one worker
After you sign in, create a Workspace and give a worker a task:
plori workspace create project
plori worker start project 'Update the selected files' --wait
worker start makes a copy of the latest saved revision, creates a task group,
and starts the worker. It waits for the copy to be ready, for up to 600 seconds.
Use --timeout to change that limit. If the Workspace has no saved revision yet,
retry when its storage is ready.
--max-tokens sets a token figure that is recorded on the new group, in
cache-weighted tokens. The default is 2,000,000. The figure does not limit model
calls; the account's credit balance is the only spend limit. To add the worker to
an existing group, use --task-group <id> without --max-tokens.
The command prints the worker ID and a worker status command on stderr. It
also prints a generated idempotency key if you omit --idempotency-key.
Retry with the same key and task if a request is interrupted.
--wait waits for the run. A completed run returns its reply and the worker's save_state.
Run completion does not mean the files are saved or merged. --wait-seconds
sets the run wait limit. It defaults to --timeout. Use worker status --wait
to wait again. Plori merges a finished worker's saved changes automatically when
they merge cleanly. The manager resolves conflicts. This also applies to workers
started with advanced worker spawn or through MCP or REST.
A failed or stopped worker's saved revision stays available without an automatic merge. The same applies while a worker waits for input. The manager can still submit that saved revision if the work should be included.
Advanced: prepare copies and merge changes
Create the Workspace. Keep
current_revisionfromworkspace get:plori workspace create project --idempotency-key project-create-1 plori workspace get "$WORKSPACE_ID"Make a copy of the files for the worker and wait for it:
plori advanced copy clone "$WORKSPACE_ID" --base-revision "$CURRENT_REVISION" --idempotency-key clone-1 plori advanced operation wait "$WORKSPACE_ID" "$OPERATION_ID" --timeout 120Create a task group and start a worker on the copy:
plori advanced task-group create "$WORKSPACE_ID" --max-tokens 2000000 --idempotency-key task-1 plori advanced worker spawn "$WORKSPACE_ID" 'Update the selected files' --name worker-1 \ --copy "$WORKER_COPY_ID" --task-group "$GROUP_ID" --idempotency-key worker-1--max-tokensis a token figure recorded on the group, in cache-weighted tokens. It does not limit model calls. Do steps 2 and 3 again for each worker.Wait for the worker's run to finish:
plori worker status "$WORKSPACE_ID" "$REQUEST_ID" --wait --wait-seconds 600Check
save_statein the result. If it is notready, check status again. When the worker has achangeset_id, useplori changeset getto check it.state: acceptedmeans the changes are in the Workspace files. The manager resolves conflicts.
Submit a saved revision manually
Use this procedure to include saved work that Plori did not merge automatically, such as a failed or stopped worker's changes.
Submit the saved revision. Plori calculates the merge:
plori changeset submit "$WORKSPACE_ID" --base-revision "$BASE_REVISION" \ --incoming-revision "$SAVED_REVISION_ID" --current-revision "$CURRENT_REVISION" \ --idempotency-key submit-1 plori changeset get "$WORKSPACE_ID" "$CHANGESET_ID"Check the changeset with
changeset getuntil the comparison is complete. Plori accepts a changeset with changes and no conflicts automatically.state: acceptedmeans the changes are in the Workspace files.If the changeset needs review, use
comparison_current_revisionfromchangeset getand 1 to 32--evidencestatements of your checks to accept it:plori changeset accept "$WORKSPACE_ID" "$CHANGESET_ID" \ --current-revision "$COMPARISON_CURRENT_ID" --evidence 'tests passed' \ --idempotency-key accept-1If another merge finished first, submit again. To discard the changes, run
changeset reject.
To fix conflicts yourself, edit the files in the copy that changeset get
returns as integration_working_copy_id. Use advanced workspace-files write, then run
changeset resolve --generation N --resolutions @resolutions.json. Each
resolution has path, expected_etag and summary.
advanced worker spawn needs --name to start a new agent as the worker, or
--executor ID to use an existing agent: --executor is that agent's id. To
give a worker more work, run advanced worker spawn again with its --executor, its
--session and a ready --copy. Do not use run for this.
To use an agent from advanced independent-agent create, first run
workspace add-agent WORKSPACE_ID AGENT_ID.
Advanced commands that create an agent, a copy, a task group or a retention pin
require --idempotency-key. So do commands that delete a copy or a revision, or
submit, resolve, accept or reject changes. Workspace creation, runs and worker starts generate
a key when you omit it. Retry with the same key and input.
Use a new key for new input.
Read and write files
plori advanced workspace-files read "$WORKSPACE_ID" /notes.txt --copy "$COPY_ID" --json
plori advanced workspace-files write "$WORKSPACE_ID" /notes.txt --copy "$COPY_ID" \
--file ./notes.txt --if-match "$ETAG"
Read JSON includes the observed ETag. Inspect the old bytes before replacing them.
Use --if-match 0 only to create a missing file. --file - reads stdin.
A stale ETag fails instead of overwriting another edit. If the write
returns HTTP 412 with current_etag, read the file again. Retry once with
that ETag only if the content still matches what you read before editing. If
the content differs, resolve the conflict before writing. See the
Files write procedure.
--revision selects a saved version for list, stat or read.
advanced workspace-files read-bytes requires a revision and returns base64 JSON, with an
8 MiB limit. Text reads refuse binary files. This command does not implement
mutable-copy binary download.
Inspect costs, retention and cleanup
plori worker list "$WORKSPACE_ID" --task-group "$GROUP_ID" --limit 25
plori workspace costs "$WORKSPACE_ID" --from 2026-09-01T00:00:00Z
plori advanced task-group costs "$GROUP_ID"
plori advanced workspace retention "$WORKSPACE_ID" --view pins
List copies, saved versions, changesets, workers and task groups with --cursor and
--limit. Continue with next_cursor. Cost reports have a separate resource
page: continue it with --resource-cursor. A missing or null cost is unknown,
not zero. A page of resource observations is not a complete total.
When an item's retention expires, a later cleanup can delete it. Expiry alone does
not delete it.
advanced workspace pin keeps a copy or a changeset until --expires-at. It requires
--kind copy|changeset, --resource, --expires-at and an idempotency key.
Pins work only when the Workspace's retention policy sets a pin limit.
advanced workspace unpin releases a pin.
advanced workspace update --retention-profile P --retention-version N moves the
Workspace to Plori's current retention policy for new copies and saved versions.
P and N must name that current policy.
advanced agent stop AGENT_ID and advanced task-group stop GROUP_ID stop running work. The files and
history stay, subject to retention. advanced agent retire AGENT_ID stops an agent from getting
new work. You cannot retire or remove the Workspace's agent. First select another one
with workspace update --manager ID, where --manager is the id of the agent that
becomes the Workspace's agent. Workspace deletion erases its files and history in a
background cleanup. Deleting a copy or a saved version deletes only that item.
Retirement, deletion and member removal require --yes outside a terminal or
interactive confirmation. An accepted deletion does not prove physical erasure.
Worker runs that you wait for use the same exit codes as other runs, including a
pause for an approval or a question, error, cancellation and timeout.
Use plori help GROUP for each command's flags. Optional worker callbacks use
--callback-url. Prefer PLORI_WORKER_CALLBACK_SECRET to --callback-secret. You can
also use --callback-secret - to read it from stdin. Callback fields require server support.
Functions
plori fn deploys and tests the functions that your
app calls. It signs in with a tenant secret key, not with your account. Set
PLORI_TENANT_KEY=plori_tsk_… or pass --tenant-key.
plori fn deploy plori/functions/ticket_triage # upload a version and run the deploy gate
plori fn test ticket_triage # run all test cases against the locked version
plori fn list # list functions and their versions
plori fn deploy takes a plori/functions/<name> directory in a git repository. When
every gate case passes, it writes the version to plori/functions.lock.json. Flags:
--dry-run checks the definition only, --yes runs the gate without a prompt,
--parallel 1 to 3 sets how many cases run at once, and --force writes the lockfile
when a case fails. plori fn test takes --version N and --case <name>. It shows
the highest possible cost and asks you to confirm before it runs the cases. Use --yes
to skip the prompt. Scripts require --yes. Each case accepts repeat from 1 to 100
(default 1).
Credits
plori account balance shows the balance and prints a billing link when credits run low. Any
command refused with HTTP 402 prints a billing link too. See
pricing for plans and one-time credit packs.