plp command. Run plp <command> --help for the same text from the installed version.
plp talks to the hosted Preloop platform at PRELOOP_URL, which defaults to https://app.preloop.dev, 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.
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.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:
agent exec delegates a bounded session to an active agent:
--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:
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:Run and inspect workflows
plp ci run
Submit a workflow to the platform, stream its events, and wait for the run to finish.
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 andneeds:, without running anything.
plp ci status
Show active and recent runs, or print one run’s status word for scripts.plp ci logs
Show run logs. Without flags, every job’s log is merged in dispatch order.
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
RUN_ID defaults to the most recent active run.
plp ci rerun
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
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.
--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.plp ci dap
Attach an interactive DAP client to a run that was started with debugging enabled.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.
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:
--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:
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.
--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:
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.
--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:
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).
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.
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.
