@aliou/pi-dev-kit 0.6.4 → 0.7.0
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.
- package/README.md +2 -2
- package/package.json +15 -10
- package/src/commands/index.ts +5 -1
- package/src/commands/update.ts +139 -91
- package/src/skills/pi-extension/SKILL.md +108 -131
- package/src/skills/pi-extension/references/additional-apis.md +256 -173
- package/src/skills/pi-extension/references/commands.md +113 -33
- package/src/skills/pi-extension/references/components.md +267 -102
- package/src/skills/pi-extension/references/hooks.md +229 -156
- package/src/skills/pi-extension/references/messages.md +97 -92
- package/src/skills/pi-extension/references/modes.md +80 -90
- package/src/skills/pi-extension/references/providers.md +255 -96
- package/src/skills/pi-extension/references/publish.md +76 -62
- package/src/skills/pi-extension/references/state.md +80 -33
- package/src/skills/pi-extension/references/structure.md +156 -245
- package/src/skills/pi-extension/references/testing.md +1 -1
- package/src/skills/pi-extension/references/tools.md +212 -816
- package/src/tools/changelog-tool.ts +237 -230
- package/src/tools/docs-tool.ts +127 -130
- package/src/tools/index.ts +5 -1
- package/src/tools/package-manager-tool.ts +152 -147
- package/src/tools/utils.ts +33 -23
- package/src/tools/version-tool.ts +51 -51
- package/src/index.ts +0 -8
|
@@ -1,113 +1,95 @@
|
|
|
1
1
|
# Messages
|
|
2
2
|
|
|
3
|
-
Pi provides several ways to
|
|
3
|
+
Pi provides several ways to show information. Choose based on persistence and interactivity.
|
|
4
4
|
|
|
5
5
|
## When to Use What
|
|
6
6
|
|
|
7
|
-
|
|
|
8
|
-
|
|
9
|
-
| `ctx.ui.notify()` |
|
|
10
|
-
| `ctx.ui.custom()` |
|
|
11
|
-
| `pi.sendMessage()` |
|
|
12
|
-
| `pi.appendEntry()` |
|
|
7
|
+
| API | Persists | LLM context | Use when |
|
|
8
|
+
|---|---:|---:|---|
|
|
9
|
+
| `ctx.ui.notify()` | No | No | Quick feedback. |
|
|
10
|
+
| `ctx.ui.custom()` | No | No | Rich interactive display. |
|
|
11
|
+
| `pi.sendMessage()` | Yes | Yes when delivered into context | Persistent custom/user-visible messages. |
|
|
12
|
+
| `pi.appendEntry()` | Yes | No | Extension state/history that should not enter model context. |
|
|
13
|
+
| Tool result `details` | Yes | Details no; content yes | Branch-aware state tied to a tool call. |
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
For tool state, prefer tool result `details`. For command results that should be visible later, use `sendMessage` plus a renderer.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
## `pi.sendMessage()`
|
|
18
|
+
|
|
19
|
+
Sends a custom message into the session.
|
|
17
20
|
|
|
18
21
|
```typescript
|
|
19
22
|
pi.sendMessage({
|
|
20
|
-
customType: "balance-result",
|
|
21
|
-
content: "Balance: $42.50",
|
|
22
|
-
display: true,
|
|
23
|
-
details: { balance: 42.
|
|
23
|
+
customType: "balance-result",
|
|
24
|
+
content: "Balance: $42.50",
|
|
25
|
+
display: true,
|
|
26
|
+
details: { balance: 42.5 },
|
|
24
27
|
});
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|---|---|---|
|
|
29
|
-
| `customType` | `string` | Identifies the message type. Paired with `registerMessageRenderer`. |
|
|
30
|
-
| `content` | `string` | Plain text content. This is what the LLM sees if the message is in context. |
|
|
31
|
-
| `display` | `boolean` | Whether to show the message in the TUI. |
|
|
32
|
-
| `details` | `object` | Arbitrary data passed to the message renderer. |
|
|
33
|
-
|
|
34
|
-
## registerMessageRenderer
|
|
35
|
-
|
|
36
|
-
Registers a custom renderer for messages with a specific `customType`:
|
|
30
|
+
Options:
|
|
37
31
|
|
|
38
32
|
```typescript
|
|
39
|
-
pi.
|
|
40
|
-
const { balance } = message.details;
|
|
41
|
-
return [
|
|
42
|
-
theme.bold("Account Balance"),
|
|
43
|
-
"",
|
|
44
|
-
theme.fg("success", ` $${balance.toFixed(2)}`),
|
|
45
|
-
].join("\n");
|
|
46
|
-
});
|
|
33
|
+
pi.sendMessage(message, { deliverAs: "steer", triggerTurn: true });
|
|
47
34
|
```
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
If no renderer is registered for a `customType`, the message's `content` field is displayed as plain text.
|
|
52
|
-
|
|
53
|
-
## Custom Message Design Guide (breadcrumbs-style)
|
|
54
|
-
|
|
55
|
-
`breadcrumbs` is a good reference for custom entries/messages (`../pi-extensions/extensions/breadcrumbs/lib/session-link.ts`, plus `commands/handoff.ts` and `commands/spawn.ts`).
|
|
56
|
-
|
|
57
|
-
### 1) Prefer paired entries for links/handovers
|
|
58
|
-
|
|
59
|
-
For cross-session workflows, use two custom message types:
|
|
60
|
-
- **Marker** in source session: short line (`Handed off to X` / `Continues in X`).
|
|
61
|
-
- **Source** in new session: header + optional expanded context.
|
|
62
|
-
|
|
63
|
-
This gives both directions of navigation and keeps history readable.
|
|
64
|
-
|
|
65
|
-
### 2) Collapsed vs expanded behavior
|
|
66
|
-
|
|
67
|
-
Keep collapsed view minimal and scannable:
|
|
68
|
-
- one semantic line
|
|
69
|
-
- optional hint (`Press Ctrl+O to expand`) only when extra content exists
|
|
36
|
+
Delivery modes:
|
|
70
37
|
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- file lists / instructions
|
|
38
|
+
- `steer`: queue while streaming and deliver before the next LLM call.
|
|
39
|
+
- `followUp`: wait until the agent finishes.
|
|
40
|
+
- `nextTurn`: store for the next user prompt.
|
|
75
41
|
|
|
76
|
-
|
|
42
|
+
## `registerMessageRenderer`
|
|
77
43
|
|
|
78
|
-
|
|
79
|
-
- missing `details` => fallback to plain `content`
|
|
80
|
-
- markdown render failure => fallback to plain text
|
|
81
|
-
- unknown fields => ignore, don't throw
|
|
44
|
+
Register a renderer for `customType`. Renderers return TUI `Component | undefined`.
|
|
82
45
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
46
|
+
```typescript
|
|
47
|
+
import type { ExtensionAPI, MessageRenderOptions, Theme } from "@earendil-works/pi-coding-agent";
|
|
48
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
49
|
+
|
|
50
|
+
interface BalanceDetails {
|
|
51
|
+
balance?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default function messagesExtension(pi: ExtensionAPI) {
|
|
55
|
+
pi.registerMessageRenderer<BalanceDetails>("balance-result", (message, options, theme) => {
|
|
56
|
+
const balance = message.details?.balance;
|
|
57
|
+
const text =
|
|
58
|
+
typeof balance === "number"
|
|
59
|
+
? `Account Balance: $${balance.toFixed(2)}`
|
|
60
|
+
: message.content;
|
|
61
|
+
|
|
62
|
+
return new Text(theme.fg("success", text), 0, 0);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
```
|
|
91
66
|
|
|
92
|
-
|
|
67
|
+
Renderer rules:
|
|
93
68
|
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
69
|
+
- Collapsed view should be one scannable line.
|
|
70
|
+
- Use `options.expanded` for details.
|
|
71
|
+
- If `details` is missing or malformed, fall back to `message.content`.
|
|
72
|
+
- Do not throw from renderers.
|
|
73
|
+
- Keep `details` small and durable; put large human-readable text in `content`.
|
|
98
74
|
|
|
99
|
-
##
|
|
75
|
+
## Command with Persistent Fallback
|
|
100
76
|
|
|
101
|
-
|
|
77
|
+
Use this when rich TUI output should persist for RPC users or future session readers.
|
|
102
78
|
|
|
103
79
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
80
|
+
pi.registerMessageRenderer<{ items?: string[] }>("my-results", (message, options, theme) => {
|
|
81
|
+
const items = message.details?.items;
|
|
82
|
+
if (!items) return new Text(message.content, 0, 0);
|
|
83
|
+
|
|
84
|
+
const visible = options.expanded ? items : items.slice(0, 5);
|
|
85
|
+
return new Text(
|
|
86
|
+
[
|
|
87
|
+
theme.fg("accent", theme.bold(`Results (${items.length})`)),
|
|
88
|
+
...visible.map((item) => ` ${theme.fg("muted", item)}`),
|
|
89
|
+
].join("\n"),
|
|
90
|
+
0,
|
|
91
|
+
0,
|
|
92
|
+
);
|
|
111
93
|
});
|
|
112
94
|
|
|
113
95
|
pi.registerCommand("results", {
|
|
@@ -120,11 +102,10 @@ pi.registerCommand("results", {
|
|
|
120
102
|
return;
|
|
121
103
|
}
|
|
122
104
|
|
|
123
|
-
const result = await ctx.ui.custom<"closed">((
|
|
105
|
+
const result = await ctx.ui.custom<"closed">((_tui, theme, _keybindings, done) => {
|
|
124
106
|
return new ResultsDisplay(theme, items, () => done("closed"));
|
|
125
107
|
});
|
|
126
108
|
|
|
127
|
-
// RPC fallback only: custom() returns undefined in RPC/Print.
|
|
128
109
|
if (result === undefined) {
|
|
129
110
|
pi.sendMessage({
|
|
130
111
|
customType: "my-results",
|
|
@@ -137,9 +118,9 @@ pi.registerCommand("results", {
|
|
|
137
118
|
});
|
|
138
119
|
```
|
|
139
120
|
|
|
140
|
-
##
|
|
121
|
+
## Notifications
|
|
141
122
|
|
|
142
|
-
|
|
123
|
+
Use for transient feedback.
|
|
143
124
|
|
|
144
125
|
```typescript
|
|
145
126
|
ctx.ui.notify("Operation complete", "info");
|
|
@@ -147,13 +128,26 @@ ctx.ui.notify("Something went wrong", "error");
|
|
|
147
128
|
ctx.ui.notify("Proceed with caution", "warning");
|
|
148
129
|
```
|
|
149
130
|
|
|
150
|
-
|
|
131
|
+
`notify` works in interactive and RPC modes and is a no-op in JSON/print.
|
|
132
|
+
|
|
133
|
+
## Custom Message Design
|
|
134
|
+
|
|
135
|
+
For session-link or handoff workflows, use paired messages:
|
|
151
136
|
|
|
152
|
-
|
|
137
|
+
- Source session marker: short line such as `Continues in <session>`.
|
|
138
|
+
- Destination session source: header plus optional expanded context.
|
|
153
139
|
|
|
154
|
-
|
|
140
|
+
Design rules:
|
|
155
141
|
|
|
156
|
-
|
|
142
|
+
- Collapsed message: one semantic line, optional expand hint only when details exist.
|
|
143
|
+
- Expanded message: markdown body, file lists, context, or routing details.
|
|
144
|
+
- Visual hierarchy: muted label, accent target/value, minimal decoration.
|
|
145
|
+
- Details: stable identifiers, link type, session IDs, short metadata.
|
|
146
|
+
- Content: user-readable text and anything the LLM may need.
|
|
147
|
+
|
|
148
|
+
## Writing Custom Entries in New Sessions
|
|
149
|
+
|
|
150
|
+
When using `ctx.newSession({ setup })`, write initial custom entries through the setup `SessionManager`.
|
|
157
151
|
|
|
158
152
|
```typescript
|
|
159
153
|
await ctx.newSession({
|
|
@@ -163,7 +157,18 @@ await ctx.newSession({
|
|
|
163
157
|
linkType: "handoff",
|
|
164
158
|
});
|
|
165
159
|
},
|
|
160
|
+
withSession: async (ctx) => {
|
|
161
|
+
await ctx.sendUserMessage("Continue from this handoff.");
|
|
162
|
+
},
|
|
166
163
|
});
|
|
167
164
|
```
|
|
168
165
|
|
|
169
|
-
Use
|
|
166
|
+
Use `withSession` for post-switch work.
|
|
167
|
+
|
|
168
|
+
## Checklist
|
|
169
|
+
|
|
170
|
+
- [ ] Picked the least persistent API that satisfies the UX.
|
|
171
|
+
- [ ] Custom message renderers return components and handle missing `details`.
|
|
172
|
+
- [ ] Collapsed message views are scannable.
|
|
173
|
+
- [ ] Large content is in `content`, not deeply nested `details`.
|
|
174
|
+
- [ ] `sendMessage` delivery mode is explicit when streaming behavior matters.
|
|
@@ -1,82 +1,71 @@
|
|
|
1
1
|
# Mode Awareness
|
|
2
2
|
|
|
3
|
-
Pi
|
|
3
|
+
Pi extensions must behave correctly in Interactive, RPC, JSON, and Print modes.
|
|
4
4
|
|
|
5
5
|
## Modes
|
|
6
6
|
|
|
7
|
-
| Mode | `ctx.hasUI` |
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
7
|
+
| Mode | `ctx.hasUI` | Notes |
|
|
8
|
+
|---|---:|---|
|
|
9
|
+
| Interactive | `true` | Full terminal UI. |
|
|
10
|
+
| RPC (`--mode rpc`) | `true` | Host handles dialogs through JSON protocol. TUI-only methods degrade. |
|
|
11
|
+
| JSON (`--mode json`) | `false` | Event stream to stdout; no extension UI. |
|
|
12
|
+
| Print (`-p`) | `false` | One-shot prompt; no extension UI. |
|
|
12
13
|
|
|
13
|
-
Important nuance:
|
|
14
|
+
Important nuance: RPC has `ctx.hasUI === true` because dialog and fire-and-forget methods work through the extension UI protocol. But `ctx.ui.custom()` and other TUI-only methods do not work in RPC.
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## Dialog Methods
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
These return values and may need mode-specific behavior.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| Method | Interactive | RPC | Print |
|
|
22
|
-
|---|---|---|---|
|
|
23
|
-
| `ctx.ui.select()` | TUI picker | JSON request to host | Returns `undefined` |
|
|
24
|
-
| `ctx.ui.confirm()` | TUI dialog | JSON request to host | Returns `false` |
|
|
25
|
-
| `ctx.ui.input()` | TUI text input | JSON request to host | Returns `undefined` |
|
|
26
|
-
| `ctx.ui.editor()` | TUI editor | JSON request to host | Returns `undefined` |
|
|
27
|
-
| `ctx.ui.custom()` | TUI component | Returns `undefined` | Returns `undefined` |
|
|
28
|
-
|
|
29
|
-
Key observation: `custom()` returns `undefined` in both RPC and Print modes. All other dialog methods work in RPC (the host presents them to the user).
|
|
30
|
-
|
|
31
|
-
Second key observation: `custom()` can also resolve to `undefined` in Interactive mode if your component calls `done(undefined)`. So `result === undefined` is not a reliable mode detector by itself.
|
|
32
|
-
|
|
33
|
-
### Fire-and-Forget Methods (no return value)
|
|
34
|
-
|
|
35
|
-
These methods are safe to call unconditionally in any mode. In modes that do not support them, they are silently ignored.
|
|
36
|
-
|
|
37
|
-
| Method | Interactive | RPC | Print |
|
|
20
|
+
| Method | Interactive | RPC | JSON/Print |
|
|
38
21
|
|---|---|---|---|
|
|
39
|
-
| `ctx.ui.
|
|
40
|
-
| `ctx.ui.
|
|
41
|
-
| `ctx.ui.
|
|
42
|
-
| `ctx.ui.
|
|
43
|
-
| `ctx.ui.
|
|
44
|
-
| `ctx.ui.setFooter()` | Footer area | No-op | No-op |
|
|
45
|
-
| `ctx.ui.setHeader()` | Header area | No-op | No-op |
|
|
46
|
-
| `ctx.ui.setWorkingMessage()` | Loader text | No-op | No-op |
|
|
47
|
-
| `ctx.ui.setEditorComponent()` | Custom editor | No-op | No-op |
|
|
48
|
-
|
|
49
|
-
You never need to check `ctx.hasUI` before calling fire-and-forget methods.
|
|
22
|
+
| `ctx.ui.select()` | TUI picker | JSON request to host | `undefined` |
|
|
23
|
+
| `ctx.ui.confirm()` | TUI dialog | JSON request to host | `false` |
|
|
24
|
+
| `ctx.ui.input()` | TUI input | JSON request to host | `undefined` |
|
|
25
|
+
| `ctx.ui.editor()` | TUI editor | JSON request to host | `undefined` |
|
|
26
|
+
| `ctx.ui.custom()` | Custom TUI component | `undefined` | `undefined` |
|
|
50
27
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Check `ctx.hasUI` when a dialog method gates behavior. If the dialog result determines what happens next (for example, blocking a tool call or cancelling a session switch), you must handle the case where the dialog cannot run.
|
|
28
|
+
Check `ctx.hasUI` when a dialog gates behavior. If there is no UI, choose a safe default.
|
|
54
29
|
|
|
55
30
|
```typescript
|
|
56
|
-
// tool_call handler: must decide to block or allow
|
|
57
31
|
pi.on("tool_call", async (event, ctx) => {
|
|
58
|
-
if (isDangerous(event))
|
|
59
|
-
if (!ctx.hasUI) {
|
|
60
|
-
// Print mode: no way to ask the user, block by default
|
|
61
|
-
return { block: true, reason: "Dangerous command blocked (no UI)" };
|
|
62
|
-
}
|
|
32
|
+
if (!isDangerous(event)) return;
|
|
63
33
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return { block: true, reason: "Blocked by user" };
|
|
67
|
-
}
|
|
34
|
+
if (!ctx.hasUI) {
|
|
35
|
+
return { block: true, reason: "Dangerous action blocked because no UI is available." };
|
|
68
36
|
}
|
|
69
|
-
|
|
37
|
+
|
|
38
|
+
const ok = await ctx.ui.confirm("Dangerous action", "Allow it?");
|
|
39
|
+
if (!ok) return { block: true, reason: "Blocked by user" };
|
|
70
40
|
});
|
|
71
41
|
```
|
|
72
42
|
|
|
73
|
-
|
|
74
|
-
- Fire-and-forget calls (`notify`, `setStatus`, `setWidget`, etc.).
|
|
75
|
-
- Dialogs where the default return value is acceptable (for example, a non-critical confirm that defaults to `false`).
|
|
43
|
+
## Fire-and-Forget Methods
|
|
76
44
|
|
|
77
|
-
|
|
45
|
+
These are safe to call without guards. Unsupported modes ignore them or forward them to the RPC host.
|
|
78
46
|
|
|
79
|
-
|
|
47
|
+
| Method | Interactive | RPC | JSON/Print |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| `notify()` | TUI notification | JSON event | No-op |
|
|
50
|
+
| `setStatus()` | Footer status | JSON event | No-op |
|
|
51
|
+
| `setWidget()` string arrays | Widget | JSON event | No-op |
|
|
52
|
+
| `setTitle()` | Terminal title | JSON event | No-op |
|
|
53
|
+
| `setEditorText()` | Editor text | JSON event | No-op |
|
|
54
|
+
| `pasteToEditor()` | Paste handling | Set editor text | No-op |
|
|
55
|
+
| `setWorkingMessage()` | Loader text | No-op | No-op |
|
|
56
|
+
| `setWorkingVisible()` | Loader visibility | No-op | No-op |
|
|
57
|
+
| `setWorkingIndicator()` | Loader indicator | No-op | No-op |
|
|
58
|
+
| `setFooter()` | Custom footer | No-op | No-op |
|
|
59
|
+
| `setHeader()` | Custom header | No-op | No-op |
|
|
60
|
+
| `setEditorComponent()` | Custom editor | No-op | No-op |
|
|
61
|
+
| `setToolsExpanded()` | Tool expansion | No-op | No-op |
|
|
62
|
+
| Theme APIs | Full | Mostly unavailable | No-op/unavailable |
|
|
63
|
+
|
|
64
|
+
Component widgets are TUI-only; string-array widgets are portable to RPC.
|
|
65
|
+
|
|
66
|
+
## Three-Tier Pattern for `ctx.ui.custom()`
|
|
67
|
+
|
|
68
|
+
Use this for commands that display rich TUI components.
|
|
80
69
|
|
|
81
70
|
```typescript
|
|
82
71
|
pi.registerCommand("quotas", {
|
|
@@ -84,19 +73,18 @@ pi.registerCommand("quotas", {
|
|
|
84
73
|
handler: async (_args, ctx) => {
|
|
85
74
|
const data = await fetchQuotas();
|
|
86
75
|
|
|
87
|
-
// Tier 1: Print
|
|
76
|
+
// Tier 1: JSON/Print, no UI.
|
|
88
77
|
if (!ctx.hasUI) {
|
|
89
78
|
console.log(formatPlain(data));
|
|
90
79
|
return;
|
|
91
80
|
}
|
|
92
81
|
|
|
93
|
-
// Tier 2: Interactive
|
|
94
|
-
|
|
95
|
-
const result = await ctx.ui.custom<"closed">((tui, theme, _kb, done) => {
|
|
82
|
+
// Tier 2: Interactive TUI. Use an explicit non-undefined sentinel.
|
|
83
|
+
const result = await ctx.ui.custom<"closed">((_tui, theme, _keybindings, done) => {
|
|
96
84
|
return new QuotasDisplay(theme, data, () => done("closed"));
|
|
97
85
|
});
|
|
98
86
|
|
|
99
|
-
// Tier 3: RPC
|
|
87
|
+
// Tier 3: RPC. custom() returns undefined.
|
|
100
88
|
if (result === undefined) {
|
|
101
89
|
ctx.ui.notify(formatPlain(data), "info");
|
|
102
90
|
}
|
|
@@ -104,53 +92,55 @@ pi.registerCommand("quotas", {
|
|
|
104
92
|
});
|
|
105
93
|
```
|
|
106
94
|
|
|
107
|
-
|
|
95
|
+
Do not use `done(undefined)` for normal interactive close paths when you use `result === undefined` as the RPC fallback detector. Use `null`, `false`, or a string sentinel.
|
|
108
96
|
|
|
109
|
-
|
|
110
|
-
- **`select`**: When the custom component is a picker/selector. The RPC host presents a list.
|
|
111
|
-
- **`confirm`**: When the custom component is a confirmation dialog (for example, permission gate).
|
|
112
|
-
- **Notify "requires interactive mode"**: When the custom component is too complex to reduce (for example, settings editor, process manager).
|
|
97
|
+
## Fallback Choices
|
|
113
98
|
|
|
114
|
-
Use `
|
|
99
|
+
- Use `notify` for display-only results.
|
|
100
|
+
- Use `select` when the rich component is a picker.
|
|
101
|
+
- Use `confirm` when the rich component is a yes/no gate.
|
|
102
|
+
- Use `input`/`editor` when text entry is enough.
|
|
103
|
+
- Use `sendMessage` + `registerMessageRenderer` when output should persist in session history.
|
|
104
|
+
- Tell the user interactive mode is required when the UI cannot be reduced safely.
|
|
115
105
|
|
|
116
|
-
|
|
106
|
+
## Examples
|
|
107
|
+
|
|
108
|
+
### Selector fallback
|
|
117
109
|
|
|
118
110
|
```typescript
|
|
119
|
-
const result = await ctx.ui.custom<string | null>((_tui, _theme,
|
|
111
|
+
const result = await ctx.ui.custom<string | null>((_tui, _theme, _keybindings, done) => {
|
|
120
112
|
return new FancyPicker(items, done); // done(value) or done(null)
|
|
121
113
|
});
|
|
122
114
|
|
|
123
|
-
// RPC fallback: use select dialog
|
|
124
115
|
if (result === undefined) {
|
|
125
|
-
const selected = await ctx.ui.select("Pick an item", items.map((
|
|
126
|
-
//
|
|
116
|
+
const selected = await ctx.ui.select("Pick an item", items.map((item) => item.label));
|
|
117
|
+
// Handle selected.
|
|
127
118
|
}
|
|
128
119
|
```
|
|
129
120
|
|
|
130
|
-
###
|
|
121
|
+
### Confirmation fallback
|
|
131
122
|
|
|
132
123
|
```typescript
|
|
133
|
-
|
|
134
|
-
if (!ctx.hasUI) {
|
|
135
|
-
return { block: true, reason: "No UI to confirm" };
|
|
136
|
-
}
|
|
124
|
+
if (!ctx.hasUI) return { block: true, reason: "No UI to confirm" };
|
|
137
125
|
|
|
138
|
-
const proceed = await ctx.ui.custom<boolean>((_tui, theme,
|
|
139
|
-
return new ConfirmDialog(theme, message, done); // done(true
|
|
126
|
+
const proceed = await ctx.ui.custom<boolean | null>((_tui, theme, _keybindings, done) => {
|
|
127
|
+
return new ConfirmDialog(theme, message, done); // done(true), done(false), or done(null)
|
|
140
128
|
});
|
|
141
129
|
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
if (proceed === undefined) {
|
|
131
|
+
const confirmed = await ctx.ui.confirm("Allow action?", message);
|
|
132
|
+
if (!confirmed) return { block: true, reason: "Blocked" };
|
|
133
|
+
} else if (proceed !== true) {
|
|
144
134
|
return { block: true, reason: "Blocked" };
|
|
145
135
|
}
|
|
146
136
|
```
|
|
147
137
|
|
|
148
138
|
## Guidelines
|
|
149
139
|
|
|
150
|
-
1. Never assume
|
|
151
|
-
2. Fire-and-forget methods are
|
|
152
|
-
3. Guard
|
|
153
|
-
4.
|
|
154
|
-
5.
|
|
155
|
-
6. For
|
|
156
|
-
7. Test
|
|
140
|
+
1. Never assume interactive mode.
|
|
141
|
+
2. Fire-and-forget methods are safe without `ctx.hasUI` guards.
|
|
142
|
+
3. Guard dialogs that decide whether to proceed.
|
|
143
|
+
4. `ctx.ui.custom()` always needs fallback.
|
|
144
|
+
5. Use explicit sentinels instead of `done(undefined)`.
|
|
145
|
+
6. For security/safety gates, default to blocking when there is no UI.
|
|
146
|
+
7. Test interactive and print modes. Test RPC fallback for `custom()`.
|