@quandev104/pi-style 0.1.3 → 0.1.5
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/CHANGELOG.md +30 -0
- package/README.md +8 -4
- package/dist/extensions/pi-style.js +3777 -958
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +2 -0
- package/extension-src/pi-style/domain/config-normalization.ts +21 -5
- package/extension-src/pi-style/domain/config-presets.ts +1 -1
- package/extension-src/pi-style/domain/config-types.ts +7 -3
- package/extension-src/pi-style/domain/status.ts +1 -1
- package/extension-src/pi-style/domain/theme.ts +6 -1
- package/extension-src/pi-style/features/editor/index.ts +169 -27
- package/extension-src/pi-style/features/messages/index.ts +66 -0
- package/extension-src/pi-style/features/tools/bash-execution.ts +112 -0
- package/extension-src/pi-style/features/tools/boxed/bash.ts +430 -172
- package/extension-src/pi-style/features/tools/boxed/batch.ts +50 -27
- package/extension-src/pi-style/features/tools/boxed/command-shape.ts +136 -0
- package/extension-src/pi-style/features/tools/boxed/edit.ts +28 -2
- package/extension-src/pi-style/features/tools/boxed/fallback.ts +47 -4
- package/extension-src/pi-style/features/tools/boxed/find.ts +2 -2
- package/extension-src/pi-style/features/tools/boxed/gh.ts +1012 -0
- package/extension-src/pi-style/features/tools/boxed/git.ts +1960 -0
- package/extension-src/pi-style/features/tools/boxed/grep.ts +2 -2
- package/extension-src/pi-style/features/tools/boxed/output-tree.ts +9 -10
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +25 -4
- package/extension-src/pi-style/features/tools/boxed/read.ts +3 -3
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +64 -4
- package/extension-src/pi-style/features/tools/boxed/shared.ts +41 -1
- package/extension-src/pi-style/features/tools/boxed/write.ts +19 -2
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +17 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +104 -10
- package/extension-src/pi-style/pi/compatibility-registry.ts +19 -3
- package/extension-src/pi-style/pi/index.ts +18 -1
- package/extension-src/pi-style/pi/session-coordinator.ts +33 -1
- package/extension-src/pi-style/shared/box.ts +86 -25
- package/extension-src/pi-style/shared/split-diff.ts +9 -9
- package/package.json +1 -1
- package/themes/titanium-light.json +82 -0
- package/themes/titanium.json +79 -0
- package/themes/.gitkeep +0 -0
|
@@ -5,7 +5,8 @@ export type CompatibilitySubtype =
|
|
|
5
5
|
| "native-skill-message"
|
|
6
6
|
| "native-custom-message"
|
|
7
7
|
| "tool-call-renderer"
|
|
8
|
-
| "tool-result-renderer"
|
|
8
|
+
| "tool-result-renderer"
|
|
9
|
+
| "native-bash-execution";
|
|
9
10
|
|
|
10
11
|
type CompatibilityShape = "supported" | "unsupported" | "conflict" | "installed" | "skipped";
|
|
11
12
|
|
|
@@ -171,6 +172,13 @@ function validateInstall(
|
|
|
171
172
|
if (!options.shape) return skippedResult(options, "unsupported", options.diagnostic ?? "unsupported shape", current);
|
|
172
173
|
const conflict = existing && activeConflict(existing, current, options.generation);
|
|
173
174
|
if (conflict) return conflict;
|
|
175
|
+
if (options.kind === "add-method") {
|
|
176
|
+
// Additive install: the slot must be unowned (the method is inherited); the
|
|
177
|
+
// delegate receives the inherited native function as its fallback identity.
|
|
178
|
+
if (current !== undefined || ownDescriptor !== undefined)
|
|
179
|
+
return skippedResult(options, "conflict", "target already owns the additive method", current);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
174
182
|
if (typeof current !== "function")
|
|
175
183
|
return skippedResult(
|
|
176
184
|
options,
|
|
@@ -225,6 +233,8 @@ export function installDelegatingPatch(options: {
|
|
|
225
233
|
expectedIdentity?: unknown;
|
|
226
234
|
hasExpectedIdentity?: boolean;
|
|
227
235
|
diagnostic?: string | undefined;
|
|
236
|
+
/** "add-method" installs a new own method on the prototype (nothing may already own the slot); the delegate receives the inherited native function as the fallback. */
|
|
237
|
+
kind?: "method" | "add-method";
|
|
228
238
|
delegate: (original: unknown, thisArg: object, args: unknown[]) => unknown;
|
|
229
239
|
}): InstallResult {
|
|
230
240
|
const { target, method } = options;
|
|
@@ -235,7 +245,9 @@ export function installDelegatingPatch(options: {
|
|
|
235
245
|
const validation = validateInstall(options, current, records.get(method), inspection.descriptor);
|
|
236
246
|
if (validation) return validation;
|
|
237
247
|
const originalDescriptor = Object.getOwnPropertyDescriptor(target, method);
|
|
238
|
-
|
|
248
|
+
// Additive installs have no current owner; the captured inherited native
|
|
249
|
+
// function (expectedIdentity) is both the fallback and the delegate's original.
|
|
250
|
+
const originalIdentity = options.kind === "add-method" ? options.expectedIdentity : current;
|
|
239
251
|
let active = true;
|
|
240
252
|
const installed = function (this: object, ...args: unknown[]): unknown {
|
|
241
253
|
if (!active) return Reflect.apply(originalIdentity as (...values: unknown[]) => unknown, this, args);
|
|
@@ -260,7 +272,11 @@ export function installDelegatingPatch(options: {
|
|
|
260
272
|
};
|
|
261
273
|
try {
|
|
262
274
|
Object.defineProperty(installed, "__piStyleCompatibilityRecord", { value: record, configurable: false });
|
|
263
|
-
|
|
275
|
+
// Additive installs have no original descriptor; write a fresh writable,
|
|
276
|
+
// configurable, non-enumerable own method so the slot stays reversible.
|
|
277
|
+
const descriptor = originalDescriptor
|
|
278
|
+
? { ...originalDescriptor, value: installed }
|
|
279
|
+
: { value: installed, writable: true, enumerable: false, configurable: true };
|
|
264
280
|
const wrote = Reflect.defineProperty(target, method, descriptor);
|
|
265
281
|
registryTestHooks.afterWrite?.();
|
|
266
282
|
const currentDescriptor = Object.getOwnPropertyDescriptor(target, method);
|
|
@@ -66,7 +66,24 @@ export default function piStyleExtension(pi: ExtensionAPI): void {
|
|
|
66
66
|
await coordinator.start(event, ctx);
|
|
67
67
|
});
|
|
68
68
|
pi.on("agent_start", () => coordinator.app.runtime.current?.dismissStartup());
|
|
69
|
-
pi.on("input", () =>
|
|
69
|
+
pi.on("input", (event, _ctx) => {
|
|
70
|
+
coordinator.app.runtime.current?.dismissStartup();
|
|
71
|
+
// Bare `!`/`!!` submit guard: Pi treats `!`-prefixed input as a direct bash
|
|
72
|
+
// command but falls through to normal message submission when the bang has
|
|
73
|
+
// no command after it — sending a literal `!` to the agent. Drop those
|
|
74
|
+
// accidental submits instead; Pi's submit path already cleared the editor
|
|
75
|
+
// (onChange("") resets isBashMode), so the input returns to the normal
|
|
76
|
+
// prompt without sending anything. Only the interactive input box is
|
|
77
|
+
// guarded; rpc/extension sources keep sending text verbatim.
|
|
78
|
+
if (event.source === "interactive") {
|
|
79
|
+
const trimmed = event.text.trimStart();
|
|
80
|
+
if (trimmed.startsWith("!")) {
|
|
81
|
+
const bangLength = trimmed.startsWith("!!") ? 2 : 1;
|
|
82
|
+
if (trimmed.slice(bangLength).trim() === "") return { action: "handled" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
});
|
|
70
87
|
pi.on("tool_execution_start", () => coordinator.app.runtime.current?.dismissStartup());
|
|
71
88
|
pi.on("model_select", (event) =>
|
|
72
89
|
coordinator.app.update(
|
|
@@ -4,10 +4,15 @@ import type { ConfigFilePort } from "../app/config-storage.js";
|
|
|
4
4
|
import { createPiStyleApp, type PiStyleApp } from "../app/index.js";
|
|
5
5
|
import { resolveTheme } from "../domain/theme.js";
|
|
6
6
|
import { setSpecialBlockTheme } from "../features/messages/special-blocks.js";
|
|
7
|
+
import { setBashExecutionTheme } from "../features/tools/bash-execution.js";
|
|
7
8
|
import { resetBashTreeRegistry } from "../features/tools/boxed/bash.js";
|
|
8
9
|
import { resetBatchRegistry } from "../features/tools/boxed/batch.js";
|
|
9
10
|
import { resetGrepRegistry } from "../features/tools/boxed/grep.js";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
setToolsRenderConfig,
|
|
13
|
+
stopAllElapsedTickers,
|
|
14
|
+
type ToolsRenderConfig,
|
|
15
|
+
} from "../features/tools/boxed/session-config.js";
|
|
11
16
|
import { createCompatibilityCoordinator } from "./compatibility-coordinator.js";
|
|
12
17
|
import {
|
|
13
18
|
type CompatibilityCleanupResult,
|
|
@@ -90,6 +95,26 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
90
95
|
const applyMessagesConfig = (config: import("../domain/config-types.js").NormalizedPiStyleConfig) => {
|
|
91
96
|
sessionUi?.setHiddenThinkingLabel?.(config.messages.hideThinkingLabel ? "" : undefined);
|
|
92
97
|
};
|
|
98
|
+
/**
|
|
99
|
+
* Auto-apply the configured pi-style theme (default "titanium") once per TUI
|
|
100
|
+
* session before any surface captures the active theme, so a fresh install
|
|
101
|
+
* renders with the intended palette. Failure-safe: an unresolvable target is
|
|
102
|
+
* never passed to Pi (its setTheme falls back to the dark theme on load
|
|
103
|
+
* error, which would clobber the user's theme), and "off" disables the
|
|
104
|
+
* surface for users who keep their own theme.
|
|
105
|
+
*/
|
|
106
|
+
const applyAutoTheme = (
|
|
107
|
+
config: import("../domain/config-types.js").NormalizedPiStyleConfig,
|
|
108
|
+
ctx: ExtensionContext,
|
|
109
|
+
) => {
|
|
110
|
+
const target = config.theme.autoApply;
|
|
111
|
+
if (ctx.mode !== "tui" || !target || target === "off") return;
|
|
112
|
+
const ui = ctx.ui;
|
|
113
|
+
if (ui?.theme?.name === target) return;
|
|
114
|
+
// Resolve before switching (see failure-safe note above).
|
|
115
|
+
if (!ui?.getTheme?.(target)) return;
|
|
116
|
+
ui.setTheme?.(target);
|
|
117
|
+
};
|
|
93
118
|
const app: PiStyleApp = createPiStyleApp(
|
|
94
119
|
undefined,
|
|
95
120
|
{
|
|
@@ -150,11 +175,16 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
150
175
|
resetBatchRegistry();
|
|
151
176
|
resetGrepRegistry();
|
|
152
177
|
resetBashTreeRegistry();
|
|
178
|
+
// Stop any 1s elapsed re-render ticker left by a tool that was still
|
|
179
|
+
// running when the session ended.
|
|
180
|
+
stopAllElapsedTickers();
|
|
153
181
|
active = false;
|
|
154
182
|
await app.reload();
|
|
155
183
|
productGate = app.productPolicy.corePatchGate;
|
|
156
184
|
active = true;
|
|
157
185
|
compatibility.install(app.config, ctx.mode === "tui", productGate);
|
|
186
|
+
// Auto-apply the configured theme before surfaces capture the active one.
|
|
187
|
+
applyAutoTheme(app.config, ctx);
|
|
158
188
|
// Session-scoped render configuration for the boxed tool/message surfaces.
|
|
159
189
|
// Populated once per session (never inside render).
|
|
160
190
|
sessionTheme = ctx.ui?.theme as never;
|
|
@@ -162,6 +192,7 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
162
192
|
applyToolsRenderConfig(app.config);
|
|
163
193
|
applyMessagesConfig(app.config);
|
|
164
194
|
if (ctx.ui?.theme) setSpecialBlockTheme(ctx.ui.theme as never);
|
|
195
|
+
if (ctx.ui?.theme) setBashExecutionTheme(ctx.ui.theme as never);
|
|
165
196
|
const toolDetails = collectToolDetails(pi.getActiveTools?.(), pi.getAllTools?.());
|
|
166
197
|
app.sessionStart(
|
|
167
198
|
{
|
|
@@ -216,6 +247,7 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
216
247
|
resetBatchRegistry();
|
|
217
248
|
resetGrepRegistry();
|
|
218
249
|
resetBashTreeRegistry();
|
|
250
|
+
stopAllElapsedTickers();
|
|
219
251
|
app.sessionShutdown();
|
|
220
252
|
// Tier C prototype patches stay installed across session switches. Pi renders
|
|
221
253
|
// the restored chat (renderBeforeBind) AFTER session_shutdown but BEFORE the
|
|
@@ -43,7 +43,15 @@ export interface BoxedRenderOptions {
|
|
|
43
43
|
isError?: boolean;
|
|
44
44
|
isPartial?: boolean;
|
|
45
45
|
isPending?: boolean;
|
|
46
|
+
/** Execution has started but the tool is still running (title `◌` instead of `✓`). */
|
|
47
|
+
running?: boolean;
|
|
48
|
+
/** A result renderer already produced a continuation for this call, so the call
|
|
49
|
+
* leaves the box open instead of closing it with a pending label. */
|
|
50
|
+
resultSeen?: boolean;
|
|
46
51
|
pendingText?: string;
|
|
52
|
+
/** Verbatim bottom-border label for the pending/running card (overrides the
|
|
53
|
+
* `… ${pendingText}` default, e.g. a live `◌ Running · 12.4s` status). */
|
|
54
|
+
pendingLabel?: string;
|
|
47
55
|
state?: Record<string, unknown>;
|
|
48
56
|
/** Wall-clock elapsed override (used when metrics are not in result.details). */
|
|
49
57
|
elapsedMs?: number;
|
|
@@ -311,8 +319,6 @@ export function formatToolParamLines(args: unknown, theme?: BoxTheme): string[]
|
|
|
311
319
|
return lines;
|
|
312
320
|
}
|
|
313
321
|
|
|
314
|
-
const RESET_INTENSITY = "\x1b[22m";
|
|
315
|
-
|
|
316
322
|
function colorFromExtra(theme: BoxTheme, extraKey: string, fallbackColor: string, text: string): string {
|
|
317
323
|
const color = getThemeExtra(theme, extraKey);
|
|
318
324
|
if (color) {
|
|
@@ -333,27 +339,56 @@ function formatBoxedStatusIcon(theme: BoxTheme, isError?: boolean): string {
|
|
|
333
339
|
|
|
334
340
|
/**
|
|
335
341
|
* Colored `➔ Name` prefix for tool titles (identity color). The status glyph
|
|
336
|
-
* (
|
|
342
|
+
* (✓/◌/✗) is appended separately by formatBoxedToolTitle.
|
|
337
343
|
*/
|
|
338
344
|
export function formatToolTitlePrefix(theme: BoxTheme, name: string): string {
|
|
339
345
|
return colorFromExtra(theme, "bashPromptColor", "bashMode", `➔ ${name}`);
|
|
340
346
|
}
|
|
341
347
|
|
|
342
|
-
export
|
|
348
|
+
export type BoxedTitleStatus = "running" | "pending";
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Boxed tool title: `➔ Name ✓` when settled, `➔ Name ◌` while running, plain
|
|
352
|
+
* `➔ Name` while pending, and a fully error-colored `➔ Name ✗` on failure.
|
|
353
|
+
* The ✓/◌ glyphs are never shown before the tool settles, so a card that is
|
|
354
|
+
* still executing never reads as finished.
|
|
355
|
+
*/
|
|
356
|
+
export function formatBoxedToolTitle(
|
|
357
|
+
theme: BoxTheme,
|
|
358
|
+
name: string,
|
|
359
|
+
isError?: boolean,
|
|
360
|
+
status?: BoxedTitleStatus,
|
|
361
|
+
): string {
|
|
343
362
|
// On failure the whole title turns error-colored (not just the ✗) so a failed
|
|
344
363
|
// tool reads instantly; on success the tool keeps its identity color and only
|
|
345
364
|
// the ✓ carries the success color.
|
|
346
365
|
const coloredTitle = isError
|
|
347
366
|
? theme.fg("error", `➔ ${name} ✗`)
|
|
348
|
-
:
|
|
367
|
+
: status === "running"
|
|
368
|
+
? `${formatToolTitlePrefix(theme, name)} ${theme.fg("text", "◌")}`
|
|
369
|
+
: status === "pending"
|
|
370
|
+
? formatToolTitlePrefix(theme, name)
|
|
371
|
+
: `${formatToolTitlePrefix(theme, name)} ${formatBoxedStatusIcon(theme, false)}`;
|
|
349
372
|
return typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
|
|
350
373
|
}
|
|
351
374
|
|
|
375
|
+
/** Live running status label for pending/running cards and streaming footers. */
|
|
376
|
+
export function formatBoxedRunningStatus(theme: BoxTheme, elapsedMs: number | undefined): string {
|
|
377
|
+
const elapsed = elapsedMs === undefined ? "" : `${theme.fg("text", ` · ${(elapsedMs / 1000).toFixed(1)}s`)}`;
|
|
378
|
+
return `${theme.fg("dim", "◌ Running")}${elapsed}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Structural line — box frame, tree branch, divider, gutter — wrapped in
|
|
382
|
+
* dim terminal-default intensity. Visible in every theme; matches omp. */
|
|
383
|
+
export function dimLine(text: string): string {
|
|
384
|
+
return `\x1b[2m${text}\x1b[22m`;
|
|
385
|
+
}
|
|
386
|
+
|
|
352
387
|
function boxText(theme: BoxTheme, text: string): string {
|
|
353
|
-
return
|
|
388
|
+
return dimLine(text);
|
|
354
389
|
}
|
|
355
390
|
function boxFrameText(theme: BoxTheme, text: string): string {
|
|
356
|
-
return
|
|
391
|
+
return dimLine(text);
|
|
357
392
|
}
|
|
358
393
|
|
|
359
394
|
export function boxedToolBgName(isError?: boolean, isPartial?: boolean): string {
|
|
@@ -571,7 +606,12 @@ export function renderBoxedToolCall(
|
|
|
571
606
|
},
|
|
572
607
|
render(width: number): string[] {
|
|
573
608
|
if (cache?.width === width) return cache.lines;
|
|
574
|
-
const title = formatBoxedToolTitle(
|
|
609
|
+
const title = formatBoxedToolTitle(
|
|
610
|
+
theme,
|
|
611
|
+
toolName,
|
|
612
|
+
options.isError,
|
|
613
|
+
options.isPending ? (options.running ? "running" : "pending") : undefined,
|
|
614
|
+
);
|
|
575
615
|
const headerLabel = options.headerDetail ? `${title} · ${options.headerDetail}` : title;
|
|
576
616
|
const renderedWidth = boxWidth(width);
|
|
577
617
|
const lines = [
|
|
@@ -579,22 +619,26 @@ export function renderBoxedToolCall(
|
|
|
579
619
|
boxBlankLine(theme, renderedWidth),
|
|
580
620
|
...detailLines.flatMap((line) => boxedWrappedLines(theme, line, renderedWidth)),
|
|
581
621
|
];
|
|
582
|
-
if (options.isPending) {
|
|
583
|
-
|
|
622
|
+
if (options.isPending && !options.resultSeen) {
|
|
623
|
+
// Pending/running card: close the box with the status label. Once a
|
|
624
|
+
// result renderer has produced a continuation, the box stays open and
|
|
625
|
+
// that continuation closes it.
|
|
626
|
+
const pendingLabel =
|
|
627
|
+
options.pendingLabel ?? theme.fg("dim", `… ${options.pendingText ?? "Waiting for output…"}`);
|
|
584
628
|
lines.push(
|
|
585
629
|
boxBlankLine(theme, renderedWidth),
|
|
586
630
|
boxLabeledBorder(
|
|
587
631
|
theme,
|
|
588
632
|
BOX_ROUND_BOTTOM_LEFT,
|
|
589
633
|
BOX_ROUND_BOTTOM_RIGHT,
|
|
590
|
-
|
|
634
|
+
pendingLabel,
|
|
591
635
|
undefined,
|
|
592
636
|
renderedWidth,
|
|
593
637
|
),
|
|
594
638
|
);
|
|
595
639
|
} else {
|
|
596
640
|
// Leave the box open with trailing breathing room; the result renderer
|
|
597
|
-
// continues it with the
|
|
641
|
+
// continues it with the result divider.
|
|
598
642
|
lines.push(boxBlankLine(theme, renderedWidth));
|
|
599
643
|
}
|
|
600
644
|
cache = { width, lines };
|
|
@@ -624,7 +668,12 @@ export function renderCompactBoxedToolCall(
|
|
|
624
668
|
invalidate() {},
|
|
625
669
|
render(width: number): string[] {
|
|
626
670
|
const renderedWidth = boxWidth(width);
|
|
627
|
-
const title = formatBoxedToolTitle(
|
|
671
|
+
const title = formatBoxedToolTitle(
|
|
672
|
+
theme,
|
|
673
|
+
toolName,
|
|
674
|
+
options.isError,
|
|
675
|
+
options.isPending ? (options.running ? "running" : "pending") : undefined,
|
|
676
|
+
);
|
|
628
677
|
const headerLabel = detailLine ? `${title} · ${detailLine}` : title;
|
|
629
678
|
const compactFooter =
|
|
630
679
|
typeof options.state?.[COMPACT_FOOTER_KEY] === "string" ? options.state[COMPACT_FOOTER_KEY] : "";
|
|
@@ -649,19 +698,23 @@ export function renderCompactBoxedToolCall(
|
|
|
649
698
|
),
|
|
650
699
|
);
|
|
651
700
|
} else if (options.isPending) {
|
|
652
|
-
const
|
|
701
|
+
const pendingLabel =
|
|
702
|
+
options.pendingLabel ??
|
|
703
|
+
(options.running
|
|
704
|
+
? formatBoxedRunningStatus(theme, undefined)
|
|
705
|
+
: theme.fg("dim", `… ${options.pendingText ?? "Waiting for output…"}`));
|
|
653
706
|
lines.push(
|
|
654
707
|
boxLabeledBorder(
|
|
655
708
|
theme,
|
|
656
709
|
BOX_ROUND_BOTTOM_LEFT,
|
|
657
710
|
BOX_ROUND_BOTTOM_RIGHT,
|
|
658
|
-
|
|
711
|
+
pendingLabel,
|
|
659
712
|
options.bottomRightLabel,
|
|
660
713
|
renderedWidth,
|
|
661
714
|
),
|
|
662
715
|
);
|
|
663
716
|
} else {
|
|
664
|
-
// No footer yet (transient, or the result opens the
|
|
717
|
+
// No footer yet (transient, or the result opens the result divider):
|
|
665
718
|
// leave the box open so the result renderer continues the same box.
|
|
666
719
|
}
|
|
667
720
|
return lines;
|
|
@@ -689,6 +742,10 @@ export function renderBoxedToolResult(
|
|
|
689
742
|
expandHint?: string;
|
|
690
743
|
isError?: boolean;
|
|
691
744
|
isPartial?: boolean;
|
|
745
|
+
/** Skip the result divider entirely (streaming continuation into an open call box). */
|
|
746
|
+
showDivider?: boolean;
|
|
747
|
+
/** Error state marker prepended to the body (default `✗ Error`). */
|
|
748
|
+
errorLabel?: string;
|
|
692
749
|
} = {},
|
|
693
750
|
): Component {
|
|
694
751
|
let cache: RenderLinesCache | null = null;
|
|
@@ -702,7 +759,7 @@ export function renderBoxedToolResult(
|
|
|
702
759
|
const renderedWidth = boxWidth(width);
|
|
703
760
|
const maxContentWidth = boxInnerWidth(renderedWidth);
|
|
704
761
|
const bodyLines = typeof body === "function" ? body(maxContentWidth) : body.render(maxContentWidth);
|
|
705
|
-
const errorPrefix = options.isError ? [theme.fg("error", "✗ Error")] : [];
|
|
762
|
+
const errorPrefix = options.isError ? [theme.fg("error", options.errorLabel ?? "✗ Error")] : [];
|
|
706
763
|
const outputLines =
|
|
707
764
|
bodyLines.length > 0
|
|
708
765
|
? [...errorPrefix, ...bodyLines]
|
|
@@ -713,14 +770,18 @@ export function renderBoxedToolResult(
|
|
|
713
770
|
? options.dividerLabel(renderedWidth)
|
|
714
771
|
: (options.dividerLabel ?? "Response");
|
|
715
772
|
const rendered = [
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
773
|
+
...(options.showDivider === false
|
|
774
|
+
? []
|
|
775
|
+
: [
|
|
776
|
+
boxLabeledBorder(
|
|
777
|
+
theme,
|
|
778
|
+
BOX_DIVIDER_LEFT,
|
|
779
|
+
BOX_DIVIDER_RIGHT,
|
|
780
|
+
theme.fg("dim", dividerText),
|
|
781
|
+
options.dividerRightLabel ? theme.fg("dim", options.dividerRightLabel) : undefined,
|
|
782
|
+
renderedWidth,
|
|
783
|
+
),
|
|
784
|
+
]),
|
|
724
785
|
boxBlankLine(theme, renderedWidth),
|
|
725
786
|
...renderBoxedOutputLines(theme, outputLines, renderedWidth, options.renderLineBudget ?? outputLines.length),
|
|
726
787
|
boxBlankLine(theme, renderedWidth),
|
|
@@ -7,6 +7,7 @@ import { highlightCode } from "@earendil-works/pi-coding-agent";
|
|
|
7
7
|
import type { Component } from "@earendil-works/pi-tui";
|
|
8
8
|
|
|
9
9
|
import { stripAnsi } from "./ansi.js";
|
|
10
|
+
import { dimLine } from "./box.js";
|
|
10
11
|
import { safeTruncateToWidth, safeVisibleWidth } from "./render-budget.js";
|
|
11
12
|
|
|
12
13
|
// ── Types ──────────────────────────────────────────────────────────
|
|
@@ -761,7 +762,7 @@ class SplitDiffRenderer {
|
|
|
761
762
|
lineKind === "add" ? "toolDiffAdded" : lineKind === "remove" ? "toolDiffRemoved" : "borderMuted";
|
|
762
763
|
const marker = this.ctx.fg(markerColor, markerChar);
|
|
763
764
|
const lineNumber = this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth));
|
|
764
|
-
const divider =
|
|
765
|
+
const divider = dimLine(" │ ");
|
|
765
766
|
const prefix = `${marker} ${lineNumber}${divider}`;
|
|
766
767
|
const prefixPlain = `${markerChar} ${" ".repeat(this.ctx.lineNumberWidth)} │ `;
|
|
767
768
|
const tailWidth = Math.max(0, columnWidth - safeVisibleWidth(prefixPlain));
|
|
@@ -791,14 +792,14 @@ class SplitDiffRenderer {
|
|
|
791
792
|
this.ctx.fg(markerColor, markerChar) +
|
|
792
793
|
" " +
|
|
793
794
|
this.ctx.fg(this.getNumberColor(lineKind), lineNumber) +
|
|
794
|
-
|
|
795
|
+
dimLine(" │ ");
|
|
795
796
|
const firstPrefixPlain = `${markerChar} ${lineNumber} │ `;
|
|
796
797
|
|
|
797
798
|
const contPrefixAnsi =
|
|
798
799
|
this.ctx.fg(markerColor, markerChar) +
|
|
799
800
|
" " +
|
|
800
801
|
this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth)) +
|
|
801
|
-
|
|
802
|
+
dimLine(" │ ");
|
|
802
803
|
const contPrefixPlain = `${markerChar} ${" ".repeat(this.ctx.lineNumberWidth)} │ `;
|
|
803
804
|
|
|
804
805
|
const codeWidth = Math.max(1, columnWidth - safeVisibleWidth(firstPrefixPlain));
|
|
@@ -865,7 +866,7 @@ class SplitDiffRenderer {
|
|
|
865
866
|
|
|
866
867
|
render(width: number): string[] {
|
|
867
868
|
const safeWidth = Math.max(20, width);
|
|
868
|
-
const columnSeparator =
|
|
869
|
+
const columnSeparator = dimLine(" │ ");
|
|
869
870
|
const separatorWidth = safeVisibleWidth(stripAnsi(columnSeparator));
|
|
870
871
|
const leftWidth = Math.max(20, Math.floor((safeWidth - separatorWidth) / 2));
|
|
871
872
|
const rightWidth = Math.max(20, safeWidth - separatorWidth - leftWidth);
|
|
@@ -877,15 +878,14 @@ class SplitDiffRenderer {
|
|
|
877
878
|
if (dividerIndex >= 0 && dividerIndex < chars.length) {
|
|
878
879
|
chars[dividerIndex] = junction;
|
|
879
880
|
}
|
|
880
|
-
return
|
|
881
|
+
return dimLine(chars.join(""));
|
|
881
882
|
};
|
|
882
883
|
|
|
883
884
|
const formatHeaderCell = (label: string, columnWidth: number): string => {
|
|
884
885
|
// Keep marker+space columns, then place label inside the line-number column.
|
|
885
886
|
const markerPad = " ";
|
|
886
887
|
const lineNumberLabel = fitToWidth(label, this.ctx.lineNumberWidth);
|
|
887
|
-
const prefixAnsi =
|
|
888
|
-
this.ctx.fg("borderMuted", markerPad) + this.ctx.fg("dim", lineNumberLabel) + this.ctx.fg("borderMuted", " │ ");
|
|
888
|
+
const prefixAnsi = dimLine(markerPad) + this.ctx.fg("dim", lineNumberLabel) + dimLine(" │ ");
|
|
889
889
|
const prefixPlain = `${markerPad}${stripAnsi(lineNumberLabel)} │ `;
|
|
890
890
|
const codeWidth = Math.max(0, columnWidth - safeVisibleWidth(prefixPlain));
|
|
891
891
|
return padRenderedLineWidth(prefixAnsi + " ".repeat(codeWidth), columnWidth);
|
|
@@ -894,7 +894,7 @@ class SplitDiffRenderer {
|
|
|
894
894
|
const lines: string[] = [];
|
|
895
895
|
lines.push(
|
|
896
896
|
padRenderedLineWidth(
|
|
897
|
-
formatBorderCell(leftWidth, "┬") +
|
|
897
|
+
formatBorderCell(leftWidth, "┬") + dimLine("─┬─") + formatBorderCell(rightWidth, "┬"),
|
|
898
898
|
safeWidth,
|
|
899
899
|
),
|
|
900
900
|
);
|
|
@@ -931,7 +931,7 @@ class SplitDiffRenderer {
|
|
|
931
931
|
|
|
932
932
|
lines.push(
|
|
933
933
|
padRenderedLineWidth(
|
|
934
|
-
formatBorderCell(leftWidth, "┴") +
|
|
934
|
+
formatBorderCell(leftWidth, "┴") + dimLine("─┴─") + formatBorderCell(rightWidth, "┴"),
|
|
935
935
|
safeWidth,
|
|
936
936
|
),
|
|
937
937
|
);
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "titanium-light",
|
|
4
|
+
"vars": {
|
|
5
|
+
"teal": "#5a8080",
|
|
6
|
+
"blue": "#547da7",
|
|
7
|
+
"green": "#588458",
|
|
8
|
+
"red": "#aa5555",
|
|
9
|
+
"yellow": "#9a7326",
|
|
10
|
+
"mediumGray": "#6c6c6c",
|
|
11
|
+
"dimGray": "#767676",
|
|
12
|
+
"lightGray": "#b0b0b0",
|
|
13
|
+
"selectedBg": "#d0d0e0",
|
|
14
|
+
"userMsgBg": "#e8e8e8",
|
|
15
|
+
"toolPendingBg": "#e8e8f0",
|
|
16
|
+
"toolSuccessBg": "#e8f0e8",
|
|
17
|
+
"toolErrorBg": "#f0e8e8",
|
|
18
|
+
"customMsgBg": "#ede7f6"
|
|
19
|
+
},
|
|
20
|
+
"colors": {
|
|
21
|
+
"accent": "teal",
|
|
22
|
+
"border": "blue",
|
|
23
|
+
"borderAccent": "teal",
|
|
24
|
+
"borderMuted": "lightGray",
|
|
25
|
+
"success": "green",
|
|
26
|
+
"error": "red",
|
|
27
|
+
"warning": "yellow",
|
|
28
|
+
"muted": "mediumGray",
|
|
29
|
+
"dim": "dimGray",
|
|
30
|
+
"text": "",
|
|
31
|
+
"thinkingText": "mediumGray",
|
|
32
|
+
|
|
33
|
+
"selectedBg": "selectedBg",
|
|
34
|
+
"userMessageBg": "userMsgBg",
|
|
35
|
+
"userMessageText": "",
|
|
36
|
+
"customMessageBg": "customMsgBg",
|
|
37
|
+
"customMessageText": "",
|
|
38
|
+
"customMessageLabel": "#7e57c2",
|
|
39
|
+
"toolPendingBg": "toolPendingBg",
|
|
40
|
+
"toolSuccessBg": "toolSuccessBg",
|
|
41
|
+
"toolErrorBg": "toolErrorBg",
|
|
42
|
+
"toolTitle": "",
|
|
43
|
+
"toolOutput": "mediumGray",
|
|
44
|
+
|
|
45
|
+
"mdHeading": "yellow",
|
|
46
|
+
"mdLink": "blue",
|
|
47
|
+
"mdLinkUrl": "dimGray",
|
|
48
|
+
"mdCode": "teal",
|
|
49
|
+
"mdCodeBlock": "green",
|
|
50
|
+
"mdCodeBlockBorder": "mediumGray",
|
|
51
|
+
"mdQuote": "mediumGray",
|
|
52
|
+
"mdQuoteBorder": "mediumGray",
|
|
53
|
+
"mdHr": "mediumGray",
|
|
54
|
+
"mdListBullet": "green",
|
|
55
|
+
|
|
56
|
+
"toolDiffAdded": "green",
|
|
57
|
+
"toolDiffRemoved": "red",
|
|
58
|
+
"toolDiffContext": "mediumGray",
|
|
59
|
+
"syntaxComment": "#008000",
|
|
60
|
+
"syntaxKeyword": "#0000FF",
|
|
61
|
+
"syntaxFunction": "#795E26",
|
|
62
|
+
"syntaxVariable": "#001080",
|
|
63
|
+
"syntaxString": "#A31515",
|
|
64
|
+
"syntaxNumber": "#098658",
|
|
65
|
+
"syntaxType": "#267F99",
|
|
66
|
+
"syntaxOperator": "#000000",
|
|
67
|
+
"syntaxPunctuation": "#000000",
|
|
68
|
+
|
|
69
|
+
"thinkingOff": "lightGray",
|
|
70
|
+
"thinkingMinimal": "#767676",
|
|
71
|
+
"thinkingLow": "blue",
|
|
72
|
+
"thinkingMedium": "teal",
|
|
73
|
+
"thinkingHigh": "#875f87",
|
|
74
|
+
"thinkingXhigh": "#8b008b",
|
|
75
|
+
"bashMode": "green"
|
|
76
|
+
},
|
|
77
|
+
"export": {
|
|
78
|
+
"pageBg": "#f8f8f8",
|
|
79
|
+
"cardBg": "#ffffff",
|
|
80
|
+
"infoBg": "#fffae6"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "titanium",
|
|
4
|
+
"vars": {
|
|
5
|
+
"brushedTitanium": "#151820",
|
|
6
|
+
"darkTitanium": "#0f1216",
|
|
7
|
+
"electricBlue": "#00b4ff",
|
|
8
|
+
"deepBlue": "#0082b3",
|
|
9
|
+
"titaniumGold": "#d4c090",
|
|
10
|
+
"brightAluminum": "#e8ecf4",
|
|
11
|
+
"dimAluminum": "#9ca3b0",
|
|
12
|
+
"warningAmber": "#ffb347",
|
|
13
|
+
"readoutGreen": "#00ff88",
|
|
14
|
+
"alertRed": "#ff4757",
|
|
15
|
+
"subtleGray": "#2a3038"
|
|
16
|
+
},
|
|
17
|
+
"colors": {
|
|
18
|
+
"accent": "electricBlue",
|
|
19
|
+
"border": "subtleGray",
|
|
20
|
+
"borderAccent": "electricBlue",
|
|
21
|
+
"borderMuted": "#1f252d",
|
|
22
|
+
"success": "readoutGreen",
|
|
23
|
+
"error": "alertRed",
|
|
24
|
+
"warning": "warningAmber",
|
|
25
|
+
"muted": "dimAluminum",
|
|
26
|
+
"dim": "#6b7280",
|
|
27
|
+
"text": "",
|
|
28
|
+
"thinkingText": "dimAluminum",
|
|
29
|
+
|
|
30
|
+
"selectedBg": "deepBlue",
|
|
31
|
+
"userMessageBg": "darkTitanium",
|
|
32
|
+
"userMessageText": "",
|
|
33
|
+
"customMessageBg": "subtleGray",
|
|
34
|
+
"customMessageText": "",
|
|
35
|
+
"customMessageLabel": "titaniumGold",
|
|
36
|
+
"toolPendingBg": "darkTitanium",
|
|
37
|
+
"toolSuccessBg": "darkTitanium",
|
|
38
|
+
"toolErrorBg": "#1a0f10",
|
|
39
|
+
"toolTitle": "",
|
|
40
|
+
"toolOutput": "dimAluminum",
|
|
41
|
+
|
|
42
|
+
"mdHeading": "electricBlue",
|
|
43
|
+
"mdLink": "electricBlue",
|
|
44
|
+
"mdLinkUrl": "deepBlue",
|
|
45
|
+
"mdCode": "readoutGreen",
|
|
46
|
+
"mdCodeBlock": "dimAluminum",
|
|
47
|
+
"mdCodeBlockBorder": "subtleGray",
|
|
48
|
+
"mdQuote": "dimAluminum",
|
|
49
|
+
"mdQuoteBorder": "subtleGray",
|
|
50
|
+
"mdHr": "subtleGray",
|
|
51
|
+
"mdListBullet": "electricBlue",
|
|
52
|
+
|
|
53
|
+
"toolDiffAdded": "readoutGreen",
|
|
54
|
+
"toolDiffRemoved": "alertRed",
|
|
55
|
+
"toolDiffContext": "dimAluminum",
|
|
56
|
+
"syntaxComment": "#6b7280",
|
|
57
|
+
"syntaxKeyword": "electricBlue",
|
|
58
|
+
"syntaxFunction": "readoutGreen",
|
|
59
|
+
"syntaxVariable": "brightAluminum",
|
|
60
|
+
"syntaxString": "titaniumGold",
|
|
61
|
+
"syntaxNumber": "warningAmber",
|
|
62
|
+
"syntaxType": "electricBlue",
|
|
63
|
+
"syntaxOperator": "electricBlue",
|
|
64
|
+
"syntaxPunctuation": "dimAluminum",
|
|
65
|
+
|
|
66
|
+
"thinkingOff": "#4a5058",
|
|
67
|
+
"thinkingMinimal": "#5a6068",
|
|
68
|
+
"thinkingLow": "#6a7078",
|
|
69
|
+
"thinkingMedium": "dimAluminum",
|
|
70
|
+
"thinkingHigh": "electricBlue",
|
|
71
|
+
"thinkingXhigh": "titaniumGold",
|
|
72
|
+
"bashMode": "readoutGreen"
|
|
73
|
+
},
|
|
74
|
+
"export": {
|
|
75
|
+
"pageBg": "brushedTitanium",
|
|
76
|
+
"cardBg": "darkTitanium",
|
|
77
|
+
"infoBg": "subtleGray"
|
|
78
|
+
}
|
|
79
|
+
}
|
package/themes/.gitkeep
DELETED
|
File without changes
|