Structured extraction library and CLI built on @mariozechner/pi-ai.
- Mini agent loop: think → respond → validate → retry
- Schema-first with TypeBox + AJV
- Layered validation (schema, sync/async functions, shell, HTTP)
- Streaming events for observability
- Unix-friendly CLI
- Optional response caching
npm install @justram/piepie uses the provider/model registry from @mariozechner/pi-ai. In the CLI, pass --model provider/model:
cat input.txt | pie \
-s schema.json \
-p "Extract fields" \
--model anthropic/claude-sonnet-4-5List available models (with optional filters):
pie --list-models --models-provider google-antigravity
pie --list-models --models-provider google-antigravity --models-filter geminipie does not require Pi to be installed. It uses the OAuth helpers from @mariozechner/pi-ai and stores credentials in ~/.pi/agent/auth.json (created automatically on first login).
Credential resolution order:
~/.pi/agent/auth.json(API keys or OAuth tokens)- Environment variables (e.g.
ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY) - OAuth login (only for supported providers)
Run --login to authenticate with an OAuth-capable provider. The CLI prints a URL and prompts you to complete login in your browser.
pie --login openai-codexSDK users can trigger the same OAuth flow programmatically (uses Node prompts and writes to
~/.pi/agent/auth.json):
import { loginWithOAuthProvider } from "@justram/pie";
await loginWithOAuthProvider("openai-codex", process.stderr);If you only need an API key and want auto-refresh, call resolveApiKeyForProvider(provider, process.stderr).
These helpers are Node-only and are not intended for browser environments.
Supported OAuth providers:
anthropicgithub-copilotgoogle-gemini-cligoogle-antigravityopenai-codex
If you skip this step, pie will still prompt for OAuth the first time you run a command that requires it.
cat input.txt | pie \
-s schema.json \
-p "Extract fields" \
--model github-copilot/gpt-4oUse environment variables:
export ANTHROPIC_API_KEY=sk-ant-...Or add API keys to ~/.pi/agent/auth.json:
{
"anthropic": { "type": "api_key", "key": "sk-ant-..." },
"openai": { "type": "api_key", "key": "sk-..." }
}Auth file entries take precedence over environment variables. To switch from OAuth to API keys, remove the OAuth entry for that provider and add an api_key entry.
pie shares the same auth store as Pi: ~/.pi/agent/auth.json. If you have already run /login in Pi, pie will reuse those credentials automatically. You can also add API keys to the same file and both tools will pick them up.
Edit or delete ~/.pi/agent/auth.json to remove a provider and force re-login.
See docs/release.md for the release checklist.
import { extractSync, getModel, Type } from "@justram/pie";
const schema = Type.Object({
sentiment: Type.Union([
Type.Literal("positive"),
Type.Literal("negative"),
Type.Literal("neutral"),
]),
confidence: Type.Number({ minimum: 0, maximum: 1 }),
});
const model = getModel("anthropic", "claude-sonnet-4-5");
const data = await extractSync("I love this product!", {
schema,
prompt: "Classify the sentiment.",
model,
});
console.log(data);import { extract } from "@justram/pie";
const stream = extract("Example text", {
schema,
prompt: "Extract fields.",
model: getModel("openai", "gpt-4o"),
});
for await (const event of stream) {
if (event.type === "validation_error") {
console.error(event.layer, event.error);
}
}
const result = await stream.result();import { createFileCache, warmCache } from "@justram/pie/cache";
import { getModel } from "@justram/pie";
const model = getModel("anthropic", "claude-sonnet-4-5");
const store = createFileCache({ directory: "./cache" });
await warmCache(["doc1", "doc2"], {
schema,
prompt: "Extract fields.",
model,
cache: { store },
});const data = await extractSync(input, {
schema,
prompt: "Extract fields.",
model,
validateCommand: "jq -e '.score > 0.5'",
});cat input.txt | pie -s schema.json -p "Extract" \
--validate "jq -e '.score > 0.5'"const data = await extractSync(input, {
schema,
prompt: "Extract fields.",
model,
validateUrl: "https://api.example.com/validate",
});cat input.txt | pie -s schema.json -p "Extract" \
--validate-url "https://api.example.com/validate"pie --help
# Basic extraction
cat input.txt | pie \
-s schema.json \
-p "Extract fields" \
--model anthropic/claude-sonnet-4-5
# Stream partial JSON to stderr
cat input.txt | pie -s schema.json -p "Extract" --stream 2>progress.jsonl- A configured model credential (see Authentication).
- A JSON Schema (
--schema) and prompt (--prompt/--prompt-file) unless you use--configor--recipe.
--schema expects a valid JSON Schema. For object outputs, define type: "object" and properties.
# Object with a single required field
cat input.txt | pie \
-s '{"type":"object","properties":{"summary":{"type":"string"}},"required":["summary"],"additionalProperties":false}' \
-p "Extract a one-line summary" \
--model google-antigravity/gemini-3-flash# Single primitive value (pie wraps/unwraps for providers that require objects)
cat input.txt | pie \
-s '{"type":"string"}' \
-p "Summarize in one sentence" \
--model google-antigravity/gemini-3-flashIf you prefer a file, pass -s schema.json with the same JSON Schema content.
Recipes live in ~/.pie/recipes or ./.pie/recipes and allow reusable setups.
pie --list-recipes
pie --recipe support-triage --input ticket.txt- The package root (
@justram/pie) exposes the SDK surface fromsrc/index.ts(extract, models, recipes, cache, errors, types). - Cache helpers are also available via the
@justram/pie/cachesubpath.
src/cli.tsis the executable entry (shebang) and delegates tosrc/main.ts.src/cli/index.tsre-exportsrunClifor tests and internal callers.
# One-time setup
just dev-install
# Verify toolchain + local dependencies
just bootstrap-check
# Daily workflows
just check
just test full
just coverage
just fmt
just deps-check
just changelog preview
just hooks installjust is the primary developer interface. npm run scripts are kept minimal for build/test/release primitives.
MIT
