@aliou/pi-dev-kit 0.6.5 → 0.7.1

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,345 +1,389 @@
1
1
  # Additional APIs
2
2
 
3
- These APIs are available on `ExtensionAPI` and `ExtensionContext` but are less commonly used. Each is shown with a minimal example.
4
-
5
- When you implement something using one of these APIs, update this skill reference with a fuller example based on your actual usage.
3
+ This reference covers less common `ExtensionAPI`, `ExtensionContext`, and `ExtensionCommandContext` APIs.
6
4
 
7
5
  ## Shortcuts
8
6
 
9
- Register global keyboard shortcuts:
7
+ Register keyboard shortcuts for interactive mode.
10
8
 
11
9
  ```typescript
12
10
  pi.registerShortcut("ctrl+shift+p", {
13
11
  description: "Toggle plan mode",
14
12
  handler: async (ctx) => {
15
13
  planModeEnabled = !planModeEnabled;
16
- ctx.ui.setStatus("plan", planModeEnabled ? "Plan Mode" : "");
14
+ ctx.ui.setStatus("plan-mode", planModeEnabled ? ctx.ui.theme.fg("accent", "Plan") : undefined);
17
15
  },
18
16
  });
19
17
  ```
20
18
 
21
- Shortcuts work only in Interactive mode.
19
+ Shortcuts are TUI-only.
22
20
 
23
21
  ## Flags
24
22
 
25
- Register boolean flags that persist across sessions:
23
+ Register CLI flags and read them in any handler.
26
24
 
27
25
  ```typescript
28
- // Register
29
- pi.registerFlag("auto-commit", {
30
- description: "Auto-commit after each turn",
26
+ pi.registerFlag("plan", {
27
+ description: "Start in plan mode",
28
+ type: "boolean",
31
29
  default: false,
32
30
  });
33
31
 
34
- // Read (in any handler)
35
- const autoCommit = pi.getFlag("auto-commit");
32
+ const planEnabled = pi.getFlag("plan") === true;
36
33
  ```
37
34
 
38
- Users toggle flags with `/flag auto-commit` in the input editor.
35
+ ## Commands and Sessions
39
36
 
40
- ## sendUserMessage
37
+ Command handlers receive `ExtensionCommandContext`, which adds session-control methods. These methods are command-only because they can deadlock from event handlers.
41
38
 
42
- Inject a user message into the conversation programmatically:
39
+ ### Wait for idle
43
40
 
44
41
  ```typescript
45
- pi.sendUserMessage("Please summarize what we just discussed");
42
+ pi.registerCommand("safe-command", {
43
+ handler: async (_args, ctx) => {
44
+ await ctx.waitForIdle();
45
+ // Safe to inspect or replace session state.
46
+ },
47
+ });
46
48
  ```
47
49
 
48
- This triggers a full agent turn as if the user typed the message. Useful for file watchers, timers, or other automated triggers.
49
-
50
- ## Session Name
50
+ ### New, fork, switch
51
51
 
52
- Set or get a name for the current session (shown in the session selector):
52
+ Use `withSession` for post-replacement work. Captured old `pi`, old command `ctx`, and old `ctx.sessionManager` are stale after replacement.
53
53
 
54
54
  ```typescript
55
- pi.setSessionName("Feature: Auth Refactor");
56
- const name = pi.getSessionName();
55
+ await ctx.newSession({
56
+ setup: async (sm) => {
57
+ sm.appendMessage({
58
+ role: "user",
59
+ content: [{ type: "text", text: "Seed context" }],
60
+ timestamp: Date.now(),
61
+ });
62
+ },
63
+ withSession: async (ctx) => {
64
+ await ctx.sendUserMessage("Continue from the new session");
65
+ },
66
+ });
57
67
  ```
58
68
 
59
- ## Labels
69
+ ```typescript
70
+ await ctx.fork(entryId, {
71
+ position: "at",
72
+ withSession: async (ctx) => ctx.ui.notify("Forked", "info"),
73
+ });
74
+
75
+ await ctx.switchSession(sessionPath, {
76
+ withSession: async (ctx) => {
77
+ await ctx.sendUserMessage("Resume this work here");
78
+ },
79
+ });
80
+ ```
60
81
 
61
- Set a label on a specific session entry (shown in `/tree` view):
82
+ ### Tree navigation
62
83
 
63
84
  ```typescript
64
- pi.setLabel(entryId, "checkpoint: before refactor");
85
+ await ctx.navigateTree(targetId, {
86
+ summarize: true,
87
+ customInstructions: "Focus on implementation decisions",
88
+ replaceInstructions: false,
89
+ label: "review-checkpoint",
90
+ });
65
91
  ```
66
92
 
67
- ## exec
93
+ ## Reload
68
94
 
69
- Run a shell command and get the result. This is the **only** way to run external binaries or shell scripts from an extension.
95
+ Use `ctx.reload()` in command handlers. Treat it as terminal for predictable behavior.
70
96
 
71
97
  ```typescript
72
- const result = await pi.exec("git status --porcelain", { cwd: process.cwd() });
73
- // result: { stdout, stderr, exitCode }
98
+ pi.registerCommand("reload-runtime", {
99
+ description: "Reload extensions, skills, prompts, and themes",
100
+ handler: async (_args, ctx) => {
101
+ await ctx.reload();
102
+ return;
103
+ },
104
+ });
74
105
  ```
75
106
 
76
- Useful for git operations, environment checks, running CLI tools, etc.
107
+ Code after `await ctx.reload()` still runs in the old call frame. Avoid post-reload work in that handler.
77
108
 
78
- **Do not use Node `child_process` APIs** (`exec`, `execSync`, `spawn`, `spawnSync`, `execFile`, `execFileSync`). `pi.exec` handles CWD resolution, output capture, and integrates with the extension lifecycle. Using `child_process` directly bypasses these guarantees and creates inconsistent behavior across environments.
109
+ Tools cannot call `ctx.reload()`. If the LLM needs a reload tool, create a tool that queues a reload command as a follow-up user message.
79
110
 
80
- The only exception is a long-lived streaming process that requires direct stdin/stdout piping — document the reason in code comments if this applies.
111
+ ```typescript
112
+ async execute() {
113
+ pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
114
+ return { content: [{ type: "text", text: "Queued /reload-runtime." }] };
115
+ }
116
+ ```
81
117
 
82
- ## Active Tools
118
+ ## Sending Messages
119
+
120
+ ### `pi.sendUserMessage(content, options?)`
83
121
 
84
- Get or set which tools are currently active:
122
+ Sends an actual user message and triggers a turn.
85
123
 
86
124
  ```typescript
87
- const tools = pi.getActiveTools(); // string[]
88
- pi.setActiveTools(["bash", "read", "write", "my_custom_tool"]);
125
+ pi.sendUserMessage("Summarize the current state.");
126
+ pi.sendUserMessage("Focus on tests next.", { deliverAs: "steer" });
127
+ pi.sendUserMessage("After that, summarize.", { deliverAs: "followUp" });
89
128
  ```
90
129
 
91
- Setting active tools restricts which tools the LLM can use.
130
+ When the agent is streaming, specify `deliverAs: "steer"` or `"followUp"`.
92
131
 
93
- ## Model Control
132
+ ### `pi.sendMessage(message, options?)`
133
+
134
+ Sends a custom message. Use `registerMessageRenderer` for custom display.
94
135
 
95
136
  ```typescript
96
- // Set the active model
97
- const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
98
- if (model) {
99
- const success = await pi.setModel(model);
100
- if (!success) ctx.ui.notify("No API key for this model", "error");
101
- }
137
+ pi.sendMessage(
138
+ {
139
+ customType: "my-extension-status",
140
+ content: "Status update",
141
+ display: true,
142
+ details: { status: "ok" },
143
+ },
144
+ { deliverAs: "nextTurn" },
145
+ );
146
+ ```
102
147
 
103
- // Get/set thinking level
104
- const level = pi.getThinkingLevel(); // "off" | "minimal" | "low" | "medium" | "high" | "xhigh"
105
- pi.setThinkingLevel("high");
148
+ Delivery modes:
149
+
150
+ - `steer`: queue while streaming and deliver before next LLM call.
151
+ - `followUp`: wait until the agent finishes.
152
+ - `nextTurn`: save for the next user prompt.
153
+
154
+ ## State and Session Metadata
155
+
156
+ ```typescript
157
+ pi.appendEntry("my-extension-state", { enabled: true });
158
+ pi.setSessionName("Feature: auth refactor");
159
+ const name = pi.getSessionName();
160
+ pi.setLabel(entryId, "checkpoint-before-refactor");
161
+ pi.setLabel(entryId, undefined); // clear
106
162
  ```
107
163
 
108
- ## System Prompt
164
+ Use labels for `/tree` bookmarks and session names for the session selector.
165
+
166
+ ## Shell Execution
109
167
 
110
- Read or modify the system prompt (typically in `before_agent_start`):
168
+ Use `pi.exec(command, args, options?)` for shell commands.
111
169
 
112
170
  ```typescript
113
- pi.on("before_agent_start", async (_event, ctx) => {
114
- const prompt = ctx.getSystemPrompt();
115
- ctx.setSystemPrompt(prompt + "\n\nExtra instructions.");
171
+ const result = await pi.exec("git", ["status", "--porcelain"], {
172
+ cwd: ctx.cwd,
173
+ signal,
174
+ timeout: 5_000,
116
175
  });
176
+
177
+ // result.stdout, result.stderr, result.code, result.killed
117
178
  ```
118
179
 
119
- The system prompt resets each turn, so modifications are not cumulative. In `before_agent_start`, `ctx.getSystemPrompt()` reflects prompt changes from earlier handlers, and the event includes `systemPromptOptions` for structured prompt inputs.
180
+ Do not use Node `child_process` APIs for normal command execution. The only exception is a documented long-lived streaming process with direct stdin/stdout needs that `pi.exec()` cannot support.
181
+
182
+ ## Active Tools
183
+
184
+ ```typescript
185
+ const active = pi.getActiveTools();
186
+ const all = pi.getAllTools();
187
+ const builtin = all.filter((tool) => tool.sourceInfo.source === "builtin");
120
188
 
121
- ### Guidance Injection Pattern
189
+ pi.setActiveTools(["read", "grep", "find"]);
190
+ ```
122
191
 
123
- Extensions that add tools or behavioral patterns the agent may not know how to use correctly should inject guidance into the system prompt. Without it, agents fall back to bash workarounds even when a better tool is available.
192
+ `pi.getAllTools()` includes built-in tools, SDK tools, and extension tools with `sourceInfo` provenance.
124
193
 
125
- **When to inject guidance:**
126
- - Your extension adds a tool that competes with a natural bash fallback (e.g. a process manager, a CI watcher, a search tool)
127
- - Correct usage depends on subtle conditions (alert flags, when-not-to-use, alert vs. poll)
128
- - You have observed agents ignoring the tool or reaching for `bash` instead
194
+ ## Model and Thinking Control
129
195
 
130
- **When not to:**
131
- - The tool description alone is self-explanatory
132
- - The tool has no plausible bash alternative
196
+ ```typescript
197
+ const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
198
+ if (model) {
199
+ const success = await pi.setModel(model);
200
+ if (!success) ctx.ui.notify("No API key for this model", "error");
201
+ }
133
202
 
134
- ---
203
+ const level = pi.getThinkingLevel();
204
+ pi.setThinkingLevel("high");
205
+ ```
135
206
 
136
- There are two ways to inject guidance, depending on complexity:
207
+ Thinking levels are `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, and `"xhigh"`. Pi clamps unsupported levels to model capabilities.
137
208
 
138
- #### Tier 1: Per-Tool Metadata (Preferred for Simple Tools)
209
+ ## System Prompt Guidance
139
210
 
140
- For most tools, use the SDK-level `promptSnippet` and `promptGuidelines` fields directly on the tool definition. No hook is needed.
211
+ Prefer tool-level `promptSnippet` and `promptGuidelines` for simple guidance. Use `before_agent_start` only for dynamic or cross-tool guidance.
141
212
 
142
- - **`promptSnippet`** — Injected into the "Available tools" system prompt section. Use for a concise (1–2 sentence) description of when to prefer this tool.
143
- - **`promptGuidelines`** — Appended verbatim to the global "Guidelines" section. Use for a short list of usage rules that still make sense without extra tool-local context.
213
+ ### Per-tool metadata
144
214
 
145
215
  ```typescript
146
- const myTool = {
216
+ const myTool = defineTool({
147
217
  name: "my_tool",
148
218
  label: "My Tool",
149
219
  description: "...",
150
- promptSnippet: "Manage background processes without blocking the conversation.",
220
+ promptSnippet: "Manage background work without blocking the conversation.",
151
221
  promptGuidelines: [
152
- "Use my_tool for long-running commands instead of bash.",
153
- "After starting my_tool, continue other work instead of waiting.",
222
+ "Use my_tool for long-running commands instead of shell backgrounding.",
223
+ "After starting my_tool, continue useful work instead of polling my_tool immediately.",
154
224
  ],
155
- parameters: ...,
156
- execute: ...,
157
- };
225
+ parameters,
226
+ async execute() {
227
+ // ...
228
+ },
229
+ });
158
230
  ```
159
231
 
160
- This is the simplest approach and works well when guidance is specific to a single tool.
161
-
162
- Because these bullets are merged into the shared global `Guidelines` section, avoid vague phrasing like `Use this tool...`. Name the exact tool (`my_tool`, `process`, `linkup_web_search`) so the bullet remains clear after injection.
163
-
164
- #### Tier 2: System Prompt Hook (For Complex Cross-Tool Orchestration)
232
+ Every `promptGuidelines` bullet must name the exact tool because Pi injects bullets flat into the global Guidelines section.
165
233
 
166
- Use the `before_agent_start` hook when:
167
- - Guidance involves **cross-tool workflow instructions** (e.g. "use tool A, then tool B, then tool C")
168
- - You need **dynamic context from config** (e.g. workspace names, team keys, feature flags)
169
- - The per-tool metadata fields aren't expressive enough
170
-
171
- **Structure: three files**
172
-
173
- `src/guidance.ts` — the guidance text as a named export:
234
+ ### System prompt hook
174
235
 
175
236
  ```typescript
176
237
  export const MY_EXTENSION_GUIDANCE = `
177
238
  ## My Extension
178
239
 
179
- Use the \`my_tool\` tool for X. Don't use bash for X.
180
-
181
- **Use \`my_tool\` when:**
182
- - Situation A
183
- - Situation B
184
-
185
- **Use \`bash\` when:**
186
- - You need the result immediately to proceed (quick commands that finish in seconds)
187
-
188
- **Never do this:**
189
- \`\`\`bash
190
- workaround_command # loses observability
191
- \`\`\`
192
-
193
- **Do this instead:**
194
- \`\`\`
195
- my_tool({ action: "start", ... })
196
- \`\`\`
240
+ Use \`my_tool\` when ...
241
+ Do not use bash workaround ...
197
242
  `;
243
+
244
+ pi.on("before_agent_start", async (event) => {
245
+ if (!configLoader.getConfig().systemPromptGuidance) return;
246
+ return {
247
+ systemPrompt: `${event.systemPrompt}\n\n${MY_EXTENSION_GUIDANCE}`,
248
+ };
249
+ });
198
250
  ```
199
251
 
200
- `src/hooks/system-prompt.ts` the hook:
252
+ Use `event.systemPromptOptions` when you need structured prompt inputs such as selected tools, loaded skills, context files, and accumulated prompt guidelines.
201
253
 
202
- ```typescript
203
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
204
- import { configLoader } from "../config";
205
- import { MY_EXTENSION_GUIDANCE } from "../guidance";
254
+ ## Compaction and Shutdown
206
255
 
207
- export function registerGuidance(pi: ExtensionAPI): void {
208
- pi.on("before_agent_start", async (event) => {
209
- const config = configLoader.getConfig();
210
- if (!config.systemPromptGuidance) return;
256
+ Use `ctx.compact()` to trigger compaction without awaiting the full operation.
211
257
 
212
- return {
213
- systemPrompt: `${event.systemPrompt}\n${MY_EXTENSION_GUIDANCE}`,
214
- };
215
- });
216
- }
258
+ ```typescript
259
+ ctx.compact({
260
+ customInstructions: "Focus on recent code changes",
261
+ onComplete: (result) => ctx.ui.notify("Compaction complete", "info"),
262
+ onError: (error) => ctx.ui.notify(`Compaction failed: ${error.message}`, "error"),
263
+ });
217
264
  ```
218
265
 
219
- `src/config.ts` add the toggle (default `true`):
266
+ Use `ctx.shutdown()` to request graceful shutdown.
220
267
 
221
268
  ```typescript
222
- export interface MyExtensionConfig {
223
- // ...
224
- /** Inject tool guidance into the system prompt each turn. Default: true. */
225
- systemPromptGuidance?: boolean;
226
- }
269
+ ctx.shutdown();
227
270
  ```
228
271
 
229
- Call `registerGuidance(pi)` from your hooks setup function.
272
+ In interactive and RPC modes, shutdown is deferred until Pi becomes idle. In print mode it is a no-op.
230
273
 
231
- ---
274
+ ## Event Bus
232
275
 
233
- **What makes guidance effective:**
234
-
235
- - Lead with the decision rule: **when to use AND when not to use**. The when-not-to-use is as important — it gives the agent permission to keep using `bash` for quick tasks and prevents overcorrection.
236
- - Name anti-patterns explicitly by their exact form (`cmd &`, `nohup`, `sleep 30 &&`). Abstract descriptions ("don't use bash workarounds") are ignored.
237
- - Use 2–3 tight code examples. More than that dilutes attention; fewer leave the pattern underspecified.
238
- - Keep the guidance section header (`## My Extension`) so it reads as a named capability, not a restriction.
239
- - Avoid stacking emphasis markers (`NEVER`, `ALWAYS`, `IMPORTANT`). One or two land; more are ignored.
240
-
241
- **Reference implementations:**
242
- - `pi-linkup` — Uses per-tool `promptSnippet`/`promptGuidelines` (simple tools, no hook needed).
243
- - `pi-linear` — Uses `guidance.ts` + `before_agent_start` hook (cross-tool workflow instructions + dynamic workspace context).
244
- - `pi-processes` — Uses both: `promptSnippet`/`promptGuidelines` on tools for basic guidance, plus system prompt hook for complex multi-tool orchestration patterns.
245
-
246
- ## Session Replacement
247
-
248
- `ctx.newSession()`, `ctx.fork()`, and `ctx.switchSession()` invalidate captured pre-replacement session-bound objects after the replacement. Use `withSession` for post-switch work and only use the fresh callback context there.
276
+ Use the shared event bus only when extensions need to coordinate.
249
277
 
250
278
  ```typescript
251
- await ctx.newSession({
252
- setup: async (sm) => {
253
- sm.appendCustomMessageEntry("my-source", "Seed context", true);
254
- },
255
- withSession: async (ctx) => {
256
- await ctx.sendUserMessage("Continue from the new session");
257
- },
279
+ pi.events.emit("my-extension:data-ready", { items });
280
+ pi.events.on("my-extension:data-ready", (data) => {
281
+ console.log(data.items.length);
258
282
  });
259
283
  ```
260
284
 
261
- Do not reuse captured `pi`, command `ctx`, or `ctx.sessionManager` after a session replacement.
285
+ Namespace event names with your extension name.
262
286
 
263
- ## Compaction
287
+ ## UI Customization
264
288
 
265
- Trigger compaction programmatically:
289
+ ### Working indicator and message
266
290
 
267
291
  ```typescript
268
- await pi.compact();
269
- ```
292
+ ctx.ui.setWorkingMessage("Thinking through plan...");
293
+ ctx.ui.setWorkingMessage(); // restore default
270
294
 
271
- ## Shutdown
295
+ ctx.ui.setWorkingVisible(false);
296
+ ctx.ui.setWorkingVisible(true);
272
297
 
273
- Shut down pi gracefully:
298
+ ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });
299
+ ctx.ui.setWorkingIndicator({ frames: [] }); // hide indicator
300
+ ctx.ui.setWorkingIndicator(); // restore default
301
+ ```
302
+
303
+ ### Widgets, title, editor text
274
304
 
275
305
  ```typescript
276
- pi.shutdown();
306
+ ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
307
+ ctx.ui.setWidget("my-widget", ["Below"], { placement: "belowEditor" });
308
+ ctx.ui.setWidget("my-widget", undefined);
309
+
310
+ ctx.ui.setTitle("pi - my project");
311
+ ctx.ui.setEditorText("Prefilled prompt");
312
+ const editorText = ctx.ui.getEditorText();
313
+ ctx.ui.pasteToEditor("Pasted content");
277
314
  ```
278
315
 
279
- ## EventBus
280
-
281
- Inter-extension communication via a shared event bus:
316
+ ### Footer
282
317
 
283
318
  ```typescript
284
- // Extension A: emit
285
- pi.events.emit("my-extension:data-ready", { items: [...] });
319
+ ctx.ui.setFooter((tui, theme, footerData) => ({
320
+ invalidate() {},
321
+ render(width) {
322
+ return [theme.fg("dim", footerData.getGitBranch() ?? "no git")];
323
+ },
324
+ dispose: footerData.onBranchChange(() => tui.requestRender()),
325
+ }));
286
326
 
287
- // Extension B: listen
288
- pi.events.on("my-extension:data-ready", (data) => {
289
- console.log("Received:", data.items.length, "items");
290
- });
327
+ ctx.ui.setFooter(undefined);
291
328
  ```
292
329
 
293
- Namespace event names with your extension name to avoid collisions. The event bus is supplementary -- most extensions do not need it. Use it when two extensions need to coordinate.
330
+ ### Autocomplete providers
294
331
 
295
- ## Theme Control
332
+ Stack on top of the current provider and delegate when your syntax does not match.
296
333
 
297
334
  ```typescript
298
- // Get current and available themes
299
- const current = ctx.ui.getTheme();
300
- const all = ctx.ui.getAllThemes();
335
+ ctx.ui.addAutocompleteProvider((current) => ({
336
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
337
+ const beforeCursor = (lines[cursorLine] ?? "").slice(0, cursorCol);
338
+ const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
339
+ if (!match) return current.getSuggestions(lines, cursorLine, cursorCol, options);
301
340
 
302
- // Set theme
303
- const result = ctx.ui.setTheme("catppuccin-mocha");
304
- // result: { success: boolean, error?: string }
341
+ return {
342
+ prefix: `#${match[1] ?? ""}`,
343
+ items: [{ value: "#123", label: "#123", description: "Issue title" }],
344
+ };
345
+ },
346
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
347
+ return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
348
+ },
349
+ shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
350
+ return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
351
+ },
352
+ }));
305
353
  ```
306
354
 
307
- ## UI Customization
355
+ ### Theme control
308
356
 
309
357
  ```typescript
310
- // Replace the footer
311
- ctx.ui.setFooter((maxWidth, theme) => {
312
- return theme.fg("muted", "Custom footer content");
313
- });
358
+ const themes = ctx.ui.getAllThemes();
359
+ const light = ctx.ui.getTheme("light");
360
+ const result = ctx.ui.setTheme("light");
361
+ if (!result.success) ctx.ui.notify(result.error, "error");
362
+ ```
314
363
 
315
- // Replace the startup header
316
- ctx.ui.setHeader((maxWidth, theme) => {
317
- return theme.fg("accent", "My Custom Header");
318
- });
364
+ ## Pi Paths
319
365
 
320
- // Set the editor component
321
- ctx.ui.setEditorComponent((tui, theme, kb) => {
322
- return new CustomEditor(tui, theme, kb);
323
- });
366
+ Use SDK helpers for Pi paths instead of `homedir()` when helpers exist. They respect `PI_CODING_AGENT_DIR` and test/custom setups.
324
367
 
325
- // Prefill the editor
326
- ctx.ui.setEditorText("Prefilled content");
368
+ Common helpers exported from the main package include:
327
369
 
328
- // Customize the streaming working indicator
329
- ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] }); // static
330
- ctx.ui.setWorkingIndicator({ frames: [] }); // hidden
331
- ctx.ui.setWorkingIndicator(); // restore default
370
+ - `getAgentDir()`
371
+ - `getSettingsPath()`
372
+ - `getSessionsDir()`
373
+ - `getPromptsDir()`
374
+ - `getToolsDir()`
375
+ - `getCustomThemesDir()`
376
+ - `getModelsPath()`
377
+ - `getAuthPath()`
378
+ - `getBinDir()`
379
+ - `getDebugLogPath()`
332
380
 
333
- // Stack autocomplete on top of built-in slash/path completion
334
- ctx.ui.addAutocompleteProvider((current) => ({
335
- async getSuggestions(lines, cursorLine, cursorCol) {
336
- const beforeCursor = (lines[cursorLine] ?? "").slice(0, cursorCol);
337
- const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
338
- if (!match) return current.getSuggestions(lines, cursorLine, cursorCol);
339
- return [{ label: `#${match[1]}123`, value: `#${match[1]}123` }];
340
- },
341
- shouldTriggerFileCompletion(input) {
342
- return current.shouldTriggerFileCompletion?.(input) ?? false;
343
- },
344
- }));
345
- ```
381
+ ## Checklist
382
+
383
+ - [ ] Session replacement code uses `withSession` for post-switch work.
384
+ - [ ] Reload handlers return immediately after `await ctx.reload()`.
385
+ - [ ] `pi.exec()` is used instead of `child_process`.
386
+ - [ ] System prompt changes return `{ systemPrompt }` from `before_agent_start`.
387
+ - [ ] `promptGuidelines` bullets name exact tools.
388
+ - [ ] UI customizations account for RPC/print degradation.
389
+ - [ ] Pi path helpers are used instead of `homedir()`.