@pify/pretty 0.1.0 → 0.2.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 +10 -2
- package/extensions/pretty.ts +33 -26
- package/package.json +10 -5
- package/src/config.ts +82 -0
- package/src/diff.ts +11 -2
- package/src/preview.ts +21 -0
package/README.md
CHANGED
|
@@ -22,10 +22,18 @@ Expand with pi's standard toggle (Ctrl+O on a tool block). Failures always show
|
|
|
22
22
|
Each renderer toggles independently (persisted per session):
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
/pretty
|
|
26
|
-
/pretty bash
|
|
25
|
+
/pretty # show which renderers are on
|
|
26
|
+
/pretty bash # toggle one back to pi's default rendering
|
|
27
|
+
/pretty bash grep # toggle several at once (v0.2)
|
|
28
|
+
/pretty off # everything back to pi's rendering (v0.2)
|
|
29
|
+
/pretty on read # explicit on/off instead of toggling (v0.2)
|
|
30
|
+
/pretty reset # all renderers back on (v0.2)
|
|
27
31
|
```
|
|
28
32
|
|
|
33
|
+
Aliases are accepted where they're obvious: `list`/`dir` → `ls`, `search`/`rg` → `grep`, `cat` → `read`, `sh`/`shell` → `bash`.
|
|
34
|
+
|
|
35
|
+
Expanded bodies are capped at 200 lines (v0.2) — a 5,000-line diff or grep result used to render in full and scroll the conversation away.
|
|
36
|
+
|
|
29
37
|
## How it works
|
|
30
38
|
|
|
31
39
|
The official `built-in-tool-renderer` pattern: each tool is re-registered with `createReadTool()`/`createBashTool()`/… delegating `execute` untouched, overriding only `renderCall`/`renderResult`. Highlighting uses pi's exported `highlightCode` + `getLanguageFromPath` — zero extra dependencies, colors always match your theme.
|
package/extensions/pretty.ts
CHANGED
|
@@ -28,8 +28,16 @@ import {
|
|
|
28
28
|
} from "@earendil-works/pi-coding-agent";
|
|
29
29
|
import { Text } from "@earendil-works/pi-tui";
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
PRETTY_CONFIG,
|
|
33
|
+
PRETTY_USAGE,
|
|
34
|
+
applyCommand,
|
|
35
|
+
parsePrettyCommand,
|
|
36
|
+
replayBranch,
|
|
37
|
+
statusLines,
|
|
38
|
+
} from "../src/config.ts";
|
|
32
39
|
import { colorizeDiff, diffStats, statsLabel } from "../src/diff.ts";
|
|
40
|
+
import { preview } from "../src/preview.ts";
|
|
33
41
|
import {
|
|
34
42
|
bashCall,
|
|
35
43
|
bashSummary,
|
|
@@ -50,8 +58,6 @@ import {
|
|
|
50
58
|
type ThemeLike,
|
|
51
59
|
} from "../src/types.ts";
|
|
52
60
|
|
|
53
|
-
const PREVIEW_LINES = 12;
|
|
54
|
-
|
|
55
61
|
type AnyTool = {
|
|
56
62
|
name: string;
|
|
57
63
|
description: string;
|
|
@@ -68,13 +74,6 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
68
74
|
return isRecord(result) && result.isError === true;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
function clip(text: string, expanded: boolean): string {
|
|
72
|
-
if (expanded) return text;
|
|
73
|
-
const lines = text.split("\n");
|
|
74
|
-
if (lines.length <= PREVIEW_LINES) return text;
|
|
75
|
-
return `${lines.slice(0, PREVIEW_LINES).join("\n")}\n…`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
77
|
function buildOriginals(cwd: string): Record<PrettyTool, AnyTool> {
|
|
79
78
|
return {
|
|
80
79
|
read: createReadTool(cwd) as unknown as AnyTool,
|
|
@@ -133,11 +132,11 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
133
132
|
) => {
|
|
134
133
|
const output = textContent(result);
|
|
135
134
|
if (options.isPartial) {
|
|
136
|
-
return new Text(`${theme.fg("warning", "Running…")}\n${
|
|
135
|
+
return new Text(`${theme.fg("warning", "Running…")}\n${preview(output, false)}`, 0, 0);
|
|
137
136
|
}
|
|
138
137
|
const failed = isFailed(result);
|
|
139
138
|
const summary = bashSummary(theme, output, failed);
|
|
140
|
-
const body = output && (options.expanded || failed) ? `\n${
|
|
139
|
+
const body = output && (options.expanded || failed) ? `\n${preview(output, options.expanded === true)}` : "";
|
|
141
140
|
return new Text(summary + body, 0, 0);
|
|
142
141
|
},
|
|
143
142
|
};
|
|
@@ -159,7 +158,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
159
158
|
: "";
|
|
160
159
|
const stats = statsLabel(theme, diffStats(diff));
|
|
161
160
|
if (!options.expanded) return new Text(stats, 0, 0);
|
|
162
|
-
return new Text(`${stats}\n${colorizeDiff(theme, diff)}`, 0, 0);
|
|
161
|
+
return new Text(`${stats}\n${colorizeDiff(theme, preview(diff, true))}`, 0, 0);
|
|
163
162
|
},
|
|
164
163
|
};
|
|
165
164
|
case "write":
|
|
@@ -200,7 +199,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
200
199
|
tool === "grep" ? { one: "match", many: "matches" } : { one: "result", many: "results" },
|
|
201
200
|
);
|
|
202
201
|
if (!options.expanded || failed || !output) return new Text(summary, 0, 0);
|
|
203
|
-
return new Text(`${summary}\n${
|
|
202
|
+
return new Text(`${summary}\n${preview(output, true)}`, 0, 0);
|
|
204
203
|
},
|
|
205
204
|
};
|
|
206
205
|
case "ls":
|
|
@@ -216,7 +215,7 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
216
215
|
const failed = isFailed(result);
|
|
217
216
|
const summary = matchSummary(theme, output, failed, { one: "entry", many: "entries" });
|
|
218
217
|
if (!options.expanded || failed) return new Text(summary, 0, 0);
|
|
219
|
-
return new Text(`${summary}\n${
|
|
218
|
+
return new Text(`${summary}\n${preview(output, true)}`, 0, 0);
|
|
220
219
|
},
|
|
221
220
|
};
|
|
222
221
|
}
|
|
@@ -254,23 +253,31 @@ export default function pretty(pi: ExtensionAPI) {
|
|
|
254
253
|
// ── Command ──────────────────────────────────────────────────────────
|
|
255
254
|
|
|
256
255
|
pi.registerCommand("pretty", {
|
|
257
|
-
description: "
|
|
256
|
+
description: "Pretty tool rendering: /pretty [status | on|off [tool…] | reset | <tool…>]",
|
|
258
257
|
handler: async (args, ctx: ExtensionContext) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
ctx.ui.notify(`Pretty renderers\n${statusLines(config).join("\n")}\nToggle with /pretty <tool>`, "info");
|
|
258
|
+
const command = parsePrettyCommand(args ?? "");
|
|
259
|
+
if (command.kind === "error") {
|
|
260
|
+
if (ctx.hasUI) ctx.ui.notify(command.message, "warning");
|
|
263
261
|
return;
|
|
264
262
|
}
|
|
265
|
-
if (
|
|
266
|
-
ctx.
|
|
263
|
+
if (command.kind === "status") {
|
|
264
|
+
if (ctx.hasUI) {
|
|
265
|
+
ctx.ui.notify(`Pretty renderers\n${statusLines(config).join("\n")}\n${PRETTY_USAGE}`, "info");
|
|
266
|
+
}
|
|
267
267
|
return;
|
|
268
268
|
}
|
|
269
|
-
|
|
269
|
+
|
|
270
|
+
const { config: next, changed } = applyCommand(config, command);
|
|
271
|
+
config = next;
|
|
270
272
|
pi.appendEntry(PRETTY_CONFIG, config);
|
|
271
|
-
applyTool(
|
|
272
|
-
|
|
273
|
-
ctx.ui.notify(
|
|
273
|
+
for (const tool of changed) applyTool(tool);
|
|
274
|
+
if (!ctx.hasUI) return;
|
|
275
|
+
ctx.ui.notify(
|
|
276
|
+
changed.length === 0
|
|
277
|
+
? "Nothing changed."
|
|
278
|
+
: changed.map((t) => `pretty ${t}: ${config.disabled.includes(t) ? "off" : "on"}`).join("\n"),
|
|
279
|
+
"info",
|
|
280
|
+
);
|
|
274
281
|
},
|
|
275
282
|
});
|
|
276
283
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pify/pretty",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Compact, theme-aware rendering for pi's built-in tools: one-line summaries, syntax-highlighted reads, colorized diffs — behavior untouched",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -27,12 +27,13 @@
|
|
|
27
27
|
"files": [
|
|
28
28
|
"extensions",
|
|
29
29
|
"src",
|
|
30
|
-
"skills",
|
|
31
30
|
"README.md",
|
|
32
31
|
"LICENSE"
|
|
33
32
|
],
|
|
34
33
|
"pi": {
|
|
35
|
-
"extensions": [
|
|
34
|
+
"extensions": [
|
|
35
|
+
"./extensions/pretty.ts"
|
|
36
|
+
]
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|
|
38
39
|
"typecheck": "tsc --noEmit",
|
|
@@ -44,8 +45,12 @@
|
|
|
44
45
|
"@earendil-works/pi-tui": "*"
|
|
45
46
|
},
|
|
46
47
|
"peerDependenciesMeta": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": {
|
|
48
|
-
|
|
48
|
+
"@earendil-works/pi-coding-agent": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@earendil-works/pi-tui": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
49
54
|
},
|
|
50
55
|
"devDependencies": {
|
|
51
56
|
"@earendil-works/pi-coding-agent": "^0.84.4",
|
package/src/config.ts
CHANGED
|
@@ -33,6 +33,88 @@ export function toggleTool(config: PrettyConfig, tool: PrettyTool): PrettyConfig
|
|
|
33
33
|
: { disabled: [...config.disabled, tool] };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export const PRETTY_USAGE = "Usage: /pretty [status | on|off [tool…] | reset | <tool…>] tools: read, bash, edit, write, grep, find, ls";
|
|
37
|
+
|
|
38
|
+
/** Aliases the model or a hurried user is likely to type. */
|
|
39
|
+
const ALIASES: Record<string, PrettyTool> = {
|
|
40
|
+
list: "ls",
|
|
41
|
+
dir: "ls",
|
|
42
|
+
search: "grep",
|
|
43
|
+
rg: "grep",
|
|
44
|
+
cat: "read",
|
|
45
|
+
shell: "bash",
|
|
46
|
+
sh: "bash",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type PrettyCommand =
|
|
50
|
+
| { kind: "status" }
|
|
51
|
+
| { kind: "toggle"; tools: PrettyTool[] }
|
|
52
|
+
| { kind: "set"; tools: PrettyTool[]; on: boolean }
|
|
53
|
+
| { kind: "reset" }
|
|
54
|
+
| { kind: "error"; message: string };
|
|
55
|
+
|
|
56
|
+
function resolveTools(words: string[]): { tools: PrettyTool[]; unknown: string[] } {
|
|
57
|
+
const tools: PrettyTool[] = [];
|
|
58
|
+
const unknown: string[] = [];
|
|
59
|
+
for (const word of words) {
|
|
60
|
+
if (word === "all") {
|
|
61
|
+
tools.push(...PRETTY_TOOLS);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const name = ALIASES[word] ?? word;
|
|
65
|
+
if (isPrettyTool(name)) {
|
|
66
|
+
if (!tools.includes(name)) tools.push(name);
|
|
67
|
+
} else {
|
|
68
|
+
unknown.push(word);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { tools, unknown };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function parsePrettyCommand(raw: string): PrettyCommand {
|
|
75
|
+
const words = (raw ?? "").trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
76
|
+
if (words.length === 0) return { kind: "status" };
|
|
77
|
+
|
|
78
|
+
const [head, ...rest] = words;
|
|
79
|
+
if (head === "status" || head === "list") return { kind: "status" };
|
|
80
|
+
if (head === "reset") return { kind: "reset" };
|
|
81
|
+
|
|
82
|
+
if (head === "on" || head === "off") {
|
|
83
|
+
const { tools, unknown } = resolveTools(rest.length > 0 ? rest : ["all"]);
|
|
84
|
+
if (unknown.length > 0) return { kind: "error", message: `Unknown tool "${unknown[0]}". ${PRETTY_USAGE}` };
|
|
85
|
+
return { kind: "set", tools, on: head === "on" };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const { tools, unknown } = resolveTools(words);
|
|
89
|
+
if (unknown.length > 0) return { kind: "error", message: `Unknown tool "${unknown[0]}". ${PRETTY_USAGE}` };
|
|
90
|
+
return { kind: "toggle", tools };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Apply a parsed command; returns the new config and the tools that changed. */
|
|
94
|
+
export function applyCommand(
|
|
95
|
+
config: PrettyConfig,
|
|
96
|
+
command: PrettyCommand,
|
|
97
|
+
): { config: PrettyConfig; changed: PrettyTool[] } {
|
|
98
|
+
switch (command.kind) {
|
|
99
|
+
case "toggle": {
|
|
100
|
+
let next = config;
|
|
101
|
+
for (const tool of command.tools) next = toggleTool(next, tool);
|
|
102
|
+
return { config: next, changed: command.tools };
|
|
103
|
+
}
|
|
104
|
+
case "set": {
|
|
105
|
+
const disabled = command.on
|
|
106
|
+
? config.disabled.filter((t) => !command.tools.includes(t))
|
|
107
|
+
: [...new Set([...config.disabled, ...command.tools])];
|
|
108
|
+
const changed = command.tools.filter((t) => config.disabled.includes(t) !== disabled.includes(t));
|
|
109
|
+
return { config: { disabled }, changed };
|
|
110
|
+
}
|
|
111
|
+
case "reset":
|
|
112
|
+
return { config: DEFAULT_CONFIG, changed: [...config.disabled] };
|
|
113
|
+
default:
|
|
114
|
+
return { config, changed: [] };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
36
118
|
export function statusLines(config: PrettyConfig): string[] {
|
|
37
119
|
return PRETTY_TOOLS.map(
|
|
38
120
|
(tool) => `${config.disabled.includes(tool) ? "○" : "●"} ${tool}`,
|
package/src/diff.ts
CHANGED
|
@@ -5,12 +5,21 @@ export interface DiffStats {
|
|
|
5
5
|
removed: number;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Count +/− lines in a display diff. File headers (+++/---) are skipped, but
|
|
10
|
+
* only where they can actually appear — before the first hunk. Inside a hunk,
|
|
11
|
+
* a removed line whose content starts with `--` is a real removal.
|
|
12
|
+
*/
|
|
9
13
|
export function diffStats(diff: string): DiffStats {
|
|
10
14
|
let added = 0;
|
|
11
15
|
let removed = 0;
|
|
16
|
+
let inHunk = false;
|
|
12
17
|
for (const line of diff.split("\n")) {
|
|
13
|
-
if (line.startsWith("
|
|
18
|
+
if (line.startsWith("@@")) {
|
|
19
|
+
inHunk = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (!inHunk && (line.startsWith("+++") || line.startsWith("---"))) continue;
|
|
14
23
|
if (line.startsWith("+")) added++;
|
|
15
24
|
else if (line.startsWith("-")) removed++;
|
|
16
25
|
}
|
package/src/preview.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One capping primitive for every body the renderers print. Collapsed rows get
|
|
3
|
+
* a short peek; expanded rows get a generous but bounded view — a 5,000-line
|
|
4
|
+
* diff or grep result should not be able to scroll the whole conversation out
|
|
5
|
+
* of the terminal just because someone pressed expand.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface PreviewLimits {
|
|
9
|
+
collapsed: number;
|
|
10
|
+
expanded: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_LIMITS: PreviewLimits = { collapsed: 12, expanded: 200 };
|
|
14
|
+
|
|
15
|
+
export function preview(text: string, expanded: boolean, limits: PreviewLimits = DEFAULT_LIMITS): string {
|
|
16
|
+
const max = expanded ? limits.expanded : limits.collapsed;
|
|
17
|
+
const lines = text.split("\n");
|
|
18
|
+
if (lines.length <= max) return text;
|
|
19
|
+
const hidden = lines.length - max;
|
|
20
|
+
return `${lines.slice(0, max).join("\n")}\n… +${hidden} more line${hidden === 1 ? "" : "s"}`;
|
|
21
|
+
}
|