@anthodev/pi-tools-style 0.1.4 → 0.2.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 +128 -52
- package/index.ts +116 -90
- package/package.json +5 -5
- package/src/config-menu.ts +261 -0
- package/src/frame.ts +282 -97
- package/src/render-decorator.ts +181 -200
- package/src/settings.ts +202 -30
- package/src/tool-category.ts +3 -30
- package/src/tool-presentation.ts +1484 -0
- package/src/tool-renderer.ts +752 -0
- package/src/tool-spinner.ts +72 -89
- package/src/web-search-presentation.ts +986 -0
package/README.md
CHANGED
|
@@ -2,30 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center"><strong>Give Pi tool activity clear, theme-aware structure without changing how tools run.</strong></p>
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<a href="https://github.com/Anthodev/pi-tools-style/releases/latest"><img alt="Latest stable release" src="https://img.shields.io/github/v/release/Anthodev/pi-tools-style?style=for-the-badge&label=release&labelColor=101418&color=9ccbfb"></a>
|
|
8
|
-
<a href="https://github.com/Anthodev/pi-tools-style/blob/develop/LICENSE"><img alt="MIT License" src="https://img.shields.io/github/license/Anthodev/pi-tools-style?style=for-the-badge&labelColor=101418&color=b9c8da"></a>
|
|
9
|
-
</p>
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> The published npm `0.1.4` release still targets Pi `1.0.x`. Version `0.2.0`, which moves to Pi `1.1.x` and the middleware support, semantic cards, and MCP, web-search, and execution views described on this page.
|
|
10
7
|
|
|
11
|
-
`pi-tools-style`
|
|
8
|
+
`pi-tools-style` gives Pi tool calls and direct shell commands clear semantic structure: status, duration, and identity in a card head, command and content in framed sections, bounded previews that expand in place with `Ctrl+O`, and colors that follow the active theme. It draws through Pi's public tool-renderer middleware and never registers or replaces a tool.
|
|
12
9
|
|
|
13
|
-
Built-in tools, MCP
|
|
10
|
+
Built-in tools, MCP servers, and third-party extensions keep control of their schemas, permissions, prompts, execution backends, and content. Cards show only the data a result already contains; outputs that cannot be framed safely pass through untouched.
|
|
14
11
|
|
|
15
12
|
## Highlights
|
|
16
13
|
|
|
17
|
-
- **Presentation without ownership** —
|
|
18
|
-
- **
|
|
14
|
+
- **Presentation without ownership** — draws semantic cards around tool activity and never registers or replaces a tool.
|
|
15
|
+
- **Public render middleware** — builds on Pi `1.1`'s `registerToolRenderer` for tool cards instead of private render wrappers.
|
|
16
|
+
- **MCP-aware** — proven `server/tool` identity, declared annotation hints, dedicated resource views, and full-output paths shown but never read.
|
|
17
|
+
- **Web-search views** — exact tool recognition, answers and sources rendered from the result, configurable extra tool names, no name heuristics.
|
|
19
18
|
- **Three icon modes** — uses portable ASCII labels by default, richer Nerd Font glyphs when enabled, or icon-free titles.
|
|
20
|
-
- **Live running state** — animates the title while a model tool call is still executing, then removes the spinner on completion.
|
|
21
19
|
- **Terminal-correct sizing** — measures visible ANSI width before padding, truncating, or drawing borders.
|
|
22
|
-
- **Renderer-friendly composition** — preserves Pi, AFT, MCP, and extension-provided output inside the frame.
|
|
23
20
|
- **Image-safe fallback** — skips Kitty, iTerm2, and Sixel rows instead of corrupting terminal image sequences.
|
|
24
|
-
- **Reload-safe state** — keeps
|
|
21
|
+
- **Reload-safe state** — keeps registration and runtime configuration idempotent across `/reload`.
|
|
25
22
|
|
|
26
23
|
## Installation
|
|
27
24
|
|
|
28
|
-
This checkout targets Pi
|
|
25
|
+
This checkout targets Pi `>=1.1.0 <2`, verified with `1.1.0`, and requires Node.js `>=22.19.0`.
|
|
29
26
|
|
|
30
27
|
To install it, use the following command:
|
|
31
28
|
|
|
@@ -45,31 +42,44 @@ Pi stores the package in user settings and references the directory directly, so
|
|
|
45
42
|
|
|
46
43
|
## What it looks like
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
Cards stay collapsed to a bounded preview and expand in place with `Ctrl+O`. The following frames are captured verbatim from provider-free offline replays at a 120-column terminal; they prove rendering, not filesystem execution. A `bash` call renders as a framed card with the tool's identity, state, and duration in the head, the supplied command directly below the head above an `Output` section, and a completion mark (shown expanded with `Ctrl+O`):
|
|
49
46
|
|
|
50
47
|
```text
|
|
51
|
-
|
|
52
|
-
│
|
|
53
|
-
|
|
48
|
+
╭─── [$] | Bash · ok ──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
49
|
+
│ $ echo "First command line" │
|
|
50
|
+
│ echo "Second command line" │
|
|
51
|
+
│ timeout: 10s │
|
|
52
|
+
├─── Output ───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
53
|
+
│ First command line │
|
|
54
|
+
│ Second command line │
|
|
55
|
+
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
The head never repeats the command; `Ctrl+O` reveals any remaining command lines above the same `Output`, with no `Command` divider row.
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
╭─ | read ⠹ ─────────────────────────╮
|
|
60
|
-
│ Reading README.md │
|
|
61
|
-
╰──────────────────────────────────────╯
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Once execution finishes, the spinner disappears and the title remains stable:
|
|
60
|
+
A `read` call renders as a framed card too: the supplied path sits directly under the head, above the previewed `Output`:
|
|
65
61
|
|
|
66
62
|
```text
|
|
67
|
-
|
|
68
|
-
│
|
|
69
|
-
|
|
63
|
+
╭─── [F] | Read · ok ──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
64
|
+
│ path: /offline/InputLiteralPath.txt │
|
|
65
|
+
├─── Output ───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
66
|
+
│ O01 │
|
|
67
|
+
│ O02 │
|
|
68
|
+
│ O03 │
|
|
69
|
+
│ O04 │
|
|
70
|
+
│ O05 │
|
|
71
|
+
│ O06 │
|
|
72
|
+
│ O07 │
|
|
73
|
+
│ O08 │
|
|
74
|
+
│ O09 │
|
|
75
|
+
│ O10 │
|
|
76
|
+
│ … 2 more lines ctrl+o to expand │
|
|
77
|
+
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
70
78
|
```
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
`find`, `grep`, and `ls` follow the same shape: their supplied path, pattern, and options render the same way, above their `Output`.
|
|
81
|
+
|
|
82
|
+
Direct `!` and `!!` commands keep their frame and `shell` title through a guarded internal adapter described under [How it works](#how-it-works), and place the command body directly below the head above `Output` like tool cards. An interrupted command renders an explicit cancelled state rather than an error. Slash-command output remains unchanged because Pi does not expose one uniform transcript component for slash commands.
|
|
73
83
|
|
|
74
84
|
## Usage
|
|
75
85
|
|
|
@@ -79,23 +89,35 @@ Boxes start enabled. Manage them from Pi with:
|
|
|
79
89
|
/tools-style
|
|
80
90
|
/tools-style on
|
|
81
91
|
/tools-style off
|
|
92
|
+
/tools-style config
|
|
82
93
|
/tools-style icons ascii
|
|
83
94
|
/tools-style icons nerd-font
|
|
84
95
|
/tools-style icons off
|
|
85
96
|
/tstyle
|
|
86
97
|
```
|
|
87
98
|
|
|
88
|
-
`/tools-style` toggles framing. `/tstyle` is a short alias with the same arguments. The explicit `on` and `off` forms set the desired state without depending on its current value.
|
|
99
|
+
`/tools-style` toggles framing. `/tstyle` is a short alias with the same arguments. The explicit `on` and `off` forms set the desired state without depending on its current value. Turning boxes off restores Pi's native rendering for downstream renderers, backgrounds, and padding.
|
|
100
|
+
|
|
101
|
+
`/tools-style config` opens an interactive menu (interactive TUI sessions only) listing the global switch, the icon mode, the shell card switch, and one row per known tool — the eight native tools, MCP calls, third-party tools, and the owned execution tools. Fuzzy search filters the rows; `Enter` or `Space` flips the selected value; `Escape` closes. Per-tool rows store explicit preferences for that exact tool name (absent means follow the global switch); already-rendered rows restyle immediately and every choice persists across restarts and `/reload`. The shell card has its own switch, so `!/!!` styling can be turned off without touching tool calls.
|
|
102
|
+
|
|
103
|
+
## Settings
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
Settings persist in `~/.pi/agent/config/tools-style.json`. The icon mode, the web-search recognition list, and the per-tool preferences share the file; saving an icon mode preserves the configured list and tool preferences. ASCII remains the package default; Nerd Font mode can be enabled once for terminals that provide the required glyphs. `icons off` keeps frames but drops the icon and its `|` separator from titles, showing the textual status (`pending`, `running`, `done`, `error`, `cancelled`) instead of an animated spinner.
|
|
91
106
|
|
|
92
107
|
```json
|
|
93
108
|
{
|
|
94
|
-
"
|
|
109
|
+
"enabled": true,
|
|
110
|
+
"iconMode": "nerd-font",
|
|
111
|
+
"shellEnabled": true,
|
|
112
|
+
"webSearchTools": ["mcp__my_web__search"],
|
|
113
|
+
"tools": {
|
|
114
|
+
"read": false,
|
|
115
|
+
"mcp__my_web__search": true
|
|
116
|
+
}
|
|
95
117
|
}
|
|
96
118
|
```
|
|
97
119
|
|
|
98
|
-
Set `PI_TOOLS_STYLE=0` before startup to load the extension with framing disabled.
|
|
120
|
+
`webSearchTools` accepts exact tool names only; see [Web-search cards](#web-search-cards). `tools` maps exact tool names to explicit `true`/`false` preferences; a name absent from the map follows the global switch. `shellEnabled` styles the direct shell card (`!`/`!!`) independently of tool calls. With `enabled: false` the global switch wins and every card returns to Pi's native rendering while the stored preferences are kept. Set `PI_TOOLS_STYLE=0` before startup to load the extension with framing disabled.
|
|
99
121
|
|
|
100
122
|
## Icons and running indicators
|
|
101
123
|
|
|
@@ -117,38 +139,90 @@ Common tools receive dedicated icons, while unknown tools fall back to their sem
|
|
|
117
139
|
| Show diagnostics | `[D]` | `` | `lsp_diagnostics`, `lens_diagnostics` |
|
|
118
140
|
| Unknown tool | `[T]` | `` | third-party fallback |
|
|
119
141
|
|
|
120
|
-
While a model tool call is active, ASCII mode cycles through `| / - \` and Nerd Font mode cycles through `⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏`. One shared timer drives all active titles and stops after completion, interruption, box disablement, stale transcript removal, or session shutdown.
|
|
142
|
+
While a model tool call is active, the status mark animates: ASCII mode cycles through `| / - \` and Nerd Font mode cycles through `⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏`. Completion replaces the spinner with `ok` or `✓` (`!` or `✗` on error) and a duration such as `1.3s`. One shared timer drives all active titles and stops after completion, interruption, box disablement, stale transcript removal, or session shutdown.
|
|
121
143
|
|
|
122
|
-
##
|
|
144
|
+
## Cards and colors
|
|
123
145
|
|
|
124
|
-
|
|
146
|
+
Card borders and backgrounds follow the active Pi theme, so switching themes updates existing cards without reloading. The color follows the card state, not a tool category:
|
|
125
147
|
|
|
126
|
-
|
|
|
148
|
+
| State | Border | Theme tokens |
|
|
127
149
|
| --- | --- | --- |
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
| Pending or running | accent | `accent`, `toolPendingBg` |
|
|
151
|
+
| Done | muted | `borderMuted`, `toolSuccessBg` |
|
|
152
|
+
| Error | error | `error`, `toolErrorBg` |
|
|
153
|
+
| Cancelled | warning | `warning` |
|
|
154
|
+
|
|
155
|
+
Direct `!` and `!!` shell cards use the same state-based border and background selection as every other card; there is no separate category color.
|
|
156
|
+
|
|
157
|
+
## Tool presentations
|
|
158
|
+
|
|
159
|
+
The plugin renders these tool-specific cards:
|
|
160
|
+
|
|
161
|
+
Supplied call arguments render directly under the head without an `Input` or `Command` divider. Other section dividers, including `Output`, `Diff`, and `Calls`, are unchanged.
|
|
162
|
+
|
|
163
|
+
| Tool | Card |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `bash`, `powershell` | Framed: identity, state, and duration in the head; the command (first line, supplied options such as `timeout`) directly below the head above `Output` (last ten lines), truncation note, `Full output: <path>` when Pi wrote one |
|
|
166
|
+
| `edit` | Framed: path and the request, kept when the result arrives, above the final `Diff` or error |
|
|
167
|
+
| `write` | Framed: path and content preview above the actual result `Output`; an empty result shows no fabricated confirmation |
|
|
168
|
+
| `read` | Framed: path and any supplied offset or limit above the `Output` preview |
|
|
169
|
+
| `find`, `grep`, `ls` | Framed: supplied pattern, path, and options above the `Output` entries with an expand hint |
|
|
170
|
+
| `ctx_execute`, `ctx_execute_file` | Framed: path, language, code, and options above the `Output` text |
|
|
171
|
+
| `codemode` | Framed: JavaScript, model `Calls`, optional cost total, `Output`, `Full output: <path>` |
|
|
172
|
+
| MCP calls | Framed: proven identity and declared hints in the head, supplied arguments above the `Output` |
|
|
173
|
+
| MCP resources | Framed: resource title in the head, supplied server and URI arguments above the `Output` |
|
|
174
|
+
| Web search | Framed in every state: query and supplied options above the answer, sources, or raw response |
|
|
175
|
+
|
|
176
|
+
Every card shows its target once — in the head or directly below it — and collapses previews behind a `… N more lines` or `… N more entries` hint. Output paths are displayed but never read.
|
|
177
|
+
|
|
178
|
+
### MCP cards
|
|
179
|
+
|
|
180
|
+
- A tool is treated as proven MCP only when Pi's public tool info reports `builtin:mcp`. An `mcp__*` name or reserved resource name that Pi has not proven is treated as an unresolved MCP call and still gets the MCP card, unless a downstream renderer already draws its own complete shell, in which case it passes through untouched; provenance is re-checked against Pi's tool list on render.
|
|
181
|
+
- The head keeps only the callable identity: `server/tool` when the result details prove both, or `namespace/tool` when a proven namespace prefixes the exposed name; otherwise it falls back to the exposed tool name. A meta line shows the namespace when it is not already in the head, the exposure form when it is not direct, and annotation hints exactly as the server declares them (read-only, destructive, idempotent, open-world). Declared hints are shown, not verified. Supplied arguments are never merged into the head; the complete arguments render directly under the head.
|
|
182
|
+
- Resource tools get their own cards. The head shows the resource title only; supplied arguments render as delivered (schemaless safe JSON) directly under the head, where `…` marks a value that has not arrived rather than a validated required field, and empty arguments are shown literally.
|
|
183
|
+
|
|
184
|
+
### Web-search cards
|
|
185
|
+
|
|
186
|
+
- Recognition is exact: Pi's native `web_search`, names listed in `webSearchTools`, and MCP tools proven as `web_search` by their namespace or result details. Pi's own tool names are refused even if configured by mistake, and no substring heuristics apply.
|
|
187
|
+
- The card renders only what the result already contains: the answer, sources (eight collapsed with a `… N more sources` hint), provider and usage metadata, warnings, and a `Full output: <path>` reference. It never executes, fetches, or reads anything.
|
|
188
|
+
- Unstructured results keep a six-line text preview instead of a source tree.
|
|
189
|
+
- MCP and web-search rendering is verified offline against recorded tool info and results. The extension does not contact servers or providers and makes no transport or quality promise.
|
|
190
|
+
|
|
191
|
+
### Execution cards
|
|
192
|
+
|
|
193
|
+
- Ownership follows the exact requested tool name and is case-sensitive: `codemode`, or any name starting with `ctx_execute` (`ctx_execute`, `ctx_execute_file`, and a future `ctx_execute*` suffix). `ctx_batch_execute`, `mcp__server__ctx_execute`, and other casings are not owned. The match needs no provenance, so these names get execution cards even when Pi reports none, and the web exception refuses them even if configured in `webSearchTools`.
|
|
194
|
+
- `ctx_execute` and `ctx_execute_file` show their metadata (path for the file form, language for both), the code, and the remaining options — `timeout` in milliseconds, `background`, `cwd`, `intent` — directly under the head, above `Output`. The path is displayed, never read. The server's own input echo is dropped only on an exact match, so a result that does not match the arguments stays intact; codemode's `Script completed`/`Script failed` wrapper is likewise dropped only when it matches exactly.
|
|
195
|
+
- Collapsed previews are independent: the code keeps the first ten visual lines, `Output` the first five. `Ctrl+O` expands the full content.
|
|
196
|
+
- `codemode` shows its JavaScript directly under the head, each model call (name, arguments, status, duration, cost) in a `Calls` section that keeps the last eight entries behind a `… N more entries` hint and prints full errors when expanded, a `Model calls: $…` total when more than one call has a positive cost, the `Output`, and a `Full output: <path>` reference. A partial result shows only the calls already reported and no output.
|
|
197
|
+
- Turning cards off, a presentation failure, or a width below the card minimum restores the captured native renderer for these tools.
|
|
135
198
|
|
|
136
199
|
## How it works
|
|
137
200
|
|
|
138
|
-
Pi `1.
|
|
201
|
+
Pi `1.1` exposes a public render middleware: extensions call `pi.registerToolRenderer()`, and the registered resolvers run in extension load order. `pi-tools-style` registers one resolver that receives `next()` — the downstream remainder of the resolver chain — and draws its own card around the renderer that `next()` returns, capturing that renderer's call and result fragments.
|
|
202
|
+
|
|
203
|
+
Ordering decides the outcome. An earlier registered authoritative non-web renderer wins outright and this plugin is never consulted; a web provider registered after it is intercepted by the web exception, while a web renderer registered before it must delegate web calls through `next()` for that exception to apply. A resolver sees only the renderer handed to it through `next()`, cannot tell which extension produced it, and cannot reorder the chain. Generic third-party renderers are preserved — drawn inside the card, or passed through untouched when they already draw their own complete shell — whereas builtin, MCP, recognized web, and owned execution renderers are replaced by these cards while styling is on and restored to Pi's native rendering when it is turned off. Execution ownership is matched by the exact requested name and is checked first, so those names never take the generic self-shell passthrough and are never promoted to web cards.
|
|
204
|
+
|
|
205
|
+
Direct `!` and `!!` shell commands are not covered by that middleware. They keep a narrow internal decorator on the shell component, guarded so that any incompatible Pi internal change disables only shell framing and reports `Shell styling unavailable: incompatible Pi renderer internals.` The public tool cards keep working.
|
|
139
206
|
|
|
140
|
-
|
|
207
|
+
Turning cards off restores Pi's native rendering. Decoration errors fail open to the native layout, terminal image sequences (Kitty, iTerm2, Sixel) pass through unframed, and the extension never calls `pi.registerTool()` or intercepts execution. Reloading re-registers the renderer and refreshes configuration without stacking wrappers or leaving duplicate timers behind.
|
|
141
208
|
|
|
142
|
-
|
|
209
|
+
### Rendering and cache boundaries
|
|
210
|
+
|
|
211
|
+
- **Header-only animation** — Owned tool cards refresh their prepared running header without rebuilding or rendering the body again. Direct-shell cards reuse Pi's existing loader cadence. For generic renderers, only a known static `Text`/`Container` tree whose descendants are all static and passive qualifies for the header-only transaction; an opaque or custom-render component keeps the ordinary SDK invalidation path so implicit dynamic rendering stays live. This does not make every generic card cost-free.
|
|
212
|
+
- **Generic cards** — A third-party renderer that does not draw its own complete shell is drawn inside the card: its real call component renders directly under the head (first ten visual lines) and its real result component becomes `Output` (first five), falling back to the safe supplied arguments or extracted text only when the renderer returns no usable component. Interactive components are never sliced, while a noninteractive sibling still keeps its own preview and hint.
|
|
213
|
+
- **Bounded preparation** — Each row retains one current primary body and one current-width layout. Unchanged text, Markdown, and syntax highlighting are reused; ordinary callbacks still re-read arguments and results, including objects mutated in place. Real component or environment invalidation clears derived preparation, while width-only changes relayout the retained view.
|
|
214
|
+
- **Exact previews** — Previews remain bounded after wrapping. Unusual Unicode clusters keep defensive width checks, and fresh large payloads still require full-context highlighting and wrapping. This reduces renderer CPU work, not all terminal painting or input latency.
|
|
143
215
|
|
|
144
216
|
## Compatibility and limits
|
|
145
217
|
|
|
146
|
-
- Pi
|
|
147
|
-
- The
|
|
218
|
+
- Pi `>=1.1.0 <2` is the supported peer range, verified with `1.1.0` in both `fullscreen` and `regular` TUI modes. Future `1.x` releases are expected to work through the public middleware, but each release is verified, not assumed.
|
|
219
|
+
- The direct-shell decorator relies on Pi component internals. Its compatibility is bounded by verification: when internals change, the guard disables shell framing only, and the peer range alone does not prove it.
|
|
220
|
+
- Pi's HTML exporter processes SGR styling only. OSC 8 hyperlinks are exported as raw control text around the link label, so exported HTML shows escape sequences that the terminal itself would render as links. The extension adds no artificial export mode or private workaround, and no claim is made about link interaction or image rendering in exports.
|
|
221
|
+
- Pi serializes call and result rows independently. An exported card can therefore show a repeated closing border, and a call row exported while still running keeps the state it had at that moment. No retroactive fixups are attempted.
|
|
222
|
+
- Windows runtime behavior is untested; the Windows-specific path test is skipped on other platforms.
|
|
148
223
|
- Kitty, iTerm2, and Sixel image output remains unboxed by design.
|
|
149
224
|
- Slash commands remain outside the current scope because their transcript output has no uniform component.
|
|
150
|
-
-
|
|
151
|
-
- Unsupported renderer shapes fail open rather than affecting tool execution.
|
|
225
|
+
- A card that cannot be framed safely — turned off globally or per name, narrower than the card minimum, a presentation or construction failure, or an unsupported renderer shape — falls back to the captured native rendering rather than affecting tool execution. A downstream renderer that already draws its own complete shell, and terminal-image output, pass through untouched.
|
|
152
226
|
- Frame characters remain selectable text and are included in copied output. Excluding decorative cells would require Pi/TUI integration for fullscreen selection; no portable exclusion path has been identified for native terminal selection in regular mode.
|
|
153
227
|
|
|
154
228
|
## Development
|
|
@@ -161,7 +235,9 @@ npm run check
|
|
|
161
235
|
npm pack --dry-run --json
|
|
162
236
|
```
|
|
163
237
|
|
|
164
|
-
|
|
238
|
+
Pi selects which extension files a package contributes through that package's `pi.extensions` field (`index.ts` here); activating the working tree means adding the checkout path to Pi's `packages` setting in the user settings and restarting Pi. An already-loaded package picks up changes after `/reload`.
|
|
239
|
+
|
|
240
|
+
`npm run check` runs TypeScript validation and the Vitest suite. Tests cover the builtin, MCP, web-search, and execution semantic views against recorded results, shell command placement and independent previews, frame layout and ANSI width parity, native restoration when cards are disabled, the public middleware resolver and its self-shell exception, the guarded shell decorator, category colors, icon modes, persisted settings, spinner lifecycle, real Pi `1.1` component integration, reload idempotence, fail-open behavior, and the no-`registerTool()` contract.
|
|
165
241
|
|
|
166
242
|
Release-note tests cover exact version sections, Markdown fences and whitespace, duplicate or missing sections, tagged Git snapshots, annotated and lightweight tags, and CLI failure paths.
|
|
167
243
|
|
package/index.ts
CHANGED
|
@@ -1,126 +1,169 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BashExecutionComponent,
|
|
3
|
-
ToolExecutionComponent,
|
|
4
3
|
type ExtensionAPI,
|
|
5
4
|
} from "@earendil-works/pi-coding-agent";
|
|
6
5
|
|
|
7
6
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
setRenderDecoratorEnabled,
|
|
11
|
-
type InstallStatus,
|
|
7
|
+
installShellRenderer,
|
|
8
|
+
type ShellInstallStatus,
|
|
12
9
|
} from "./src/render-decorator.js";
|
|
13
|
-
import { formatToolTitle } from "./src/tool-icon.ts";
|
|
14
10
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
installToolRenderer,
|
|
12
|
+
invalidateToolPresentations,
|
|
13
|
+
initializeToolRendererEnabled,
|
|
14
|
+
reportShellIncompatibility,
|
|
15
|
+
isToolRendererEnabled,
|
|
16
|
+
setShellCompatibilityNotifier,
|
|
17
|
+
setToolRendererEnabled,
|
|
18
|
+
setToolRendererImplementation,
|
|
19
|
+
type ToolRendererSession,
|
|
20
|
+
} from "./src/tool-renderer.js";
|
|
21
|
+
import { clearToolSpinners } from "./src/tool-spinner.ts";
|
|
22
|
+
import { createToolView, invalidateToolViewCache } from "./src/tool-presentation.js";
|
|
23
|
+
import { layoutToolView } from "./src/frame.js";
|
|
24
|
+
import { createWebSearchView, invalidateWebSearchViewCache, isWebSearchTool } from "./src/web-search-presentation.js";
|
|
18
25
|
import {
|
|
19
|
-
|
|
26
|
+
flushSettingsWrites,
|
|
27
|
+
getSettings,
|
|
28
|
+
getWebSearchTools,
|
|
20
29
|
isIconMode,
|
|
30
|
+
isShellEnabledByConfig,
|
|
21
31
|
loadSettings,
|
|
22
|
-
|
|
32
|
+
saveSettings,
|
|
23
33
|
} from "./src/settings.ts";
|
|
24
34
|
import {
|
|
25
|
-
createCategoryBorderStyle,
|
|
26
|
-
createToolBorderStyle,
|
|
27
35
|
setThemeProvider,
|
|
28
|
-
type BorderStyle,
|
|
29
36
|
} from "./src/tool-category.js";
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
const SHELL_RENDER_KEY = Symbol.for("pi-tools-style:shell-render");
|
|
38
|
+
import { openToolsStyleConfig, type ToolsStyleConfigChange } from "./src/config-menu.js";
|
|
33
39
|
|
|
34
40
|
export interface InstallationResult {
|
|
35
|
-
shell:
|
|
36
|
-
|
|
41
|
+
shell: ShellInstallStatus;
|
|
42
|
+
session: ToolRendererSession;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
export function installToolsStyle(): InstallationResult {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
enabled,
|
|
51
|
-
key: SHELL_RENDER_KEY,
|
|
52
|
-
styleBorderFor: () => createCategoryBorderStyle("execute"),
|
|
53
|
-
title: () => formatToolTitle("shell", getIconMode()),
|
|
54
|
-
trimOuterRules: true,
|
|
45
|
+
export function installToolsStyle(pi: ExtensionAPI): InstallationResult {
|
|
46
|
+
setToolRendererImplementation({
|
|
47
|
+
createToolView: (toolName, snapshot, theme) => snapshot.presentation === "web"
|
|
48
|
+
? createWebSearchView(toolName, snapshot, theme)
|
|
49
|
+
: createToolView(toolName, snapshot, theme),
|
|
50
|
+
layoutToolView,
|
|
51
|
+
isWebSearchTool: (toolName, toolInfo, details) => isWebSearchTool(toolName, toolInfo, details, getWebSearchTools()),
|
|
52
|
+
invalidateToolView: (state) => {
|
|
53
|
+
invalidateToolViewCache(state);
|
|
54
|
+
invalidateWebSearchViewCache(state);
|
|
55
|
+
},
|
|
55
56
|
});
|
|
57
|
+
const session = installToolRenderer(pi);
|
|
58
|
+
const shell = installShellRenderer(BashExecutionComponent);
|
|
56
59
|
|
|
57
|
-
return { shell,
|
|
60
|
+
return { shell, session };
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
export default function toolsStyleExtension(pi: ExtensionAPI): void {
|
|
61
|
-
const installation = installToolsStyle();
|
|
64
|
+
const installation = installToolsStyle(pi);
|
|
65
|
+
// Settings installs memory synchronously; runtime effects precede awaiting that write's durability.
|
|
66
|
+
const applyChange = (change: ToolsStyleConfigChange): Promise<void> => {
|
|
67
|
+
let write: Promise<void>;
|
|
68
|
+
switch (change.kind) {
|
|
69
|
+
case "enabled":
|
|
70
|
+
write = saveSettings({ enabled: change.enabled });
|
|
71
|
+
setToolRendererEnabled(change.enabled);
|
|
72
|
+
if (!change.enabled) clearToolSpinners();
|
|
73
|
+
break;
|
|
74
|
+
case "iconMode":
|
|
75
|
+
write = saveSettings({ iconMode: change.iconMode });
|
|
76
|
+
invalidateToolPresentations();
|
|
77
|
+
break;
|
|
78
|
+
case "tool":
|
|
79
|
+
write = saveSettings({ tools: { [change.name]: change.enabled } });
|
|
80
|
+
invalidateToolPresentations(change.name);
|
|
81
|
+
break;
|
|
82
|
+
case "shellEnabled":
|
|
83
|
+
write = saveSettings({ shellEnabled: change.enabled });
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (installation.shell === "unsupported" && isToolRendererEnabled() && isShellEnabledByConfig()) reportShellIncompatibility();
|
|
87
|
+
return write;
|
|
88
|
+
};
|
|
62
89
|
pi.on("session_start", async (_event, context) => {
|
|
63
90
|
clearToolSpinners();
|
|
91
|
+
installation.session.reset();
|
|
92
|
+
setShellCompatibilityNotifier(context.hasUI
|
|
93
|
+
? () => context.ui.notify("Shell styling unavailable: incompatible Pi renderer internals.", "warning")
|
|
94
|
+
: undefined);
|
|
64
95
|
await loadSettings();
|
|
96
|
+
initializeToolRendererEnabled(getSettings().enabled);
|
|
65
97
|
setThemeProvider(() => context.ui.theme);
|
|
98
|
+
invalidateToolPresentations();
|
|
99
|
+
if (installation.shell === "unsupported" && isToolRendererEnabled() && isShellEnabledByConfig()) reportShellIncompatibility();
|
|
66
100
|
});
|
|
67
|
-
pi.on("session_shutdown", () => {
|
|
101
|
+
pi.on("session_shutdown", async () => {
|
|
102
|
+
setShellCompatibilityNotifier(undefined);
|
|
68
103
|
clearToolSpinners();
|
|
104
|
+
installation.session.shutdown();
|
|
69
105
|
setThemeProvider(undefined);
|
|
70
|
-
|
|
106
|
+
await flushSettingsWrites();
|
|
107
|
+
});
|
|
108
|
+
pi.on("tool_execution_start", (event) => {
|
|
109
|
+
installation.session.executionStarted(event.toolCallId);
|
|
110
|
+
});
|
|
111
|
+
pi.on("tool_execution_end", (event) => {
|
|
112
|
+
installation.session.executionEnded(event.toolCallId);
|
|
71
113
|
});
|
|
72
114
|
|
|
73
115
|
const command = {
|
|
74
116
|
description: "Configure tool boxes and icon mode",
|
|
75
117
|
handler: async (args, context) => {
|
|
76
|
-
const [action = "", value, ...extra] = args
|
|
77
|
-
.trim()
|
|
78
|
-
.toLowerCase()
|
|
79
|
-
.split(/\s+/u);
|
|
118
|
+
const [action = "", value, ...extra] = args.trim().toLowerCase().split(/\s+/u);
|
|
80
119
|
const notifyUsage = (): void => {
|
|
81
120
|
context.ui.notify(
|
|
82
|
-
"Usage: /tools-style [on|off|icons ascii|icons nerd-font|icons off]",
|
|
121
|
+
"Usage: /tools-style [on|off|config|icons ascii|icons nerd-font|icons off]",
|
|
83
122
|
"warning",
|
|
84
123
|
);
|
|
85
124
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!isIconMode(value) || extra.length > 0) {
|
|
125
|
+
if (action === "config") {
|
|
126
|
+
if (value !== undefined) {
|
|
89
127
|
notifyUsage();
|
|
90
128
|
return;
|
|
91
129
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
// RPC hasUI cannot host public custom TUI components.
|
|
131
|
+
if (context.mode !== "tui") {
|
|
132
|
+
context.ui.notify("Tool presentation configuration requires interactive TUI mode.", "warning");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await openToolsStyleConfig(pi, context, applyChange);
|
|
95
136
|
return;
|
|
96
137
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
value
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
138
|
+
let change: ToolsStyleConfigChange;
|
|
139
|
+
let success: string;
|
|
140
|
+
if (action === "icons") {
|
|
141
|
+
if (!isIconMode(value) || extra.length > 0) {
|
|
142
|
+
notifyUsage();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
change = { kind: "iconMode", iconMode: value };
|
|
146
|
+
success = `Tool icons: ${value}.`;
|
|
147
|
+
} else {
|
|
148
|
+
if ((action !== "" && action !== "on" && action !== "off") || value !== undefined) {
|
|
149
|
+
notifyUsage();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const enabled = resolveRequestedState(action, isToolRendererEnabled());
|
|
153
|
+
change = { kind: "enabled", enabled };
|
|
154
|
+
success = `Tool boxes ${enabled ? "enabled" : "disabled"}.`;
|
|
104
155
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
156
|
+
try {
|
|
157
|
+
await applyChange(change);
|
|
158
|
+
} catch {
|
|
159
|
+
try {
|
|
160
|
+
context.ui.notify("Tool presentation choice is active in this session, but not saved.", "error");
|
|
161
|
+
} catch {
|
|
162
|
+
// A detached command UI must not turn a handled write rejection into another failure.
|
|
163
|
+
}
|
|
114
164
|
return;
|
|
115
165
|
}
|
|
116
|
-
|
|
117
|
-
const current = isRenderDecoratorEnabled(TOOL_RENDER_KEY);
|
|
118
|
-
const enabled = resolveRequestedState(action, current);
|
|
119
|
-
|
|
120
|
-
setRenderDecoratorEnabled(TOOL_RENDER_KEY, enabled);
|
|
121
|
-
setRenderDecoratorEnabled(SHELL_RENDER_KEY, enabled);
|
|
122
|
-
if (!enabled) clearToolSpinners();
|
|
123
|
-
context.ui.notify(`Tool boxes ${enabled ? "enabled" : "disabled"}.`);
|
|
166
|
+
context.ui.notify(success);
|
|
124
167
|
},
|
|
125
168
|
} satisfies Parameters<ExtensionAPI["registerCommand"]>[1];
|
|
126
169
|
|
|
@@ -137,20 +180,3 @@ function resolveRequestedState(requested: string, current: boolean): boolean {
|
|
|
137
180
|
return !current;
|
|
138
181
|
}
|
|
139
182
|
|
|
140
|
-
function toolName(component: ToolExecutionComponent): string {
|
|
141
|
-
const candidate = (component as unknown as { toolName?: unknown }).toolName;
|
|
142
|
-
return typeof candidate === "string" && candidate.length > 0
|
|
143
|
-
? candidate
|
|
144
|
-
: "tool";
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function toolTitle(component: ToolExecutionComponent): string {
|
|
148
|
-
const mode = getIconMode();
|
|
149
|
-
const title = formatToolTitle(toolName(component), mode);
|
|
150
|
-
const spinner = toolSpinnerFrame(component, mode);
|
|
151
|
-
return spinner ? `${title} ${spinner}` : title;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function toolBorderStyle(component: ToolExecutionComponent): BorderStyle {
|
|
155
|
-
return createToolBorderStyle(toolName(component));
|
|
156
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anthodev/pi-tools-style",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Presentation-only boxes for Pi tool calls and shell commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@earendil-works/pi-coding-agent": "1.0
|
|
44
|
-
"@earendil-works/pi-tui": "1.0
|
|
43
|
+
"@earendil-works/pi-coding-agent": ">=1.1.0 <2",
|
|
44
|
+
"@earendil-works/pi-tui": ">=1.1.0 <2"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
48
|
-
"@earendil-works/pi-tui": "1.
|
|
47
|
+
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
48
|
+
"@earendil-works/pi-tui": "1.1.0",
|
|
49
49
|
"@types/node": "24.12.0",
|
|
50
50
|
"typescript": "5.9.3",
|
|
51
51
|
"vitest": "4.1.9"
|