@herbertgao/pi-extensions 2026.8.8 → 2026.8.9

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.
Files changed (74) hide show
  1. package/README.md +3 -3
  2. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +24 -0
  3. package/node_modules/@czottmann/pi-automode/README.md +78 -112
  4. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +18 -14
  5. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +44 -0
  6. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +5 -0
  7. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +198 -104
  8. package/node_modules/@czottmann/pi-automode/docs/configuration.md +155 -0
  9. package/node_modules/@czottmann/pi-automode/docs/defaults.md +55 -14
  10. package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +90 -0
  11. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +61 -26
  12. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +5 -0
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +172 -18
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +127 -30
  15. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +3 -0
  16. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +273 -31
  17. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +60 -5
  18. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +92 -8
  19. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +203 -13
  20. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +1 -0
  21. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +11 -0
  22. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +9 -1
  23. package/node_modules/@czottmann/pi-automode/package.json +9 -2
  24. package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +63 -0
  25. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +3 -3
  26. package/node_modules/@herbertgao/pi-cc-extensions/README.md +3 -3
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +8 -3
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +56 -29
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +15 -13
  31. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  32. package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +29 -0
  33. package/node_modules/@herbertgao/pi-handoff/README.md +15 -18
  34. package/node_modules/@herbertgao/pi-handoff/package.json +11 -4
  35. package/node_modules/@herbertgao/pi-handoff/src/complete-text.ts +33 -0
  36. package/node_modules/@herbertgao/pi-handoff/src/index.ts +127 -232
  37. package/node_modules/@herbertgao/pi-handoff/src/session-query.ts +15 -30
  38. package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +14 -0
  39. package/node_modules/@herbertgao/pi-mermaid-open/README.md +21 -4
  40. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +11 -0
  41. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/viewer.mjs +305 -0
  42. package/node_modules/@herbertgao/pi-mermaid-open/package.json +15 -4
  43. package/node_modules/@herbertgao/pi-mermaid-open/src/index.ts +351 -101
  44. package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +12 -0
  45. package/node_modules/@herbertgao/pi-preferred-thinking/README.md +30 -14
  46. package/node_modules/@herbertgao/pi-preferred-thinking/package.json +7 -4
  47. package/node_modules/@herbertgao/pi-preferred-thinking/src/index.ts +140 -85
  48. package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +12 -0
  49. package/node_modules/@herbertgao/pi-recap/README.md +5 -3
  50. package/node_modules/@herbertgao/pi-recap/package.json +9 -3
  51. package/node_modules/@herbertgao/pi-recap/src/index.ts +3 -7
  52. package/node_modules/@herbertgao/pi-recap/src/model-picker.ts +6 -19
  53. package/node_modules/@herbertgao/pi-recap/src/models.ts +9 -21
  54. package/node_modules/@herbertgao/pi-recap/src/tui.ts +1 -1
  55. package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +49 -0
  56. package/node_modules/@herbertgao/pi-rename/README.md +13 -11
  57. package/node_modules/@herbertgao/pi-rename/package.json +13 -3
  58. package/node_modules/@herbertgao/pi-rename/src/index.ts +135 -59
  59. package/node_modules/@herbertgao/pi-rename/src/models.ts +5 -13
  60. package/node_modules/@herbertgao/pi-rename/src/naming.ts +2 -6
  61. package/node_modules/@herbertgao/pi-stash/package.json +3 -3
  62. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  63. package/node_modules/@herbertgao/pi-subagents/README.md +16 -3
  64. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  65. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +15 -4
  66. package/node_modules/@herbertgao/pi-subagents/src/index.ts +16 -0
  67. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +5 -1
  68. package/node_modules/pi-web-access/CHANGELOG.md +13 -0
  69. package/node_modules/pi-web-access/README.md +2 -2
  70. package/node_modules/pi-web-access/chrome-cookies.ts +12 -7
  71. package/node_modules/pi-web-access/gemini-search.ts +28 -10
  72. package/node_modules/pi-web-access/index.ts +24 -10
  73. package/node_modules/pi-web-access/package.json +1 -1
  74. package/package.json +13 -12
@@ -0,0 +1,305 @@
1
+ import net from "node:net"
2
+ import { readFile, rm } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import {
5
+ allocateImageId,
6
+ deleteKittyImage,
7
+ encodeKitty,
8
+ getPngDimensions,
9
+ } from "@earendil-works/pi-tui"
10
+
11
+ const pngPath = process.env.PI_MERMAID_OPEN_PNG
12
+ const temporaryDirectory = pngPath ? path.dirname(pngPath) : undefined
13
+ const herdrSocketPath = process.env.HERDR_SOCKET_PATH
14
+ const herdrPaneId = process.env.HERDR_PANE_ID
15
+ const zoomSteps = [1, 1.25, 1.5, 2, 3]
16
+ const panStep = 5
17
+
18
+ let closing = false
19
+
20
+ function calculateImageCellSize(image, maxWidth, maxHeight, cell) {
21
+ const scale = Math.min(
22
+ (maxWidth * cell.widthPx) / image.widthPx,
23
+ (maxHeight * cell.heightPx) / image.heightPx,
24
+ )
25
+ return {
26
+ columns: Math.ceil((image.widthPx * scale) / cell.widthPx),
27
+ rows: Math.ceil((image.heightPx * scale) / cell.heightPx),
28
+ scale,
29
+ }
30
+ }
31
+
32
+ function encodeKittyFrame(
33
+ base64Data,
34
+ imageId,
35
+ columns,
36
+ rows,
37
+ sourceX,
38
+ sourceY,
39
+ sourceWidth,
40
+ sourceHeight,
41
+ ) {
42
+ const sequence = encodeKitty(base64Data, {
43
+ columns,
44
+ rows,
45
+ imageId,
46
+ moveCursor: false,
47
+ })
48
+ return sequence.replace(
49
+ "\x1b_G",
50
+ `\x1b_Gx=${sourceX},y=${sourceY},w=${sourceWidth},h=${sourceHeight},`,
51
+ )
52
+ }
53
+
54
+ function herdrRequest(method, params) {
55
+ if (!herdrSocketPath || !herdrPaneId) {
56
+ return Promise.reject(new Error("Herdr graphics context is missing"))
57
+ }
58
+
59
+ return new Promise((resolve, reject) => {
60
+ const socket = net.createConnection(herdrSocketPath)
61
+ let buffer = ""
62
+ let settled = false
63
+ const timeout = setTimeout(
64
+ () => settle(reject, new Error(`Herdr request timed out: ${method}`)),
65
+ 1500,
66
+ )
67
+ const settle = (callback, value) => {
68
+ if (settled) return
69
+ settled = true
70
+ clearTimeout(timeout)
71
+ socket.destroy()
72
+ callback(value)
73
+ }
74
+
75
+ socket.setEncoding("utf8")
76
+ socket.once("error", (error) => settle(reject, error))
77
+ socket.on("data", (chunk) => {
78
+ buffer += chunk
79
+ const newline = buffer.indexOf("\n")
80
+ if (newline === -1) return
81
+
82
+ let response
83
+ try {
84
+ response = JSON.parse(buffer.slice(0, newline))
85
+ } catch {
86
+ settle(reject, new Error("Invalid Herdr API response"))
87
+ return
88
+ }
89
+ if (response.error) {
90
+ settle(
91
+ reject,
92
+ new Error(
93
+ response.error.message ?? `Herdr request failed: ${method}`,
94
+ ),
95
+ )
96
+ } else {
97
+ settle(resolve, response.result)
98
+ }
99
+ })
100
+ socket.once("connect", () => {
101
+ socket.write(
102
+ JSON.stringify({ id: "pi-mermaid-open", method, params }) + "\n",
103
+ )
104
+ })
105
+ })
106
+ }
107
+
108
+ async function finish(clearGraphics = async () => {}) {
109
+ if (closing) return
110
+ closing = true
111
+
112
+ if (process.stdin.isTTY) process.stdin.setRawMode(false)
113
+ process.stdin.pause()
114
+ try {
115
+ await clearGraphics()
116
+ } catch {
117
+ // The Herdr pane may already be gone.
118
+ }
119
+ process.stdout.write("\x1b[2J\x1b[H")
120
+
121
+ if (temporaryDirectory) {
122
+ await rm(temporaryDirectory, { recursive: true, force: true })
123
+ }
124
+
125
+ process.exit(0)
126
+ }
127
+
128
+ function waitForClose(onKey, clearGraphics) {
129
+ if (process.stdin.isTTY) process.stdin.setRawMode(true)
130
+ process.stdin.resume()
131
+ process.stdin.on("data", (data) => {
132
+ const input = data.toString("utf8")
133
+ if (
134
+ input === "\x03" ||
135
+ input === "\x1b" ||
136
+ input === "\r" ||
137
+ input === "\n"
138
+ ) {
139
+ void finish(clearGraphics)
140
+ return
141
+ }
142
+
143
+ const key = {
144
+ "\x1b[A": "up",
145
+ "\x1bOA": "up",
146
+ "\x1b[B": "down",
147
+ "\x1bOB": "down",
148
+ "\x1b[C": "right",
149
+ "\x1bOC": "right",
150
+ "\x1b[D": "left",
151
+ "\x1bOD": "left",
152
+ h: "left",
153
+ j: "down",
154
+ k: "up",
155
+ l: "right",
156
+ }[input]
157
+ if (key) void onKey(key)
158
+ else if (input === "+" || input === "=") void onKey("zoom-in")
159
+ else if (input === "-") void onKey("zoom-out")
160
+ else if (input === "0") void onKey("reset")
161
+ })
162
+ }
163
+
164
+ process.once("SIGINT", () => void finish())
165
+ process.once("SIGTERM", () => void finish())
166
+ process.once("SIGHUP", () => void finish())
167
+
168
+ if (!pngPath) {
169
+ process.stdout.write("Mermaid image path is missing. Press Enter to close.\n")
170
+ waitForClose(() => {})
171
+ } else {
172
+ try {
173
+ const base64Data = (await readFile(pngPath)).toString("base64")
174
+ const dimensions = getPngDimensions(base64Data)
175
+ if (!dimensions) throw new Error("invalid PNG")
176
+
177
+ const columns = process.stdout.columns ?? 160
178
+ const rows = process.stdout.rows ?? 80
179
+ const availableWidth = Math.max(1, columns - 2)
180
+ const availableHeight = Math.max(1, rows - 4)
181
+ const graphicsInfo = await herdrRequest("pane.graphics.info", {
182
+ pane_id: herdrPaneId,
183
+ })
184
+ const cellDimensions = {
185
+ widthPx: graphicsInfo.cell_width_px ?? 9,
186
+ heightPx: graphicsInfo.cell_height_px ?? 18,
187
+ }
188
+ const imageId = allocateImageId()
189
+ let zoomIndex = 0
190
+ let panX = 0
191
+ let panY = 0
192
+ let imageColumns = 0
193
+ let imageRows = 0
194
+ let maxPanX = 0
195
+ let maxPanY = 0
196
+ let renderQueue = Promise.resolve()
197
+
198
+ const clearGraphics = async () => {
199
+ process.stdout.write(deleteKittyImage(imageId))
200
+ }
201
+
202
+ async function renderFrame() {
203
+ const size = calculateImageCellSize(
204
+ dimensions,
205
+ availableWidth * zoomSteps[zoomIndex],
206
+ availableHeight * zoomSteps[zoomIndex],
207
+ cellDimensions,
208
+ )
209
+ imageColumns = size.columns
210
+ imageRows = size.rows
211
+ maxPanX = Math.max(0, imageColumns - availableWidth)
212
+ maxPanY = Math.max(0, imageRows - availableHeight)
213
+ panX = Math.max(-maxPanX, Math.min(0, panX))
214
+ panY = Math.max(-maxPanY, Math.min(0, panY))
215
+
216
+ const visibleColumns = Math.min(imageColumns, availableWidth)
217
+ const visibleRows = Math.min(imageRows, availableHeight)
218
+ const sourceX = Math.min(
219
+ dimensions.widthPx - 1,
220
+ Math.floor((-panX * cellDimensions.widthPx) / size.scale),
221
+ )
222
+ const sourceY = Math.min(
223
+ dimensions.heightPx - 1,
224
+ Math.floor((-panY * cellDimensions.heightPx) / size.scale),
225
+ )
226
+ const sourceWidth = Math.min(
227
+ dimensions.widthPx - sourceX,
228
+ Math.ceil((visibleColumns * cellDimensions.widthPx) / size.scale),
229
+ )
230
+ const sourceHeight = Math.min(
231
+ dimensions.heightPx - sourceY,
232
+ Math.ceil((visibleRows * cellDimensions.heightPx) / size.scale),
233
+ )
234
+ const imageTop =
235
+ imageRows <= availableHeight
236
+ ? Math.floor((availableHeight - imageRows) / 2)
237
+ : 0
238
+ const imageLeft =
239
+ imageColumns <= availableWidth
240
+ ? Math.floor((columns - imageColumns) / 2)
241
+ : 0
242
+
243
+ const frame = encodeKittyFrame(
244
+ base64Data,
245
+ imageId,
246
+ visibleColumns,
247
+ visibleRows,
248
+ sourceX,
249
+ sourceY,
250
+ sourceWidth,
251
+ sourceHeight,
252
+ )
253
+ const hint = `Zoom +/- · Pan hjkl/arrows · Reset 0 · Close Enter/Esc`
254
+ const hintLeft = Math.max(0, Math.floor((columns - hint.length) / 2))
255
+ process.stdout.write(
256
+ `\x1b[2J\x1b[H\x1b[${imageTop + 1};${imageLeft + 1}H${frame}\x1b[${rows - 1};${hintLeft + 1}H${hint}`,
257
+ )
258
+ }
259
+
260
+ function scheduleRender() {
261
+ renderQueue = renderQueue.then(renderFrame)
262
+ void renderQueue.catch(() => void finish())
263
+ }
264
+
265
+ function pan(deltaX, deltaY) {
266
+ const nextX = Math.max(-maxPanX, Math.min(0, panX + deltaX))
267
+ const nextY = Math.max(-maxPanY, Math.min(0, panY + deltaY))
268
+ if (nextX === panX && nextY === panY) return
269
+ panX = nextX
270
+ panY = nextY
271
+ scheduleRender()
272
+ }
273
+
274
+ function handleKey(key) {
275
+ if (key === "up") pan(0, panStep)
276
+ else if (key === "down") pan(0, -panStep)
277
+ else if (key === "left") pan(panStep, 0)
278
+ else if (key === "right") pan(-panStep, 0)
279
+ else if (key === "zoom-in" && zoomIndex < zoomSteps.length - 1) {
280
+ zoomIndex++
281
+ panX = 0
282
+ panY = 0
283
+ scheduleRender()
284
+ } else if (key === "zoom-out" && zoomIndex > 0) {
285
+ zoomIndex--
286
+ panX = 0
287
+ panY = 0
288
+ scheduleRender()
289
+ } else if (key === "reset") {
290
+ zoomIndex = 0
291
+ panX = 0
292
+ panY = 0
293
+ scheduleRender()
294
+ }
295
+ }
296
+
297
+ await renderFrame()
298
+ waitForClose(handleKey, clearGraphics)
299
+ } catch (error) {
300
+ process.stdout.write(
301
+ `Could not display Mermaid PNG: ${error instanceof Error ? error.message : String(error)}\nPress Enter to close.\n`,
302
+ )
303
+ waitForClose(() => {})
304
+ }
305
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-mermaid-open",
3
- "version": "0.1.3",
4
- "description": "Extract and open Mermaid diagrams from pi agent responses.",
3
+ "version": "0.2.0",
4
+ "description": "Find skipped Mermaid diagrams and show them in a terminal image viewer.",
5
5
  "keywords": [
6
6
  "diagram",
7
7
  "mermaid",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "files": [
25
25
  "src/**/*.ts",
26
+ "herdr-plugin/**/*",
26
27
  "README.md",
27
28
  "CHANGELOG.md",
28
29
  "LICENSE"
@@ -32,6 +33,16 @@
32
33
  "access": "public",
33
34
  "provenance": true
34
35
  },
36
+ "scripts": {
37
+ "test": "node --experimental-strip-types --test tests/**/*.test.ts"
38
+ },
39
+ "dependencies": {
40
+ "grok-mermaid": "0.2.2"
41
+ },
42
+ "peerDependencies": {
43
+ "@earendil-works/pi-coding-agent": ">=0.84.0",
44
+ "@earendil-works/pi-tui": ">=0.84.0"
45
+ },
35
46
  "engines": {
36
47
  "node": ">=20"
37
48
  },
@@ -42,8 +53,8 @@
42
53
  },
43
54
  "x-upstream": {
44
55
  "package": "@tifan/pi-mermaid-open",
45
- "version": "0.1.3",
56
+ "version": "0.2.0",
46
57
  "repository": "https://github.com/tifandotme/pi-extensions",
47
- "commit": "460d580"
58
+ "commit": "b39d1e6359c78718cbf8fe5c74c82b89065e9a0b"
48
59
  }
49
60
  }