@thoth-agents/pi-thoth-theme 0.3.0 → 0.5.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 +24 -2
- package/dist/index.ts +221 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,6 +27,26 @@ from the repository root. Restart Pi after installation.
|
|
|
27
27
|
Select the `thoth` theme in Pi's theme settings. Theme selection is independent
|
|
28
28
|
of the module toggles below.
|
|
29
29
|
|
|
30
|
+
## User messages
|
|
31
|
+
|
|
32
|
+
The `thoth` theme renders user messages in `#E8DCB5` on `#120E0A`.
|
|
33
|
+
The extension surrounds the original Markdown with a top `╭── <icon> user ─…╮`
|
|
34
|
+
and bottom `╰─…╯` frame, separated from the content by blank lines. Both borders
|
|
35
|
+
fill the content width after Pi's horizontal padding and adapt on terminal resize.
|
|
36
|
+
Framing is skipped when the width is missing, invalid, or too narrow to fit the
|
|
37
|
+
title, one fill cell and the closing corner (13 columns with the current icons).
|
|
38
|
+
This is display-only: stored messages, assistant replies and thinking are unchanged.
|
|
39
|
+
Blank content and already-framed messages are left alone. The frame is independent
|
|
40
|
+
of the optional modules below.
|
|
41
|
+
|
|
42
|
+
The frame reuses `icons`: `"nerd"` (the existing default) selects nf-fa-user
|
|
43
|
+
(U+F007), `"unicode"` selects `●` (U+25CF), and `"ascii"` selects `@`.
|
|
44
|
+
Set `"icons": "nerd"` in the package configuration and use a Nerd Font in your
|
|
45
|
+
terminal for the Nerd glyph; use `"icons": "unicode"` for its Unicode fallback.
|
|
46
|
+
|
|
47
|
+
Markdown accents and syntax colors remain shared with assistant messages; only
|
|
48
|
+
ordinary user text and the frame use `userMessageText` (`#E8DCB5`).
|
|
49
|
+
|
|
30
50
|
## Modules
|
|
31
51
|
|
|
32
52
|
- **Status line**: a responsive footer below the input showing cumulative
|
|
@@ -37,8 +57,10 @@ of the module toggles below.
|
|
|
37
57
|
drop speed, then cache, then tokens before truncating cost. Editor-border
|
|
38
58
|
metadata is decorated in place without claiming the custom editor slot.
|
|
39
59
|
- **Tools**: boxed calls and results for `read`, `bash`, `powershell`, `ls`,
|
|
40
|
-
`grep`, `find`, `edit` and `
|
|
41
|
-
glyphs or ASCII alternatives. `
|
|
60
|
+
`grep`, `find`, `edit`, `write` and `codemode`, with Nerd Font icons, native Unicode
|
|
61
|
+
glyphs or ASCII alternatives. `codemode` previews up to eight nested calls
|
|
62
|
+
with status and duration; expanded results show the script, output and errors
|
|
63
|
+
without replacing Pi's builtin codemode extension. `bash` and `powershell` show a live elapsed time
|
|
42
64
|
while running. Execution and parameters delegate to Pi's built-in tools;
|
|
43
65
|
Pi's native output toggle still
|
|
44
66
|
collapses and expands results. Registered non-built-in tools keep their own
|
package/dist/index.ts
CHANGED
|
@@ -54,6 +54,15 @@ function getToolElapsedMs(options) {
|
|
|
54
54
|
state.completedElapsedMs = elapsedMs;
|
|
55
55
|
return elapsedMs;
|
|
56
56
|
}
|
|
57
|
+
function renderToolFooter(kit, theme, options) {
|
|
58
|
+
if (kit?.toolFooter) return kit.toolFooter(theme, options);
|
|
59
|
+
const elapsedMs = getToolElapsedMs(options);
|
|
60
|
+
const elapsed = elapsedMs === void 0 ? "" : formatDuration(Math.floor(elapsedMs / 1e3) * 1e3);
|
|
61
|
+
const terminalGlyph = terminalFooterGlyphs[options.status];
|
|
62
|
+
const status = terminalGlyph ?? (options.status === "in_progress" ? "running" : options.status);
|
|
63
|
+
const summary = typeof options.summary === "string" ? [options.summary] : options.summary ?? [];
|
|
64
|
+
return [status, elapsed, ...terminalGlyph ? summary : []].filter(Boolean).join(" \xB7 ");
|
|
65
|
+
}
|
|
57
66
|
function registration() {
|
|
58
67
|
try {
|
|
59
68
|
const record = shared[registryKey];
|
|
@@ -71,6 +80,41 @@ function registerRenderKit(kit, owner) {
|
|
|
71
80
|
function withdrawRenderKit(token) {
|
|
72
81
|
if (registration()?.token === token) delete shared[registryKey];
|
|
73
82
|
}
|
|
83
|
+
function getRenderKit() {
|
|
84
|
+
try {
|
|
85
|
+
const kit = registration()?.kit;
|
|
86
|
+
if (kit?.version === RENDER_KIT_VERSION && (!("resolveToolRenderers" in kit) || typeof kit.resolveToolRenderers === "function") && (!("toolFooter" in kit) || typeof kit.toolFooter === "function") && (!("icon" in kit) || typeof kit.icon === "function") && [
|
|
87
|
+
"card",
|
|
88
|
+
"collapse",
|
|
89
|
+
"cachedComponent",
|
|
90
|
+
"indicator",
|
|
91
|
+
"statusGlyph",
|
|
92
|
+
"widgetHeading",
|
|
93
|
+
"treeRow",
|
|
94
|
+
"fg"
|
|
95
|
+
].every(
|
|
96
|
+
(method2) => typeof kit[method2] === "function"
|
|
97
|
+
))
|
|
98
|
+
return kit;
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
function createKitRenderMemo() {
|
|
104
|
+
let cached;
|
|
105
|
+
return {
|
|
106
|
+
render(width, build) {
|
|
107
|
+
const kit = getRenderKit();
|
|
108
|
+
if (!cached || cached.width !== width || cached.kit !== kit) {
|
|
109
|
+
cached = { width, kit, lines: build(kit) };
|
|
110
|
+
}
|
|
111
|
+
return cached.lines;
|
|
112
|
+
},
|
|
113
|
+
invalidate() {
|
|
114
|
+
cached = void 0;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
74
118
|
var nativeFrames, terminalFooterGlyphs, RENDER_KIT_VERSION, registryKey, shared;
|
|
75
119
|
var init_render_kit = __esm({
|
|
76
120
|
"../pi-core/src/render-kit.ts"() {
|
|
@@ -95,6 +139,9 @@ var init_render_kit = __esm({
|
|
|
95
139
|
completed: "\u2713",
|
|
96
140
|
deleted: "\u2713",
|
|
97
141
|
failed: "\u2717",
|
|
142
|
+
timedOut: "\u25F7",
|
|
143
|
+
resolved: "\u21BB",
|
|
144
|
+
expectedExit: "\u2713",
|
|
98
145
|
cancelled: "\u2717",
|
|
99
146
|
interrupted: "\u2717",
|
|
100
147
|
blocked: "\u2717"
|
|
@@ -115,6 +162,9 @@ var init_panel_frame = __esm({
|
|
|
115
162
|
}
|
|
116
163
|
});
|
|
117
164
|
|
|
165
|
+
// src/index.ts
|
|
166
|
+
import { visibleWidth as visibleWidth8 } from "@earendil-works/pi-tui";
|
|
167
|
+
|
|
118
168
|
// ../pi-core/src/validation.ts
|
|
119
169
|
function isRecord(value) {
|
|
120
170
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -477,6 +527,7 @@ function cachedComponent(renderFn) {
|
|
|
477
527
|
|
|
478
528
|
// src/shared/icons.ts
|
|
479
529
|
var ICONS = {
|
|
530
|
+
user: { nerd: "\uF007", unicode: "\u25CF", ascii: "@" },
|
|
480
531
|
branch: { nerd: "\uE0A0", unicode: "\u2442", ascii: "git" },
|
|
481
532
|
folder: { nerd: "\uF07C", unicode: "dir", ascii: "dir" },
|
|
482
533
|
model: { nerd: "\u{F06A9}", unicode: "\u25CF", ascii: "*" },
|
|
@@ -548,7 +599,10 @@ var STATUS_ICONS = {
|
|
|
548
599
|
running: { nerd: "\u25D0", unicode: "\u25D0", ascii: "*" },
|
|
549
600
|
completed: { nerd: "\uF00C", unicode: "\u2713", ascii: "+" },
|
|
550
601
|
failed: { nerd: "\uF00D", unicode: "\u2717", ascii: "x" },
|
|
551
|
-
|
|
602
|
+
timedOut: { nerd: "\uF017", unicode: "\u25F7", ascii: "T" },
|
|
603
|
+
resolved: { nerd: "\uF021", unicode: "\u21BB", ascii: "~" },
|
|
604
|
+
expectedExit: { nerd: "\uF00C", unicode: "\u2713", ascii: "+" },
|
|
605
|
+
cancelled: { ...STOP, ascii: "#" },
|
|
552
606
|
interrupted: STOP,
|
|
553
607
|
stopping: STOP,
|
|
554
608
|
deleted: BLOCKED,
|
|
@@ -923,6 +977,9 @@ var STATUS_ROLE = {
|
|
|
923
977
|
running: "accent",
|
|
924
978
|
completed: "success",
|
|
925
979
|
failed: "error",
|
|
980
|
+
timedOut: "warning",
|
|
981
|
+
resolved: "muted",
|
|
982
|
+
expectedExit: "dim",
|
|
926
983
|
cancelled: "muted",
|
|
927
984
|
interrupted: "warning",
|
|
928
985
|
stopping: "warning",
|
|
@@ -934,13 +991,16 @@ var TOOL_FOOTER_STATUS = {
|
|
|
934
991
|
completed: "completed",
|
|
935
992
|
deleted: "completed",
|
|
936
993
|
failed: "failed",
|
|
994
|
+
timedOut: "timedOut",
|
|
995
|
+
resolved: "resolved",
|
|
996
|
+
expectedExit: "expectedExit",
|
|
937
997
|
cancelled: "failed",
|
|
938
998
|
interrupted: "failed",
|
|
939
999
|
blocked: "failed"
|
|
940
1000
|
};
|
|
941
1001
|
function startsWithFooterGlyph(footer, mode) {
|
|
942
1002
|
const plain = stripTerminalSequences(footer);
|
|
943
|
-
return ["completed", "failed"].some((status) => {
|
|
1003
|
+
return ["completed", "failed", "timedOut", "resolved", "expectedExit"].some((status) => {
|
|
944
1004
|
const glyph = statusIcon(status, mode);
|
|
945
1005
|
return plain.startsWith(glyph) && (plain.length === glyph.length || /^\s/u.test(plain.slice(glyph.length)));
|
|
946
1006
|
});
|
|
@@ -2254,6 +2314,148 @@ function createCustomPowerShellTool(_cwd, config) {
|
|
|
2254
2314
|
return createCustomShellTool(POWERSHELL_CONFIG, config);
|
|
2255
2315
|
}
|
|
2256
2316
|
|
|
2317
|
+
// src/tools/codemode.ts
|
|
2318
|
+
var COLLAPSED_CALLS = 8;
|
|
2319
|
+
var RESULT_PRESENT = "codemodeResultPresent";
|
|
2320
|
+
var RESULT_BORDER = "codemodeResultBorder";
|
|
2321
|
+
var CALL_COMPONENT = "codemodeCallComponent";
|
|
2322
|
+
var CALL_STATUS = {
|
|
2323
|
+
running: "running",
|
|
2324
|
+
ok: "completed",
|
|
2325
|
+
error: "failed",
|
|
2326
|
+
cancelled: "cancelled"
|
|
2327
|
+
};
|
|
2328
|
+
function displayLines(text) {
|
|
2329
|
+
return text.split("\n").map(escapeControlCharacters);
|
|
2330
|
+
}
|
|
2331
|
+
function scriptText(args) {
|
|
2332
|
+
return args && typeof args === "object" && "code" in args && typeof args.code === "string" ? args.code : "(script unavailable)";
|
|
2333
|
+
}
|
|
2334
|
+
function displayCalls(details) {
|
|
2335
|
+
if (!details || typeof details !== "object" || !("calls" in details) || !Array.isArray(details.calls))
|
|
2336
|
+
return [];
|
|
2337
|
+
return details.calls.flatMap((call) => {
|
|
2338
|
+
if (!call || typeof call !== "object" || !("name" in call) || typeof call.name !== "string" || !("status" in call) || call.status !== "running" && call.status !== "ok" && call.status !== "error" && call.status !== "cancelled")
|
|
2339
|
+
return [];
|
|
2340
|
+
return [
|
|
2341
|
+
{
|
|
2342
|
+
name: call.name,
|
|
2343
|
+
status: call.status,
|
|
2344
|
+
durationMs: "durationMs" in call && typeof call.durationMs === "number" && Number.isFinite(call.durationMs) && call.durationMs >= 0 ? call.durationMs : void 0,
|
|
2345
|
+
error: "error" in call && typeof call.error === "string" ? call.error : void 0
|
|
2346
|
+
}
|
|
2347
|
+
];
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
function createCodemodeRenderer(config) {
|
|
2351
|
+
const fallbackKit = createRenderKit({}, void 0, config.icons);
|
|
2352
|
+
return {
|
|
2353
|
+
renderShell: "self",
|
|
2354
|
+
renderCall(_args, theme, context) {
|
|
2355
|
+
if (context?.state) {
|
|
2356
|
+
context.state[RESULT_PRESENT] = false;
|
|
2357
|
+
context.state[RESULT_BORDER] = void 0;
|
|
2358
|
+
}
|
|
2359
|
+
const memo = createKitRenderMemo();
|
|
2360
|
+
const component = {
|
|
2361
|
+
invalidate: () => memo.invalidate(),
|
|
2362
|
+
render(width) {
|
|
2363
|
+
return memo.render(width, (registeredKit) => {
|
|
2364
|
+
const kit = registeredKit ?? fallbackKit;
|
|
2365
|
+
const hasResult = context?.state?.[RESULT_PRESENT] === true;
|
|
2366
|
+
const status = context?.executionStarted ? "running" : "pending";
|
|
2367
|
+
return kit.card(
|
|
2368
|
+
theme,
|
|
2369
|
+
{
|
|
2370
|
+
title: theme.fg("toolTitle", "codemode"),
|
|
2371
|
+
footer: hasResult ? "" : renderToolFooter(kit, theme, { status, context }),
|
|
2372
|
+
part: hasResult ? "start" : "full",
|
|
2373
|
+
...context?.state?.[RESULT_BORDER]
|
|
2374
|
+
},
|
|
2375
|
+
width
|
|
2376
|
+
);
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
if (context?.state) context.state[CALL_COMPONENT] = component;
|
|
2381
|
+
return component;
|
|
2382
|
+
},
|
|
2383
|
+
renderResult(result, options, theme, context) {
|
|
2384
|
+
const status = options.isPartial ? "running" : context?.isError ? "failed" : "completed";
|
|
2385
|
+
const border = {
|
|
2386
|
+
isError: status === "failed",
|
|
2387
|
+
isSuccess: status === "completed" && context?.executionStarted !== false
|
|
2388
|
+
};
|
|
2389
|
+
if (context?.state) {
|
|
2390
|
+
context.state[RESULT_PRESENT] = true;
|
|
2391
|
+
context.state[RESULT_BORDER] = border;
|
|
2392
|
+
context.state[CALL_COMPONENT]?.invalidate?.();
|
|
2393
|
+
}
|
|
2394
|
+
const calls = displayCalls(result.details);
|
|
2395
|
+
const visibleCalls = options.expanded ? calls : calls.slice(0, COLLAPSED_CALLS);
|
|
2396
|
+
const output = displayLines(getResultText(result) || "(no output)");
|
|
2397
|
+
const errors = calls.flatMap(
|
|
2398
|
+
(call) => call.error ? displayLines(`${call.name}: ${call.error}`).map(
|
|
2399
|
+
(line) => theme.fg("error", line)
|
|
2400
|
+
) : []
|
|
2401
|
+
);
|
|
2402
|
+
const memo = createKitRenderMemo();
|
|
2403
|
+
return {
|
|
2404
|
+
invalidate: () => memo.invalidate(),
|
|
2405
|
+
render(width) {
|
|
2406
|
+
return memo.render(width, (registeredKit) => {
|
|
2407
|
+
const kit = registeredKit ?? fallbackKit;
|
|
2408
|
+
return kit.card(
|
|
2409
|
+
theme,
|
|
2410
|
+
{
|
|
2411
|
+
part: context?.state?.[CALL_COMPONENT] ? "end" : "full",
|
|
2412
|
+
title: theme.fg("toolTitle", "codemode"),
|
|
2413
|
+
body: (bodyWidth) => {
|
|
2414
|
+
if (calls.length === 0) {
|
|
2415
|
+
return options.expanded ? [] : kit.collapse(theme, output, options);
|
|
2416
|
+
}
|
|
2417
|
+
const rows = visibleCalls.map((call, i) => {
|
|
2418
|
+
const duration = call.durationMs === void 0 ? "" : ` \xB7 ${formatDuration(call.durationMs)}`;
|
|
2419
|
+
return kit.treeRow(
|
|
2420
|
+
theme,
|
|
2421
|
+
{
|
|
2422
|
+
text: `${kit.statusGlyph(theme, CALL_STATUS[call.status])} ${escapeControlCharacters(call.name)}${duration}`,
|
|
2423
|
+
last: i === visibleCalls.length - 1
|
|
2424
|
+
},
|
|
2425
|
+
bodyWidth
|
|
2426
|
+
);
|
|
2427
|
+
});
|
|
2428
|
+
if (!options.expanded && calls.length > COLLAPSED_CALLS) {
|
|
2429
|
+
rows.push(
|
|
2430
|
+
theme.fg(
|
|
2431
|
+
"dim",
|
|
2432
|
+
`+${calls.length - COLLAPSED_CALLS} more \xB7 ctrl+o to expand`
|
|
2433
|
+
)
|
|
2434
|
+
);
|
|
2435
|
+
}
|
|
2436
|
+
return rows;
|
|
2437
|
+
},
|
|
2438
|
+
sections: options.expanded ? [
|
|
2439
|
+
{
|
|
2440
|
+
title: "Script",
|
|
2441
|
+
rows: displayLines(scriptText(context?.args))
|
|
2442
|
+
},
|
|
2443
|
+
{ title: "Output", rows: output },
|
|
2444
|
+
...errors.length ? [{ title: "Errors", rows: errors }] : []
|
|
2445
|
+
] : void 0,
|
|
2446
|
+
wrap: options.expanded,
|
|
2447
|
+
footer: renderToolFooter(kit, theme, { status, context }),
|
|
2448
|
+
...border
|
|
2449
|
+
},
|
|
2450
|
+
width
|
|
2451
|
+
);
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2257
2459
|
// src/tools/edit.ts
|
|
2258
2460
|
var COLLAPSED_DIFF_LINES = 6;
|
|
2259
2461
|
function parseDiffStats(diffText) {
|
|
@@ -3345,6 +3547,7 @@ function createCustomWriteTool(cwd, config) {
|
|
|
3345
3547
|
// src/tools/index.ts
|
|
3346
3548
|
function createToolRendererResolver(pi, config, cwd = process.cwd()) {
|
|
3347
3549
|
const renderers = /* @__PURE__ */ new Map([
|
|
3550
|
+
["codemode", createCodemodeRenderer(config)],
|
|
3348
3551
|
["read", createCustomReadTool(cwd, config)],
|
|
3349
3552
|
["bash", createCustomBashTool(cwd, config)],
|
|
3350
3553
|
["powershell", createCustomPowerShellTool(cwd, config)],
|
|
@@ -3928,6 +4131,22 @@ function registerWelcome(pi, ctx, config, options) {
|
|
|
3928
4131
|
// src/index.ts
|
|
3929
4132
|
function thothTheme(pi) {
|
|
3930
4133
|
const config = loadConfig();
|
|
4134
|
+
pi.registerMarkdownTransformer((markdown, context) => {
|
|
4135
|
+
if (context.messageType !== "user" || !markdown.trim()) return markdown;
|
|
4136
|
+
if (/^╭── [\uF007●@] user ─+╮\r?\n\r?\n[\s\S]*\r?\n\r?\n╰─+╯$/.test(markdown))
|
|
4137
|
+
return markdown;
|
|
4138
|
+
const width = context.availableWidth;
|
|
4139
|
+
const title = `\u256D\u2500\u2500 ${icon("user", config.icons)} user `;
|
|
4140
|
+
const titleWidth = visibleWidth8(title);
|
|
4141
|
+
if (!Number.isInteger(width) || width < titleWidth + 2) return markdown;
|
|
4142
|
+
const top = `${title}${"\u2500".repeat(width - titleWidth - 1)}\u256E`;
|
|
4143
|
+
const bottom = `\u2570${"\u2500".repeat(width - 2)}\u256F`;
|
|
4144
|
+
return `${top}
|
|
4145
|
+
|
|
4146
|
+
${markdown}
|
|
4147
|
+
|
|
4148
|
+
${bottom}`;
|
|
4149
|
+
});
|
|
3931
4150
|
const owner = {};
|
|
3932
4151
|
const cwd = process.cwd();
|
|
3933
4152
|
const resolveToolRenderers = config.tools.enabled ? createToolRendererResolver(pi, config, cwd) : void 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thoth-agents/pi-thoth-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Modular Thoth theme, status line, tool styling, and welcome header for Pi.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@earendil-works/pi-tui": "1.0.2",
|
|
51
51
|
"@earendil-works/pi-ai": "1.0.2",
|
|
52
52
|
"@rolldown/binding-wasm32-wasi": "^1.2.4",
|
|
53
|
-
"@thoth-agents/pi-core": "^0.
|
|
53
|
+
"@thoth-agents/pi-core": "^0.4.0"
|
|
54
54
|
},
|
|
55
55
|
"upstream": {
|
|
56
56
|
"repository": "https://github.com/QuangThai/pi-omp-theme",
|