Skip to content

Plune CLI — Getting started

The Plune CLI (@plune-ai/cli) is AI-powered assertion testing for LLM apps — a test runner for model behaviour. You describe the checks in one plune.yaml; Plune calls the model, evaluates each assertion, caches results, and reports a pass/fail summary with token cost — locally, in CI, or as a regression diff between two runs. It is the Evaluate step in Plune’s Generate → Evaluate → Gate flow.

v0.3.0MITNode ≥ 20

Terminal window
npm install -g @plune-ai/cli # or: pnpm add -g @plune-ai/cli
plune --version

Or run it without installing:

Terminal window
npx -y @plune-ai/cli run
  1. Scaffold a plune.yaml, an example dataset, and .env.example:

    Terminal window
    plune init
  2. Add your provider key — read from the environment / .env, never written to disk:

    Terminal window
    echo 'ANTHROPIC_API_KEY=sk-ant-...' >> .env
  3. Run the assertions:

    Terminal window
    plune run
    # → 1/1 passed · 0 failed · 0 errored · $0.0008
  4. Re-render the last run, or diff two runs to catch regressions:

    Terminal window
    plune report --format markdown
    plune diff baseline.json current.json --fail-on-regression

Each run writes its full result to .plune/last-run.json.

Plune reads a single plune.yaml, discovered by walking up from the working directory (or passed with -c <path>). This is what plune init scaffolds:

version: 1
provider:
type: anthropic # anthropic | openai | openrouter
model: claude-3-5-sonnet-latest
evals:
- id: example
prompt: "Answer concisely. {{question}}" # {{vars}} come from each dataset row
dataset: datasets/example.jsonl # a file path, or an inline `examples:` list
assertions:
- type: contains
value: "Paris"

Datasets are JSONL — one row per line, shaped { "vars": { ... }, "expected"?: "..." }.

The provider API key is read from the environment based on provider.type — never written to disk:

Providerprovider.typeEnvironment variable
AnthropicanthropicANTHROPIC_API_KEY
OpenAIopenaiOPENAI_API_KEY
OpenRouteropenrouterOPENROUTER_API_KEY

Ten built-in assertion types cover plain text, JSON-schema, LLM-as-judge, and RAG metrics:

TypePasses when…
exact-matchoutput equals value (optional trim, ignore_case)
containsoutput contains value
contains-anyoutput contains at least one of values
contains-alloutput contains every one of values
json-schemaoutput validates against the JSON schema
llm-judgean LLM grades the output against criteria (≥ pass_threshold)
semantic-similarityembedding similarity to referencethreshold
faithfulnessoutput is grounded in context (RAG)
answer-relevanceoutput actually answers the question (RAG)
context-precisioncontext is relevant to the question (RAG)
CommandSummary
plune runRun the suite. Flags: --dry-run, --only <id|tag> (repeatable), --bail, --no-cache, --concurrency <n>, --format console|json|markdown, -o, --output <file>.
plune reportRe-render the most recent run. Flags: --format, -o.
plune diff <baseline> <current>Compare two plune run --format json outputs and report pass→fail regressions. Flags: --fail-on-regression, --format, -o.
plune initScaffold plune.yaml, a sample dataset, and .env.example. Flags: --yes (non-interactive), --force.

Global flags: -c, --config <path> · -v, --verbose · --no-color.

The same engine that powers plune run is exported for use from your own code. Unlike the CLI, the library does not parse argv or auto-load .env — set the provider key in process.env yourself.

import { run } from "@plune-ai/cli";
import type { RunResult } from "@plune-ai/cli";
const result: RunResult = await run({ dryRun: false, configPath: "plune.yaml" });
console.log(result.summary); // { total, passed, failed, errored, ... }

Run Plune on every pull request and post a regression diff as a sticky comment with the companion GitHub Action, eval-action:

- uses: plune-ai/eval-action@v1
with:
config: plune.yaml
fail-on-regression: true

MIT © Plune Contributors.