# How to Extend pi-coding-agent with Custom CLI Flags and Functionality

> Extend pi-coding-agent with custom CLI flags and functionality. Learn to register new options and access values in event handlers without modifying core code. Enhance your workflow.

- Repository: [Mario Zechner/pi-mono](https://github.com/badlogic/pi-mono)
- Tags: how-to-guide
- Published: 2026-02-15

---

**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`](https://github.com/badlogic/pi-mono/blob/main/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`](https://github.com/badlogic/pi-mono/blob/main/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`](https://github.com/badlogic/pi-mono/blob/main/core/extensions/loader.ts) discovers and loads extension modules, while [`core/extensions/runner.ts`](https://github.com/badlogic/pi-mono/blob/main/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`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) and allows you to specify the flag name, description, type, and default value.

```typescript
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`](https://github.com/badlogic/pi-mono/blob/main/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 `--help` output
- **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`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts)).

```typescript
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`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/extensions/runner.ts).

```typescript
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.

```typescript
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:

```typescript
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:

```typescript
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`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/extensions/loader.ts) handles discovery and initialization via `discoverAndLoadExtensions`.

```bash

# 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`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/cli/args.ts):

```typescript
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:

```bash
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 like `session_start`, `input`, or `model_select` to conditionally enable functionality.
- **Load extensions** using the `--extension` or `-e` CLI argument, or through user settings for persistent use.
- **Test implementations** by importing `parseArgs` from [`packages/coding-agent/src/cli/args.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/cli/args.ts) to verify flag parsing logic.
- **Hook into events** such as `tool_call` and `model_select` to 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`](https://github.com/badlogic/pi-mono/blob/main/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`](https://github.com/badlogic/pi-mono/blob/main/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`](https://github.com/badlogic/pi-mono/blob/main/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.