@agnishc/edb-compact-tools 0.10.6 → 0.10.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/CHANGELOG.md +23 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/constants.test.ts +22 -0
- package/src/constants.ts +42 -0
- package/src/index.ts +2 -510
- package/src/message-frame.ts +73 -0
- package/src/patches.ts +170 -0
- package/src/text.test.ts +124 -0
- package/src/text.ts +34 -0
- package/src/tool-block.ts +64 -0
- package/src/tool-meta.test.ts +182 -0
- package/src/tool-meta.ts +160 -0
- package/src/tool-renderer.ts +103 -0
- package/src/types.ts +15 -0
package/src/tool-meta.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { ANSI_PURPLE, ANSI_RESET } from "./constants.js";
|
|
2
|
+
import { clip, lineCount, oneLine, outputWasTruncated, textContent } from "./text.js";
|
|
3
|
+
|
|
4
|
+
// ── Skill path detection ─────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export function isSkillPath(path: unknown): boolean {
|
|
7
|
+
if (typeof path !== "string") return false;
|
|
8
|
+
return path.includes(".agents/skills/") || path.includes(".pi/agent/skills/");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function purple(text: string): string {
|
|
12
|
+
return `${ANSI_PURPLE}${text}${ANSI_RESET}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ── Tool metadata registry ───────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
type ToolMeta = {
|
|
18
|
+
color: string | ((args?: any) => string);
|
|
19
|
+
icon: string;
|
|
20
|
+
label: (args: any) => string;
|
|
21
|
+
summary: (result: any) => string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const TOOL_REGISTRY: Record<string, ToolMeta> = {
|
|
25
|
+
bash: {
|
|
26
|
+
color: "bashMode",
|
|
27
|
+
icon: "⚙️",
|
|
28
|
+
label: (args) => clip(oneLine(args?.command), 140),
|
|
29
|
+
summary: (result) => {
|
|
30
|
+
const text = textContent(result);
|
|
31
|
+
const lines = lineCount(text);
|
|
32
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
33
|
+
const exitMatch = text.match(/Exit code:\s*(-?\d+)/i) ?? text.match(/exit(?:ed)?(?: code)?\s*(-?\d+)/i);
|
|
34
|
+
const exit = exitMatch?.[1] ?? (result?.isError ? "1" : "0");
|
|
35
|
+
return `exit ${exit} · ${lines} line${lines === 1 ? "" : "s"}${truncated}`;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
read: {
|
|
39
|
+
color: (args) => (isSkillPath(args?.path) ? "purple" : "toolTitle"),
|
|
40
|
+
icon: "📖",
|
|
41
|
+
label: (args) => clip(oneLine(args?.path), 140),
|
|
42
|
+
summary: (result) => {
|
|
43
|
+
const text = textContent(result);
|
|
44
|
+
const lines = lineCount(text);
|
|
45
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
46
|
+
return `${lines} line${lines === 1 ? "" : "s"}${truncated}`;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
grep: {
|
|
50
|
+
color: "success",
|
|
51
|
+
icon: "🔎",
|
|
52
|
+
label: (args) => {
|
|
53
|
+
const pattern = oneLine(args?.pattern);
|
|
54
|
+
const path = oneLine(args?.path ?? args?.glob ?? ".");
|
|
55
|
+
return clip(`${pattern}${path ? ` in ${path}` : ""}`, 140);
|
|
56
|
+
},
|
|
57
|
+
summary: (result) => {
|
|
58
|
+
const text = textContent(result);
|
|
59
|
+
const lines = lineCount(text);
|
|
60
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
61
|
+
return `${lines} result${lines === 1 ? "" : "s"}${truncated}`;
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
find: {
|
|
65
|
+
color: "accent",
|
|
66
|
+
icon: "🧭",
|
|
67
|
+
label: (args) => clip(oneLine(args?.path ?? args?.pattern ?? "."), 140),
|
|
68
|
+
summary: (result) => {
|
|
69
|
+
const text = textContent(result);
|
|
70
|
+
const lines = lineCount(text);
|
|
71
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
72
|
+
return `${lines} result${lines === 1 ? "" : "s"}${truncated}`;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
ls: {
|
|
76
|
+
color: "warning",
|
|
77
|
+
icon: "📁",
|
|
78
|
+
label: (args) => clip(oneLine(args?.path), 140),
|
|
79
|
+
summary: (result) => {
|
|
80
|
+
const text = textContent(result);
|
|
81
|
+
const lines = lineCount(text);
|
|
82
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
83
|
+
return `${lines} item${lines === 1 ? "" : "s"}${truncated}`;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
edit: {
|
|
87
|
+
color: "toolDiffAdded",
|
|
88
|
+
icon: "✏️",
|
|
89
|
+
label: (args) => {
|
|
90
|
+
const count = Array.isArray(args?.edits) ? args.edits.length : args?.oldText && args?.newText ? 1 : 0;
|
|
91
|
+
return clip(
|
|
92
|
+
`${oneLine(args?.path ?? args?.file_path)}${count ? ` · ${count} replacement${count === 1 ? "" : "s"}` : ""}`,
|
|
93
|
+
140,
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
summary: (result) => {
|
|
97
|
+
const text = textContent(result);
|
|
98
|
+
const lines = lineCount(text);
|
|
99
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
100
|
+
const diff = typeof result?.details?.diff === "string" ? result.details.diff : "";
|
|
101
|
+
const added = diff
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.filter((line: string) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
104
|
+
const removed = diff
|
|
105
|
+
.split(/\r?\n/)
|
|
106
|
+
.filter((line: string) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
107
|
+
return diff ? `+${added} -${removed}` : `${lines} line${lines === 1 ? "" : "s"}${truncated}`;
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
write: {
|
|
111
|
+
color: "accent",
|
|
112
|
+
icon: "📝",
|
|
113
|
+
label: (args) => {
|
|
114
|
+
const bytes = typeof args?.content === "string" ? Buffer.byteLength(args.content, "utf8") : 0;
|
|
115
|
+
return clip(`${oneLine(args?.path ?? args?.file_path)}${bytes ? ` · ${bytes} bytes` : ""}`, 140);
|
|
116
|
+
},
|
|
117
|
+
summary: (result) => {
|
|
118
|
+
const text = textContent(result);
|
|
119
|
+
const lines = lineCount(text);
|
|
120
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
121
|
+
return `${lines} line${lines === 1 ? "" : "s"}${truncated}`;
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const DEFAULT_META: ToolMeta = {
|
|
127
|
+
color: "accent",
|
|
128
|
+
icon: "🧩",
|
|
129
|
+
label: (args) => {
|
|
130
|
+
const compactArgs = oneLine(JSON.stringify(args ?? {}));
|
|
131
|
+
return clip(compactArgs === "{}" ? "" : compactArgs, 140);
|
|
132
|
+
},
|
|
133
|
+
summary: (result) => {
|
|
134
|
+
const text = textContent(result);
|
|
135
|
+
const lines = lineCount(text);
|
|
136
|
+
const truncated = outputWasTruncated(text) ? " · truncated" : "";
|
|
137
|
+
return `${lines} result${lines === 1 ? "" : "s"}${truncated}`;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function getMeta(toolName: string): ToolMeta {
|
|
142
|
+
return TOOL_REGISTRY[toolName] ?? DEFAULT_META;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function toolColor(toolName: string, args?: any): string {
|
|
146
|
+
const color = getMeta(toolName).color;
|
|
147
|
+
return typeof color === "function" ? color(args) : color;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function toolIcon(toolName: string): string {
|
|
151
|
+
return getMeta(toolName).icon;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function callLabel(toolName: string, args: any): string {
|
|
155
|
+
return getMeta(toolName).label(args);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function summaryFor(toolName: string, result: any): string {
|
|
159
|
+
return getMeta(toolName).summary(result);
|
|
160
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { MAX_EXPANDED_LINES } from "./constants.js";
|
|
3
|
+
import { lineCount, previewLines, textContent } from "./text.js";
|
|
4
|
+
import { EmptyBlock, ToolBlock } from "./tool-block.js";
|
|
5
|
+
import { callLabel, purple, summaryFor, toolColor, toolIcon } from "./tool-meta.js";
|
|
6
|
+
import type { CompactTheme, ToolBlockKind } from "./types.js";
|
|
7
|
+
|
|
8
|
+
// ── Color resolution ─────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function resolveColor(toolName: string, args?: any, override?: string): string {
|
|
11
|
+
if (override) return override;
|
|
12
|
+
return toolColor(toolName, args);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function makeColorFn(color: string, theme: CompactTheme): (text: string) => string {
|
|
16
|
+
return color === "purple" ? purple : (text: string) => theme.fg(color, text);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ── Line builders ────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function topLine(toolName: string, theme: CompactTheme, label: string, args?: any): string {
|
|
22
|
+
const color = toolColor(toolName, args);
|
|
23
|
+
const title = `${toolIcon(toolName)} ${toolName}`;
|
|
24
|
+
const coloredTitle = color === "purple" ? purple(theme.bold(title)) : theme.fg(color, theme.bold(title));
|
|
25
|
+
return `${coloredTitle} ${theme.fg("toolOutput", label)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function midLine(_toolName: string, theme: CompactTheme, text: string): string {
|
|
29
|
+
return theme.fg("toolOutput", text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function bottomLine(_toolName: string, _theme: CompactTheme, text = ""): string {
|
|
33
|
+
return text.trimEnd();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Block builders ───────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function toolText(
|
|
39
|
+
kind: ToolBlockKind,
|
|
40
|
+
toolName: string,
|
|
41
|
+
lines: string[],
|
|
42
|
+
theme: CompactTheme,
|
|
43
|
+
borderColor: string,
|
|
44
|
+
args?: any,
|
|
45
|
+
): ToolBlock {
|
|
46
|
+
const color = resolveColor(toolName, args, borderColor);
|
|
47
|
+
return new ToolBlock(kind, lines, theme, makeColorFn(color, theme));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Renderers ────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export function renderCall(_toolName: string, _args: any, _theme: CompactTheme, _context: any) {
|
|
53
|
+
return new EmptyBlock();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderResult(toolName: string, result: any, options: any, theme: CompactTheme, context: any) {
|
|
57
|
+
const args = context?.args;
|
|
58
|
+
|
|
59
|
+
if (options?.isPartial) {
|
|
60
|
+
return toolText(
|
|
61
|
+
"full",
|
|
62
|
+
toolName,
|
|
63
|
+
[
|
|
64
|
+
topLine(toolName, theme, callLabel(toolName, args), args),
|
|
65
|
+
bottomLine(toolName, theme, theme.fg("muted", "running…")),
|
|
66
|
+
],
|
|
67
|
+
theme,
|
|
68
|
+
"warning",
|
|
69
|
+
args,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const summary = summaryFor(toolName, result);
|
|
74
|
+
const text = textContent(result);
|
|
75
|
+
const failed = Boolean(context?.isError || result?.isError);
|
|
76
|
+
const statusColor = failed ? "error" : "success";
|
|
77
|
+
const statusIcon = failed ? "✗" : "✓";
|
|
78
|
+
const expandHint = options?.expanded ? "" : ` ${theme.fg("dim", keyHint("app.tools.expand", "expand"))}`;
|
|
79
|
+
|
|
80
|
+
const top = topLine(toolName, theme, callLabel(toolName, args), args);
|
|
81
|
+
const bottom = bottomLine(
|
|
82
|
+
toolName,
|
|
83
|
+
theme,
|
|
84
|
+
`${theme.fg(statusColor, statusIcon)} ${theme.fg("toolOutput", summary)}${expandHint}`,
|
|
85
|
+
);
|
|
86
|
+
const borderColor = failed ? "error" : toolColor(toolName, args) === "purple" ? "purple" : "success";
|
|
87
|
+
|
|
88
|
+
if (!options?.expanded || !text.trim()) {
|
|
89
|
+
return toolText("full", toolName, [top, bottom], theme, borderColor, args);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const diff = toolName === "edit" && typeof result?.details?.diff === "string" ? result.details.diff : "";
|
|
93
|
+
const previewText = diff || text;
|
|
94
|
+
const mode = toolName === "bash" ? "tail" : "head";
|
|
95
|
+
const lines = previewLines(previewText, mode).map((line) => midLine(toolName, theme, line));
|
|
96
|
+
if (lineCount(previewText) > MAX_EXPANDED_LINES) {
|
|
97
|
+
const omitted = lineCount(previewText) - MAX_EXPANDED_LINES;
|
|
98
|
+
lines.push(midLine(toolName, theme, theme.fg("dim", `… ${omitted} more line(s)`)));
|
|
99
|
+
}
|
|
100
|
+
lines.unshift(top);
|
|
101
|
+
lines.push(bottomLine(toolName, theme, `${theme.fg(statusColor, statusIcon)} ${theme.fg("toolOutput", summary)}`));
|
|
102
|
+
return toolText("full", toolName, lines, theme, borderColor, args);
|
|
103
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type CompactTheme = {
|
|
2
|
+
fg: (color: any, text: string) => string;
|
|
3
|
+
bg?: (color: any, text: string) => string;
|
|
4
|
+
bold: (text: string) => string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type BuiltinToolName = "read" | "bash" | "grep" | "find" | "ls" | "edit" | "write";
|
|
8
|
+
|
|
9
|
+
export type BuiltinTool = {
|
|
10
|
+
description: string;
|
|
11
|
+
parameters: unknown;
|
|
12
|
+
execute: (id: string, params: unknown, signal?: AbortSignal, onUpdate?: unknown, ctx?: unknown) => Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ToolBlockKind = "call" | "result" | "full";
|