fix(docs): align skill examples with extension APIs

This commit is contained in:
can1357
2026-04-23 23:03:16 +02:00
parent b99d7c4186
commit 80dac4a6c9
7 changed files with 48 additions and 37 deletions
+18 -7
View File
@@ -37,11 +37,19 @@ export default function myExtension(pi: ExtensionAPI) {
});
// Slash command: /greet
pi.commands.register("greet", {
pi.registerCommand("greet", {
description: "Send a greeting into the conversation",
handler: async (args, ctx) => {
const name = args.trim() || "world";
await pi.sendMessage(`Hello, ${name}!`, { triggerTurn: false });
pi.sendMessage(
{
customType: "greeting",
content: `Hello, ${name}!`,
display: true,
attribution: "user",
},
{ triggerTurn: false }
);
ctx.ui.notify(`Greeted ${name}`, "info");
},
});
@@ -71,8 +79,8 @@ omp discovers extension modules in this order:
1. **Project-scoped auto-discovery** — `<cwd>/.omp/extensions/`
2. **User-scoped auto-discovery** — `~/.omp/agent/extensions/`
3. **Marketplace-installed plugins** — `~/.claude/plugins/cache/` (extensions shipped inside marketplace plugin packages)
4. **CLI flag** — `omp --extension-path ./my-ext.ts` (also `-e`; `--hook` is treated as an alias)
3. **Marketplace-installed plugins** — `~/.omp/plugins/node_modules/` (extensions shipped inside installed plugin packages)
4. **CLI flag** — `omp --extension ./my-ext.ts` (also `-e`; `--hook` is treated as an alias)
5. **Settings `extensions` array** — paths listed in `~/.omp/agent/config.yml` or `<cwd>/.omp/settings.json`
Within each scope, de-duplication is by resolved absolute path — first seen wins.
@@ -120,7 +128,7 @@ Multiple entry points are supported:
## Registering commands
```ts
pi.commands.register("my-cmd", {
pi.registerCommand("my-cmd", {
description: "What the command does",
handler: async (args, ctx) => {
// args: everything the user typed after /my-cmd
@@ -178,13 +186,16 @@ pi.registerTool({
```ts
pi.on("tool_call", async (event, ctx) => {
// event.toolName, event.input, event.toolCallId
if (event.toolName === "bash" && event.input.command?.includes("rm -rf /")) {
if (event.toolName !== "bash") return;
const command = String((event.input as { command?: unknown }).command ?? "");
if (command.includes("rm -rf /")) {
return { block: true, reason: "Blocked by safety policy" };
}
});
pi.on("turn_end", async (_event, ctx) => {
ctx.ui.setStatus("tokens", `~${ctx.getContextUsage()?.total ?? "?"} tokens`);
ctx.ui.setStatus("tokens", `~${ctx.getContextUsage()?.tokens ?? "?"} tokens`);
});
```
+9 -18
View File
@@ -7,7 +7,7 @@ description: Use when creating a new omp hook. Covers HookAPI, event catalog, bl
Hooks are event-driven interceptors that run alongside the agent loop. They are best used for cross-cutting concerns: safety policy, secret redaction, context pruning, audit logging. A hook module registers handlers via `pi.on(event, handler)` and can block tool execution, override tool output, or rewrite the message context before each LLM call.
> **Relationship to extensions:** The hook subsystem (`HookAPI`) is the legacy API. The extension runner now handles everything hooks can do plus more. Both share the same event model. Use `ExtensionAPI` for new work; use `HookAPI` only if you are maintaining an existing hook module.
> **Relationship to extensions:** The hook subsystem (`HookAPI`) is the legacy API. The extension runner now handles everything hooks can do plus more. `ExtensionAPI` supports the hook event model plus extension-only events. Use `ExtensionAPI` for new work; use `HookAPI` only if you are maintaining an existing hook module.
## Factory signature
@@ -41,9 +41,6 @@ export default function myExtension(pi: ExtensionAPI): void {
|---|---|---|
| `tool_call` | Before every tool execution | `{ block?: boolean; reason?: string }` |
| `tool_result` | After every tool execution | `{ content?; details?; isError?: boolean }` |
| `tool_execution_start` | When tool starts (observability) | — |
| `tool_execution_update` | Streaming tool update | — |
| `tool_execution_end` | When tool finishes | — |
### Session lifecycle
@@ -65,13 +62,12 @@ export default function myExtension(pi: ExtensionAPI): void {
| Event | Fires | Can return |
|---|---|---|
| `before_agent_start` | Before agent starts a turn | `{ message?: { customType; content; display; details }; systemPrompt?: string }` |
| `before_agent_start` | Before agent starts a turn | `{ message?: { customType; content; display; details; attribution? } }` |
| `agent_start` | Agent streaming starts | — |
| `agent_end` | Agent streaming ends | — |
| `turn_start` | Start of a user→agent turn | — |
| `turn_end` | End of a user→agent turn | — |
| `context` | Before each LLM API call | `{ messages?: Message[] }` |
| `input` | Before user input is submitted | `{ value: string }` |
| `auto_compaction_start` | Auto-compaction begins | — |
| `auto_compaction_end` | Auto-compaction ends | — |
| `auto_retry_start` | Auto-retry begins | — |
@@ -79,12 +75,7 @@ export default function myExtension(pi: ExtensionAPI): void {
| `ttsr_triggered` | TTSR (too-short response) triggered | — |
| `todo_reminder` | Todo reminder fires | — |
### User shell/notebook interception
| Event | Fires | Can return |
|---|---|---|
| `user_bash` | Before user-initiated bash | `{ result?: BashResult }` |
| `user_python` | Before user-initiated python | `{ result?: PythonResult }` |
Extension-only events such as `tool_execution_start`, `tool_execution_update`, `tool_execution_end`, `input`, `user_bash`, and `user_python` require `ExtensionAPI`.
## Pre-tool blocking contract
@@ -129,7 +120,7 @@ omp.on("tool_result", async (event, ctx) => {
Contract:
- Handlers run in registration order; each sees the **accumulated** result so far (middleware chain).
- Handlers run in registration order. For `HookAPI`, each handler receives the original tool result event, and the last returned override wins.
- `content` replaces the full content array for the LLM.
- `details` replaces the structured details object.
- `isError` overrides the error flag (typed, but note: `HookToolWrapper` behavior for error path rethrows regardless).
@@ -251,13 +242,13 @@ export default function contextFilter(omp: HookAPI): void {
|---|---|
| `notify(message, type?)` | Show an in-app notification |
| `setStatus(key, text)` | Set footer status text (keyed, sorted by key) |
| `select(title, options, opts?)` | Show a selection dialog |
| `confirm(title, message, opts?)` | Show a yes/no dialog |
| `input(title, placeholder?, opts?)` | Show a text input dialog |
| `editor(title, prefill?, opts?)` | Show a multi-line editor |
| `select(title, options)` | Show a selection dialog |
| `confirm(title, message)` | Show a yes/no dialog |
| `input(title, placeholder?)` | Show a text input dialog |
| `editor(title, prefill?, { signal }?)` | Show a multi-line editor |
| `setEditorText(text)` | Set the input editor content |
| `getEditorText()` | Get current input editor content |
| `custom(factory, opts?)` | Render a custom TUI component |
| `custom(factory)` | Render a custom TUI component |
| `theme` | Current theme object |
`ctx.hasUI` is `false` in headless/print/subagent mode — always guard interactive calls.
@@ -1,6 +1,6 @@
# hello-extension
A minimal `oh-my-pi` extension that demonstrates the two most common authoring patterns: subscribing to `session_start` to log a greeting on load, and registering a `/hello` slash command that sends a notification to the user. It is intentionally small — use it as a copy-paste starting point for your own extension.
A minimal `oh-my-pi` extension that demonstrates the two most common authoring patterns: subscribing to `session_start` to notify on load, and registering a `/hello` slash command that sends a greeting into the conversation. It is intentionally small — use it as a copy-paste starting point for your own extension.
## Install
@@ -23,7 +23,7 @@ extensions:
**Option C — load once via CLI flag:**
```
omp --extension-path ./hello-extension
omp --extension ./hello-extension
```
## Usage
@@ -34,6 +34,6 @@ After loading, type `/hello` in the omp prompt to trigger the notification.
- Default export factory receiving `ExtensionAPI`
- `pi.on("session_start", ...)` — session lifecycle hook
- `pi.commands.register(...)` — slash command registration
- `pi.registerCommand(...)` — slash command registration
- `ctx.ui.notify(...)` — user-facing notification
- `package.json` with `omp.extensions` manifest field
+13 -5
View File
@@ -2,17 +2,25 @@
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
export default function helloExtension(pi: ExtensionAPI) {
// Log a greeting to the console whenever a session starts.
// Show a greeting whenever a session starts.
pi.on("session_start", async (_event, ctx) => {
console.log("[hello-extension] session started in", ctx.cwd);
ctx.ui.notify("Hello from hello-extension!", "info");
});
// Register a /hello slash command that sends a greeting into the conversation.
pi.commands.register("hello", {
pi.registerCommand("hello", {
description: "Send a greeting into the conversation",
handler: async (_args, ctx) => {
await pi.sendMessage("Hello from my extension!", { triggerTurn: false });
handler: async (args, ctx) => {
const name = args.trim() || "there";
pi.sendMessage(
{
customType: "hello-extension",
content: `Hello, ${name}!`,
display: true,
attribution: "user",
},
{ triggerTurn: false }
);
ctx.ui.notify("Message sent!", "info");
},
});
@@ -1,6 +1,6 @@
# mini-marketplace
A minimal `oh-my-pi` marketplace catalog that demonstrates the `marketplace.json` format. It lists one plugin (`hello-extension`) using a relative path source.
A minimal `oh-my-pi` marketplace catalog that demonstrates the `marketplace.json` format. It lists one plugin (`my-plugin`) using a relative path source.
## Install command
@@ -26,11 +26,12 @@ omp plugin install my-plugin@example-marketplace
```
mini-marketplace/
marketplace.json ← catalog (normally at .claude-plugin/marketplace.json in a real repo)
.claude-plugin/
marketplace.json ← catalog
README.md
my-plugin/
package.json ← omp.extensions manifest
index.ts ← extension entry point
```
In a real published marketplace, `marketplace.json` lives at `.claude-plugin/marketplace.json` inside the Git repository root. For this local example it is at the directory root so you can point `/marketplace add` directly at this folder.
Published and local marketplaces use the same catalog location: `.claude-plugin/marketplace.json` inside the marketplace root. Point `/marketplace add` at this folder to load the example.
+1 -1
View File
@@ -19,7 +19,7 @@ Restart `omp`. The hook is active for all sessions.
Or load once:
```
omp --extension-path ./safety-hook
omp --extension ./safety-hook
```
## How it works