@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.
- package/README.md +3 -3
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +24 -0
- package/node_modules/@czottmann/pi-automode/README.md +78 -112
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +18 -14
- package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +44 -0
- package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +5 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +198 -104
- package/node_modules/@czottmann/pi-automode/docs/configuration.md +155 -0
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +55 -14
- package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +90 -0
- package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +61 -26
- package/node_modules/@czottmann/pi-automode/examples/automode.local.json +5 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +172 -18
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +127 -30
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +3 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +273 -31
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +60 -5
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +92 -8
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +203 -13
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +1 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +11 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +9 -1
- package/node_modules/@czottmann/pi-automode/package.json +9 -2
- package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +63 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +8 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +56 -29
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +15 -13
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +29 -0
- package/node_modules/@herbertgao/pi-handoff/README.md +15 -18
- package/node_modules/@herbertgao/pi-handoff/package.json +11 -4
- package/node_modules/@herbertgao/pi-handoff/src/complete-text.ts +33 -0
- package/node_modules/@herbertgao/pi-handoff/src/index.ts +127 -232
- package/node_modules/@herbertgao/pi-handoff/src/session-query.ts +15 -30
- package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +14 -0
- package/node_modules/@herbertgao/pi-mermaid-open/README.md +21 -4
- package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +11 -0
- package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/viewer.mjs +305 -0
- package/node_modules/@herbertgao/pi-mermaid-open/package.json +15 -4
- package/node_modules/@herbertgao/pi-mermaid-open/src/index.ts +351 -101
- package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +12 -0
- package/node_modules/@herbertgao/pi-preferred-thinking/README.md +30 -14
- package/node_modules/@herbertgao/pi-preferred-thinking/package.json +7 -4
- package/node_modules/@herbertgao/pi-preferred-thinking/src/index.ts +140 -85
- package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +12 -0
- package/node_modules/@herbertgao/pi-recap/README.md +5 -3
- package/node_modules/@herbertgao/pi-recap/package.json +9 -3
- package/node_modules/@herbertgao/pi-recap/src/index.ts +3 -7
- package/node_modules/@herbertgao/pi-recap/src/model-picker.ts +6 -19
- package/node_modules/@herbertgao/pi-recap/src/models.ts +9 -21
- package/node_modules/@herbertgao/pi-recap/src/tui.ts +1 -1
- package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +49 -0
- package/node_modules/@herbertgao/pi-rename/README.md +13 -11
- package/node_modules/@herbertgao/pi-rename/package.json +13 -3
- package/node_modules/@herbertgao/pi-rename/src/index.ts +135 -59
- package/node_modules/@herbertgao/pi-rename/src/models.ts +5 -13
- package/node_modules/@herbertgao/pi-rename/src/naming.ts +2 -6
- package/node_modules/@herbertgao/pi-stash/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +16 -3
- package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +15 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +16 -0
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +5 -1
- package/node_modules/pi-web-access/CHANGELOG.md +13 -0
- package/node_modules/pi-web-access/README.md +2 -2
- package/node_modules/pi-web-access/chrome-cookies.ts +12 -7
- package/node_modules/pi-web-access/gemini-search.ts +28 -10
- package/node_modules/pi-web-access/index.ts +24 -10
- package/node_modules/pi-web-access/package.json +1 -1
- package/package.json +13 -12
|
@@ -1,8 +1,28 @@
|
|
|
1
|
-
import { spawn } from "node:child_process"
|
|
2
|
-
import { mkdir } from "node:fs/promises"
|
|
1
|
+
import { execFile, spawn } from "node:child_process"
|
|
2
|
+
import { mkdtemp, mkdir, readFile, rm } from "node:fs/promises"
|
|
3
3
|
import os from "node:os"
|
|
4
4
|
import path from "node:path"
|
|
5
|
-
import {
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
import { promisify } from "node:util"
|
|
7
|
+
import { diagramKind, render as renderNativeMermaid } from "grok-mermaid"
|
|
8
|
+
import {
|
|
9
|
+
Box,
|
|
10
|
+
CancellableLoader,
|
|
11
|
+
deleteKittyImage,
|
|
12
|
+
getCapabilities,
|
|
13
|
+
Image,
|
|
14
|
+
matchesKey,
|
|
15
|
+
Spacer,
|
|
16
|
+
Text,
|
|
17
|
+
type Component,
|
|
18
|
+
type TUI,
|
|
19
|
+
} from "@earendil-works/pi-tui"
|
|
20
|
+
import {
|
|
21
|
+
getAgentDir,
|
|
22
|
+
type ExtensionAPI,
|
|
23
|
+
type ExtensionCommandContext,
|
|
24
|
+
type Theme,
|
|
25
|
+
} from "@earendil-works/pi-coding-agent"
|
|
6
26
|
|
|
7
27
|
type TextBlock = {
|
|
8
28
|
type: "text"
|
|
@@ -20,30 +40,53 @@ type MessageEntry = {
|
|
|
20
40
|
message: unknown
|
|
21
41
|
}
|
|
22
42
|
|
|
43
|
+
type MermaidFenceLanguage = "mermaid" | "mmd"
|
|
44
|
+
|
|
45
|
+
type NativeMermaidMode = "off" | "final" | "streaming"
|
|
46
|
+
|
|
47
|
+
type NativeStatus =
|
|
48
|
+
| { kind: "rendered"; width: number }
|
|
49
|
+
| { kind: "skipped"; reason: SkipReason }
|
|
50
|
+
|
|
51
|
+
type SkipReason =
|
|
52
|
+
| { kind: "disabled" }
|
|
53
|
+
| { kind: "mmd-fence" }
|
|
54
|
+
| { kind: "unsupported" }
|
|
55
|
+
| { kind: "invalid" }
|
|
56
|
+
| { kind: "too-wide"; width: number; availableWidth: number }
|
|
57
|
+
| { kind: "warnings" }
|
|
58
|
+
|
|
23
59
|
type MermaidDiagram = {
|
|
24
60
|
source: string
|
|
25
61
|
diagramType: string
|
|
26
62
|
label: string
|
|
63
|
+
fenceLanguage: MermaidFenceLanguage
|
|
27
64
|
messageOffset: number
|
|
28
65
|
discoveredIndex: number
|
|
66
|
+
nativeStatus: NativeStatus
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type NativeMermaidConfig = {
|
|
70
|
+
mode: NativeMermaidMode
|
|
71
|
+
availableWidth: number
|
|
29
72
|
}
|
|
30
73
|
|
|
31
|
-
|
|
74
|
+
type PiSettings = {
|
|
75
|
+
outputPad?: 0 | 1
|
|
76
|
+
markdown?: {
|
|
77
|
+
mermaid?: NativeMermaidMode
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const execFileAsync = promisify(execFile)
|
|
32
82
|
const OUTPUT_DIR = path.join(getAgentDir(), "artifacts", "mermaid")
|
|
83
|
+
const HERDR_PLUGIN_ID = "pi-mermaid-open"
|
|
84
|
+
const HERDR_PLUGIN_ROOT = path.resolve(
|
|
85
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
86
|
+
"..",
|
|
87
|
+
"herdr-plugin",
|
|
88
|
+
)
|
|
33
89
|
const MERMAID_FENCE_PATTERN = /```\s*(mermaid|mmd)\b[^\n]*\n([\s\S]*?)```/gi
|
|
34
|
-
const KNOWN_TYPES = [
|
|
35
|
-
"sequenceDiagram",
|
|
36
|
-
"classDiagram",
|
|
37
|
-
"stateDiagram",
|
|
38
|
-
"erDiagram",
|
|
39
|
-
"flowchart",
|
|
40
|
-
"graph",
|
|
41
|
-
"gantt",
|
|
42
|
-
"journey",
|
|
43
|
-
"pie",
|
|
44
|
-
"mindmap",
|
|
45
|
-
"timeline",
|
|
46
|
-
]
|
|
47
90
|
|
|
48
91
|
function isTextBlock(value: unknown): value is TextBlock {
|
|
49
92
|
return (
|
|
@@ -99,18 +142,7 @@ function classifyDiagram(source: string): string {
|
|
|
99
142
|
.map((line) => line.trim())
|
|
100
143
|
.find((line) => line.length > 0 && !line.startsWith("%%"))
|
|
101
144
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for (const type of KNOWN_TYPES) {
|
|
105
|
-
if (
|
|
106
|
-
firstMeaningfulLine === type ||
|
|
107
|
-
firstMeaningfulLine.startsWith(`${type} `)
|
|
108
|
-
) {
|
|
109
|
-
return type
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return firstMeaningfulLine.split(/\s+/)[0] ?? "mermaid"
|
|
145
|
+
return firstMeaningfulLine?.split(/\s+/)[0] ?? "mermaid"
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
function extractTitle(source: string): string | undefined {
|
|
@@ -152,12 +184,71 @@ function conciseError(stderr: string, stdout: string): string {
|
|
|
152
184
|
return text || "Unknown error"
|
|
153
185
|
}
|
|
154
186
|
|
|
155
|
-
function
|
|
187
|
+
function getNativeMermaidMode(settings: PiSettings): NativeMermaidMode {
|
|
188
|
+
const mode = settings.markdown?.mermaid
|
|
189
|
+
return mode === "off" || mode === "final" ? mode : "streaming"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function readNativeMermaidConfig(): Promise<NativeMermaidConfig> {
|
|
193
|
+
let settings: PiSettings = {}
|
|
194
|
+
try {
|
|
195
|
+
settings = JSON.parse(
|
|
196
|
+
await readFile(path.join(getAgentDir(), "settings.json"), "utf8"),
|
|
197
|
+
) as PiSettings
|
|
198
|
+
} catch {
|
|
199
|
+
// Pi's defaults are streaming Mermaid with one column of output padding.
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const columns = process.stdout.columns ?? 80
|
|
203
|
+
const outputPad = settings.outputPad ?? 1
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
mode: getNativeMermaidMode(settings),
|
|
207
|
+
availableWidth: Math.max(1, columns - outputPad * 2),
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function getNativeStatus(
|
|
212
|
+
source: string,
|
|
213
|
+
fenceLanguage: MermaidFenceLanguage,
|
|
214
|
+
config: NativeMermaidConfig,
|
|
215
|
+
): NativeStatus {
|
|
216
|
+
if (config.mode === "off")
|
|
217
|
+
return { kind: "skipped", reason: { kind: "disabled" } }
|
|
218
|
+
if (fenceLanguage !== "mermaid") {
|
|
219
|
+
return { kind: "skipped", reason: { kind: "mmd-fence" } }
|
|
220
|
+
}
|
|
221
|
+
if (diagramKind(source) === null) {
|
|
222
|
+
return { kind: "skipped", reason: { kind: "unsupported" } }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const art = renderNativeMermaid(source)
|
|
226
|
+
if (!art) return { kind: "skipped", reason: { kind: "invalid" } }
|
|
227
|
+
if (art.width > config.availableWidth) {
|
|
228
|
+
return {
|
|
229
|
+
kind: "skipped",
|
|
230
|
+
reason: {
|
|
231
|
+
kind: "too-wide",
|
|
232
|
+
width: art.width,
|
|
233
|
+
availableWidth: config.availableWidth,
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (art.warnings.length > 0) {
|
|
238
|
+
return { kind: "skipped", reason: { kind: "warnings" } }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { kind: "rendered", width: art.width }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function discoverDiagrams(
|
|
245
|
+
entries: readonly unknown[],
|
|
246
|
+
nativeConfig: NativeMermaidConfig,
|
|
247
|
+
): MermaidDiagram[] {
|
|
156
248
|
const assistantMessages = entries
|
|
157
249
|
.filter(isMessageEntry)
|
|
158
250
|
.map((entry) => entry.message)
|
|
159
251
|
.filter(isAssistantMessage)
|
|
160
|
-
.slice(-SCAN_ASSISTANT_MESSAGE_LIMIT)
|
|
161
252
|
.toReversed()
|
|
162
253
|
|
|
163
254
|
const diagrams: MermaidDiagram[] = []
|
|
@@ -168,6 +259,9 @@ function discoverDiagrams(entries: readonly unknown[]): MermaidDiagram[] {
|
|
|
168
259
|
const source = trimOuterBlankLines(match[2] ?? "")
|
|
169
260
|
if (!source) continue
|
|
170
261
|
|
|
262
|
+
const fenceLanguage = (
|
|
263
|
+
match[1] ?? "mermaid"
|
|
264
|
+
).toLowerCase() as MermaidFenceLanguage
|
|
171
265
|
const discoveredIndex = diagrams.length + 1
|
|
172
266
|
const diagramType = classifyDiagram(source)
|
|
173
267
|
const label = extractTitle(source) ?? `${diagramType} ${discoveredIndex}`
|
|
@@ -176,8 +270,10 @@ function discoverDiagrams(entries: readonly unknown[]): MermaidDiagram[] {
|
|
|
176
270
|
source,
|
|
177
271
|
diagramType,
|
|
178
272
|
label,
|
|
273
|
+
fenceLanguage,
|
|
179
274
|
messageOffset: messageIndex + 1,
|
|
180
275
|
discoveredIndex,
|
|
276
|
+
nativeStatus: getNativeStatus(source, fenceLanguage, nativeConfig),
|
|
181
277
|
})
|
|
182
278
|
}
|
|
183
279
|
})
|
|
@@ -185,36 +281,72 @@ function discoverDiagrams(entries: readonly unknown[]): MermaidDiagram[] {
|
|
|
185
281
|
return diagrams
|
|
186
282
|
}
|
|
187
283
|
|
|
284
|
+
export function formatNativeStatus(status: NativeStatus): string {
|
|
285
|
+
if (status.kind === "rendered") return `✓ rendered · ${status.width} cols`
|
|
286
|
+
|
|
287
|
+
switch (status.reason.kind) {
|
|
288
|
+
case "disabled":
|
|
289
|
+
return "✗ skipped · Mermaid disabled"
|
|
290
|
+
case "mmd-fence":
|
|
291
|
+
return "✗ skipped · mmd fence"
|
|
292
|
+
case "unsupported":
|
|
293
|
+
return "✗ skipped · unsupported"
|
|
294
|
+
case "invalid":
|
|
295
|
+
return "✗ skipped · invalid"
|
|
296
|
+
case "too-wide":
|
|
297
|
+
return `✗ skipped · too wide (${status.reason.width} > ${status.reason.availableWidth})`
|
|
298
|
+
case "warnings":
|
|
299
|
+
return "⚠ skipped · parser warnings"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
188
303
|
function pickerLabel(diagram: MermaidDiagram): string {
|
|
189
304
|
return [
|
|
190
|
-
|
|
305
|
+
formatNativeStatus(diagram.nativeStatus),
|
|
191
306
|
diagram.diagramType,
|
|
192
307
|
diagram.label,
|
|
308
|
+
relativeMessageLabel(diagram.messageOffset),
|
|
193
309
|
].join(" · ")
|
|
194
310
|
}
|
|
195
311
|
|
|
196
|
-
async function
|
|
312
|
+
async function createPngArtifactPath(diagram: MermaidDiagram): Promise<string> {
|
|
197
313
|
await mkdir(OUTPUT_DIR, { recursive: true })
|
|
198
314
|
const baseName = [
|
|
199
315
|
timestampForFilename(),
|
|
200
316
|
slugify(diagram.diagramType),
|
|
201
317
|
slugify(diagram.label),
|
|
202
318
|
].join("-")
|
|
203
|
-
return path.join(OUTPUT_DIR, `${baseName}.
|
|
319
|
+
return path.join(OUTPUT_DIR, `${baseName}.png`)
|
|
204
320
|
}
|
|
205
321
|
|
|
206
|
-
async function
|
|
322
|
+
async function renderMermaidToPng(
|
|
207
323
|
source: string,
|
|
208
|
-
|
|
324
|
+
pngPath: string,
|
|
209
325
|
): Promise<{
|
|
210
326
|
ok: boolean
|
|
211
327
|
error?: string
|
|
212
328
|
}> {
|
|
329
|
+
const bunx = os.platform() === "win32" ? "bunx.cmd" : "bunx"
|
|
330
|
+
let command = bunx
|
|
331
|
+
try {
|
|
332
|
+
await execFileAsync(bunx, ["--version"])
|
|
333
|
+
} catch {
|
|
334
|
+
command = os.platform() === "win32" ? "npx.cmd" : "npx"
|
|
335
|
+
}
|
|
336
|
+
|
|
213
337
|
return new Promise((resolve) => {
|
|
214
|
-
const command = os.platform() === "win32" ? "bunx.cmd" : "bunx"
|
|
215
338
|
const child = spawn(
|
|
216
339
|
command,
|
|
217
|
-
[
|
|
340
|
+
[
|
|
341
|
+
"-y",
|
|
342
|
+
"@mermaid-js/mermaid-cli",
|
|
343
|
+
"-i",
|
|
344
|
+
"-",
|
|
345
|
+
"-o",
|
|
346
|
+
pngPath,
|
|
347
|
+
"--scale",
|
|
348
|
+
"4",
|
|
349
|
+
],
|
|
218
350
|
{
|
|
219
351
|
env: { ...process.env, PUPPETEER_CHROME_SKIP_DOWNLOAD: "true" },
|
|
220
352
|
},
|
|
@@ -248,45 +380,126 @@ async function renderMermaidToSvg(
|
|
|
248
380
|
})
|
|
249
381
|
}
|
|
250
382
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
383
|
+
class MermaidImageViewer implements Component {
|
|
384
|
+
private readonly box: Box
|
|
385
|
+
private readonly done: () => void
|
|
386
|
+
private readonly image: Image
|
|
387
|
+
private readonly tui: TUI
|
|
388
|
+
|
|
389
|
+
constructor(
|
|
390
|
+
base64Data: string,
|
|
391
|
+
pngPath: string,
|
|
392
|
+
diagram: MermaidDiagram,
|
|
393
|
+
tui: TUI,
|
|
394
|
+
theme: Theme,
|
|
395
|
+
done: () => void,
|
|
396
|
+
) {
|
|
397
|
+
this.done = done
|
|
398
|
+
this.tui = tui
|
|
399
|
+
this.box = new Box(1, 1, (text) => theme.bg("customMessageBg", text))
|
|
400
|
+
this.box.addChild(new Text(theme.fg("accent", diagram.label), 0, 0))
|
|
401
|
+
this.box.addChild(
|
|
402
|
+
new Text(theme.fg("dim", formatNativeStatus(diagram.nativeStatus)), 0, 0),
|
|
403
|
+
)
|
|
404
|
+
this.box.addChild(new Spacer(1))
|
|
405
|
+
this.image = new Image(
|
|
406
|
+
base64Data,
|
|
407
|
+
"image/png",
|
|
408
|
+
{ fallbackColor: (text) => theme.fg("muted", text) },
|
|
409
|
+
{
|
|
410
|
+
maxWidthCells: Math.max(10, tui.terminal.columns - 8),
|
|
411
|
+
maxHeightCells: Math.max(4, tui.terminal.rows - 8),
|
|
412
|
+
filename: pngPath,
|
|
413
|
+
},
|
|
414
|
+
)
|
|
415
|
+
this.box.addChild(this.image)
|
|
416
|
+
this.box.addChild(new Spacer(1))
|
|
417
|
+
this.box.addChild(new Text(theme.fg("dim", "Esc or Enter to close"), 0, 0))
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
handleInput(data: string): void {
|
|
421
|
+
if (matchesKey(data, "escape") || matchesKey(data, "return")) this.done()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
render(width: number): string[] {
|
|
425
|
+
return this.box.render(width)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
invalidate(): void {
|
|
429
|
+
this.box.invalidate()
|
|
264
430
|
}
|
|
265
431
|
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
432
|
+
dispose(): void {
|
|
433
|
+
const imageId = this.image.getImageId()
|
|
434
|
+
if (imageId !== undefined) {
|
|
435
|
+
this.tui.terminal.write(deleteKittyImage(imageId))
|
|
436
|
+
}
|
|
271
437
|
}
|
|
438
|
+
}
|
|
272
439
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
440
|
+
async function showPngInHerdrOverlay(pngPath: string): Promise<boolean> {
|
|
441
|
+
try {
|
|
442
|
+
await execFileAsync("herdr", [
|
|
443
|
+
"plugin",
|
|
444
|
+
"link",
|
|
445
|
+
HERDR_PLUGIN_ROOT,
|
|
446
|
+
"--enabled",
|
|
447
|
+
])
|
|
448
|
+
await execFileAsync("herdr", [
|
|
449
|
+
"plugin",
|
|
450
|
+
"pane",
|
|
451
|
+
"open",
|
|
452
|
+
"--plugin",
|
|
453
|
+
HERDR_PLUGIN_ID,
|
|
454
|
+
"--entrypoint",
|
|
455
|
+
"viewer",
|
|
456
|
+
"--placement",
|
|
457
|
+
"overlay",
|
|
458
|
+
"--env",
|
|
459
|
+
`PI_MERMAID_OPEN_PNG=${pngPath}`,
|
|
460
|
+
"--focus",
|
|
461
|
+
])
|
|
462
|
+
return true
|
|
463
|
+
} catch {
|
|
464
|
+
return false
|
|
278
465
|
}
|
|
466
|
+
}
|
|
279
467
|
|
|
280
|
-
|
|
468
|
+
async function showPngInTerminal(
|
|
469
|
+
ctx: ExtensionCommandContext,
|
|
470
|
+
pngPath: string,
|
|
471
|
+
diagram: MermaidDiagram,
|
|
472
|
+
): Promise<void> {
|
|
473
|
+
const base64Data = (await readFile(pngPath)).toString("base64")
|
|
474
|
+
await ctx.ui.custom<void>(
|
|
475
|
+
(tui, theme, _keybindings, done) =>
|
|
476
|
+
new MermaidImageViewer(base64Data, pngPath, diagram, tui, theme, done),
|
|
477
|
+
{
|
|
478
|
+
overlay: true,
|
|
479
|
+
overlayOptions: {
|
|
480
|
+
width: "92%",
|
|
481
|
+
maxHeight: "92%",
|
|
482
|
+
margin: 2,
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
)
|
|
281
486
|
}
|
|
282
487
|
|
|
283
488
|
export default function (pi: ExtensionAPI): void {
|
|
284
489
|
pi.registerCommand("mermaid-open", {
|
|
285
|
-
description: "Open a Mermaid diagram
|
|
490
|
+
description: "Open a skipped Mermaid diagram in a terminal popup",
|
|
286
491
|
handler: async (_args, ctx) => {
|
|
287
492
|
await ctx.waitForIdle()
|
|
288
493
|
|
|
289
|
-
const
|
|
494
|
+
const nativeConfig = await readNativeMermaidConfig()
|
|
495
|
+
const diagrams = discoverDiagrams(
|
|
496
|
+
ctx.sessionManager.getBranch(),
|
|
497
|
+
nativeConfig,
|
|
498
|
+
)
|
|
499
|
+
const skippedDiagrams = diagrams.filter(
|
|
500
|
+
(diagram) => diagram.nativeStatus.kind === "skipped",
|
|
501
|
+
)
|
|
502
|
+
|
|
290
503
|
if (diagrams.length === 0) {
|
|
291
504
|
ctx.ui.notify(
|
|
292
505
|
"No Mermaid diagrams found in recent assistant messages.",
|
|
@@ -294,52 +507,89 @@ export default function (pi: ExtensionAPI): void {
|
|
|
294
507
|
)
|
|
295
508
|
return
|
|
296
509
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (ctx.mode === "tui" && diagrams.length > 1) {
|
|
300
|
-
const labels = diagrams.map(pickerLabel)
|
|
301
|
-
const choice = await ctx.ui.select("Open Mermaid diagram:", labels)
|
|
302
|
-
if (!choice) return
|
|
303
|
-
selected = diagrams[labels.indexOf(choice)]
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (!selected) return
|
|
307
|
-
|
|
308
|
-
let svgPath: string
|
|
309
|
-
try {
|
|
310
|
-
svgPath = await createSvgArtifactPath(selected)
|
|
311
|
-
} catch (error) {
|
|
312
|
-
ctx.ui.notify(
|
|
313
|
-
`Failed to create Mermaid artifact path: ${error instanceof Error ? error.message : String(error)}`,
|
|
314
|
-
"error",
|
|
315
|
-
)
|
|
510
|
+
if (skippedDiagrams.length === 0) {
|
|
511
|
+
ctx.ui.notify("No skipped Mermaid diagrams found.", "info")
|
|
316
512
|
return
|
|
317
513
|
}
|
|
318
514
|
|
|
319
|
-
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
"
|
|
515
|
+
let selected = skippedDiagrams[0]
|
|
516
|
+
if (ctx.mode === "tui" && skippedDiagrams.length > 1) {
|
|
517
|
+
const labels = skippedDiagrams.map(pickerLabel)
|
|
518
|
+
const choice = await ctx.ui.select(
|
|
519
|
+
"Open skipped Mermaid diagram:",
|
|
520
|
+
labels,
|
|
324
521
|
)
|
|
325
|
-
return
|
|
522
|
+
if (!choice) return
|
|
523
|
+
selected = skippedDiagrams[labels.indexOf(choice)]
|
|
326
524
|
}
|
|
327
525
|
|
|
328
|
-
if (
|
|
329
|
-
ctx.ui.notify(`Rendered Mermaid SVG: ${svgPath}`, "info")
|
|
330
|
-
return
|
|
331
|
-
}
|
|
526
|
+
if (!selected) return
|
|
332
527
|
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
ctx.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
528
|
+
const imageCapabilities = getCapabilities()
|
|
529
|
+
const canDisplayInHerdr =
|
|
530
|
+
ctx.mode === "tui" &&
|
|
531
|
+
Boolean(process.env["HERDR_PANE_ID"]) &&
|
|
532
|
+
imageCapabilities.images === "kitty"
|
|
533
|
+
const canDisplayInTerminal =
|
|
534
|
+
ctx.mode === "tui" && Boolean(imageCapabilities.images)
|
|
535
|
+
const temporaryDirectory = canDisplayInTerminal
|
|
536
|
+
? await mkdtemp(path.join(os.tmpdir(), "pi-mermaid-open-"))
|
|
537
|
+
: undefined
|
|
538
|
+
const pngPath = temporaryDirectory
|
|
539
|
+
? path.join(temporaryDirectory, "diagram.png")
|
|
540
|
+
: await createPngArtifactPath(selected)
|
|
541
|
+
|
|
542
|
+
if (ctx.mode === "tui") {
|
|
543
|
+
ctx.ui.setWidget("pi-mermaid-open", (tui, theme) => {
|
|
544
|
+
const color = (text: string) => theme.fg("dim", text)
|
|
545
|
+
return new CancellableLoader(
|
|
546
|
+
tui,
|
|
547
|
+
color,
|
|
548
|
+
color,
|
|
549
|
+
"rendering Mermaid...",
|
|
550
|
+
)
|
|
551
|
+
})
|
|
340
552
|
}
|
|
341
553
|
|
|
342
|
-
|
|
554
|
+
let handedOffToHerdr = false
|
|
555
|
+
try {
|
|
556
|
+
const renderResult = await renderMermaidToPng(selected.source, pngPath)
|
|
557
|
+
if (!renderResult.ok) {
|
|
558
|
+
ctx.ui.notify(
|
|
559
|
+
`Mermaid render failed\nError: ${renderResult.error ?? "Unknown error"}`,
|
|
560
|
+
"error",
|
|
561
|
+
)
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (canDisplayInHerdr) {
|
|
566
|
+
handedOffToHerdr = await showPngInHerdrOverlay(pngPath)
|
|
567
|
+
if (handedOffToHerdr) return
|
|
568
|
+
ctx.ui.notify(
|
|
569
|
+
"Herdr overlay unavailable; opening Mermaid in Pi instead.",
|
|
570
|
+
"warning",
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (ctx.mode !== "tui" || !canDisplayInTerminal) {
|
|
575
|
+
ctx.ui.notify(
|
|
576
|
+
canDisplayInTerminal
|
|
577
|
+
? `Rendered Mermaid PNG: ${pngPath}`
|
|
578
|
+
: `Saved Mermaid PNG: ${pngPath}\nKitty graphics are unavailable.`,
|
|
579
|
+
canDisplayInTerminal ? "info" : "warning",
|
|
580
|
+
)
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
await showPngInTerminal(ctx, pngPath, selected)
|
|
585
|
+
} finally {
|
|
586
|
+
if (ctx.mode === "tui") {
|
|
587
|
+
ctx.ui.setWidget("pi-mermaid-open", undefined)
|
|
588
|
+
}
|
|
589
|
+
if (temporaryDirectory && !handedOffToHerdr) {
|
|
590
|
+
await rm(temporaryDirectory, { recursive: true, force: true })
|
|
591
|
+
}
|
|
592
|
+
}
|
|
343
593
|
},
|
|
344
594
|
})
|
|
345
595
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tifan/pi-preferred-thinking
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#116](https://github.com/HerbertGao/pi-extensions/pull/116) [`ecda398`](https://github.com/HerbertGao/pi-extensions/commit/ecda398b9a95212c512d53325c617e485e6ded8d) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Adopt Pi 0.84.2 native per-model thinking pins from enabledModels and retire the extension-specific preference file.
|
|
8
|
+
|
|
9
|
+
## 1.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- [`92208f9`](https://github.com/tifandotme/pi-extensions/commit/92208f9bc3679afbb6a055d91e3860c984a2f2af) Thanks [@tifandotme](https://github.com/tifandotme)! - Move per-model thinking preferences to Pi's native `enabledModels` setting and require Pi 0.84.2 or newer.
|
|
14
|
+
|
|
3
15
|
## 0.3.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> HerbertGao-maintained fork of [@tifan/pi-preferred-thinking](https://github.com/tifandotme/pi-extensions/tree/master/packages/pi-preferred-thinking), distributed under MIT with the original attribution preserved.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
This extension is for model-specific preferences. Pi's built-in `defaultThinkingLevel` remains global, while `preferredThinking` lets you choose different levels for different models. Invalid or missing values are ignored.
|
|
8
|
-
|
|
9
|
-

|
|
5
|
+
Set and apply per-model thinking levels from Pi's native `enabledModels` setting.
|
|
10
6
|
|
|
11
7
|
## Install
|
|
12
8
|
|
|
@@ -14,26 +10,46 @@ This extension is for model-specific preferences. Pi's built-in `defaultThinking
|
|
|
14
10
|
pi install npm:@herbertgao/pi-preferred-thinking
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
This package requires Pi 0.84.2 or newer.
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
## How it works
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
Run `/preferred-thinking` to choose a thinking level for the current model. The
|
|
18
|
+
extension writes the model pin to Pi's `settings.json` and reloads Pi so the
|
|
19
|
+
native model scope uses it.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
When Pi selects a scoped model, the extension applies its native thinking pin.
|
|
22
|
+
It also applies the pin when the model picker is used, which keeps model cycling
|
|
23
|
+
and model selection consistent.
|
|
24
|
+
|
|
25
|
+
If a reasoning model has no saved pin, the extension shows a short hint above
|
|
26
|
+
the editor. It does not change the current thinking level in that case.
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
Add models to Pi's `enabledModels` setting. The extension preserves other
|
|
31
|
+
entries and updates only the selected model:
|
|
24
32
|
|
|
25
33
|
```json
|
|
26
34
|
{
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"openai-codex/gpt-5.6-
|
|
30
|
-
|
|
35
|
+
"enabledModels": [
|
|
36
|
+
"openai-codex/gpt-5.6-luna:xhigh",
|
|
37
|
+
"openai-codex/gpt-5.6-terra:high"
|
|
38
|
+
]
|
|
31
39
|
}
|
|
32
40
|
```
|
|
33
41
|
|
|
42
|
+
The extension requires `enabledModels` to exist before it can save a pin. If
|
|
43
|
+
the selected model is not listed, it adds an exact entry without removing other
|
|
44
|
+
models or patterns.
|
|
45
|
+
|
|
46
|
+
The old `~/.config/pi/extensions/pi-preferred-thinking.json` file is no longer
|
|
47
|
+
read. Existing users must move preferences to `enabledModels` or set them with
|
|
48
|
+
`/preferred-thinking`.
|
|
49
|
+
|
|
34
50
|
## Release notes
|
|
35
51
|
|
|
36
|
-
See [CHANGELOG.md](CHANGELOG.md)
|
|
52
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
37
53
|
|
|
38
54
|
## License
|
|
39
55
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herbertgao/pi-preferred-thinking",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Set and apply per-model thinking levels from Pi's native settings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
7
7
|
"pi-extension",
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"access": "public",
|
|
33
33
|
"provenance": true
|
|
34
34
|
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@earendil-works/pi-coding-agent": ">=0.84.2"
|
|
37
|
+
},
|
|
35
38
|
"engines": {
|
|
36
39
|
"node": ">=20"
|
|
37
40
|
},
|
|
@@ -42,8 +45,8 @@
|
|
|
42
45
|
},
|
|
43
46
|
"x-upstream": {
|
|
44
47
|
"package": "@tifan/pi-preferred-thinking",
|
|
45
|
-
"version": "0.
|
|
48
|
+
"version": "1.0.0",
|
|
46
49
|
"repository": "https://github.com/tifandotme/pi-extensions",
|
|
47
|
-
"commit": "
|
|
50
|
+
"commit": "b39d1e6359c78718cbf8fe5c74c82b89065e9a0b"
|
|
48
51
|
}
|
|
49
52
|
}
|