@pi-unipi/utility 0.2.8 → 2.0.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 +70 -144
- package/package.json +1 -1
- package/src/commands.ts +2 -2
- package/src/diff/highlighter.ts +1 -1
- package/src/diff/theme.ts +0 -0
- package/src/index.ts +21 -18
package/README.md
CHANGED
|
@@ -1,94 +1,68 @@
|
|
|
1
1
|
# @pi-unipi/utility
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Environment info, diagnostics, cleanup, name badge, and diff rendering. The grab-bag package for maintaining your development environment and making tool output readable.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The diff rendering is the standout feature — Shiki-powered syntax-highlighted diffs for `write` and `edit` tool output. Side-by-side view for edits, unified view for writes, with color presets and auto-fallback on narrow terminals.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Commands
|
|
8
8
|
|
|
9
9
|
| Command | Description |
|
|
10
10
|
|---------|-------------|
|
|
11
|
-
| `/unipi:continue` | Continue agent without polluting context |
|
|
12
|
-
| `/unipi:reload` | Explain how to reload extensions |
|
|
13
|
-
| `/unipi:status` | Show status of all unipi modules |
|
|
14
|
-
| `/unipi:cleanup` | Clean stale DBs, temp files, old sessions |
|
|
15
11
|
| `/unipi:env` | Show environment info (Node, Pi, OS, paths) |
|
|
16
12
|
| `/unipi:doctor` | Run diagnostics across all modules |
|
|
17
|
-
| `/unipi:
|
|
13
|
+
| `/unipi:status` | Show status of all unipi modules |
|
|
14
|
+
| `/unipi:cleanup` | Clean stale DBs, temp files, old sessions |
|
|
15
|
+
| `/unipi:reload` | Explain how to reload extensions |
|
|
16
|
+
| `/unipi:name-badge` | Toggle name badge overlay |
|
|
18
17
|
| `/unipi:badge-gen` | Generate session name via LLM and enable badge |
|
|
19
|
-
| `/unipi:util-settings` |
|
|
20
|
-
| `/unipi:badge-settings` | Settings overlay (deprecated alias for `/unipi:util-settings`) |
|
|
18
|
+
| `/unipi:util-settings` | Unified settings for badge and diff rendering |
|
|
21
19
|
|
|
22
|
-
###
|
|
20
|
+
### Examples
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
```
|
|
23
|
+
/unipi:env # Show environment
|
|
24
|
+
/unipi:doctor # Run diagnostics
|
|
25
|
+
/unipi:cleanup # Clean stale files
|
|
26
|
+
/unipi:cleanup --dry-run # Preview what would be cleaned
|
|
27
|
+
/unipi:name-badge # Toggle the session name badge
|
|
28
|
+
/unipi:badge-gen # Generate a session name via LLM
|
|
29
|
+
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
## Special Triggers
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|--------|------|-------------|
|
|
35
|
-
| **ProcessLifecycle** | `lifecycle/process` | Parent PID polling, orphan detection, signal handlers, cleanup registry |
|
|
36
|
-
| **cleanupStale** | `lifecycle/cleanup` | Stale DB/temp/session/cache cleanup with dry-run support |
|
|
37
|
-
| **TTLCache** | `cache/ttl-cache` | Memory or SQLite-backed TTL cache with auto-expiration |
|
|
38
|
-
| **AnalyticsCollector** | `analytics/collector` | Privacy-respecting event collection with daily rollup |
|
|
39
|
-
| **runDiagnostics** | `diagnostics/engine` | Cross-module health checks with plugin architecture |
|
|
40
|
-
| **detectCapabilities** | `display/capabilities` | Terminal feature detection (color, Nerd Font, unicode) |
|
|
41
|
-
| **Width Utilities** | `display/width` | ANSI-aware clamp, wrap, collapse, pad, center |
|
|
42
|
-
| **SettingsInspector** | `tui/settings-inspector` | Reusable settings overlay data model |
|
|
43
|
-
|
|
44
|
-
## Installation
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pi install npm:@pi-unipi/utility
|
|
48
|
-
```
|
|
33
|
+
Utility registers with the info-screen dashboard, showing module status and diagnostic results. The footer subscribes to utility events for its extension status segment.
|
|
49
34
|
|
|
50
|
-
|
|
35
|
+
The diff rendering feature wraps Pi's built-in `write` and `edit` tools. When enabled, these tools show syntax-highlighted diffs instead of plain output. This is a transparent replacement — the agent doesn't need to know about it.
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
pi install npm:@pi-unipi/unipi
|
|
54
|
-
```
|
|
37
|
+
## Agent Tools
|
|
55
38
|
|
|
56
|
-
|
|
39
|
+
| Tool | Description |
|
|
40
|
+
|------|-------------|
|
|
41
|
+
| `ctx_batch` | Atomic batch execution with rollback support |
|
|
42
|
+
| `ctx_env` | Environment inspection for debugging |
|
|
43
|
+
| `write` | Write file with syntax-highlighted diff (when diff enabled) |
|
|
44
|
+
| `edit` | Edit file with split/unified diff view (when diff enabled) |
|
|
57
45
|
|
|
58
|
-
###
|
|
46
|
+
### Batch Execution
|
|
59
47
|
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
/unipi:cleanup # Clean stale files
|
|
63
|
-
/unipi:cleanup --dry-run # Preview what would be cleaned
|
|
64
|
-
/unipi:env # Show environment
|
|
65
|
-
/unipi:doctor # Run diagnostics
|
|
66
|
-
```
|
|
48
|
+
```typescript
|
|
49
|
+
import { BatchBuilder } from "@pi-unipi/utility/tools/batch";
|
|
67
50
|
|
|
68
|
-
|
|
51
|
+
const report = await new BatchBuilder()
|
|
52
|
+
.addCommand("search", { query: "refactor" })
|
|
53
|
+
.addTool("memory_search", { query: "patterns" })
|
|
54
|
+
.withOptions({ failFast: true, commandTimeoutMs: 30000 })
|
|
55
|
+
.execute(myExecutor);
|
|
69
56
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
if (!report.success) {
|
|
58
|
+
console.log("Failed:", report.results.find(r => !r.success)?.error);
|
|
59
|
+
}
|
|
73
60
|
```
|
|
74
61
|
|
|
75
|
-
|
|
76
|
-
It auto-restores visibility on session restart.
|
|
62
|
+
## Configurables
|
|
77
63
|
|
|
78
64
|
### Diff Rendering
|
|
79
65
|
|
|
80
|
-
Shiki-powered, syntax-highlighted diffs for `write` and `edit` tool output. When enabled, the default tools are replaced with enhanced versions that show side-by-side or stacked diffs with syntax highlighting.
|
|
81
|
-
|
|
82
|
-
**Features:**
|
|
83
|
-
- Split view (side-by-side) for `edit` tool, auto-falls back to unified on narrow terminals
|
|
84
|
-
- Unified view (stacked single-column) for `write` tool overwrites
|
|
85
|
-
- 4 color presets: default, midnight, subtle, neon
|
|
86
|
-
- LRU cache (192 entries) for Shiki highlights
|
|
87
|
-
- Large diff fallback (skip highlighting above 80k chars)
|
|
88
|
-
- Environment variable color overrides (`DIFF_ADD_BG`, `DIFF_REM_BG`, etc.)
|
|
89
|
-
|
|
90
|
-
**Configuration:**
|
|
91
|
-
|
|
92
66
|
```
|
|
93
67
|
/unipi:util-settings # Open unified settings TUI
|
|
94
68
|
```
|
|
@@ -106,26 +80,38 @@ Or edit `.unipi/config/util-settings.json` directly:
|
|
|
106
80
|
}
|
|
107
81
|
```
|
|
108
82
|
|
|
109
|
-
|
|
110
|
-
|
|
83
|
+
| Setting | Default | Options |
|
|
84
|
+
|---------|---------|---------|
|
|
85
|
+
| `enabled` | true | true/false |
|
|
86
|
+
| `theme` | "default" | default, midnight, subtle, neon |
|
|
87
|
+
| `shikiTheme` | "github-dark" | github-dark, dracula, one-dark-pro, catppuccin-mocha, nord, tokyo-night |
|
|
88
|
+
| `splitMinWidth` | 150 | Minimum terminal width for split view |
|
|
111
89
|
|
|
112
|
-
|
|
90
|
+
Environment variable overrides: `DIFF_ADD_BG`, `DIFF_REM_BG`, etc.
|
|
113
91
|
|
|
114
|
-
|
|
115
|
-
|
|
92
|
+
Features:
|
|
93
|
+
- Split view (side-by-side) for `edit`, auto-falls back to unified on narrow terminals
|
|
94
|
+
- Unified view (stacked) for `write` overwrites
|
|
95
|
+
- LRU cache (192 entries) for Shiki highlights
|
|
96
|
+
- Large diff fallback (skip highlighting above 80k chars)
|
|
116
97
|
|
|
117
|
-
|
|
118
|
-
.addCommand("search", { query: "refactor" })
|
|
119
|
-
.addTool("memory_search", { query: "patterns" })
|
|
120
|
-
.withOptions({ failFast: true, commandTimeoutMs: 30000 })
|
|
121
|
-
.execute(myExecutor);
|
|
98
|
+
### Name Badge
|
|
122
99
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
100
|
+
The badge is a persistent HUD overlay in the top-right corner showing the current session name. It auto-restores visibility on session restart.
|
|
101
|
+
|
|
102
|
+
## Programmatic API
|
|
103
|
+
|
|
104
|
+
| Module | Path | Description |
|
|
105
|
+
|--------|------|-------------|
|
|
106
|
+
| ProcessLifecycle | `lifecycle/process` | Parent PID polling, orphan detection, signal handlers |
|
|
107
|
+
| cleanupStale | `lifecycle/cleanup` | Stale DB/temp/session/cache cleanup with dry-run |
|
|
108
|
+
| TTLCache | `cache/ttl-cache` | Memory or SQLite-backed TTL cache |
|
|
109
|
+
| AnalyticsCollector | `analytics/collector` | Privacy-respecting event collection with daily rollup |
|
|
110
|
+
| runDiagnostics | `diagnostics/engine` | Cross-module health checks with plugin architecture |
|
|
111
|
+
| detectCapabilities | `display/capabilities` | Terminal feature detection (color, Nerd Font, unicode) |
|
|
112
|
+
| Width Utilities | `display/width` | ANSI-aware clamp, wrap, collapse, pad, center |
|
|
127
113
|
|
|
128
|
-
### TTL Cache
|
|
114
|
+
### TTL Cache
|
|
129
115
|
|
|
130
116
|
```typescript
|
|
131
117
|
import { TTLCache } from "@pi-unipi/utility/cache/ttl-cache";
|
|
@@ -135,16 +121,7 @@ await cache.set("key", { data: "value" });
|
|
|
135
121
|
const value = await cache.get("key");
|
|
136
122
|
```
|
|
137
123
|
|
|
138
|
-
###
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
import { runDiagnostics, formatDiagnosticsReport } from "@pi-unipi/utility/diagnostics/engine";
|
|
142
|
-
|
|
143
|
-
const report = await runDiagnostics();
|
|
144
|
-
console.log(formatDiagnosticsReport(report));
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### Terminal Capabilities (Code)
|
|
124
|
+
### Terminal Capabilities
|
|
148
125
|
|
|
149
126
|
```typescript
|
|
150
127
|
import { detectCapabilities, getIcon } from "@pi-unipi/utility/display/capabilities";
|
|
@@ -154,64 +131,13 @@ console.log("Nerd Font:", caps.nerdFont);
|
|
|
154
131
|
console.log(getIcon("", "[OK]")); // Uses Nerd Font if available
|
|
155
132
|
```
|
|
156
133
|
|
|
157
|
-
## Architecture
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
packages/utility/src/
|
|
161
|
-
├── index.ts # Extension entry point
|
|
162
|
-
├── commands.ts # Command registration
|
|
163
|
-
├── types.ts # Shared types
|
|
164
|
-
├── info-screen.ts # Info-screen integration
|
|
165
|
-
├── lifecycle/
|
|
166
|
-
│ ├── process.ts # Process lifecycle manager
|
|
167
|
-
│ └── cleanup.ts # Stale cleanup utility
|
|
168
|
-
├── cache/
|
|
169
|
-
│ └── ttl-cache.ts # TTL cache (memory + SQLite)
|
|
170
|
-
├── analytics/
|
|
171
|
-
│ └── collector.ts # Event collection + rollup
|
|
172
|
-
├── diagnostics/
|
|
173
|
-
│ └── engine.ts # Health check engine
|
|
174
|
-
├── display/
|
|
175
|
-
│ ├── capabilities.ts # Terminal detection
|
|
176
|
-
│ └── width.ts # Width utilities
|
|
177
|
-
├── diff/
|
|
178
|
-
│ ├── settings.ts # Unified settings (badge + diff) read/write + migration
|
|
179
|
-
│ ├── theme.ts # Diff color presets, resolution chain, hex ↔ ANSI
|
|
180
|
-
│ ├── parser.ts # Diff parsing (structuredPatch, word diff analysis)
|
|
181
|
-
│ ├── highlighter.ts # Shiki singleton, LRU cache, language detection
|
|
182
|
-
│ ├── renderer.ts # Split/unified renderers, ANSI utilities
|
|
183
|
-
│ └── wrapper.ts # write/edit tool wrapping with diff output
|
|
184
|
-
├── tui/
|
|
185
|
-
│ ├── settings-inspector.ts # Settings overlay model
|
|
186
|
-
│ ├── name-badge.ts # Name badge overlay component
|
|
187
|
-
│ ├── name-badge-state.ts # Name badge state manager
|
|
188
|
-
│ ├── badge-settings.ts # Badge settings (thin wrapper over diff/settings)
|
|
189
|
-
│ └── util-settings-tui.ts # Unified settings TUI (badge + diff)
|
|
190
|
-
└── tools/
|
|
191
|
-
├── batch.ts # Batch execution
|
|
192
|
-
└── env.ts # Environment info
|
|
193
|
-
```
|
|
194
|
-
|
|
195
134
|
## Privacy
|
|
196
135
|
|
|
197
|
-
The analytics collector is
|
|
198
|
-
- No file contents
|
|
136
|
+
The analytics collector is privacy-respecting:
|
|
137
|
+
- No file contents recorded
|
|
199
138
|
- No sensitive data (API keys, tokens, passwords) — redacted automatically
|
|
200
139
|
- Strings truncated to 500 characters
|
|
201
|
-
- All data stays local
|
|
202
|
-
|
|
203
|
-
## Dependencies
|
|
204
|
-
|
|
205
|
-
- `@pi-unipi/core` — Shared constants, events, utilities
|
|
206
|
-
- `@mariozechner/pi-coding-agent` — Pi extension API
|
|
207
|
-
- `@sinclair/typebox` — Schema validation (peer dependency)
|
|
208
|
-
- `diff` — Unified diff generation (for diff rendering)
|
|
209
|
-
- `@shikijs/cli` — Shiki syntax highlighting (for diff rendering)
|
|
210
|
-
- `sqlite3` — Optional, for persistent cache/analytics
|
|
211
|
-
|
|
212
|
-
### Dev Dependencies
|
|
213
|
-
|
|
214
|
-
- `@types/diff` — TypeScript types for the diff library
|
|
140
|
+
- All data stays local
|
|
215
141
|
|
|
216
142
|
## License
|
|
217
143
|
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -109,7 +109,7 @@ export function registerNameBadgeCommands(
|
|
|
109
109
|
|
|
110
110
|
// Redirect to unified settings
|
|
111
111
|
ctx.ui.custom(
|
|
112
|
-
(tui
|
|
112
|
+
(tui, _theme, _keybindings, done) => {
|
|
113
113
|
const overlay = new UtilSettingsTui();
|
|
114
114
|
overlay.onClose = () => done(undefined);
|
|
115
115
|
overlay.requestRender = () => tui.requestRender();
|
|
@@ -145,7 +145,7 @@ export function registerNameBadgeCommands(
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
ctx.ui.custom(
|
|
148
|
-
(tui
|
|
148
|
+
(tui, _theme, _keybindings, done) => {
|
|
149
149
|
const overlay = new UtilSettingsTui();
|
|
150
150
|
overlay.onClose = () => done(undefined);
|
|
151
151
|
overlay.requestRender = () => tui.requestRender();
|
package/src/diff/highlighter.ts
CHANGED
|
@@ -249,7 +249,7 @@ export function normalizeShikiContrast(
|
|
|
249
249
|
// ─── Shiki Highlighter ──────────────────────────────────────────────────────────
|
|
250
250
|
|
|
251
251
|
/** Shiki highlighter instance (lazy singleton) */
|
|
252
|
-
let shikiHighlighter:
|
|
252
|
+
let shikiHighlighter: import("shiki").Highlighter | null = null;
|
|
253
253
|
let shikiInitPromise: Promise<any> | null = null;
|
|
254
254
|
|
|
255
255
|
/**
|
package/src/diff/theme.ts
CHANGED
|
File without changes
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - TUI: settings inspector pattern, name badge
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
import type { ExtensionAPI, InputEvent, AgentEndEvent } from "@mariozechner/pi-coding-agent";
|
|
16
16
|
import {
|
|
17
17
|
UNIPI_EVENTS,
|
|
18
18
|
MODULES,
|
|
@@ -44,7 +44,7 @@ let firstMessageSeen = false;
|
|
|
44
44
|
let firstUserText = "";
|
|
45
45
|
|
|
46
46
|
/** Stored UI context from first input, used to show badge overlay after agent responds */
|
|
47
|
-
let firstInputCtx:
|
|
47
|
+
let firstInputCtx: import("@mariozechner/pi-coding-agent").ExtensionContext | null = null;
|
|
48
48
|
|
|
49
49
|
/** All commands registered by this module */
|
|
50
50
|
const ALL_COMMANDS = [
|
|
@@ -124,7 +124,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
// First-message hook: capture user text for deferred badge generation
|
|
127
|
-
pi.on("input", async (_event
|
|
127
|
+
pi.on("input", async (_event, ctx) => {
|
|
128
128
|
// Only trigger on first user message
|
|
129
129
|
if (firstMessageSeen) return;
|
|
130
130
|
firstMessageSeen = true;
|
|
@@ -138,12 +138,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
138
138
|
if (sessionName) return;
|
|
139
139
|
|
|
140
140
|
// Store first message text for later use in agent_end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
// Note: InputEvent.text is the documented property; content may exist at runtime
|
|
142
|
+
const content = (_event as unknown as Record<string, unknown>).content;
|
|
143
|
+
firstUserText = typeof content === "string"
|
|
144
|
+
? content
|
|
145
|
+
: Array.isArray(content)
|
|
146
|
+
? (content as Array<Record<string, unknown>>)
|
|
147
|
+
.filter((c) => c.type === "text")
|
|
148
|
+
.map((c) => String(c.text))
|
|
147
149
|
.join(" ")
|
|
148
150
|
: "";
|
|
149
151
|
|
|
@@ -152,7 +154,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
152
154
|
});
|
|
153
155
|
|
|
154
156
|
// After agent completes first response, generate badge name with full conversation context
|
|
155
|
-
pi.on("agent_end", async (event
|
|
157
|
+
pi.on("agent_end", async (event, _ctx) => {
|
|
156
158
|
// Only act if we captured a first input and are waiting for badge generation
|
|
157
159
|
if (!firstInputCtx) return;
|
|
158
160
|
const ctx = firstInputCtx;
|
|
@@ -168,7 +170,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
168
170
|
}
|
|
169
171
|
|
|
170
172
|
// Build conversation summary from full message history (user + assistant)
|
|
171
|
-
const messages
|
|
173
|
+
const messages = event.messages;
|
|
172
174
|
const summaryParts: string[] = [];
|
|
173
175
|
|
|
174
176
|
// Include the user's first message
|
|
@@ -177,16 +179,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
// Include assistant's response text
|
|
180
|
-
const assistantMsgs = messages.filter((m
|
|
182
|
+
const assistantMsgs = messages.filter((m) => m.role === "assistant");
|
|
181
183
|
for (const msg of assistantMsgs) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
.
|
|
184
|
+
const content = msg.content;
|
|
185
|
+
if (Array.isArray(content)) {
|
|
186
|
+
const textParts = content
|
|
187
|
+
.filter((c): c is { type: "text"; text: string } => "text" in c && c.type === "text")
|
|
188
|
+
.map((c) => c.text)
|
|
186
189
|
.join(" ");
|
|
187
190
|
if (textParts) summaryParts.push(`Assistant: ${textParts}`);
|
|
188
|
-
} else if (typeof
|
|
189
|
-
summaryParts.push(`Assistant: ${
|
|
191
|
+
} else if (typeof content === "string" && content) {
|
|
192
|
+
summaryParts.push(`Assistant: ${content}`);
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
|