@eminent337/aery 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,18 @@
1
- > pi can create extensions. Ask it to build one for your use case.
1
+ > Aery can create extensions. Ask it to build one for your use case.
2
2
 
3
3
  # Extensions
4
4
 
5
- Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
5
+ Extensions are TypeScript modules that extend Aery's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
6
6
 
7
- > **Placement for /reload:** Put extensions in `~/.pi/agent/extensions/` (global) or `.pi/extensions/` (project-local) for auto-discovery. Use `pi -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
7
+ > **Placement for /reload:** Put extensions in `~/.aery/agent/extensions/` (global) or `.aery/extensions/` (project-local) for auto-discovery. Use `aery -e ./path.ts` only for quick tests. Extensions in auto-discovered locations can be hot-reloaded with `/reload`.
8
8
 
9
9
  **Key capabilities:**
10
- - **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
10
+ - **Custom tools** - Register tools the LLM can call via `aery.registerTool()`
11
11
  - **Event interception** - Block or modify tool calls, inject context, customize compaction
12
12
  - **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
13
13
  - **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
14
- - **Custom commands** - Register commands like `/mycommand` via `pi.registerCommand()`
15
- - **Session persistence** - Store state that survives restarts via `pi.appendEntry()`
14
+ - **Custom commands** - Register commands like `/mycommand` via `aery.registerCommand()`
15
+ - **Session persistence** - Store state that survives restarts via `aery.appendEntry()`
16
16
  - **Custom rendering** - Control how tool calls/results and messages appear in TUI
17
17
 
18
18
  **Example use cases:**
@@ -53,19 +53,19 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
53
53
 
54
54
  ## Quick Start
55
55
 
56
- Create `~/.pi/agent/extensions/my-extension.ts`:
56
+ Create `~/.aery/agent/extensions/my-extension.ts`:
57
57
 
58
58
  ```typescript
59
59
  import type { ExtensionAPI } from "@eminent337/aery";
60
60
  import { Type } from "@sinclair/typebox";
61
61
 
62
- export default function (pi: ExtensionAPI) {
62
+ export default function (aery: ExtensionAPI) {
63
63
  // React to events
64
- pi.on("session_start", async (_event, ctx) => {
64
+ aery.on("session_start", async (_event, ctx) => {
65
65
  ctx.ui.notify("Extension loaded!", "info");
66
66
  });
67
67
 
68
- pi.on("tool_call", async (event, ctx) => {
68
+ aery.on("tool_call", async (event, ctx) => {
69
69
  if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
70
70
  const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
71
71
  if (!ok) return { block: true, reason: "Blocked by user" };
@@ -73,7 +73,7 @@ export default function (pi: ExtensionAPI) {
73
73
  });
74
74
 
75
75
  // Register a custom tool
76
- pi.registerTool({
76
+ aery.registerTool({
77
77
  name: "greet",
78
78
  label: "Greet",
79
79
  description: "Greet someone by name",
@@ -89,7 +89,7 @@ export default function (pi: ExtensionAPI) {
89
89
  });
90
90
 
91
91
  // Register a command
92
- pi.registerCommand("hello", {
92
+ aery.registerCommand("hello", {
93
93
  description: "Say hello",
94
94
  handler: async (args, ctx) => {
95
95
  ctx.ui.notify(`Hello ${args || "world"}!`, "info");
@@ -101,7 +101,7 @@ export default function (pi: ExtensionAPI) {
101
101
  Test with `--extension` (or `-e`) flag:
102
102
 
103
103
  ```bash
104
- pi -e ./my-extension.ts
104
+ aery -e ./my-extension.ts
105
105
  ```
106
106
 
107
107
  ## Extension Locations
@@ -112,10 +112,10 @@ Extensions are auto-discovered from:
112
112
 
113
113
  | Location | Scope |
114
114
  |----------|-------|
115
- | `~/.pi/agent/extensions/*.ts` | Global (all projects) |
116
- | `~/.pi/agent/extensions/*/index.ts` | Global (subdirectory) |
117
- | `.pi/extensions/*.ts` | Project-local |
118
- | `.pi/extensions/*/index.ts` | Project-local (subdirectory) |
115
+ | `~/.aery/agent/extensions/*.ts` | Global (all projects) |
116
+ | `~/.aery/agent/extensions/*/index.ts` | Global (subdirectory) |
117
+ | `.aery/extensions/*.ts` | Project-local |
118
+ | `.aery/extensions/*/index.ts` | Project-local (subdirectory) |
119
119
 
120
120
  Additional paths via `settings.json`:
121
121
 
@@ -132,7 +132,7 @@ Additional paths via `settings.json`:
132
132
  }
133
133
  ```
134
134
 
135
- To share extensions via npm or git as pi packages, see [packages.md](packages.md).
135
+ To share extensions via npm or git as Aery packages, see [packages.md](packages.md).
136
136
 
137
137
  ## Available Imports
138
138
 
@@ -145,7 +145,7 @@ To share extensions via npm or git as pi packages, see [packages.md](packages.md
145
145
 
146
146
  npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.
147
147
 
148
- For distributed pi packages installed with `pi install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`npm install --omit=dev`), so `devDependencies` are not available at runtime.
148
+ For distributed Aery packages installed with `aery install` (npm or git), runtime deps must be in `dependencies`. Package installation uses production installs (`npm install --omit=dev`), so `devDependencies` are not available at runtime.
149
149
 
150
150
  Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
151
151
 
@@ -156,9 +156,9 @@ An extension exports a default factory function that receives `ExtensionAPI`. Th
156
156
  ```typescript
157
157
  import type { ExtensionAPI } from "@eminent337/aery";
158
158
 
159
- export default function (pi: ExtensionAPI) {
159
+ export default function (aery: ExtensionAPI) {
160
160
  // Subscribe to events
161
- pi.on("event_name", async (event, ctx) => {
161
+ aery.on("event_name", async (event, ctx) => {
162
162
  // ctx.ui for user interaction
163
163
  const ok = await ctx.ui.confirm("Title", "Are you sure?");
164
164
  ctx.ui.notify("Done!", "success");
@@ -167,16 +167,16 @@ export default function (pi: ExtensionAPI) {
167
167
  });
168
168
 
169
169
  // Register tools, commands, shortcuts, flags
170
- pi.registerTool({ ... });
171
- pi.registerCommand("name", { ... });
172
- pi.registerShortcut("ctrl+x", { ... });
173
- pi.registerFlag("my-flag", { ... });
170
+ aery.registerTool({ ... });
171
+ aery.registerCommand("name", { ... });
172
+ aery.registerShortcut("ctrl+x", { ... });
173
+ aery.registerFlag("my-flag", { ... });
174
174
  }
175
175
  ```
176
176
 
177
177
  Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
178
178
 
179
- If the factory returns a `Promise`, pi awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.
179
+ If the factory returns a `Promise`, Aery awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `aery.registerProvider()` are flushed.
180
180
 
181
181
  ### Async factory functions
182
182
 
@@ -185,7 +185,7 @@ Use an async factory for one-time startup work such as fetching remote configura
185
185
  ```typescript
186
186
  import type { ExtensionAPI } from "@eminent337/aery";
187
187
 
188
- export default async function (pi: ExtensionAPI) {
188
+ export default async function (aery: ExtensionAPI) {
189
189
  const response = await fetch("http://localhost:1234/v1/models");
190
190
  const payload = (await response.json()) as {
191
191
  data: Array<{
@@ -196,7 +196,7 @@ export default async function (pi: ExtensionAPI) {
196
196
  }>;
197
197
  };
198
198
 
199
- pi.registerProvider("local-openai", {
199
+ aery.registerProvider("local-openai", {
200
200
  baseUrl: "http://localhost:1234/v1",
201
201
  apiKey: "LOCAL_OPENAI_API_KEY",
202
202
  api: "openai-completions",
@@ -213,21 +213,21 @@ export default async function (pi: ExtensionAPI) {
213
213
  }
214
214
  ```
215
215
 
216
- This pattern makes the fetched models available during normal startup and to `pi --list-models`.
216
+ This pattern makes the fetched models available during normal startup and to `aery --list-models`.
217
217
 
218
218
  ### Extension Styles
219
219
 
220
220
  **Single file** - simplest, for small extensions:
221
221
 
222
222
  ```
223
- ~/.pi/agent/extensions/
223
+ ~/.aery/agent/extensions/
224
224
  └── my-extension.ts
225
225
  ```
226
226
 
227
227
  **Directory with index.ts** - for multi-file extensions:
228
228
 
229
229
  ```
230
- ~/.pi/agent/extensions/
230
+ ~/.aery/agent/extensions/
231
231
  └── my-extension/
232
232
  ├── index.ts # Entry point (exports default function)
233
233
  ├── tools.ts # Helper module
@@ -237,7 +237,7 @@ This pattern makes the fetched models available during normal startup and to `pi
237
237
  **Package with dependencies** - for extensions that need npm packages:
238
238
 
239
239
  ```
240
- ~/.pi/agent/extensions/
240
+ ~/.aery/agent/extensions/
241
241
  └── my-extension/
242
242
  ├── package.json # Declares dependencies and entry points
243
243
  ├── package-lock.json
@@ -254,7 +254,7 @@ This pattern makes the fetched models available during normal startup and to `pi
254
254
  "zod": "^3.0.0",
255
255
  "chalk": "^5.0.0"
256
256
  },
257
- "pi": {
257
+ "aery": {
258
258
  "extensions": ["./src/index.ts"]
259
259
  }
260
260
  }
@@ -267,7 +267,7 @@ Run `npm install` in the extension directory, then imports from `node_modules/`
267
267
  ### Lifecycle Overview
268
268
 
269
269
  ```
270
- pi starts
270
+ Aery starts
271
271
 
272
272
  ├─► session_start { reason: "startup" }
273
273
  └─► resources_discover { reason: "startup" }
@@ -337,7 +337,7 @@ Fired after `session_start` so extensions can contribute additional skill, promp
337
337
  The startup path uses `reason: "startup"`. Reload uses `reason: "reload"`.
338
338
 
339
339
  ```typescript
340
- pi.on("resources_discover", async (event, _ctx) => {
340
+ aery.on("resources_discover", async (event, _ctx) => {
341
341
  // event.cwd - current working directory
342
342
  // event.reason - "startup" | "reload"
343
343
  return {
@@ -357,7 +357,7 @@ See [session.md](session.md) for session storage internals and the SessionManage
357
357
  Fired when a session is started, loaded, or reloaded.
358
358
 
359
359
  ```typescript
360
- pi.on("session_start", async (event, ctx) => {
360
+ aery.on("session_start", async (event, ctx) => {
361
361
  // event.reason - "startup" | "reload" | "new" | "resume" | "fork"
362
362
  // event.previousSessionFile - present for "new", "resume", and "fork"
363
363
  ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
@@ -369,7 +369,7 @@ pi.on("session_start", async (event, ctx) => {
369
369
  Fired before starting a new session (`/new`) or switching sessions (`/resume`).
370
370
 
371
371
  ```typescript
372
- pi.on("session_before_switch", async (event, ctx) => {
372
+ aery.on("session_before_switch", async (event, ctx) => {
373
373
  // event.reason - "new" or "resume"
374
374
  // event.targetSessionFile - session we're switching to (only for "resume")
375
375
 
@@ -380,7 +380,7 @@ pi.on("session_before_switch", async (event, ctx) => {
380
380
  });
381
381
  ```
382
382
 
383
- After a successful switch or new-session action, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "new" | "resume"` and `previousSessionFile`.
383
+ After a successful switch or new-session action, Aery emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "new" | "resume"` and `previousSessionFile`.
384
384
  Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
385
385
 
386
386
  #### session_before_fork
@@ -388,7 +388,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
388
388
  Fired when forking via `/fork` or cloning via `/clone`.
389
389
 
390
390
  ```typescript
391
- pi.on("session_before_fork", async (event, ctx) => {
391
+ aery.on("session_before_fork", async (event, ctx) => {
392
392
  // event.entryId - ID of the selected entry
393
393
  // event.position - "before" for /fork, "at" for /clone
394
394
  return { cancel: true }; // Cancel fork/clone
@@ -397,7 +397,7 @@ pi.on("session_before_fork", async (event, ctx) => {
397
397
  });
398
398
  ```
399
399
 
400
- After a successful fork or clone, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `previousSessionFile`.
400
+ After a successful fork or clone, Aery emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `previousSessionFile`.
401
401
  Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
402
402
 
403
403
  #### session_before_compact / session_compact
@@ -405,7 +405,7 @@ Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `
405
405
  Fired on compaction. See [compaction.md](compaction.md) for details.
406
406
 
407
407
  ```typescript
408
- pi.on("session_before_compact", async (event, ctx) => {
408
+ aery.on("session_before_compact", async (event, ctx) => {
409
409
  const { preparation, branchEntries, customInstructions, signal } = event;
410
410
 
411
411
  // Cancel:
@@ -421,7 +421,7 @@ pi.on("session_before_compact", async (event, ctx) => {
421
421
  };
422
422
  });
423
423
 
424
- pi.on("session_compact", async (event, ctx) => {
424
+ aery.on("session_compact", async (event, ctx) => {
425
425
  // event.compactionEntry - the saved compaction
426
426
  // event.fromExtension - whether extension provided it
427
427
  });
@@ -432,14 +432,14 @@ pi.on("session_compact", async (event, ctx) => {
432
432
  Fired on `/tree` navigation. See [tree.md](tree.md) for tree navigation concepts.
433
433
 
434
434
  ```typescript
435
- pi.on("session_before_tree", async (event, ctx) => {
435
+ aery.on("session_before_tree", async (event, ctx) => {
436
436
  const { preparation, signal } = event;
437
437
  return { cancel: true };
438
438
  // OR provide custom summary:
439
439
  return { summary: { summary: "...", details: {} } };
440
440
  });
441
441
 
442
- pi.on("session_tree", async (event, ctx) => {
442
+ aery.on("session_tree", async (event, ctx) => {
443
443
  // event.newLeafId, oldLeafId, summaryEntry, fromExtension
444
444
  });
445
445
  ```
@@ -449,7 +449,7 @@ pi.on("session_tree", async (event, ctx) => {
449
449
  Fired on exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM).
450
450
 
451
451
  ```typescript
452
- pi.on("session_shutdown", async (_event, ctx) => {
452
+ aery.on("session_shutdown", async (_event, ctx) => {
453
453
  // Cleanup, save state, etc.
454
454
  });
455
455
  ```
@@ -461,7 +461,7 @@ pi.on("session_shutdown", async (_event, ctx) => {
461
461
  Fired after user submits prompt, before agent loop. Can inject a message and/or modify the system prompt.
462
462
 
463
463
  ```typescript
464
- pi.on("before_agent_start", async (event, ctx) => {
464
+ aery.on("before_agent_start", async (event, ctx) => {
465
465
  // event.prompt - user's prompt text
466
466
  // event.images - attached images (if any)
467
467
  // event.systemPrompt - current system prompt
@@ -484,9 +484,9 @@ pi.on("before_agent_start", async (event, ctx) => {
484
484
  Fired once per user prompt.
485
485
 
486
486
  ```typescript
487
- pi.on("agent_start", async (_event, ctx) => {});
487
+ aery.on("agent_start", async (_event, ctx) => {});
488
488
 
489
- pi.on("agent_end", async (event, ctx) => {
489
+ aery.on("agent_end", async (event, ctx) => {
490
490
  // event.messages - messages from this prompt
491
491
  });
492
492
  ```
@@ -496,11 +496,11 @@ pi.on("agent_end", async (event, ctx) => {
496
496
  Fired for each turn (one LLM response + tool calls).
497
497
 
498
498
  ```typescript
499
- pi.on("turn_start", async (event, ctx) => {
499
+ aery.on("turn_start", async (event, ctx) => {
500
500
  // event.turnIndex, event.timestamp
501
501
  });
502
502
 
503
- pi.on("turn_end", async (event, ctx) => {
503
+ aery.on("turn_end", async (event, ctx) => {
504
504
  // event.turnIndex, event.message, event.toolResults
505
505
  });
506
506
  ```
@@ -513,16 +513,16 @@ Fired for message lifecycle updates.
513
513
  - `message_update` fires for assistant streaming updates.
514
514
 
515
515
  ```typescript
516
- pi.on("message_start", async (event, ctx) => {
516
+ aery.on("message_start", async (event, ctx) => {
517
517
  // event.message
518
518
  });
519
519
 
520
- pi.on("message_update", async (event, ctx) => {
520
+ aery.on("message_update", async (event, ctx) => {
521
521
  // event.message
522
522
  // event.assistantMessageEvent (token-by-token stream event)
523
523
  });
524
524
 
525
- pi.on("message_end", async (event, ctx) => {
525
+ aery.on("message_end", async (event, ctx) => {
526
526
  // event.message
527
527
  });
528
528
  ```
@@ -537,15 +537,15 @@ In parallel tool mode:
537
537
  - `tool_execution_end` is emitted in assistant source order, matching final tool result message order
538
538
 
539
539
  ```typescript
540
- pi.on("tool_execution_start", async (event, ctx) => {
540
+ aery.on("tool_execution_start", async (event, ctx) => {
541
541
  // event.toolCallId, event.toolName, event.args
542
542
  });
543
543
 
544
- pi.on("tool_execution_update", async (event, ctx) => {
544
+ aery.on("tool_execution_update", async (event, ctx) => {
545
545
  // event.toolCallId, event.toolName, event.args, event.partialResult
546
546
  });
547
547
 
548
- pi.on("tool_execution_end", async (event, ctx) => {
548
+ aery.on("tool_execution_end", async (event, ctx) => {
549
549
  // event.toolCallId, event.toolName, event.result, event.isError
550
550
  });
551
551
  ```
@@ -555,7 +555,7 @@ pi.on("tool_execution_end", async (event, ctx) => {
555
555
  Fired before each LLM call. Modify messages non-destructively. See [session.md](session.md) for message types.
556
556
 
557
557
  ```typescript
558
- pi.on("context", async (event, ctx) => {
558
+ aery.on("context", async (event, ctx) => {
559
559
  // event.messages - deep copy, safe to modify
560
560
  const filtered = event.messages.filter(m => !shouldPrune(m));
561
561
  return { messages: filtered };
@@ -567,7 +567,7 @@ pi.on("context", async (event, ctx) => {
567
567
  Fired after the provider-specific payload is built, right before the request is sent. Handlers run in extension load order. Returning `undefined` keeps the payload unchanged. Returning any other value replaces the payload for later handlers and for the actual request.
568
568
 
569
569
  ```typescript
570
- pi.on("before_provider_request", (event, ctx) => {
570
+ aery.on("before_provider_request", (event, ctx) => {
571
571
  console.log(JSON.stringify(event.payload, null, 2));
572
572
 
573
573
  // Optional: replace payload
@@ -582,7 +582,7 @@ This is mainly useful for debugging provider serialization and cache behavior.
582
582
  Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.
583
583
 
584
584
  ```typescript
585
- pi.on("after_provider_response", (event, ctx) => {
585
+ aery.on("after_provider_response", (event, ctx) => {
586
586
  // event.status - HTTP status code
587
587
  // event.headers - normalized response headers
588
588
  if (event.status === 429) {
@@ -600,7 +600,7 @@ Header availability depends on provider and transport. Providers that abstract H
600
600
  Fired when the model changes via `/model` command, model cycling (`Ctrl+P`), or session restore.
601
601
 
602
602
  ```typescript
603
- pi.on("model_select", async (event, ctx) => {
603
+ aery.on("model_select", async (event, ctx) => {
604
604
  // event.model - newly selected model
605
605
  // event.previousModel - previous model (undefined if first selection)
606
606
  // event.source - "set" | "cycle" | "restore"
@@ -622,7 +622,7 @@ Use this to update UI elements (status bars, footers) or perform model-specific
622
622
 
623
623
  Fired after `tool_execution_start`, before the tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
624
624
 
625
- Before `tool_call` runs, pi waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.sessionManager` is up to date through the current assistant tool-calling message.
625
+ Before `tool_call` runs, Aery waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.sessionManager` is up to date through the current assistant tool-calling message.
626
626
 
627
627
  In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.sessionManager`.
628
628
 
@@ -637,7 +637,7 @@ Behavior guarantees:
637
637
  ```typescript
638
638
  import { isToolCallEventType } from "@eminent337/aery";
639
639
 
640
- pi.on("tool_call", async (event, ctx) => {
640
+ aery.on("tool_call", async (event, ctx) => {
641
641
  // event.toolName - "bash", "read", "write", "edit", etc.
642
642
  // event.toolCallId
643
643
  // event.input - tool parameters (mutable)
@@ -674,7 +674,7 @@ Use `isToolCallEventType` with explicit type parameters:
674
674
  import { isToolCallEventType } from "@eminent337/aery";
675
675
  import type { MyToolInput } from "my-extension";
676
676
 
677
- pi.on("tool_call", (event) => {
677
+ aery.on("tool_call", (event) => {
678
678
  if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {
679
679
  event.input.action; // typed
680
680
  }
@@ -695,7 +695,7 @@ Use `ctx.signal` for nested async work inside the handler. This lets Esc cancel
695
695
  ```typescript
696
696
  import { isBashToolResult } from "@eminent337/aery";
697
697
 
698
- pi.on("tool_result", async (event, ctx) => {
698
+ aery.on("tool_result", async (event, ctx) => {
699
699
  // event.toolName, event.toolCallId, event.input
700
700
  // event.content, event.details, event.isError
701
701
 
@@ -723,7 +723,7 @@ Fired when user executes `!` or `!!` commands. **Can intercept.**
723
723
  ```typescript
724
724
  import { createLocalBashOperations } from "@eminent337/aery";
725
725
 
726
- pi.on("user_bash", (event, ctx) => {
726
+ aery.on("user_bash", (event, ctx) => {
727
727
  // event.command - the bash command
728
728
  // event.excludeFromContext - true if !! prefix
729
729
  // event.cwd - working directory
@@ -731,7 +731,7 @@ pi.on("user_bash", (event, ctx) => {
731
731
  // Option 1: Provide custom operations (e.g., SSH)
732
732
  return { operations: remoteBashOps };
733
733
 
734
- // Option 2: Wrap pi's built-in local bash backend
734
+ // Option 2: Wrap Aery's built-in local bash backend
735
735
  const local = createLocalBashOperations();
736
736
  return {
737
737
  operations: {
@@ -760,7 +760,7 @@ Fired when user input is received, after extension commands are checked but befo
760
760
  5. Agent processing begins (`before_agent_start`, etc.)
761
761
 
762
762
  ```typescript
763
- pi.on("input", async (event, ctx) => {
763
+ aery.on("input", async (event, ctx) => {
764
764
  // event.text - raw input (before skill/template expansion)
765
765
  // event.images - attached images, if any
766
766
  // event.source - "interactive" (typed), "rpc" (API), or "extension" (via sendUserMessage)
@@ -836,10 +836,10 @@ Use this for abort-aware nested work started by extension handlers, for example:
836
836
  - file or process helpers that accept `AbortSignal`
837
837
 
838
838
  `ctx.signal` is typically defined during active turn events such as `tool_call`, `tool_result`, `message_update`, and `turn_end`.
839
- It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while pi is idle.
839
+ It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while Aery is idle.
840
840
 
841
841
  ```typescript
842
- pi.on("tool_result", async (event, ctx) => {
842
+ aery.on("tool_result", async (event, ctx) => {
843
843
  const response = await fetch("https://example.com/api", {
844
844
  method: "POST",
845
845
  body: JSON.stringify(event),
@@ -857,7 +857,7 @@ Control flow helpers.
857
857
 
858
858
  ### ctx.shutdown()
859
859
 
860
- Request a graceful shutdown of pi.
860
+ Request a graceful shutdown of Aery.
861
861
 
862
862
  - **Interactive mode:** Deferred until the agent becomes idle (after processing all queued steering and follow-up messages).
863
863
  - **RPC mode:** Deferred until the next idle state (after completing the current command response, when waiting for the next command).
@@ -866,7 +866,7 @@ Request a graceful shutdown of pi.
866
866
  Emits `session_shutdown` event to all extensions before exiting. Available in all contexts (event handlers, tools, commands, shortcuts).
867
867
 
868
868
  ```typescript
869
- pi.on("tool_call", (event, ctx) => {
869
+ aery.on("tool_call", (event, ctx) => {
870
870
  if (isFatal(event.input)) {
871
871
  ctx.shutdown();
872
872
  }
@@ -905,7 +905,7 @@ ctx.compact({
905
905
  Returns the current effective system prompt. This includes any modifications made by `before_agent_start` handlers for the current turn.
906
906
 
907
907
  ```typescript
908
- pi.on("before_agent_start", (event, ctx) => {
908
+ aery.on("before_agent_start", (event, ctx) => {
909
909
  const prompt = ctx.getSystemPrompt();
910
910
  console.log(`System prompt length: ${prompt.length}`);
911
911
  });
@@ -920,7 +920,7 @@ Command handlers receive `ExtensionCommandContext`, which extends `ExtensionCont
920
920
  Wait for the agent to finish streaming:
921
921
 
922
922
  ```typescript
923
- pi.registerCommand("my-cmd", {
923
+ aery.registerCommand("my-cmd", {
924
924
  handler: async (args, ctx) => {
925
925
  await ctx.waitForIdle();
926
926
  // Agent is now idle, safe to modify session
@@ -1004,7 +1004,7 @@ To discover available sessions, use the static `SessionManager.list()` or `Sessi
1004
1004
  ```typescript
1005
1005
  import { SessionManager } from "@eminent337/aery";
1006
1006
 
1007
- pi.registerCommand("switch", {
1007
+ aery.registerCommand("switch", {
1008
1008
  description: "Switch to another session",
1009
1009
  handler: async (args, ctx) => {
1010
1010
  const sessions = await SessionManager.list(ctx.cwd);
@@ -1025,7 +1025,7 @@ pi.registerCommand("switch", {
1025
1025
  Run the same reload flow as `/reload`.
1026
1026
 
1027
1027
  ```typescript
1028
- pi.registerCommand("reload-runtime", {
1028
+ aery.registerCommand("reload-runtime", {
1029
1029
  description: "Reload extensions, skills, prompts, and themes",
1030
1030
  handler: async (_args, ctx) => {
1031
1031
  await ctx.reload();
@@ -1052,8 +1052,8 @@ Example tool the LLM can call to trigger reload:
1052
1052
  import type { ExtensionAPI } from "@eminent337/aery";
1053
1053
  import { Type } from "@sinclair/typebox";
1054
1054
 
1055
- export default function (pi: ExtensionAPI) {
1056
- pi.registerCommand("reload-runtime", {
1055
+ export default function (aery: ExtensionAPI) {
1056
+ aery.registerCommand("reload-runtime", {
1057
1057
  description: "Reload extensions, skills, prompts, and themes",
1058
1058
  handler: async (_args, ctx) => {
1059
1059
  await ctx.reload();
@@ -1061,13 +1061,13 @@ export default function (pi: ExtensionAPI) {
1061
1061
  },
1062
1062
  });
1063
1063
 
1064
- pi.registerTool({
1064
+ aery.registerTool({
1065
1065
  name: "reload_runtime",
1066
1066
  label: "Reload Runtime",
1067
1067
  description: "Reload extensions, skills, prompts, and themes",
1068
1068
  parameters: Type.Object({}),
1069
1069
  async execute() {
1070
- pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
1070
+ aery.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
1071
1071
  return {
1072
1072
  content: [{ type: "text", text: "Queued /reload-runtime as a follow-up command." }],
1073
1073
  };
@@ -1078,17 +1078,17 @@ export default function (pi: ExtensionAPI) {
1078
1078
 
1079
1079
  ## ExtensionAPI Methods
1080
1080
 
1081
- ### pi.on(event, handler)
1081
+ ### aery.on(event, handler)
1082
1082
 
1083
1083
  Subscribe to events. See [Events](#events) for event types and return values.
1084
1084
 
1085
- ### pi.registerTool(definition)
1085
+ ### aery.registerTool(definition)
1086
1086
 
1087
1087
  Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) for full details.
1088
1088
 
1089
- `pi.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `pi.getAllTools()` and are callable by the LLM without `/reload`.
1089
+ `aery.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `aery.getAllTools()` and are callable by the LLM without `/reload`.
1090
1090
 
1091
- Use `pi.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
1091
+ Use `aery.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
1092
1092
 
1093
1093
  Use `promptSnippet` to opt a custom tool into a one-line entry in `Available tools`, and `promptGuidelines` to append tool-specific bullets to the default `Guidelines` section when the tool is active.
1094
1094
 
@@ -1098,7 +1098,7 @@ See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full examp
1098
1098
  import { Type } from "@sinclair/typebox";
1099
1099
  import { StringEnum } from "@eminent337/aery-ai";
1100
1100
 
1101
- pi.registerTool({
1101
+ aery.registerTool({
1102
1102
  name: "my_tool",
1103
1103
  label: "My Tool",
1104
1104
  description: "What this tool does",
@@ -1131,12 +1131,12 @@ pi.registerTool({
1131
1131
  });
1132
1132
  ```
1133
1133
 
1134
- ### pi.sendMessage(message, options?)
1134
+ ### aery.sendMessage(message, options?)
1135
1135
 
1136
1136
  Inject a custom message into the session.
1137
1137
 
1138
1138
  ```typescript
1139
- pi.sendMessage({
1139
+ aery.sendMessage({
1140
1140
  customType: "my-extension",
1141
1141
  content: "Message text",
1142
1142
  display: true,
@@ -1154,23 +1154,23 @@ pi.sendMessage({
1154
1154
  - `"nextTurn"` - Queued for next user prompt. Does not interrupt or trigger anything.
1155
1155
  - `triggerTurn: true` - If agent is idle, trigger an LLM response immediately. Only applies to `"steer"` and `"followUp"` modes (ignored for `"nextTurn"`).
1156
1156
 
1157
- ### pi.sendUserMessage(content, options?)
1157
+ ### aery.sendUserMessage(content, options?)
1158
1158
 
1159
1159
  Send a user message to the agent. Unlike `sendMessage()` which sends custom messages, this sends an actual user message that appears as if typed by the user. Always triggers a turn.
1160
1160
 
1161
1161
  ```typescript
1162
1162
  // Simple text message
1163
- pi.sendUserMessage("What is 2+2?");
1163
+ aery.sendUserMessage("What is 2+2?");
1164
1164
 
1165
1165
  // With content array (text + images)
1166
- pi.sendUserMessage([
1166
+ aery.sendUserMessage([
1167
1167
  { type: "text", text: "Describe this image:" },
1168
1168
  { type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
1169
1169
  ]);
1170
1170
 
1171
1171
  // During streaming - must specify delivery mode
1172
- pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
1173
- pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
1172
+ aery.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
1173
+ aery.sendUserMessage("And then summarize", { deliverAs: "followUp" });
1174
1174
  ```
1175
1175
 
1176
1176
  **Options:**
@@ -1182,15 +1182,15 @@ When not streaming, the message is sent immediately and triggers a new turn. Whe
1182
1182
 
1183
1183
  See [send-user-message.ts](../examples/extensions/send-user-message.ts) for a complete example.
1184
1184
 
1185
- ### pi.appendEntry(customType, data?)
1185
+ ### aery.appendEntry(customType, data?)
1186
1186
 
1187
1187
  Persist extension state (does NOT participate in LLM context).
1188
1188
 
1189
1189
  ```typescript
1190
- pi.appendEntry("my-state", { count: 42 });
1190
+ aery.appendEntry("my-state", { count: 42 });
1191
1191
 
1192
1192
  // Restore on reload
1193
- pi.on("session_start", async (_event, ctx) => {
1193
+ aery.on("session_start", async (_event, ctx) => {
1194
1194
  for (const entry of ctx.sessionManager.getEntries()) {
1195
1195
  if (entry.type === "custom" && entry.customType === "my-state") {
1196
1196
  // Reconstruct from entry.data
@@ -1199,35 +1199,35 @@ pi.on("session_start", async (_event, ctx) => {
1199
1199
  });
1200
1200
  ```
1201
1201
 
1202
- ### pi.setSessionName(name)
1202
+ ### aery.setSessionName(name)
1203
1203
 
1204
1204
  Set the session display name (shown in session selector instead of first message).
1205
1205
 
1206
1206
  ```typescript
1207
- pi.setSessionName("Refactor auth module");
1207
+ aery.setSessionName("Refactor auth module");
1208
1208
  ```
1209
1209
 
1210
- ### pi.getSessionName()
1210
+ ### aery.getSessionName()
1211
1211
 
1212
1212
  Get the current session name, if set.
1213
1213
 
1214
1214
  ```typescript
1215
- const name = pi.getSessionName();
1215
+ const name = aery.getSessionName();
1216
1216
  if (name) {
1217
1217
  console.log(`Session: ${name}`);
1218
1218
  }
1219
1219
  ```
1220
1220
 
1221
- ### pi.setLabel(entryId, label)
1221
+ ### aery.setLabel(entryId, label)
1222
1222
 
1223
1223
  Set or clear a label on an entry. Labels are user-defined markers for bookmarking and navigation (shown in `/tree` selector).
1224
1224
 
1225
1225
  ```typescript
1226
1226
  // Set a label
1227
- pi.setLabel(entryId, "checkpoint-before-refactor");
1227
+ aery.setLabel(entryId, "checkpoint-before-refactor");
1228
1228
 
1229
1229
  // Clear a label
1230
- pi.setLabel(entryId, undefined);
1230
+ aery.setLabel(entryId, undefined);
1231
1231
 
1232
1232
  // Read labels via sessionManager
1233
1233
  const label = ctx.sessionManager.getLabel(entryId);
@@ -1235,14 +1235,14 @@ const label = ctx.sessionManager.getLabel(entryId);
1235
1235
 
1236
1236
  Labels persist in the session and survive restarts. Use them to mark important points (turns, checkpoints) in the conversation tree.
1237
1237
 
1238
- ### pi.registerCommand(name, options)
1238
+ ### aery.registerCommand(name, options)
1239
1239
 
1240
1240
  Register a command.
1241
1241
 
1242
- If multiple extensions register the same command name, pi keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
1242
+ If multiple extensions register the same command name, Aery keeps them all and assigns numeric invocation suffixes in load order, for example `/review:1` and `/review:2`.
1243
1243
 
1244
1244
  ```typescript
1245
- pi.registerCommand("stats", {
1245
+ aery.registerCommand("stats", {
1246
1246
  description: "Show session statistics",
1247
1247
  handler: async (args, ctx) => {
1248
1248
  const count = ctx.sessionManager.getEntries().length;
@@ -1256,7 +1256,7 @@ Optional: add argument auto-completion for `/command ...`:
1256
1256
  ```typescript
1257
1257
  import type { AutocompleteItem } from "@eminent337/aery-tui";
1258
1258
 
1259
- pi.registerCommand("deploy", {
1259
+ aery.registerCommand("deploy", {
1260
1260
  description: "Deploy to an environment",
1261
1261
  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
1262
1262
  const envs = ["dev", "staging", "prod"];
@@ -1270,13 +1270,13 @@ pi.registerCommand("deploy", {
1270
1270
  });
1271
1271
  ```
1272
1272
 
1273
- ### pi.getCommands()
1273
+ ### aery.getCommands()
1274
1274
 
1275
1275
  Get the slash commands available for invocation via `prompt` in the current session. Includes extension commands, prompt templates, and skill commands.
1276
1276
  The list matches the RPC `get_commands` ordering: extensions first, then templates, then skills.
1277
1277
 
1278
1278
  ```typescript
1279
- const commands = pi.getCommands();
1279
+ const commands = aery.getCommands();
1280
1280
  const bySource = commands.filter((command) => command.source === "extension");
1281
1281
  const userScoped = commands.filter((command) => command.sourceInfo.scope === "user");
1282
1282
  ```
@@ -1303,16 +1303,16 @@ Use `sourceInfo` as the canonical provenance field. Do not infer ownership from
1303
1303
  Built-in interactive commands (like `/model` and `/settings`) are not included here. They are handled only in interactive
1304
1304
  mode and would not execute if sent via `prompt`.
1305
1305
 
1306
- ### pi.registerMessageRenderer(customType, renderer)
1306
+ ### aery.registerMessageRenderer(customType, renderer)
1307
1307
 
1308
1308
  Register a custom TUI renderer for messages with your `customType`. See [Custom UI](#custom-ui).
1309
1309
 
1310
- ### pi.registerShortcut(shortcut, options)
1310
+ ### aery.registerShortcut(shortcut, options)
1311
1311
 
1312
1312
  Register a keyboard shortcut. See [keybindings.md](keybindings.md) for the shortcut format and built-in keybindings.
1313
1313
 
1314
1314
  ```typescript
1315
- pi.registerShortcut("ctrl+shift+p", {
1315
+ aery.registerShortcut("ctrl+shift+p", {
1316
1316
  description: "Toggle plan mode",
1317
1317
  handler: async (ctx) => {
1318
1318
  ctx.ui.notify("Toggled!");
@@ -1320,39 +1320,39 @@ pi.registerShortcut("ctrl+shift+p", {
1320
1320
  });
1321
1321
  ```
1322
1322
 
1323
- ### pi.registerFlag(name, options)
1323
+ ### aery.registerFlag(name, options)
1324
1324
 
1325
1325
  Register a CLI flag.
1326
1326
 
1327
1327
  ```typescript
1328
- pi.registerFlag("plan", {
1328
+ aery.registerFlag("plan", {
1329
1329
  description: "Start in plan mode",
1330
1330
  type: "boolean",
1331
1331
  default: false,
1332
1332
  });
1333
1333
 
1334
1334
  // Check value
1335
- if (pi.getFlag("--plan")) {
1335
+ if (aery.getFlag("--plan")) {
1336
1336
  // Plan mode enabled
1337
1337
  }
1338
1338
  ```
1339
1339
 
1340
- ### pi.exec(command, args, options?)
1340
+ ### aery.exec(command, args, options?)
1341
1341
 
1342
1342
  Execute a shell command.
1343
1343
 
1344
1344
  ```typescript
1345
- const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
1345
+ const result = await aery.exec("git", ["status"], { signal, timeout: 5000 });
1346
1346
  // result.stdout, result.stderr, result.code, result.killed
1347
1347
  ```
1348
1348
 
1349
- ### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
1349
+ ### aery.getActiveTools() / aery.getAllTools() / aery.setActiveTools(names)
1350
1350
 
1351
1351
  Manage active tools. This works for both built-in tools and dynamically registered tools.
1352
1352
 
1353
1353
  ```typescript
1354
- const active = pi.getActiveTools();
1355
- const all = pi.getAllTools();
1354
+ const active = aery.getActiveTools();
1355
+ const all = aery.getAllTools();
1356
1356
  // [{
1357
1357
  // name: "read",
1358
1358
  // description: "Read file contents...",
@@ -1362,59 +1362,59 @@ const all = pi.getAllTools();
1362
1362
  const names = all.map(t => t.name);
1363
1363
  const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
1364
1364
  const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
1365
- pi.setActiveTools(["read", "bash"]); // Switch to read-only
1365
+ aery.setActiveTools(["read", "bash"]); // Switch to read-only
1366
1366
  ```
1367
1367
 
1368
- `pi.getAllTools()` returns `name`, `description`, `parameters`, and `sourceInfo`.
1368
+ `aery.getAllTools()` returns `name`, `description`, `parameters`, and `sourceInfo`.
1369
1369
 
1370
1370
  Typical `sourceInfo.source` values:
1371
1371
  - `builtin` for built-in tools
1372
1372
  - `sdk` for tools passed via `createAgentSession({ customTools })`
1373
1373
  - extension source metadata for tools registered by extensions
1374
1374
 
1375
- ### pi.setModel(model)
1375
+ ### aery.setModel(model)
1376
1376
 
1377
1377
  Set the current model. Returns `false` if no API key is available for the model. See [models.md](models.md) for configuring custom models.
1378
1378
 
1379
1379
  ```typescript
1380
1380
  const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
1381
1381
  if (model) {
1382
- const success = await pi.setModel(model);
1382
+ const success = await aery.setModel(model);
1383
1383
  if (!success) {
1384
1384
  ctx.ui.notify("No API key for this model", "error");
1385
1385
  }
1386
1386
  }
1387
1387
  ```
1388
1388
 
1389
- ### pi.getThinkingLevel() / pi.setThinkingLevel(level)
1389
+ ### aery.getThinkingLevel() / aery.setThinkingLevel(level)
1390
1390
 
1391
1391
  Get or set the thinking level. Level is clamped to model capabilities (non-reasoning models always use "off").
1392
1392
 
1393
1393
  ```typescript
1394
- const current = pi.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
1395
- pi.setThinkingLevel("high");
1394
+ const current = aery.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
1395
+ aery.setThinkingLevel("high");
1396
1396
  ```
1397
1397
 
1398
- ### pi.events
1398
+ ### aery.events
1399
1399
 
1400
1400
  Shared event bus for communication between extensions:
1401
1401
 
1402
1402
  ```typescript
1403
- pi.events.on("my:event", (data) => { ... });
1404
- pi.events.emit("my:event", { ... });
1403
+ aery.events.on("my:event", (data) => { ... });
1404
+ aery.events.emit("my:event", { ... });
1405
1405
  ```
1406
1406
 
1407
- ### pi.registerProvider(name, config)
1407
+ ### aery.registerProvider(name, config)
1408
1408
 
1409
1409
  Register or override a model provider dynamically. Useful for proxies, custom endpoints, or team-wide model configurations.
1410
1410
 
1411
1411
  Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a `/reload`.
1412
1412
 
1413
- If you need to discover models from a remote endpoint, prefer an async extension factory over deferring the fetch to `session_start`. pi waits for the factory before startup continues, so the registered models are available immediately, including to `pi --list-models`.
1413
+ If you need to discover models from a remote endpoint, prefer an async extension factory over deferring the fetch to `session_start`. Aery waits for the factory before startup continues, so the registered models are available immediately, including to `aery --list-models`.
1414
1414
 
1415
1415
  ```typescript
1416
1416
  // Register a new provider with custom models
1417
- pi.registerProvider("my-proxy", {
1417
+ aery.registerProvider("my-proxy", {
1418
1418
  baseUrl: "https://proxy.example.com",
1419
1419
  apiKey: "PROXY_API_KEY", // env var name or literal
1420
1420
  api: "anthropic-messages",
@@ -1432,12 +1432,12 @@ pi.registerProvider("my-proxy", {
1432
1432
  });
1433
1433
 
1434
1434
  // Override baseUrl for an existing provider (keeps all models)
1435
- pi.registerProvider("anthropic", {
1435
+ aery.registerProvider("anthropic", {
1436
1436
  baseUrl: "https://proxy.example.com"
1437
1437
  });
1438
1438
 
1439
1439
  // Register provider with OAuth support for /login
1440
- pi.registerProvider("corporate-ai", {
1440
+ aery.registerProvider("corporate-ai", {
1441
1441
  baseUrl: "https://ai.corp.com",
1442
1442
  api: "openai-responses",
1443
1443
  models: [...],
@@ -1472,17 +1472,17 @@ pi.registerProvider("corporate-ai", {
1472
1472
 
1473
1473
  See [custom-provider.md](custom-provider.md) for advanced topics: custom streaming APIs, OAuth details, model definition reference.
1474
1474
 
1475
- ### pi.unregisterProvider(name)
1475
+ ### aery.unregisterProvider(name)
1476
1476
 
1477
1477
  Remove a previously registered provider and its models. Built-in models that were overridden by the provider are restored. Has no effect if the provider was not registered.
1478
1478
 
1479
1479
  Like `registerProvider`, this takes effect immediately when called after the initial load phase, so a `/reload` is not required.
1480
1480
 
1481
1481
  ```typescript
1482
- pi.registerCommand("my-setup-teardown", {
1482
+ aery.registerCommand("my-setup-teardown", {
1483
1483
  description: "Remove the custom proxy provider",
1484
1484
  handler: async (_args, _ctx) => {
1485
- pi.unregisterProvider("my-proxy");
1485
+ aery.unregisterProvider("my-proxy");
1486
1486
  },
1487
1487
  });
1488
1488
  ```
@@ -1492,11 +1492,11 @@ pi.registerCommand("my-setup-teardown", {
1492
1492
  Extensions with state should store it in tool result `details` for proper branching support:
1493
1493
 
1494
1494
  ```typescript
1495
- export default function (pi: ExtensionAPI) {
1495
+ export default function (aery: ExtensionAPI) {
1496
1496
  let items: string[] = [];
1497
1497
 
1498
1498
  // Reconstruct state from session
1499
- pi.on("session_start", async (_event, ctx) => {
1499
+ aery.on("session_start", async (_event, ctx) => {
1500
1500
  items = [];
1501
1501
  for (const entry of ctx.sessionManager.getBranch()) {
1502
1502
  if (entry.type === "message" && entry.message.role === "toolResult") {
@@ -1507,7 +1507,7 @@ export default function (pi: ExtensionAPI) {
1507
1507
  }
1508
1508
  });
1509
1509
 
1510
- pi.registerTool({
1510
+ aery.registerTool({
1511
1511
  name: "my_tool",
1512
1512
  // ...
1513
1513
  async execute(toolCallId, params, signal, onUpdate, ctx) {
@@ -1523,11 +1523,11 @@ export default function (pi: ExtensionAPI) {
1523
1523
 
1524
1524
  ## Custom Tools
1525
1525
 
1526
- Register tools the LLM can call via `pi.registerTool()`. Tools appear in the system prompt and can have custom rendering.
1526
+ Register tools the LLM can call via `aery.registerTool()`. Tools appear in the system prompt and can have custom rendering.
1527
1527
 
1528
1528
  Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, custom tools are left out of that section.
1529
1529
 
1530
- Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `pi.setActiveTools([...])`).
1530
+ Use `promptGuidelines` to add tool-specific bullets to the default system prompt `Guidelines` section. These bullets are included only while the tool is active (for example, after `aery.setActiveTools([...])`).
1531
1531
 
1532
1532
  Note: Some models are idiots and include the @ prefix in tool path arguments. Built-in tools strip a leading @ before resolving paths. If your custom tool accepts a path, normalize a leading @ as well.
1533
1533
 
@@ -1568,7 +1568,7 @@ import { Type } from "@sinclair/typebox";
1568
1568
  import { StringEnum } from "@eminent337/aery-ai";
1569
1569
  import { Text } from "@eminent337/aery-tui";
1570
1570
 
1571
- pi.registerTool({
1571
+ aery.registerTool({
1572
1572
  name: "my_tool",
1573
1573
  label: "My Tool",
1574
1574
  description: "What this tool does (shown to LLM)",
@@ -1601,8 +1601,8 @@ pi.registerTool({
1601
1601
  details: { progress: 50 },
1602
1602
  });
1603
1603
 
1604
- // Run commands via pi.exec (captured from extension closure)
1605
- const result = await pi.exec("some-command", [], { signal });
1604
+ // Run commands via aery.exec (captured from extension closure)
1605
+ const result = await aery.exec("some-command", [], { signal });
1606
1606
 
1607
1607
  // Return result
1608
1608
  return {
@@ -1631,12 +1631,12 @@ async execute(toolCallId, params) {
1631
1631
 
1632
1632
  **Important:** Use `StringEnum` from `@eminent337/aery-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
1633
1633
 
1634
- **Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when pi resumes an older session whose stored tool call arguments no longer match the current schema. Return the object you want validated against `parameters`. Keep the public schema strict. Do not add deprecated compatibility fields to `parameters` just to keep old resumed sessions working.
1634
+ **Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when Aery resumes an older session whose stored tool call arguments no longer match the current schema. Return the object you want validated against `parameters`. Keep the public schema strict. Do not add deprecated compatibility fields to `parameters` just to keep old resumed sessions working.
1635
1635
 
1636
1636
  Example: an older session may contain an `edit` tool call with top-level `oldText` and `newText`, while the current schema only accepts `edits: [{ oldText, newText }]`.
1637
1637
 
1638
1638
  ```typescript
1639
- pi.registerTool({
1639
+ aery.registerTool({
1640
1640
  name: "edit",
1641
1641
  label: "Edit",
1642
1642
  description: "Edit a single file using exact text replacement",
@@ -1684,13 +1684,13 @@ Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`,
1684
1684
 
1685
1685
  ```bash
1686
1686
  # Extension's read tool replaces built-in read
1687
- pi -e ./tool-override.ts
1687
+ aery -e ./tool-override.ts
1688
1688
  ```
1689
1689
 
1690
1690
  Alternatively, use `--no-tools` to start without any built-in tools:
1691
1691
  ```bash
1692
1692
  # No built-in tools, only extension tools
1693
- pi --no-tools -e ./my-extension.ts
1693
+ aery --no-tools -e ./my-extension.ts
1694
1694
  ```
1695
1695
 
1696
1696
  See [examples/extensions/tool-override.ts](../examples/extensions/tool-override.ts) for a complete example that overrides `read` with logging and access control.
@@ -1726,7 +1726,7 @@ const remoteRead = createReadTool(cwd, {
1726
1726
  });
1727
1727
 
1728
1728
  // Register, checking flag at execution time
1729
- pi.registerTool({
1729
+ aery.registerTool({
1730
1730
  ...remoteRead,
1731
1731
  async execute(id, params, signal, onUpdate, _ctx) {
1732
1732
  const ssh = getSshConfig();
@@ -1741,7 +1741,7 @@ pi.registerTool({
1741
1741
 
1742
1742
  **Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
1743
1743
 
1744
- For `user_bash`, extensions can reuse pi's local shell backend via `createLocalBashOperations()` instead of reimplementing local process spawning, shell resolution, and process-tree termination.
1744
+ For `user_bash`, extensions can reuse Aery's local shell backend via `createLocalBashOperations()` instead of reimplementing local process spawning, shell resolution, and process-tree termination.
1745
1745
 
1746
1746
  The bash tool also supports a spawn hook to adjust the command, cwd, or env before execution:
1747
1747
 
@@ -1816,14 +1816,14 @@ See [examples/extensions/truncated-tool.ts](../examples/extensions/truncated-too
1816
1816
  One extension can register multiple tools with shared state:
1817
1817
 
1818
1818
  ```typescript
1819
- export default function (pi: ExtensionAPI) {
1819
+ export default function (aery: ExtensionAPI) {
1820
1820
  let connection = null;
1821
1821
 
1822
- pi.registerTool({ name: "db_connect", ... });
1823
- pi.registerTool({ name: "db_query", ... });
1824
- pi.registerTool({ name: "db_close", ... });
1822
+ aery.registerTool({ name: "db_connect", ... });
1823
+ aery.registerTool({ name: "db_query", ... });
1824
+ aery.registerTool({ name: "db_close", ... });
1825
1825
 
1826
- pi.on("session_shutdown", async () => {
1826
+ aery.on("session_shutdown", async () => {
1827
1827
  connection?.close();
1828
1828
  });
1829
1829
  }
@@ -1838,7 +1838,7 @@ By default, tool output is wrapped in a `Box` that handles padding and backgroun
1838
1838
  Set `renderShell: "self"` when the tool should render its own shell instead of using the default `Box`. This is useful for tools that need complete control over framing or background behavior, for example large previews that must stay visually stable after the tool settles.
1839
1839
 
1840
1840
  ```typescript
1841
- pi.registerTool({
1841
+ aery.registerTool({
1842
1842
  name: "my_tool",
1843
1843
  label: "My Tool",
1844
1844
  description: "Custom shell example",
@@ -2077,7 +2077,7 @@ ctx.ui.setFooter((tui, theme) => ({
2077
2077
  ctx.ui.setFooter(undefined); // Restore built-in footer
2078
2078
 
2079
2079
  // Terminal title
2080
- ctx.ui.setTitle("pi - my-project");
2080
+ ctx.ui.setTitle("aery - my-project");
2081
2081
 
2082
2082
  // Editor text
2083
2083
  ctx.ui.setEditorText("Prefill text");
@@ -2190,8 +2190,8 @@ class VimEditor extends CustomEditor {
2190
2190
  }
2191
2191
  }
2192
2192
 
2193
- export default function (pi: ExtensionAPI) {
2194
- pi.on("session_start", (_event, ctx) => {
2193
+ export default function (aery: ExtensionAPI) {
2194
+ aery.on("session_start", (_event, ctx) => {
2195
2195
  ctx.ui.setEditorComponent((_tui, theme, keybindings) =>
2196
2196
  new VimEditor(theme, keybindings)
2197
2197
  );
@@ -2214,7 +2214,7 @@ Register a custom renderer for messages with your `customType`:
2214
2214
  ```typescript
2215
2215
  import { Text } from "@eminent337/aery-tui";
2216
2216
 
2217
- pi.registerMessageRenderer("my-extension", (message, options, theme) => {
2217
+ aery.registerMessageRenderer("my-extension", (message, options, theme) => {
2218
2218
  const { expanded } = options;
2219
2219
  let text = theme.fg("accent", `[${message.customType}] `);
2220
2220
  text += message.content;
@@ -2227,10 +2227,10 @@ pi.registerMessageRenderer("my-extension", (message, options, theme) => {
2227
2227
  });
2228
2228
  ```
2229
2229
 
2230
- Messages are sent via `pi.sendMessage()`:
2230
+ Messages are sent via `aery.sendMessage()`:
2231
2231
 
2232
2232
  ```typescript
2233
- pi.sendMessage({
2233
+ aery.sendMessage({
2234
2234
  customType: "my-extension", // Matches registerMessageRenderer
2235
2235
  content: "Status update",
2236
2236
  display: true, // Show in TUI
@@ -2357,7 +2357,7 @@ All examples in [examples/extensions/](../examples/extensions/).
2357
2357
  | `custom-provider-gitlab-duo/` | GitLab Duo integration | `registerProvider` with OAuth |
2358
2358
  | **Messages & Communication** |||
2359
2359
  | `message-renderer.ts` | Custom message rendering | `registerMessageRenderer`, `sendMessage` |
2360
- | `event-bus.ts` | Inter-extension events | `pi.events` |
2360
+ | `event-bus.ts` | Inter-extension events | `aery.events` |
2361
2361
  | **Session Metadata** |||
2362
2362
  | `session-name.ts` | Name sessions for selector | `setSessionName`, `getSessionName` |
2363
2363
  | `bookmark.ts` | Bookmark entries for /tree | `setLabel` |