fix(docs): align skill examples with extension APIs
This commit is contained in:
@@ -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`);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user