@zenodinh/pi-render 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +110 -2
- package/index.ts +284 -0
- package/package.json +70 -5
- package/src/commands/canvas.ts +57 -0
- package/src/core/code-theme.ts +275 -0
- package/src/core/log.ts +57 -0
- package/src/core/paint.ts +64 -0
- package/src/core/registry.ts +135 -0
- package/src/core/settings.ts +119 -0
- package/src/core/types/code-theme.ts +24 -0
- package/src/core/types/host.ts +169 -0
- package/src/core/types/log.ts +28 -0
- package/src/core/types/paint.ts +54 -0
- package/src/core/types/registry.ts +44 -0
- package/src/core/types/settings.ts +31 -0
- package/src/core/types.ts +34 -0
- package/src/renderers/content/artifacts/cache.ts +234 -0
- package/src/renderers/content/artifacts/cards.ts +136 -0
- package/src/renderers/content/artifacts/engines.ts +396 -0
- package/src/renderers/content/artifacts/local-binary.ts +80 -0
- package/src/renderers/content/artifacts/prereqs.ts +128 -0
- package/src/renderers/content/artifacts/server.ts +181 -0
- package/src/renderers/content/code-panel.ts +161 -0
- package/src/renderers/content/image-card.ts +252 -0
- package/src/renderers/content/index.ts +79 -0
- package/src/renderers/content/json-panel.ts +116 -0
- package/src/renderers/content/table.ts +174 -0
- package/src/renderers/content/types.ts +20 -0
- package/src/renderers/tool/index.ts +113 -0
- package/src/renderers/tool/runtime.ts +267 -0
- package/src/renderers/tool/specs/bash.ts +168 -0
- package/src/renderers/tool/specs/codemode.ts +248 -0
- package/src/renderers/tool/specs/edit.ts +213 -0
- package/src/renderers/tool/specs/ls.ts +136 -0
- package/src/renderers/tool/specs/read.ts +296 -0
- package/src/renderers/tool/specs/search.ts +325 -0
- package/src/renderers/tool/specs/write.ts +142 -0
- package/src/renderers/tool/types.ts +45 -0
- package/themes/dracula-soft.json +81 -0
- package/themes/one-dark.json +80 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// ported from pi-pretty-tui/src/features/edit-write-collapse/render.ts — survives because: the
|
|
2
|
+
// collapsed write summary is derived from the CALL args (path + content size), the only place the
|
|
3
|
+
// data exists — the host write result carries just a one-line confirmation. The tool-run wrapper,
|
|
4
|
+
// metrics, model-facing text and the monkey-patched highlight token do not port.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* specs/write.ts — the `write` tool row: collapsed path + size, expanded written content.
|
|
8
|
+
*
|
|
9
|
+
* shape: none — a data bundle (name, spec, descriptor) over straight-line spec functions; the only
|
|
10
|
+
* branch is the two-value written/error model, below the ≥3-branch dispatch-object trigger.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
CallModel,
|
|
15
|
+
ModuleDescriptor,
|
|
16
|
+
RenderContext,
|
|
17
|
+
RowPaint,
|
|
18
|
+
RowSpec,
|
|
19
|
+
RowView,
|
|
20
|
+
ToolResult,
|
|
21
|
+
} from "../../../core/types.ts";
|
|
22
|
+
|
|
23
|
+
const ICON = "✎";
|
|
24
|
+
const ERROR_MARK = "✗";
|
|
25
|
+
const MAX_ERROR_SUMMARY = 80;
|
|
26
|
+
const MAX_PREVIEW_LINES = 20;
|
|
27
|
+
|
|
28
|
+
/** Two-value model: a failed write carries only its error text; a written row carries args-derived counts. */
|
|
29
|
+
type WriteModel =
|
|
30
|
+
| { kind: "error"; display: string; error: string }
|
|
31
|
+
| { kind: "written"; display: string; content: string | undefined; lines: number; bytes: number };
|
|
32
|
+
|
|
33
|
+
/** The error flag reaches the result renderer on ctx, not on the {content, details} wrapper (host DP3). */
|
|
34
|
+
function isError(result: ToolResult, ctx: RenderContext): boolean {
|
|
35
|
+
return ctx.isError === true || result.isError === true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// boundary: host args are unknown-typed; every field is read through a typeof guard, never a cast.
|
|
39
|
+
function readPath(args: Record<string, unknown> | undefined): string {
|
|
40
|
+
const direct = args?.file_path;
|
|
41
|
+
if (typeof direct === "string" && direct !== "") return direct;
|
|
42
|
+
const alt = args?.path;
|
|
43
|
+
return typeof alt === "string" ? alt : "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readContent(args: Record<string, unknown> | undefined): string | undefined {
|
|
47
|
+
const content = args?.content;
|
|
48
|
+
return typeof content === "string" ? content : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Shortens a path under cwd; anything else stays as given. */
|
|
52
|
+
function displayPath(raw: string, cwd: string | undefined): string {
|
|
53
|
+
if (raw === "") return "";
|
|
54
|
+
if (cwd && raw.startsWith(`${cwd}/`)) return raw.slice(cwd.length + 1);
|
|
55
|
+
return raw;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function textOf(result: ToolResult): string {
|
|
59
|
+
return (result.content ?? [])
|
|
60
|
+
.filter((block) => block.type === "text")
|
|
61
|
+
.map((block) => block.text ?? "")
|
|
62
|
+
.join("\n")
|
|
63
|
+
.trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Lines are logical (trailing blanks trimmed) so "" is 0 lines; bytes are the raw UTF-8 length. */
|
|
67
|
+
function writeCounts(content: string): { lines: number; bytes: number } {
|
|
68
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
69
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
70
|
+
return { lines: lines.length, bytes: Buffer.byteLength(content, "utf8") };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function firstLine(text: string): string {
|
|
74
|
+
const line = (text.split("\n")[0] ?? "").trim();
|
|
75
|
+
return line.length > MAX_ERROR_SUMMARY ? `${line.slice(0, MAX_ERROR_SUMMARY - 1)}…` : line;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** shape: none — a two-value expansion branch with one string body each. */
|
|
79
|
+
function writtenLine(m: Extract<WriteModel, { kind: "written" }>, paint: RowPaint): string {
|
|
80
|
+
const head = m.display ? `${ICON} ${paint.title(m.display)}` : ICON;
|
|
81
|
+
// No content yet (args still streaming) is the pinned special case: no fabricated counts.
|
|
82
|
+
if (m.content === undefined) return `${head} ${paint.muted("· writing…")}`;
|
|
83
|
+
return `${head} ${paint.muted(`(${m.lines} lines · ${m.bytes} bytes)`)}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** shape: none — one preview slice plus an optional hidden-lines hint; no discriminator. */
|
|
87
|
+
function writtenBody(m: Extract<WriteModel, { kind: "written" }>, paint: RowPaint): string {
|
|
88
|
+
if (m.content === undefined) return "";
|
|
89
|
+
const lines = m.content.replace(/\r\n/g, "\n").split("\n");
|
|
90
|
+
const shown = lines.slice(0, MAX_PREVIEW_LINES).map((line) => paint.output(line));
|
|
91
|
+
const hidden = lines.length - shown.length;
|
|
92
|
+
if (hidden > 0) shown.push(paint.muted(`… ${hidden} more lines (${lines.length} total)`));
|
|
93
|
+
return shown.join("\n");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** shape: none — a single collapsed line; error text is truncated, never re-wrapped. */
|
|
97
|
+
function errorLine(m: Extract<WriteModel, { kind: "error" }>, paint: RowPaint): string {
|
|
98
|
+
const head = m.display ? `${ICON} ${paint.title(m.display)} ` : "";
|
|
99
|
+
return `${head}${paint.error(`${ERROR_MARK} ${firstLine(m.error)}`)}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** shape: none — one map over the error's lines; no branch. */
|
|
103
|
+
function errorBody(m: Extract<WriteModel, { kind: "error" }>, paint: RowPaint): string {
|
|
104
|
+
return m.error
|
|
105
|
+
.split("\n")
|
|
106
|
+
.map((line) => paint.error(line))
|
|
107
|
+
.join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** shape: none — a single collapsed-line builder; the args gate is one boolean check. */
|
|
111
|
+
function buildCall(args: Record<string, unknown>, ctx: RenderContext): CallModel {
|
|
112
|
+
const display = displayPath(readPath(args), ctx.cwd);
|
|
113
|
+
if (ctx.argsComplete === false || display === "") return { title: "write" };
|
|
114
|
+
return { title: `write: ${display}` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** shape: none — the written/error model is discriminated on one boolean; no dispatch object. */
|
|
118
|
+
function build(result: ToolResult, ctx: RenderContext): WriteModel | "unknown" {
|
|
119
|
+
const display = displayPath(readPath(ctx.args), ctx.cwd);
|
|
120
|
+
if (isError(result, ctx)) return { kind: "error", display, error: textOf(result) || "Error" };
|
|
121
|
+
const content = readContent(ctx.args);
|
|
122
|
+
// Neither a path nor content: nothing recognizable, so the runtime degrades to the raw result.
|
|
123
|
+
if (display === "" && content === undefined) return "unknown";
|
|
124
|
+
const { lines, bytes } = writeCounts(content ?? "");
|
|
125
|
+
return { kind: "written", display, content, lines, bytes };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** shape: none — two straight-line branches (state, then model kind); no dispatch object. */
|
|
129
|
+
function project(m: WriteModel, view: RowView, paint: RowPaint): string {
|
|
130
|
+
if (m.kind === "error") return view.expanded ? errorBody(m, paint) : errorLine(m, paint);
|
|
131
|
+
return view.expanded ? writtenBody(m, paint) : writtenLine(m, paint);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const writeRow: {
|
|
135
|
+
name: string;
|
|
136
|
+
spec: RowSpec<WriteModel>;
|
|
137
|
+
descriptor: ModuleDescriptor;
|
|
138
|
+
} = {
|
|
139
|
+
name: "write",
|
|
140
|
+
spec: { buildCall, build, project },
|
|
141
|
+
descriptor: { key: "row.write", name: "Write row", defaultEnabled: true, settings: [] },
|
|
142
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderers/tool/types.ts — the tool-lane contracts (what a row spec is, and what it produces).
|
|
3
|
+
*
|
|
4
|
+
* Boundary: lane-local. A tool spec and the tool runtime import these; no other lane does. The
|
|
5
|
+
* host shapes they build on live in core (types/host.ts, types/paint.ts) — a row contract is the
|
|
6
|
+
* spec's own vocabulary, not the host's.
|
|
7
|
+
*
|
|
8
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { RenderContext, ToolResult } from "../../core/types/host.ts";
|
|
12
|
+
import type { RowPaint } from "../../core/types/paint.ts";
|
|
13
|
+
|
|
14
|
+
/** Collapsed call-line model: the args gate keeps it name-only until argsComplete. */
|
|
15
|
+
export interface CallModel {
|
|
16
|
+
/** The one-line call title, e.g. "bash: npm test". Required; never a guessed argument value. */
|
|
17
|
+
title: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Per-frame projection inputs, derived from the result plus the host's options. */
|
|
21
|
+
export interface RowView {
|
|
22
|
+
/** True while the row is drawn expanded. Required. */
|
|
23
|
+
expanded: boolean;
|
|
24
|
+
/** True when the call failed. Required. */
|
|
25
|
+
isError: boolean;
|
|
26
|
+
/** True while the result is still streaming. Required. */
|
|
27
|
+
isPartial: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* One tool row's three-stage contract: cheap call build, parse-once result build, per-frame project.
|
|
32
|
+
*
|
|
33
|
+
* @typeParam M — the parsed result model this spec derives; `unknown` is the erased default, so a
|
|
34
|
+
* `Record<string, RowSpec>` accepts every concrete spec without a cast.
|
|
35
|
+
*/
|
|
36
|
+
export interface RowSpec<M = unknown> {
|
|
37
|
+
/** Builds the collapsed call line; runs on every argument change. Required. */
|
|
38
|
+
buildCall(args: Record<string, unknown>, ctx: RenderContext): CallModel;
|
|
39
|
+
/** Parses the result once per result, memoized in ctx.state. Required; returns "unknown" when unrecognized. */
|
|
40
|
+
build(result: ToolResult, ctx: RenderContext): M | "unknown";
|
|
41
|
+
/** Draws the collapsed-line body; re-runs on every frame, so it stays allocation-light. Required. */
|
|
42
|
+
project(m: M, view: RowView, paint: RowPaint): string;
|
|
43
|
+
/** Enriches M, e.g. with a resolved link target, then asks the host to redraw. Optional. */
|
|
44
|
+
decorate?(m: M, ctx: RenderContext): Promise<Partial<M>>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "dracula-soft",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"text": "#f6f6f4",
|
|
7
|
+
"muted": "#7b7f8b",
|
|
8
|
+
"purple": "#bf9eee",
|
|
9
|
+
"pink": "#f286c4",
|
|
10
|
+
"cyan": "#97e1f1",
|
|
11
|
+
"green": "#62e884",
|
|
12
|
+
"red": "#ee6666",
|
|
13
|
+
"orange": "#ffb86c",
|
|
14
|
+
"yellow": "#e7ee98",
|
|
15
|
+
"selection": "#44475a",
|
|
16
|
+
"surface": "#343746"
|
|
17
|
+
},
|
|
18
|
+
"colors": {
|
|
19
|
+
"accent": "purple",
|
|
20
|
+
"border": "muted",
|
|
21
|
+
"borderAccent": "pink",
|
|
22
|
+
"borderMuted": "selection",
|
|
23
|
+
"success": "green",
|
|
24
|
+
"error": "red",
|
|
25
|
+
"warning": "orange",
|
|
26
|
+
"muted": "muted",
|
|
27
|
+
"dim": "#646773",
|
|
28
|
+
"text": "text",
|
|
29
|
+
"thinkingText": "muted",
|
|
30
|
+
"selectedBg": "selection",
|
|
31
|
+
"scrollbarTrack": "surface",
|
|
32
|
+
"scrollbarThumb": "muted",
|
|
33
|
+
"searchMatchBg": "#52554c",
|
|
34
|
+
"searchMatchText": "muted",
|
|
35
|
+
"userMessageBg": "surface",
|
|
36
|
+
"userMessageText": "text",
|
|
37
|
+
"customMessageBg": "#403d53",
|
|
38
|
+
"customMessageText": "muted",
|
|
39
|
+
"customMessageLabel": "purple",
|
|
40
|
+
"toolPendingBg": "#2e313e",
|
|
41
|
+
"toolSuccessBg": "#324c44",
|
|
42
|
+
"toolErrorBg": "#4c353f",
|
|
43
|
+
"toolTitle": "text",
|
|
44
|
+
"toolOutput": "muted",
|
|
45
|
+
"mdHeading": "purple",
|
|
46
|
+
"mdLink": "cyan",
|
|
47
|
+
"mdLinkUrl": "muted",
|
|
48
|
+
"mdCode": "green",
|
|
49
|
+
"mdCodeBlock": "yellow",
|
|
50
|
+
"mdCodeBlockBorder": "muted",
|
|
51
|
+
"mdQuote": "muted",
|
|
52
|
+
"mdQuoteBorder": "muted",
|
|
53
|
+
"mdHr": "muted",
|
|
54
|
+
"mdListBullet": "cyan",
|
|
55
|
+
"toolDiffAdded": "green",
|
|
56
|
+
"toolDiffRemoved": "red",
|
|
57
|
+
"toolDiffContext": "muted",
|
|
58
|
+
"syntaxComment": "muted",
|
|
59
|
+
"syntaxKeyword": "pink",
|
|
60
|
+
"syntaxFunction": "green",
|
|
61
|
+
"syntaxVariable": "orange",
|
|
62
|
+
"syntaxString": "yellow",
|
|
63
|
+
"syntaxNumber": "purple",
|
|
64
|
+
"syntaxType": "cyan",
|
|
65
|
+
"syntaxOperator": "muted",
|
|
66
|
+
"syntaxPunctuation": "muted",
|
|
67
|
+
"thinkingOff": "#53516c",
|
|
68
|
+
"thinkingMinimal": "#776b97",
|
|
69
|
+
"thinkingLow": "#9b84c3",
|
|
70
|
+
"thinkingMedium": "#bf9eee",
|
|
71
|
+
"thinkingHigh": "#d4a7c3",
|
|
72
|
+
"thinkingXhigh": "#eaaf97",
|
|
73
|
+
"thinkingMax": "#ffb86c",
|
|
74
|
+
"bashMode": "cyan"
|
|
75
|
+
},
|
|
76
|
+
"export": {
|
|
77
|
+
"pageBg": "#282a36",
|
|
78
|
+
"cardBg": "surface",
|
|
79
|
+
"infoBg": "#52554c"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "one-dark",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"text": "#abb2bf",
|
|
7
|
+
"muted": "#7f848e",
|
|
8
|
+
"blue": "#61afef",
|
|
9
|
+
"purple": "#c678dd",
|
|
10
|
+
"green": "#98c379",
|
|
11
|
+
"red": "#e06c75",
|
|
12
|
+
"orange": "#d19a66",
|
|
13
|
+
"yellow": "#e5c07b",
|
|
14
|
+
"selection": "#4e5666",
|
|
15
|
+
"surface": "#2c313c"
|
|
16
|
+
},
|
|
17
|
+
"colors": {
|
|
18
|
+
"accent": "blue",
|
|
19
|
+
"border": "muted",
|
|
20
|
+
"borderAccent": "blue",
|
|
21
|
+
"borderMuted": "#3e4452",
|
|
22
|
+
"success": "green",
|
|
23
|
+
"error": "red",
|
|
24
|
+
"warning": "yellow",
|
|
25
|
+
"muted": "muted",
|
|
26
|
+
"dim": "#636b78",
|
|
27
|
+
"text": "text",
|
|
28
|
+
"thinkingText": "muted",
|
|
29
|
+
"selectedBg": "selection",
|
|
30
|
+
"scrollbarTrack": "surface",
|
|
31
|
+
"scrollbarThumb": "selection",
|
|
32
|
+
"searchMatchBg": "#524d44",
|
|
33
|
+
"searchMatchText": "muted",
|
|
34
|
+
"userMessageBg": "surface",
|
|
35
|
+
"userMessageText": "text",
|
|
36
|
+
"customMessageBg": "#41384f",
|
|
37
|
+
"customMessageText": "muted",
|
|
38
|
+
"customMessageLabel": "purple",
|
|
39
|
+
"toolPendingBg": "#323842",
|
|
40
|
+
"toolSuccessBg": "#3c4740",
|
|
41
|
+
"toolErrorBg": "#493840",
|
|
42
|
+
"toolTitle": "text",
|
|
43
|
+
"toolOutput": "muted",
|
|
44
|
+
"mdHeading": "red",
|
|
45
|
+
"mdLink": "blue",
|
|
46
|
+
"mdLinkUrl": "muted",
|
|
47
|
+
"mdCode": "green",
|
|
48
|
+
"mdCodeBlock": "orange",
|
|
49
|
+
"mdCodeBlockBorder": "muted",
|
|
50
|
+
"mdQuote": "muted",
|
|
51
|
+
"mdQuoteBorder": "muted",
|
|
52
|
+
"mdHr": "muted",
|
|
53
|
+
"mdListBullet": "yellow",
|
|
54
|
+
"toolDiffAdded": "green",
|
|
55
|
+
"toolDiffRemoved": "red",
|
|
56
|
+
"toolDiffContext": "muted",
|
|
57
|
+
"syntaxComment": "muted",
|
|
58
|
+
"syntaxKeyword": "purple",
|
|
59
|
+
"syntaxFunction": "blue",
|
|
60
|
+
"syntaxVariable": "red",
|
|
61
|
+
"syntaxString": "green",
|
|
62
|
+
"syntaxNumber": "orange",
|
|
63
|
+
"syntaxType": "yellow",
|
|
64
|
+
"syntaxOperator": "#56b6c2",
|
|
65
|
+
"syntaxPunctuation": "muted",
|
|
66
|
+
"thinkingOff": "#5c5a74",
|
|
67
|
+
"thinkingMinimal": "#5e769d",
|
|
68
|
+
"thinkingLow": "#5f93c6",
|
|
69
|
+
"thinkingMedium": "#61afef",
|
|
70
|
+
"thinkingHigh": "#8db5c8",
|
|
71
|
+
"thinkingXhigh": "#b9baa2",
|
|
72
|
+
"thinkingMax": "#e5c07b",
|
|
73
|
+
"bashMode": "green"
|
|
74
|
+
},
|
|
75
|
+
"export": {
|
|
76
|
+
"pageBg": "#282c34",
|
|
77
|
+
"cardBg": "surface",
|
|
78
|
+
"infoBg": "#524d44"
|
|
79
|
+
}
|
|
80
|
+
}
|