How to Extend pi-coding-agent with Custom CLI Flags and Functionality
You can extend pi-coding-agent by creating an extension file that calls pi.registerFlag() to define new CLI options, then accessing those values via pi.getFlag() inside event handlers like session_start or model_select to implement custom behavior without modifying core source code.
The pi-coding-agent in the badlogic/pi-mono repository provides a modular extension system that allows developers to add custom CLI flags and functionality. By leveraging the extension API defined in packages/coding-agent/src/core/extensions/types.ts, you can register new command-line options that the parser recognizes and hook into the agent's event lifecycle to customize behavior based on user input.
Understanding the Extension Architecture
The extension system is orchestrated across several key files in the packages/coding-agent/src/ directory. The CLI argument parser in cli/args.ts handles flag parsing and populates the unknownFlags map (lines 44-45), which is then merged into the shared runtime state. The extension loader in core/extensions/loader.ts discovers and loads extension modules, while core/extensions/runner.ts binds the runtime and provides the pi API to extensions.
Registering Custom CLI Flags in pi-coding-agent
The registerFlag API
To add a custom CLI flag, use the pi.registerFlag() method provided in the extension context. This method is defined in packages/coding-agent/src/core/extensions/types.ts and allows you to specify the flag name, description, type, and default value.
export default async function (pi) {
pi.registerFlag("debug", {
description: "Enable verbose debugging output",
type: "boolean",
default: false,
});
}
When the user runs pi --debug, the flag is parsed by packages/coding-agent/src/cli/args.ts and placed into the unknownFlags map. The same map is later merged into the shared runtime state (flagValues in ExtensionRuntimeState, lines 46-48).
Flag Configuration Options
When registering a flag, you can configure:
- description: Text displayed in
--helpoutput - type: Currently supports
"boolean"for toggle flags - default: The fallback value when the flag is not provided
Accessing Flag Values at Runtime
Using pi.getFlag()
After registration, retrieve flag values anywhere in your extension using pi.getFlag(). This method accesses the flagValues map stored in ExtensionRuntimeState (defined in packages/coding-agent/src/core/extensions/types.ts).
export default async function (pi) {
pi.registerFlag("debug", {
description: "Enable verbose debugging output",
type: "boolean",
default: false,
});
pi.on("session_start", (event, ctx) => {
const debug = pi.getFlag("debug");
if (debug) {
console.log("[debug] New session started – enabling extra logging");
}
});
}
Accessing flagValues in Event Context
For advanced use cases, you can access the raw flagValues map through the event context's runtime property. This is populated by the extension runner in packages/coding-agent/src/core/extensions/runner.ts.
pi.on("input", (event, ctx) => {
const debug = ctx.runtime?.flagValues?.get("debug");
if (debug) {
console.log("[debug] User input:", event.text);
}
});
Implementing Custom Functionality Based on Flags
Hooking into Session Events
Custom flags can trigger behavior modifications during the session lifecycle. The session_start event is the ideal place to initialize features based on CLI input.
pi.on("session_start", (event, ctx) => {
if (pi.getFlag("verbose")) {
ctx.logger.setLevel("debug");
}
});
Modifying Model Selection
You can override the default model selection logic using the model_select event. This example shows how to switch to a fast model when the --fast-model flag is present:
export default async function (pi) {
pi.registerFlag("fast-model", {
description: "Prefer a low-latency model",
type: "boolean",
default: false,
});
pi.on("model_select", async (event, ctx) => {
if (pi.getFlag("fast-model")) {
const fast = ctx.modelRegistry.getModelById("gpt-4o-mini");
if (fast) {
await pi.setModel(fast);
console.log("[debug] Switched to fast model:", fast.name);
}
}
});
}
Debugging Tool Calls
For development and debugging extensions, you can instrument tool execution by listening to tool_call and tool_result events when a debug flag is active:
pi.on("tool_call", (event, ctx) => {
if (pi.getFlag("debug")) {
console.log("[debug] Tool called:", event.toolName, event);
}
});
Loading and Testing Your Extension
Loading Extensions via CLI
Extensions are loaded using the --extension (or -e) flag followed by the path to your extension file. The extension loader in packages/coding-agent/src/core/extensions/loader.ts handles discovery and initialization via discoverAndLoadExtensions.
# Load a single extension
pi --extension ./extensions/debug-mode.ts --debug "List all .ts files"
# Load multiple extensions
pi -e ./extensions/debug-mode.ts -e ./extensions/fast-model.ts --debug
Extensions can also be loaded via user settings for persistent configuration across sessions.
Testing Flag Parsing
To verify your custom flags are correctly registered and parsed, write unit tests using the parseArgs function from packages/coding-agent/src/cli/args.ts:
import { parseArgs } from "../../src/cli/args.js";
test("debug flag is parsed and stored", () => {
const args = parseArgs(["--debug"], new Map());
expect(args.unknownFlags.get("debug")).toBe(true);
});
Run the test with the repository's test runner:
npx tsx ../../node_modules/vitest/dist/cli.js --run test/my-flag.test.ts
Summary
- Register flags using
pi.registerFlag()in your extension file to define new CLI options with descriptions, types, and defaults. - Access values via
pi.getFlag()inside event handlers likesession_start,input, ormodel_selectto conditionally enable functionality. - Load extensions using the
--extensionor-eCLI argument, or through user settings for persistent use. - Test implementations by importing
parseArgsfrompackages/coding-agent/src/cli/args.tsto verify flag parsing logic. - Hook into events such as
tool_callandmodel_selectto implement custom behavior based on flag state without modifying core agent code.
Frequently Asked Questions
How do I register multiple custom flags in a single extension?
You can register as many flags as needed by calling pi.registerFlag() multiple times within the same extension file. Each flag must have a unique name. The extension loader in packages/coding-agent/src/core/extensions/loader.ts processes all registrations before the CLI parser validates the arguments, ensuring all custom flags are available when the agent starts.
What flag types are currently supported by pi-coding-agent?
Currently, the extension system primarily supports "boolean" type flags for toggle options. The flag definitions are processed by the parser in packages/coding-agent/src/cli/args.ts and stored in the unknownFlags map. Support for additional types like string or number may be added in future versions of the badlogic/pi-mono repository.
How do I ensure my extension loads before the agent starts processing commands?
Extensions are loaded automatically when specified via the --extension or -e CLI flag, or through user settings. The extension runner in packages/coding-agent/src/core/extensions/runner.ts initializes all extensions and registers their flags before the main application loop begins. To verify load order, implement a session_start event handler and confirm it executes before any user input is processed.
Can I modify the default model selection behavior using custom flags?
Yes, you can override model selection by listening to the model_select event and calling pi.setModel() when your custom flag is active. The event context provides access to ctx.modelRegistry.getModelById() to retrieve alternative models. This approach allows you to implement flags like --fast-model that switch to low-latency alternatives without hardcoding changes in the core agent.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →