Download the PHP package pigagent/pig without Composer
On this page you can find all versions of the php package pigagent/pig. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package pig
pig — PHP AI Agent
English · 简体中文
A PHP port of pi, the agent harness whose coding agent runs on a famously small core. Same architecture, same file layout, written for PHP 8.3 with no runtime dependencies — no Guzzle, no ReactPHP, no amphp, no ncurses. Just the standard library.
Status: it runs.
bin/pigis a coding agent you can talk to in a terminal: streaming answers, tools with live output, edits shown as diffs, Escape to interrupt, typing while it works, and!cmdto run a shell command the model can then see (!!cmdkeeps it out of the conversation). Sessions are saved as they happen —--continuepicks up the last one,/resumepicks from a list. When the context fills it summarises itself and carries on, which/compactalso does on demand, and/modelswitches models mid-session. Skills are picked up from~/.pig/agent/skillsand from Claude's and Codex's folders too, and images a tool returns are drawn in the terminal. Hooks are PHP files that can block a tool, edit what the model is shown, or add commands of their own, and a folder with anindex.phpin it is a tool the model can call.-pprints one answer and exits, and--mode jsonor--mode rpcdrops the terminal for JSON lines, for an editor.
Packages
| Package | Namespace | State |
|---|---|---|
pig/async |
Pig\Async\ |
Event loop, futures, coroutines — done |
pig/ai |
Pig\Ai\ |
Unified LLM API — Anthropic, OpenAI chat-completions, OpenAI Responses and Gemini all stream end to end |
pig/agent-core |
Pig\Agent\ |
Agent loop with tool calling, JSON Schema validation of tool arguments, and state — done |
pig/tui |
Pig\Tui\ |
Terminal UI with differential rendering — done |
pig/coding-agent |
Pig\CodingAgent\ |
Coding agent — tools, prompt, interactive CLI, saved sessions, compaction, model switching, skills, hooks, custom tools and all three modes (terminal, print, RPC) done |
pig/async has no counterpart upstream: JavaScript ships an event loop and PHP does not. It
exists so one stream_select() can wait on the model's socket and on the keyboard at the same
time, which is what makes interrupting a running turn — and typing while the model streams —
possible at all.
Behind a proxy, which some networks require to reach a provider at all:
HTTP CONNECT and SOCKS5, with a username and password if the proxy wants one. TLS starts after
the tunnel is open and verifies the provider's certificate, so the proxy carries the bytes and can
read none of them; a hostname is handed to the proxy to resolve, because a local resolver is often
the other thing that does not work. Loopback always goes direct, no_proxy and proxy.bypass add
to that, and --no-proxy turns the lot off.
CodingAgent::session() is the whole of startup as a call: it resolves the model and the thinking
level, loads the skills, context files, hooks and custom tools, opens or creates the session file,
and hands back a session plus a list of warnings. bin/pig is a caller of it rather than the place
it lives, so the resolution order can be tested instead of run and looked at.
A different thing with the same name: Agent\StreamProxy sends the conversation to a gateway
server that holds the provider keys and makes the call, speaking upstream's /api/stream wire
format so a gateway written for pi works unchanged. Nothing turns it on — it is a streamFn you
pass in — because it hands the conversation to whoever runs that server, which is the point for a
team that wants no keys on laptops and a reason to avoid it otherwise.
A tool call is checked against the tool's own JSON Schema before it runs — Ai\Utils\JsonSchema
is a draft-07 subset with AJV's wording, because the reader of a validation failure is the model
that has to correct itself from it. An unknown keyword is ignored rather than failed, so adding a
keyword to a tool's schema can never break the tool.
Try it
The installer checks PHP and its extensions before touching anything, installs the package with
Composer, and offers to put Composer's global bin directory on your PATH — which is the step that is
otherwise easy to miss, because Composer says nothing about it and pig is then command not found.
Composer directly works too, with that last step left to you:
To work on pig itself, clone it instead — everything below is the same either way, with bin/pig in
place of pig:
--read-only takes away edit, write and bash; --theme light for a light terminal;
--api-key <key> uses a key for this run only, without saving it;
-c/--continue to pick up where you left off, or -r/--resume on its own to choose from
a list — type in that list to search it, and the search matches anything said in the
conversation, not just the line it opened with. Escape there starts a new conversation instead;
ctrl+c leaves without starting one. --session <id> resumes an earlier session by its ID or
path directly (printed upon quitting a session). /resume inside a session shows the same list, search and
all. -h for the flags, -v for the version.
/help inside lists every key, the prompt's own editing keys included, and every command.
/reload reloads extensions, skills, commands, tools, and context files (CLAUDE.md / AGENTS.md) live without restarting pig (upgrading pig's core code itself still requires restarting the process).
Shift+ctrl+d writes a debug log — the frame, how wide each line came out, and the conversation —
which is the thing to attach to a bug report.
A start asks Packagist once, in the background, whether there is a newer pig, and says so under
the conversation if there is — with the update command (pig update).
Nothing is said when the answer is no, when the network is not there, or when anything else goes
wrong. Run pig update anytime to self-update pig to the latest release (or pig update --models to
refresh model catalogs). --no-update-check skips the asking for one run, and "update": {"check": false} in
~/.pig/agent/settings.json turns it off for good. /changelog shows what changed, and the entries
newer than the version you last saw are shown once, by themselves, after an upgrade.
With a Claude Pro or Max subscription there is no key to set: /login gives you a URL to open
and takes the code that comes back. A GitHub Copilot subscription works the same way — it shows
a code to type at github.com and waits, and escape stops the waiting. Antigravity is the third:
it opens a browser and catches the redirect on localhost:51121, so that port has to be free, and
it needs a client id and secret of its own, which pig does not ship — set ANTIGRAVITY_CLIENT_ID
and ANTIGRAVITY_CLIENT_SECRET, or antigravity.clientId and antigravity.clientSecret in
~/.pig/agent/settings.json. It is what gets you Gemini 3, Claude and GPT-OSS through a Google
subscription. Whichever you use, the
token is kept in ~/.pi/agent/auth.json — pi's own file, when pi has one, so signing in once is
signing in once. /logout forgets it.
There is also bin/pig-ai login — the same four sign-ins from a plain command line, for a
machine you are setting up over ssh. bin/pig-ai list names them.
If that directory still has an old pi's oauth.json in it,
pig moves it across on the first run and says which providers it moved — the old file is renamed,
not deleted.
Anything that is not an option is a message, and @some/file is read in front of it:
An image goes in as an image, so a screenshot reaches the model without a tool call. -- ends
the options, for a message that starts with a dash or an @.
Ctrl+G opens whatever is in the prompt in $VISUAL or $EDITOR and puts the result back —
for the message that turned out to be three paragraphs. It works while the model is still
answering: the editor gets the terminal, and the answer keeps arriving behind it.
Typing while the model works is the point of the event loop, and there are two keys for it,
upstream's: Enter steers — the message goes in after the tool that is running, for "no, the
other file" — and Alt+Enter (Option+Enter) queues a follow-up for after the turn. Each waits
above the prompt as Steering: or Follow-up:, and Alt+Up takes them all back into the
editor without stopping anything; escape takes them back and stops the turn.
The model gets four tools by default — read, bash, edit, write — which is upstream's set;
--tools read,grep,find,ls names the set outright, and --read-only swaps in the read-only four.
Fewer tools is deliberate: a model with seven spends part of every turn choosing between them, and
searching through bash with rg is what the prompt already asks for.
--model takes a part of a name rather than a whole id — --model sonnet, --model 'opus 4.1' —
and --model sonnet:high sets the thinking level at the same time. --list-models lists the ones
you have a key for, with their context and output limits; --list-models gem pro narrows that,
fuzzily, over the provider and the id together. /model offers the same list, and switching to
a model with no key is refused by name rather than failing on the next turn. Ctrl+L opens that
list; Ctrl+P steps to the next model on it without opening it, Shift+Ctrl+P back to the previous
one.
--models narrows the session to a few of them, comma-separated, and then Ctrl+P walks only
those: --models sonnet,haiku opens on sonnet and cycles between the two, and
--models 'anthropic/*:high' takes every Anthropic model with thinking set high for each. A
pattern may be a glob or a part of a name, and :level on the end sets the level for that entry.
/model offers the scope too, so the list and the keystroke agree about what is on offer.
171 models are known: Anthropic's, OpenAI's own on the Responses API, Gemini, and Groq,
Cerebras, xAI, Zai and Mistral, which all speak OpenAI chat-completions. Set the matching
*_API_KEY and --model reaches them.
GitHub Copilot's nineteen are there too, and Antigravity's fourteen — the models those
subscriptions serve, under the same ids their own providers use. So gpt-5 and claude-sonnet-4-6
name two models each: a bare name means the direct provider, and a subscription's is
--model github-copilot/gpt-5 or --model antigravity/claude-sonnet-4-6.
/label before the refactor names where you are, and /tree shows the name beside what was
said — pi's own label entries, so a name set in either tool shows up in the other.
/tree goes back to an earlier point in the conversation and carries on from there. It draws the
whole conversation as a tree, forks and all, so the road not taken is a row you can move the
cursor onto rather than something the file merely still contains:
• marks the path you are on. Ctrl+O cycles five filters (everything, no tool results, only what
you said, only what you named, absolutely everything), typing searches — what you have typed is on
the Search: line above the rows — and l names the row under the cursor. Going back offers to summarise the branch you are leaving, so an hour of exploring
arrives on the branch you are joining instead of being left behind. Going back to something you
said takes it out of the conversation and puts it back in the prompt, to be asked differently.
Sessions are written in pi's own format, in pi's own directory layout — so a conversation
started in one opens in the other, and --resume lists what is in ~/.pi/agent/sessions/
beside pig's own. Carrying on from a pi session appends to that file, in that format.
Resuming brings back the model and the thinking level that conversation was being had with, not
whatever a new one would open with. --model still wins if you name one.
/changelog shows what changed release by release, and new entries are shown once after an
upgrade (not when you resume a conversation).
/export writes the conversation out as one self-contained HTML file — markdown rendered,
code highlighted, no JavaScript in it at all. pig --export <session.jsonl> [out.html] does the
same to a session on disk without opening it.
/bug [what went wrong] writes a bug report — pig, PHP and OS versions, the model and provider
(never a key), the last provider error, /doctor's findings, the last few crashes, and the
transcript only if you say yes — to ~/.pig/agent/bug-reports/, copies it, and opens a GitHub
issue with it prefilled. Nothing leaves the machine unless you open that link. When pig itself
falls over, the crash is written down and the next start says so, with /bug as the way to
report it.
A turn that fails because the provider is busy — a 429, a 503, a socket that died — is
waited out and sent again, doubling from two seconds, up to three times, with what the
provider said and a countdown on screen and escape to stop. When the provider names the moment
its quota comes back, that is the wait instead of the doubling — coming back in two seconds to a
quota that resets in forty is three more refusals. Past a minute it is not waited out at all: the
turn ends with what the provider said, because a retry that resumes after lunch is not a retry. A turn that fails because the
conversation outgrew the model's window is a different thing and is treated as one: it is
summarised first, then sent again, because the same request would be exactly as long in four
seconds. Escape stops that summarising as well, and stops it whether it was /compact that asked
or a full window. retry.enabled: false in the settings turns the first off.
A provider pig has never heard of goes in ~/.pig/agent/models.json — your own box, a proxy, a
local server — and its models then work everywhere a built-in one does, including --model,
/model and --list-models:
apiKey is the name of an environment variable if one answers to it, so the key itself
need not be in the file — and an all-capitals name with no variable set is treated as no key at
all, said at startup by name, rather than being sent to the endpoint as though it were the key. A model may carry a
"cost": { "input": …, "output": …, "cacheRead": …, "cacheWrite": … } block, in dollars per
million tokens — so three dollars per million is 3.0, not 0.000003 — and leaving it out means
free, which is what a local model is. A price that is not a number is named and the model skipped,
rather than quietly costing nothing in /session and the footer. api is one of openai-completions, openai-responses,
anthropic-messages or google-generative-ai, and can be set on the provider or per model;
authHeader: true sends the key as Authorization: Bearer … for a proxy that wants it there.
Anything wrong with the file is printed and skipped — the rest of it, and every built-in
model, still work. It is pi's format, and pi's own ~/.pi/agent/models.json is read when pig
has none.
Settings live in ~/.pig/agent/settings.json, and a project can override them in
.pig/settings.json. The theme, model and thinking level you pick are remembered.
/settings shows what can be changed from inside a session — theme, thinking, whether
reasoning is drawn, whether pictures are drawn, whether messages you type mid-run go over one at
a time or together, auto-compact, auto-retry — with what each one is set to now. Enter changes
the row you are on and the list stays open; escape closes it.
The keys the application takes — escape, ctrl+c/d/z, shift+tab, ctrl+p, ctrl+l, ctrl+o, ctrl+t,
ctrl+g, alt+enter, alt+up — can be moved in ~/.pig/agent/keybindings.json, under pi's action names and key spelling:
{"app.model.select": "ctrl+m", "app.tools.expand": ["ctrl+e", "shift+ctrl+o"]}. A binding
replaces the default, so moving ctrl+o frees it for tmux; [] unbinds; the help shows the keys that
actually work; a key or action the file names wrong is a line on the shell at startup.
A markdown file in ~/.pig/agent/commands/ or .pig/commands/ becomes a slash command: review.md
is /review, its body is the prompt, and $1 and $@ are filled from what follows. Those and
the ones a hook registers work outside the terminal too — pig -p "/review src/Foo.php" sends the
prompt, and pig -p "/deploy staging" runs the hook's command.
A PHP file in ~/.pig/agent/hooks/ or .pig/hooks/ that returns a callable is a hook. It gets
sixteen events — every tool call and result, every turn, the context on its way to the model,
compaction, /tree, startup and shutdown — and can block a tool, rewrite what the model is
shown, or add a slash command of its own:
A hook can also say something — $pi->sendMessage('build', 'the tests are failing') puts
it in the conversation where the model reads it, with display: false if it is for the model
and not for you, and triggerTurn: true to have the agent answer it there and then. A hook can
draw its own messages with registerMessageRenderer(). And $pi->appendEntry('permissions', $data) writes something into the session file that the model never sees — hook state that is
still there after a restart, costing no context.
A hook runs inside pig, so it can hand back an object and reach pig's own classes — and a
hook that loops or calls exit() takes the session with it. Broken ones are named on the
shell at startup rather than crashing; /hooks lists what loaded and --no-hooks skips them.
Which is why a project's own .pig/ is not loaded until you say so. The first time pig opens a
directory that has one — hooks, tools, extensions, skills, commands or a settings.json under
.pig/, or extensions/ with PHP in it — it asks whether to trust the project, and remembers the
answer in ~/.pig/agent/trust.json (trust a parent folder once and every checkout under it is
covered). An untrusted project keeps its .pig/ out and says so on screen; your own
~/.pig/agent/ is always yours. /trust changes the saved decision, and with no terminal to ask
on (-p, --mode json|rpc) an undecided project is untrusted, so a script run in a stranger's
repository cannot run that repository's hooks.
A hook can also ask, mid-turn, and wait for the answer:
The tool call parks, a picker appears, and the keystroke resumes it — the handler gets a
plain bool back. select, input, a multi-line editor, notify, a keyed footer line and
custom (draw your own component) are there too. Escape answers no, so walking away from the
question does not wave the tool through.
A folder with an index.php in ~/.pig/agent/tools/ or .pig/tools/ is a tool the model can
call — a folder, because that directory also holds the fd and rg binaries pig may have
downloaded, and a file there is one of those. Same loader as hooks, same trade-off; /tools
lists everything the model has and where each one came from, and --no-tools skips them:
$ctx is the session: the conversation so far, which model is answering, whether the agent
is busy, a way to stop it, and the same ui a hook gets. A tool can also be told when the
session starts, switches, jumps or ends, which is how one that keeps state rebuilds or lets
go of it. And it can draw its own call and its own result in the transcript,
so a tool whose answer is a table is not squeezed through formatting meant for files.
An extension is a PHP file (or a folder with index.php) in ~/.pig/agent/extensions/, .pig/extensions/
or extensions/ that unifies hooks, commands and tools into a single definition. The factory is given an
ExtensionApi, which inherits HookApi and adds registerTool(). --no-extensions skips them, and
--extension <path> loads an explicit file.
Skills are folders with a SKILL.md in them. pig reads ~/.pig/agent/skills and .pig/skills, and
also ~/.claude/skills, .claude/skills, ~/.codex/skills, ~/.pi/agent/skills and .pi/skills,
so a skill written for another agent — or for pi, before the move — works here unchanged. Two folders
holding the same name is an override rather than an error: pig > pi > claude > codex, a project
folder beats the home one, and --skills-dir beats all of them. /skills lists what was found and
where each one came from, and the startup says which file an override took the name from;
--no-skills loads none of them, as --no-hooks and --no-tools do for theirs. The five folders
another tool owns can also be turned off one at a time, under upstream's own settings keys —
skills.enableCodexUser, enableClaudeUser, enableClaudeProject, enablePiUser,
enablePiProject — which is what to reach for when a large ~/.claude/skills does not belong in
front of the model. pig's own two folders have no such key: skills.enabled and --no-skills are
the switch for those, and skills.ignoredSkills takes glob patterns for anything narrower.
Rpc\RpcClient is the other end of --mode rpc: it starts the agent, sends the twenty-two commands
and hands back events, with every call suspending its own fiber rather than returning a promise — so
a host reads like a program that blocks, and nothing blocks.
There are three ways in, and the terminal is only the default. -p says it, prints the
answer and exits — for a shell script, or a pipe:
--mode json is the same run with every event on standard output instead: the streaming a
terminal would draw, for something that wants to parse it. Warnings stay on standard error, so
the lines are all JSON.
--mode rpc reads commands as JSON lines and never stops: for an editor, or anything else
driving pig from code.
--mode web launches an interactive browser-based chat interface matching pi-web with zero external dependencies (pure PHP non-blocking WebSocket & HTTP engine, two-level workspace directory and session drawer, telemetry status bar, and real-time full-duplex RPC streaming):
You can also type /web from inside any interactive terminal session to launch the web interface on the fly.
To keep it running after you close the terminal, run it as a daemon:
pig web start without -d is the same as --mode web in the foreground. No framework
behind it: pcntl_fork() twice, posix_setsid(), a pid file — so ext-posix is needed for
-d and stop, and nothing else changes.
Every conversation you open in the browser gets a tab above the chat — labelled by its name
or its first line, working dot indicators while an agent streams, × to close, + for a new one —
persisted in the browser across reloads. The web shell follows pi-web's process model: each
conversation is backed by its own isolated pig --mode rpc child process in a managed pool (SessionPool),
so tabs run true parallel tasks concurrently without interfering with each other's stream or tools.
When launched via /web from inside the terminal, the terminal's own session stays entirely separate.
A hook that asks — a tool_call guard's confirm(), a select, an input, an editor —
asks in the page when pig was started with --mode web: the question goes out over the WebSocket
as the same hook_ui_request line RPC mode uses, a dialog opens, and the answer resumes the parked
tool call. Escape is no. Started from inside the terminal with /web, the terminal stays the place
hooks ask, because two places asking one question is one too many.
The accounts button (or /accounts) opens the Antigravity accounts the pig-antigravity
extension keeps: which Google account is live, when each token runs out, use / remove / rotate,
and the live account's quota pools with their reset times — the same figures /antigravity.usage
prints. Every change goes through the same code as /antigravity.accounts, so the store and
auth.json move together.
Twenty-three commands — prompt, steer, abort, switch models, compact, run a shell command,
walk the conversation tree, export — and the answer arrives as the same streaming events the terminal
draws. A hook can still ask: the question goes out as a hook_ui_request line and the tool
call parks until the host answers it, which is the terminal trick with a different transport.
examples/ask.php is the same stack with no UI at all:
Everything under that line is pig's own: one non-blocking TLS socket, HTTP/1.1 written by hand,
SSE parsed as it arrives, coroutines on Fiber. Ctrl-C mid-answer exercises the same abort path
the TUI uses.
Built-in Tools
pig ships with four default coding tools matching upstream: read, bash, edit, write.
Advanced file search (grep, find, ls) is reachable via --tools:
Web search and documentation reading are provided via the pure PHP extensions/pig-web-search/
extension — web_search (DuckDuckGo), fetch_web_page (the HTML the server sends, as text),
browse_web_page and /search <query> — keeping the core agent at its four tools.
browse_web_page is the one for pages that are built by JavaScript: it launches the Chrome already
installed on the machine in headless mode, speaks the DevTools Protocol to it over pig's own
WebSocket client, waits for the page to load, and reads the text out of the live DOM. No
Puppeteer, no driver, nothing downloaded — about two hundred lines of PHP, and a machine without
Chrome is told so rather than failed three layers down.
MCP servers are read from ~/.pig/agent/mcp.json and <project>/.pig/mcp.json — pi's file,
pi's keys — by extensions/pig-mcp/, and each server's tools reach the model as
mcp__<server>__<tool>, through the same hooks and permission gate as bash. Stdio and
streamable HTTP, ${VAR} and !command in env and headers, /mcp for status and
/mcp — a manager in the terminal (tools, reconnect, exposure, enable/disable, sign in), the
status elsewhere — and pig mcp add|remove|list|login|logout from the shell. An HTTP server with
no Authorization header signs in with OAuth (discovery, dynamic client registration, PKCE, a
loopback callback), tokens in ~/.pig/agent/mcp-auth.json and refreshed by the connection; a
server whose authorization server has no registration endpoint — GitHub's is one — takes a
pre-registered client under "oauth": { "clientId", "clientSecret", "callbackPort" }, and one
that advertises the wrong authorization server, or none, takes "authServerMetadataUrl". Credentials
are kept per server name and URL, so two servers at one URL can be two accounts; a code whose iss
names another authorization server is refused before it is exchanged (RFC 9207); and a server
asking for more scope gets a new token that keeps the scope it already had. A server
that offers resources brings list_mcp_resources, list_mcp_resource_templates and
read_mcp_resource, and what servers log goes to ~/.pig/agent/mcp.log.
codemode, in PHP (extensions/pig-codemode/): with a server at the default exposure the model
is shown one tool, codemode, whose description carries the server's tools as PHP signatures, and
it writes a script — $tools->mcp__gh__search_code([...]), parallel([...]) for independent
calls, ordinary PHP to filter the results, return for what it wants to see. Three slow tools cost
one, a sixty-tool server costs a catalog, and a large result costs what the script kept of it. The
script runs in a child php -n with disable_functions, open_basedir and a memory_limit
between it and the machine; nested calls go through pig's own tools and hooks, so the permission
gate holds inside a script too. A server with "exposure": "deferred" has its tools held back until the
model asks tool_search for them (BM25 over names, descriptions and schemas), which is how a
server with sixty tools costs the context eight. The tool's description is short — the intro, one
line per global, and the path of extensions/pig-codemode/CODEMODE.md, which the model reads when
it needs a detail — and a script that calls a tool that does not exist is told the close matches.
Requirements
PHP >= 8.3 with ext-json, ext-mbstring, ext-openssl, and ext-pcntl for the terminal UI.
stty is the one external binary that is required. fd and rg are needed by the find and
grep tools, and pig downloads them into ~/.pig/agent/bin/ on first use if they are not installed
(PIG_OFFLINE=1 turns that off).
Fiber arrived in PHP 8.1 and everything rests on it, so 8.1 is the absolute floor. 8.3 is the
floor actually declared: 8.1 is end-of-life and 8.2 loses security support at the end of 2026.
No Composer dependencies at runtime, which includes the provider protocols: the HTTP client,
the event-stream parser and all five providers are written here. Upstream does none of that — the
Anthropic, OpenAI and Google SDKs do it for pi. That is not a preference: PHP has no official SDK
for the OpenAI or Gemini APIs at all, and the official Anthropic one streams by iterating a PSR-18
response body synchronously, which would block the single stream_select() that watches the model's
socket and the keyboard together — and with it Esc-to-interrupt and typing while the model streams.
CLAUDE.md has the full arithmetic, and it is a dated judgement rather than a principle.
Development
The model registry's rows are generated, as upstream's are: scripts/generate-models.php reads
models.dev, keeps the models whose protocol is ported, and rewrites the tables in Ai\Models in
place — --dry-run prints them instead, and every run reports what was added, removed and changed
so a regeneration can be read rather than trusted. Everything around the rows — the base URLs, the
two subscription catalogues, the collision rules — is hand-written and untouched.
test/live.php is not part of the suite and never runs by itself: twelve scenarios per provider
against the real API, which is the only way to answer whether a provider accepts what pig sends —
a replayed thinking signature, a tool result invented for an interrupted call, a conversation carried
over from another provider, a prompt past the window. It takes its keys where pig takes its own, so a
machine that has signed in needs nothing, and php test/live.php anthropic google names which to run
— or php test/live.php google/<model-id> to try one particular model. The id has to be one the
registry carries, which is pinned at the upstream anchor commit; a model released since then is
declared in ~/.pig/agent/models.json and is then reachable both here and from bin/pig. Each call is
capped at a few hundred tokens.
Verify against the floor, not just your PHP: 8.3 rejects 8.4-only syntax at parse time, and it is easy to reach for a feature the declared floor does not have.
PIG_TIMING=1 bin/pig prints what each part of starting up cost, on standard error, before the
terminal takes over.
See CLAUDE.md for the porting rules, the decisions on record, and the traps found so far.
Upstream
Ported against pi at commit d0a4c37 (2026-01-02) — the snapshot where agent-loop.ts was 418
lines, before the harness grew. Class, file and method names track upstream so a diff against a
newer upstream commit stays mechanical.
License
MIT
All versions of pig with dependencies
composer-runtime-api Version ^2.0
ext-json Version *
ext-mbstring Version *
ext-openssl Version *
ext-pcntl Version *
ext-pcre Version *