Docs

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 --key or PLORI_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

  1. Create the Workspace. Keep current_revision from workspace get:

    plori workspace create project --idempotency-key project-create-1
    plori workspace get "$WORKSPACE_ID"
    
  2. 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 120
    
  3. Create 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-tokens is 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.

  4. Wait for the worker's run to finish:

    plori worker status "$WORKSPACE_ID" "$REQUEST_ID" --wait --wait-seconds 600
    

    Check save_state in the result. If it is not ready, check status again. When the worker has a changeset_id, use plori changeset get to check it. state: accepted means 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.

  1. 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"
    
  2. Check the changeset with changeset get until the comparison is complete. Plori accepts a changeset with changes and no conflicts automatically. state: accepted means the changes are in the Workspace files.

  3. If the changeset needs review, use comparison_current_revision from changeset get and 1 to 32 --evidence statements of your checks to accept it:

    plori changeset accept "$WORKSPACE_ID" "$CHANGESET_ID" \
      --current-revision "$COMPARISON_CURRENT_ID" --evidence 'tests passed' \
      --idempotency-key accept-1
    

    If 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.