> ## Documentation Index
> Fetch the complete documentation index at: https://preloop.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI reference

> Every plp command, subcommand, and flag, with defaults and examples.

This reference lists every `plp` command. Run `plp <command> --help` for the same text from the installed version.

`plp` talks to the hosted Preloop platform at the origin compiled into the binary — `https://app.preloop.dev` for every published build — using the signed-in session. `PRELOOP_TOKEN` is an optional explicit bearer token for a script or a delegated child process; there is no administrator-token fallback.

CI commands live under the `plp ci` group; `plp --help` lists the group, `version` and `update` stay top level.

## Sign in and select an organization

Hosted Preloop uses an RFC 8628 device flow. `login` opens the Preloop login page with the device's user code and prints that URL and code as a fallback for machines without a browser; sign in there, and the page approves the device and selects or creates your organization. The CLI polls until approval completes, then stores the session credential for the CLI. Access tokens are short-lived and refreshed by the platform; they are always scoped to the selected organization.

```sh theme={null}
plp login
plp whoami
plp org use org_01H...
plp logout
```

`org use` changes the organization on which subsequent commands operate and checks that the signed-in user belongs to it. An organization is the only tenant: a request for another organization is refused rather than treated as an administrator request. A user may delegate only within the current organization.

### Manage coding agents

Agent administration is performed by a non-delegated user session. These actions are human-gated: an agent cannot create or delete another agent, register or revoke credentials, or approve a device flow.

```sh theme={null}
plp agent create ci-bot --roles ci-agent
plp agent list
plp agent credentials ci-bot
plp agent delete ci-bot
```

`agent list` returns agents in the selected organization. `agent credentials` shows credential metadata and revocation state, never a private key or bearer value. Agent credentials are registered by the user, then the agent authenticates its own session with either a federated OIDC ID token or an RFC 7523 Ed25519 key assertion:

```sh theme={null}
plp agent login --agent ci-bot --oidc-token "$OIDC_ID_TOKEN"
plp agent login --agent ci-bot --cid "$CID" --assertion "$ASSERTION"
```

Assertions are single-use and replay is refused. The helper never prints a token or assertion.

`agent exec` delegates a bounded session to an active agent:

```sh theme={null}
plp agent exec --agent ci-bot --scopes runs:read,artifacts:read --ttl 15m -- plp ci run -f .github/workflows/ci.yml
plp agent grant-job RUN_ID/JOB --agent ci-bot
```

The resulting delegated token is the intersection of the user's grants, the agent's ceiling, and the requested scopes. `--ttl` must be at most 15 minutes because delegated access tokens carry the revocation epoch directly; a longer request is refused. Cross-organization agents, inactive agents, an empty intersection, and requests outside the ceiling are refused. `grant-job` is a task grant: explicit actions scoped to `run:RUN_ID/job:JOB`, live only for that job and its grant lifetime; it is not a standing organization grant.

The built-in `ci-agent` role is intentionally narrow: it can create/read runs, cancel its own runs, read and control its own debug sessions, and read artifacts and workflow/job/step data. It has no `secrets:use`, `oidc:use`, or `runs:publish` permission. Delegation never grants human-gated actions, including environment approval/override, fork approval, agent administration, policy changes, or secret administration.

### Grant scopes

A grant is an action list attached to a scope; the scope is what makes a
repository- or environment-scoped delegation possible. The canonical grammar
is hierarchical — `org` contains every repository, a repository contains its
environments and workflows, a workflow contains its jobs, a run contains its
jobs:

```text theme={null}
org
repo:github.com:<repo_id>
repo:github.com:<repo_id>/env:<name>
repo:github.com:<repo_id>/workflow:<url-escaped-path>
repo:github.com:<repo_id>/workflow:<path>/job:<key>
run:<run_id>
run:<run_id>/job:<key>
```

The forge is `github.com`; the short `github` spelling early tokens minted is
accepted and normalized, and any other spelling is refused, because a scope
the platform can never build would leave the grant silently dead. Workflow paths
are URL-escaped so they may embed `/` as `%2F`.

A delegated token (`--act agt_…`) carries the intersection of the user's
grants, the agent's ceiling and the request; the platform re-checks that
intersection at every decision, so a scope in the token is an upper bound, not
the decision itself.

## Approve human-gated work

Approvals use the logged-in human session. There is no administrator override
flag, and delegated agents are refused by the server:

```sh theme={null}
plp ci approve RUN_ID --job JOB_ID --note "approved for production"
plp ci approve-fork RUN_ID --note "fork reviewed"
```

The first command satisfies an environment approval gate; the second approves
a run held by fork policy. Both actions are audited with the authenticated
principal.

## Run and inspect workflows

### plp ci run

Submit a workflow to the platform, stream its events, and wait for the run to finish.

```sh theme={null}
plp ci run -f .github/workflows/ci.yml --event pull_request
plp ci run -d -f ci.yml && plp ci logs        # submit detached, then follow the latest run
plp ci run --job test --secret TOKEN=x
```

| Flag | Description |
| - | - |
| `-f, --file <FILE>` | Workflow file. A bare name resolves inside `.github/workflows/`. Omit it when the repository has exactly one workflow. |
| `--event <EVENT>` | Simulated trigger, such as `push`, `pull_request`, or `merge_group`. Default `push`. |
| `--job <JOB>` | Run one job by its key, with the jobs it `needs`. |
| `--payload <PATH>` | JSON webhook body for the simulated event. Fields you supply win over derived ones. |
| `--base <BASE>` | Base ref for `pull_request` and `merge_group` events. |
| `--debug` | Pause a failed job in a debug session. |
| `--secret <NAME=VALUE>` | Run-scoped secret. Repeatable. |
| `--strict-secrets` | Fail instead of warning when the workflow reads a secret the platform has not stored. |
| `-d, --detach` | Submit and return without streaming events. |
| `--push` | After a successful run, push the tested commit and publish the result. Requires a GitHub `origin`. |
| `--create-pr` | Open a pull request for the branch when none is open. Implies `--push`. |
| `--pr-draft[=BOOL]` | Open new pull requests as drafts. Default `true`. Use `--pr-draft=false` for ready-for-review pull requests. |
| `--no-merge` | For `pull_request` events, test the branch alone instead of its merge with the base. |
| `-m, --message <TEXT>` | Commit message for the commit `--push` creates from a dirty tree. Without it, `$EDITOR` opens in a terminal and a generated message is used otherwise. |

**Working tree.** A run snapshots your working tree, including uncommitted and untracked files that git does not ignore, and uploads it with the run. The run never depends on what you pushed.

**Interactive debugging.** In a terminal, a non-detached run pauses a failed job in a debug session for `plp ci debug`. Pass `--debug` to opt in for detached or non-interactive runs.

**`pull_request` runs.** Like GitHub, a `pull_request` run tests the merge of the base branch's current tip into your head; the base is fetched from `origin`. A conflicted pull request fails the submission with the conflicted files listed, and an unfetchable base fails loudly. `--no-merge` tests the branch alone. `push` events are unaffected: they test the commit itself.

**Derived event data.** The CLI fills in what git can answer: changed files (for `paths:` filters, from `git diff` against the base), the target branch for `branches:` filters, and the current branch or tag. It does not invent a pull request number, actor, or labels. Pass `--payload` when a workflow depends on those.

**Secret preflight.** When the workflow reads a `secrets.*` name the platform has not stored, the submission reports the missing names so you can store them. `--strict-secrets` turns that warning into a failure. The check reads the platform's store — global, repository, and each `environment:` tier the workflow declares — never GitHub's, whose values are write-only. A job that passes `secrets: inherit` to a reusable workflow is called out, because its callee's names cannot be listed statically. `GITHUB_TOKEN` and the `ACTIONS_*` runtime tokens are ignored: the platform mints those per job.

**`--push`.** A dirty tree is committed as a snapshot of exactly the files that were tested, and that commit is pushed only after the run succeeds. Publishing needs a GitHub App with `checks: write` to report check runs.

### plp ci plan

Show the expanded job graph, including matrix fan-out and `needs:`, without running anything.

| Flag | Description |
| - | - |
| `-f, --file <FILE>` | Workflow file. Same resolution as `run`. |
| `--json` | Print machine-readable output. |

### plp ci status

Show active and recent runs, or print one run's status word for scripts.

```sh theme={null}
plp ci status                  # table of recent runs
plp ci status <run-id>         # one status word, for scripts
plp ci status --json | jq .    # raw status JSON
```

| Argument or flag | Description |
| - | - |
| `[RUN_ID]` | Print only this run's machine-readable status word. |
| `--json` | Print raw status JSON, with no prose. |
| `--limit <N>` | Recent runs to show in the table. Default `20`. |

### plp ci logs

Show run logs. Without flags, every job's log is merged in dispatch order.

```sh theme={null}
plp ci logs                          # the latest run
plp ci logs --job test               # one job
plp ci logs --job test --step 3      # one step of that job
plp ci logs -f --job test            # follow a job as it runs
```

| Argument or flag | Description |
| - | - |
| `[RUN_ID]` | Run to show. Defaults to the most recent run. |
| `--job <JOB>` | A workflow job key (`build`) or the job's UUID. |
| `--step <STEP>` | A 1-based step number in execution order. Needs `--job` when the run has more than one job. |
| `-f, --follow` | Stream one job's console feed and exit when that job finishes. Needs `--job` unless the run has one job. Cannot be combined with `--step`. |

Steps that the runner adds itself, such as `Set up job` and `Complete job`, appear in the whole-job log but do not take a `--step` number.

### plp ci cancel

```sh theme={null}
plp ci cancel [RUN_ID]
```

Cancels the run. `RUN_ID` defaults to the most recent active run.

### plp ci rerun

```sh theme={null}
plp ci rerun                       # the newest completed run, all jobs
plp ci rerun <run-id> --failed     # failed and cancelled jobs, plus their dependents
plp ci rerun <run-id> --job test   # one job and its dependents
```

| Argument or flag | Description |
| - | - |
| `[RUN_ID]` | Run to re-run. Defaults to the most recent completed run. |
| `--failed` | Re-run only failed or cancelled jobs and the jobs that depend on them. Conflicts with `--job`. |
| `--job <JOB_ID>` | Re-run one job, identified as `plp ci status <run-id>` shows it, and its dependents. |

With no flag, every job runs again as a new attempt on the same run. `github.run_attempt` increments, and the previous attempt stays in history. A run that the archiver has already moved to history can only be re-run in full, as a new run.

### plp ci push

```sh theme={null}
plp ci push [RUN_ID]
```

Publishes a completed run's result to GitHub: pushes the tested commit, creates or updates the pull request, and reports check runs. `RUN_ID` defaults to the most recent run. Re-running the command is safe.

### plp ci manifest

Read a run's signed manifest for each job, including the exact tested source
tree, workflow-byte digest and binding state, environment, actions, result, and
evidence gaps.

Run-level output selects only each job's newest attempt and one representation.
The aggregate is capped at 31 MiB of body bytes (with framing headroom under
the CLI's 32 MiB response limit); use `--job` if the server returns `413`.

```sh theme={null}
plp ci manifest <run-id>
plp ci manifest <run-id> --job build --verify
plp ci manifest <run-id> --dsse --export run.dsse.json
```

| Argument or flag | Description |
| - | - |
| `[RUN_ID]` | Run whose manifests to read. |
| `--job <JOB_ID>` | One job instead of every job in the run. |
| `--json` | Print canonical manifest JSON. |
| `--dsse` | Print signed DSSE envelopes. |
| `--verify` | Fetch the platform's JWKS and verify each signature and both digests. |
| `--export <PATH>` | Write the selected payload to a file. |

`--verify` accepts a record whose signature is valid but whose evidence is
incomplete; inspect `collection.missing` and `spec.source` before treating it
as proof of a clean run.

## Debug and inspect

### plp ci debug

Attach to a job paused at a failed step, inspect it, and issue a verdict.

```sh theme={null}
plp ci debug                              # the only paused session
plp ci debug <run-id> --json              # show the paused session as JSON
plp ci debug <run-id> --verdict retry --from 3
```

| Argument or flag | Description |
| - | - |
| `[SESSION]` | Session ID, run ID, or job name. Optional when exactly one session is paused. |
| `--json` | Print the paused session as JSON and exit. |
| `--verdict <retry\|continue\|abort>` | Issue a verdict without attaching. |
| `--takeover` | Replace a controller lease another `plp ci debug` still holds, such as one left behind by a killed client. Without it such a session attaches read-only. |
| `--revert <none\|untracked\|all>` | With `retry`, remove the failed attempt's workspace changes. Default `none`. |
| `--from <STEP>` | With `retry`, re-run from a 1-based step number or step name. The step must be at or before the failed step. |
| `--from-start` | With `retry`, re-run from the first user step of the job. |

### plp ci dap

Attach an interactive DAP client to a run that was started with debugging enabled.

| Argument or flag | Description |
| - | - |
| `<RUN_ID>` | Run whose DAP endpoint to open. |
| `--token <TOKEN>` | Explicit API bearer token. Defaults to the signed-in hosted session, or `PRELOOP_TOKEN` when supplied for a script. |

## Secrets

### plp ci secrets

Manage the secrets jobs read: list, inspect, store, restrict, and delete them. Every subcommand is one call to the platform's secrets API, so what the command line can do is what the API can do. Values are write-only: no subcommand prints one, and the API never returns one.

```sh theme={null}
plp ci secrets list
plp ci secrets get DEPLOY_KEY --repo acme/api
printf %s "$TOKEN" | plp ci secrets set NPM_TOKEN --stdin
plp ci secrets scope DEPLOY_KEY --repo acme/api --branch refs/heads/main --workflow .github/workflows/deploy.yml
plp ci secrets delete DEPLOY_KEY --repo acme/api
plp ci secrets scopes
```

| Subcommand | API call | Description |
| - | - | - |
| `list` | `GET /api/v1/secrets` | Names, tier, and bindings. With a selector, only that tier. |
| `get <NAME>` | `GET /api/v1/secrets/{name}` | One secret's tier and bindings. |
| `set <NAME>` | `PUT /api/v1/secrets/{name}` | Create a secret or replace its value, optionally with bindings. |
| `scope <NAME>` | `PATCH /api/v1/secrets/{name}` | Replace the bindings only. Never sends a value. |
| `delete <NAME>` (alias `rm`) | `DELETE /api/v1/secrets/{name}` | Remove a secret. |
| `scopes` | `GET /api/v1/secrets/scopes` | The tiers and binding kinds the platform supports. |

Names are `UPPER_SNAKE`: letters, digits, and underscores.

#### Import from GitHub Actions

`plp ci migrate apply` runs this import as its secrets phase after gate `set_secret`; the standalone command remains for migrating a repository later. It moves the GitHub Actions values into the platform without handling a plaintext value locally:

```sh theme={null}
plp ci secrets import --repo acme/api
plp ci secrets import --repo acme/api --secrets NPM_TOKEN CODECOV_TOKEN
plp ci secrets import --repo acme/api --scope env --environment production --push
```

With no `--secrets`, names come from the same parser walk as `plp ci run`'s secret preflight. `--scope repo` (default) writes the repository tier; `--scope env` writes one environment tier per `--environment` (or per environment the workflows pin a job to) and generates one workflow job per environment, because GitHub only exposes an environment's secrets to a job that declares it; `--scope org` writes the organization tier of the repository's owner.

The command creates a five-minute import intent, then commits a throwaway workflow to a local branch with git plumbing — the worktree and index are untouched. It prints, and with `--push` runs, the exact push:

```sh theme={null}
git push origin refs/heads/preloop/import-secrets-<id>:refs/heads/preloop/import-secrets-<id>
```

Pushing a workflow file needs a git credential that may write workflows (a `gh` login carries the `workflow` scope; a classic PAT needs it too). The workflow runs on `ubuntu-latest` with `id-token: write` and `contents: write`, reads only the explicitly mapped `secrets.NAME` values, seals each value to the intent's one-use RSA key (RSA-OAEP-SHA256 + AES-256-CBC + HMAC-SHA256), and posts the sealed bytes with a GitHub OIDC token for the `preloop-secret-import` audience. The platform accepts the post only when the token's signature, issuer, audience, `repository`, `ref` (the throwaway branch), and — for `--scope env` — `environment` match the intent. Every entry is imported once; a replay or an expired intent is refused. The workflow deletes its branch in a final `always()` job.

After a push, the command polls the intent and reports per-secret progress. A declined push leaves the local branch in place so it can be pushed manually. The intent's private key never leaves the platform process and is dropped when the intent is consumed; no value is ever logged, committed, or returned by the API.

**Selectors.** A secret lives in one tier. Give exactly one form; it is checked before any request is sent.

| Selector | Tier |
| - | - |
| none | Global: every trusted job. |
| `--repo <OWNER/NAME>` | One repository. |
| `--repo <OWNER/NAME> --env <ENV>` | One environment of a repository, matched against a job's `environment:`. |
| `--org <ORG>` | One organization. `set` also takes `--repos`. |

`--org` never combines with `--repo` or `--env`, and `--env` needs `--repo`. `list` with no selector shows every tier.

**Setting a value.** Give the value through exactly one source:

| Flag | Source |
| - | - |
| `--stdin` | Standard input. One trailing newline is dropped. |
| `--from-file <PATH>` | A file. One trailing newline is dropped. |
| `--from-env <VAR>` | An environment variable. |
| `--value <VALUE>` | The command line. It is visible in your shell history and in the process list; prefer one of the sources above. |

With none of them, a terminal gets a hidden prompt; anything else is refused. Pass `--stdin`, `--from-file` or `--from-env` in scripts and agents.

```sh theme={null}
plp ci secrets set DOCKERHUB_TOKEN                                   # hidden prompt
op read op://ci/npm/token | plp ci secrets set NPM_TOKEN --stdin
plp ci secrets set DEPLOY_KEY --repo acme/api --from-file ./deploy.key
plp ci secrets set DB_PASSWORD --repo acme/api --env prod --from-env DB_PASSWORD
plp ci secrets set NPM_TOKEN --org acme --repos acme/api,acme/lib --stdin
```

For `--org`, `--repos <OWNER/NAME,…>` names the repositories that may read the secret; each one must belong to the organization. Omit it for every repository of the organization. Each `set` defines the list in full, so a `set` without `--repos` makes the secret visible to every repository again.

**Bindings.** A binding restricts which jobs may read a secret. A secret with no bindings is available to every job its tier reaches. These flags build bindings for `set` and `scope`:

| Flag | Matches |
| - | - |
| `--branch <GLOB>` | The job's ref, such as `refs/heads/main` or `refs/heads/release/**`. Repeatable. |
| `--workflow <PATH>` | The workflow file that defines the job, such as `.github/workflows/deploy.yml`. Repeatable. |
| `--job <KEY>` | A job key inside `--workflow`. Repeatable; needs `--workflow`. |
| `--caller <PATH>` | A caller workflow of a reusable-workflow call. Repeatable. |
| `--caller-job <KEY>` | The job inside `--caller` that makes the call. Repeatable; needs `--caller`. |
| `--subtree` | With `--caller`, every job nested below the called workflow, not just its direct jobs. |
| `--binding <JSON>` | One more binding, as a JSON object with the same fields: `branches`, `workflows`, `jobs`, `callers`, `caller_jobs`, `subtree`. Repeatable. |
| `--clear` | `scope` only: remove every binding. Cannot combine with the flags above. |

One set of flags builds one binding, and every field in it must match (AND). Each `--binding` adds another, and a job gets the secret when any one binding matches (OR).

```sh theme={null}
# only the release job of deploy.yml, on main
plp ci secrets set DEPLOY_KEY --repo acme/api --stdin \
  --branch refs/heads/main --workflow .github/workflows/deploy.yml --job release

# main-branch deploys, or anything in the nightly workflow
plp ci secrets scope DEPLOY_KEY --repo acme/api \
  --branch refs/heads/main --workflow .github/workflows/deploy.yml \
  --binding '{"workflows":[".github/workflows/nightly.yml"]}'

# only jobs below a reusable workflow called from ci.yml
plp ci secrets scope DEPLOY_KEY --repo acme/api \
  --caller .github/workflows/ci.yml --caller-job call-deploy --subtree

# lift every restriction
plp ci secrets scope DEPLOY_KEY --repo acme/api --clear
```

A `set` without scope flags keeps the secret's existing bindings, so rotating a value never widens access. A `set` with scope flags replaces them. Invalid combinations are refused before the request, with the flag named: `--job` needs `--workflow`, `--caller-job` and `--subtree` need `--caller`, and a `--caller` plus `--caller-job` binding with no `--workflow`, `--job` or `--subtree` would only match the caller job, which runs no steps. The platform applies the same rules and is the authority.

**Output.** `list` prints a table of name, scope, repositories (organization secrets), and bindings; `get` prints the tier and each binding; `scopes` prints what the platform supports. Add `--json` to `list`, `get`, or `scopes` for the platform's response as-is.

```text theme={null}
$ plp ci secrets list
NAME         SCOPE                REPOS              BINDINGS
NPM_TOKEN    org acme             acme/api,acme/lib  branches=refs/heads/main
DEPLOY_KEY   repo acme/api        -                  branches=refs/heads/main workflows=.github/workflows/deploy.yml jobs=release
DB_PASSWORD  env acme/api (prod)  -                  unrestricted

$ plp ci secrets get DEPLOY_KEY --repo acme/api
DEPLOY_KEY
  scope:    repo acme/api
  bindings: 1 (a job gets the secret when any one matches)
    1. branches=refs/heads/main workflows=.github/workflows/deploy.yml jobs=release
```

Secret values remain write-only. A signed-in user with the required organization grant can manage a secret, but an agent cannot list or use secrets unless a user explicitly delegates those actions; the built-in `ci-agent` role cannot use secrets.

## Migrate from GitHub Actions

`plp ci migrate` moves a repository's workflows to Preloop in one end-to-end flow: workflows (copy into `.preloop/workflows/` and open the pull request), secrets (the GitHub OIDC import after gate `set_secret`), and OIDC (each provider's per-organization issuer and trust check, after gate `configure_trust`). Release, publish, deploy, and `environment:` workflows stay on GitHub.

| Subcommand | Description |
| - | - |
| `scan` | Report which workflows can move, and why. Reads `<dir>/.github/workflows`; never contacts GitHub. `--dir <PATH>`, `--json`. |
| `plan` | One JSON document with all three phases, verdicts, secret names, commands, human gates, and next actions. Reads the workflows and `git remote`. `--skip-secrets`, `--skip-oidc`. |
| `apply` | Copy the moving workflows, branch, commit, push, and open the pull request with your own `git` and `gh`; then import the plain secrets and run the OIDC trust checks. `--skip-secrets`, `--skip-oidc`, `--dry-run`. |
| `status` | Compare the scan with the workflows GitHub still has enabled. |
| `verify` | Check all three phases. Exits `0` when done, `10` when actions remain, `3` when `gh`/`git` is missing or unauthenticated. `--skip-secrets`, `--skip-oidc`. |
| `guide` | Print the agent playbook. |

## Self-management

### plp update

Check for a newer release, or install one. Installs are atomic.

```sh theme={null}
plp update --check
```

| Flag | Description |
| - | - |
| `--check` | Report whether a newer release exists. Install nothing. |
| `--version <VERSION>` | Install this release tag or version instead of the latest stable one. |
| `--repository <OWNER/NAME>` | Release source. Default `preloopdev/preloop`. Env: `PRELOOP_RELEASE_REPOSITORY`. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.