@mariozechner/pi-coding-agent 0.6.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/README.md +485 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/export-html.d.ts +7 -0
- package/dist/export-html.d.ts.map +1 -0
- package/dist/export-html.js +650 -0
- package/dist/export-html.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +514 -0
- package/dist/main.js.map +1 -0
- package/dist/session-manager.d.ts +70 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +323 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/tools/bash.d.ts +7 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +130 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +9 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +207 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +19 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.d.ts +9 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +165 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +8 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +81 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/tui/assistant-message.d.ts +11 -0
- package/dist/tui/assistant-message.d.ts.map +1 -0
- package/dist/tui/assistant-message.js +53 -0
- package/dist/tui/assistant-message.js.map +1 -0
- package/dist/tui/custom-editor.d.ts +10 -0
- package/dist/tui/custom-editor.d.ts.map +1 -0
- package/dist/tui/custom-editor.js +24 -0
- package/dist/tui/custom-editor.js.map +1 -0
- package/dist/tui/footer.d.ts +11 -0
- package/dist/tui/footer.d.ts.map +1 -0
- package/dist/tui/footer.js +101 -0
- package/dist/tui/footer.js.map +1 -0
- package/dist/tui/model-selector.d.ts +23 -0
- package/dist/tui/model-selector.d.ts.map +1 -0
- package/dist/tui/model-selector.js +157 -0
- package/dist/tui/model-selector.js.map +1 -0
- package/dist/tui/session-selector.d.ts +37 -0
- package/dist/tui/session-selector.d.ts.map +1 -0
- package/dist/tui/session-selector.js +176 -0
- package/dist/tui/session-selector.js.map +1 -0
- package/dist/tui/thinking-selector.d.ts +11 -0
- package/dist/tui/thinking-selector.d.ts.map +1 -0
- package/dist/tui/thinking-selector.js +48 -0
- package/dist/tui/thinking-selector.js.map +1 -0
- package/dist/tui/tool-execution.d.ts +26 -0
- package/dist/tui/tool-execution.d.ts.map +1 -0
- package/dist/tui/tool-execution.js +246 -0
- package/dist/tui/tool-execution.js.map +1 -0
- package/dist/tui/tui-renderer.d.ts +44 -0
- package/dist/tui/tui-renderer.d.ts.map +1 -0
- package/dist/tui/tui-renderer.js +539 -0
- package/dist/tui/tui-renderer.js.map +1 -0
- package/dist/tui/user-message.d.ts +9 -0
- package/dist/tui/user-message.d.ts.map +1 -0
- package/dist/tui/user-message.js +18 -0
- package/dist/tui/user-message.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Container, Input, Spacer, Text } from "@mariozechner/pi-tui";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
/**
|
|
4
|
+
* Dynamic border component that adjusts to viewport width
|
|
5
|
+
*/
|
|
6
|
+
class DynamicBorder {
|
|
7
|
+
render(width) {
|
|
8
|
+
return [chalk.blue("─".repeat(Math.max(1, width)))];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Custom session list component with multi-line items and search
|
|
13
|
+
*/
|
|
14
|
+
class SessionList {
|
|
15
|
+
allSessions = [];
|
|
16
|
+
filteredSessions = [];
|
|
17
|
+
selectedIndex = 0;
|
|
18
|
+
searchInput;
|
|
19
|
+
onSelect;
|
|
20
|
+
onCancel;
|
|
21
|
+
maxVisible = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
|
|
22
|
+
constructor(sessions) {
|
|
23
|
+
this.allSessions = sessions;
|
|
24
|
+
this.filteredSessions = sessions;
|
|
25
|
+
this.searchInput = new Input();
|
|
26
|
+
// Handle Enter in search input - select current item
|
|
27
|
+
this.searchInput.onSubmit = () => {
|
|
28
|
+
if (this.filteredSessions[this.selectedIndex]) {
|
|
29
|
+
const selected = this.filteredSessions[this.selectedIndex];
|
|
30
|
+
if (this.onSelect) {
|
|
31
|
+
this.onSelect(selected.path);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
filterSessions(query) {
|
|
37
|
+
if (!query.trim()) {
|
|
38
|
+
this.filteredSessions = this.allSessions;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const searchTokens = query
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.split(/\s+/)
|
|
44
|
+
.filter((t) => t);
|
|
45
|
+
this.filteredSessions = this.allSessions.filter((session) => {
|
|
46
|
+
// Search through all messages in the session
|
|
47
|
+
const searchText = session.allMessagesText.toLowerCase();
|
|
48
|
+
return searchTokens.every((token) => searchText.includes(token));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));
|
|
52
|
+
}
|
|
53
|
+
render(width) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
// Render search input
|
|
56
|
+
lines.push(...this.searchInput.render(width));
|
|
57
|
+
lines.push(""); // Blank line after search
|
|
58
|
+
if (this.filteredSessions.length === 0) {
|
|
59
|
+
lines.push(chalk.gray(" No sessions found"));
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
// Format dates
|
|
63
|
+
const formatDate = (date) => {
|
|
64
|
+
const now = new Date();
|
|
65
|
+
const diffMs = now.getTime() - date.getTime();
|
|
66
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
67
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
68
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
69
|
+
if (diffMins < 1)
|
|
70
|
+
return "just now";
|
|
71
|
+
if (diffMins < 60)
|
|
72
|
+
return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
|
|
73
|
+
if (diffHours < 24)
|
|
74
|
+
return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
|
|
75
|
+
if (diffDays === 1)
|
|
76
|
+
return "1 day ago";
|
|
77
|
+
if (diffDays < 7)
|
|
78
|
+
return `${diffDays} days ago`;
|
|
79
|
+
return date.toLocaleDateString();
|
|
80
|
+
};
|
|
81
|
+
// Calculate visible range with scrolling
|
|
82
|
+
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible));
|
|
83
|
+
const endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);
|
|
84
|
+
// Render visible sessions (2 lines per session + blank line)
|
|
85
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
86
|
+
const session = this.filteredSessions[i];
|
|
87
|
+
const isSelected = i === this.selectedIndex;
|
|
88
|
+
// Normalize first message to single line
|
|
89
|
+
const normalizedMessage = session.firstMessage.replace(/\n/g, " ").trim();
|
|
90
|
+
// First line: cursor + message
|
|
91
|
+
const cursor = isSelected ? chalk.blue("› ") : " ";
|
|
92
|
+
const maxMsgWidth = width - 2; // Account for cursor
|
|
93
|
+
const truncatedMsg = normalizedMessage.substring(0, maxMsgWidth);
|
|
94
|
+
const messageLine = cursor + (isSelected ? chalk.bold(truncatedMsg) : truncatedMsg);
|
|
95
|
+
// Second line: metadata (dimmed)
|
|
96
|
+
const modified = formatDate(session.modified);
|
|
97
|
+
const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
|
|
98
|
+
const metadata = ` ${modified} · ${msgCount}`;
|
|
99
|
+
const metadataLine = chalk.dim(metadata);
|
|
100
|
+
lines.push(messageLine);
|
|
101
|
+
lines.push(metadataLine);
|
|
102
|
+
lines.push(""); // Blank line between sessions
|
|
103
|
+
}
|
|
104
|
+
// Add scroll indicator if needed
|
|
105
|
+
if (startIndex > 0 || endIndex < this.filteredSessions.length) {
|
|
106
|
+
const scrollInfo = chalk.gray(` (${this.selectedIndex + 1}/${this.filteredSessions.length})`);
|
|
107
|
+
lines.push(scrollInfo);
|
|
108
|
+
}
|
|
109
|
+
return lines;
|
|
110
|
+
}
|
|
111
|
+
handleInput(keyData) {
|
|
112
|
+
// Up arrow
|
|
113
|
+
if (keyData === "\x1b[A") {
|
|
114
|
+
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
115
|
+
}
|
|
116
|
+
// Down arrow
|
|
117
|
+
else if (keyData === "\x1b[B") {
|
|
118
|
+
this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);
|
|
119
|
+
}
|
|
120
|
+
// Enter
|
|
121
|
+
else if (keyData === "\r") {
|
|
122
|
+
const selected = this.filteredSessions[this.selectedIndex];
|
|
123
|
+
if (selected && this.onSelect) {
|
|
124
|
+
this.onSelect(selected.path);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Escape - cancel
|
|
128
|
+
else if (keyData === "\x1b") {
|
|
129
|
+
if (this.onCancel) {
|
|
130
|
+
this.onCancel();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Ctrl+C - exit process
|
|
134
|
+
else if (keyData === "\x03") {
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
// Pass everything else to search input
|
|
138
|
+
else {
|
|
139
|
+
this.searchInput.handleInput(keyData);
|
|
140
|
+
this.filterSessions(this.searchInput.getValue());
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Component that renders a session selector
|
|
146
|
+
*/
|
|
147
|
+
export class SessionSelectorComponent extends Container {
|
|
148
|
+
sessionList;
|
|
149
|
+
constructor(sessionManager, onSelect, onCancel) {
|
|
150
|
+
super();
|
|
151
|
+
// Load all sessions
|
|
152
|
+
const sessions = sessionManager.loadAllSessions();
|
|
153
|
+
// Add header
|
|
154
|
+
this.addChild(new Spacer(1));
|
|
155
|
+
this.addChild(new Text(chalk.bold("Resume Session"), 1, 0));
|
|
156
|
+
this.addChild(new Spacer(1));
|
|
157
|
+
this.addChild(new DynamicBorder());
|
|
158
|
+
this.addChild(new Spacer(1));
|
|
159
|
+
// Create session list
|
|
160
|
+
this.sessionList = new SessionList(sessions);
|
|
161
|
+
this.sessionList.onSelect = onSelect;
|
|
162
|
+
this.sessionList.onCancel = onCancel;
|
|
163
|
+
this.addChild(this.sessionList);
|
|
164
|
+
// Add bottom border
|
|
165
|
+
this.addChild(new Spacer(1));
|
|
166
|
+
this.addChild(new DynamicBorder());
|
|
167
|
+
// Auto-cancel if no sessions
|
|
168
|
+
if (sessions.length === 0) {
|
|
169
|
+
setTimeout(() => onCancel(), 100);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
getSessionList() {
|
|
173
|
+
return this.sessionList;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=session-selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-selector.js","sourceRoot":"","sources":["../../src/tui/session-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, Container, Input, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport type { SessionManager } from \"../session-manager.js\";\n\n/**\n * Dynamic border component that adjusts to viewport width\n */\nclass DynamicBorder implements Component {\n\trender(width: number): string[] {\n\t\treturn [chalk.blue(\"─\".repeat(Math.max(1, width)))];\n\t}\n}\n\ninterface SessionItem {\n\tpath: string;\n\tid: string;\n\tcreated: Date;\n\tmodified: Date;\n\tmessageCount: number;\n\tfirstMessage: string;\n\tallMessagesText: string;\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionItem[] = [];\n\tprivate filteredSessions: SessionItem[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionItem[]) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tif (!query.trim()) {\n\t\t\tthis.filteredSessions = this.allSessions;\n\t\t} else {\n\t\t\tconst searchTokens = query\n\t\t\t\t.toLowerCase()\n\t\t\t\t.split(/\\s+/)\n\t\t\t\t.filter((t) => t);\n\t\t\tthis.filteredSessions = this.allSessions.filter((session) => {\n\t\t\t\t// Search through all messages in the session\n\t\t\t\tconst searchText = session.allMessagesText.toLowerCase();\n\t\t\t\treturn searchTokens.every((token) => searchText.includes(token));\n\t\t\t});\n\t\t}\n\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tlines.push(chalk.gray(\" No sessions found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Format dates\n\t\tconst formatDate = (date: Date): string => {\n\t\t\tconst now = new Date();\n\t\t\tconst diffMs = now.getTime() - date.getTime();\n\t\t\tconst diffMins = Math.floor(diffMs / 60000);\n\t\t\tconst diffHours = Math.floor(diffMs / 3600000);\n\t\t\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\t\t\tif (diffMins < 1) return \"just now\";\n\t\t\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\t\t\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\t\t\tif (diffDays === 1) return \"1 day ago\";\n\t\t\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\t\t\treturn date.toLocaleDateString();\n\t\t};\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Normalize first message to single line\n\t\t\tconst normalizedMessage = session.firstMessage.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message\n\t\t\tconst cursor = isSelected ? chalk.blue(\"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor\n\t\t\tconst truncatedMsg = normalizedMessage.substring(0, maxMsgWidth);\n\t\t\tconst messageLine = cursor + (isSelected ? chalk.bold(truncatedMsg) : truncatedMsg);\n\n\t\t\t// Second line: metadata (dimmed)\n\t\t\tconst modified = formatDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadata = ` ${modified} · ${msgCount}`;\n\t\t\tconst metadataLine = chalk.dim(metadata);\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollInfo = chalk.gray(` (${this.selectedIndex + 1}/${this.filteredSessions.length})`);\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\t// Up arrow\n\t\tif (keyData === \"\\x1b[A\") {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (keyData === \"\\x1b[B\") {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Enter\n\t\telse if (keyData === \"\\r\") {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (keyData === \"\\x1b\") {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Ctrl+C - exit process\n\t\telse if (keyData === \"\\x03\") {\n\t\t\tprocess.exit(0);\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\n\tconstructor(sessionManager: SessionManager, onSelect: (sessionPath: string) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\t// Load all sessions\n\t\tconst sessions = sessionManager.loadAllSessions();\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(chalk.bold(\"Resume Session\"), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list\n\t\tthis.sessionList = new SessionList(sessions);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Auto-cancel if no sessions\n\t\tif (sessions.length === 0) {\n\t\t\tsetTimeout(() => onCancel(), 100);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@mariozechner/pi-agent";
|
|
2
|
+
import { Container, SelectList } from "@mariozechner/pi-tui";
|
|
3
|
+
/**
|
|
4
|
+
* Component that renders a thinking level selector with borders
|
|
5
|
+
*/
|
|
6
|
+
export declare class ThinkingSelectorComponent extends Container {
|
|
7
|
+
private selectList;
|
|
8
|
+
constructor(currentLevel: ThinkingLevel, onSelect: (level: ThinkingLevel) => void, onCancel: () => void);
|
|
9
|
+
getSelectList(): SelectList;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=thinking-selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking-selector.d.ts","sourceRoot":"","sources":["../../src/tui/thinking-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAkB,SAAS,EAAmB,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAY9F;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,UAAU,CAAa;IAE/B,YAAY,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,EAmCtG;IAED,aAAa,IAAI,UAAU,CAE1B;CACD","sourcesContent":["import type { ThinkingLevel } from \"@mariozechner/pi-agent\";\nimport { type Component, Container, type SelectItem, SelectList } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\n\n/**\n * Dynamic border component that adjusts to viewport width\n */\nclass DynamicBorder implements Component {\n\trender(width: number): string[] {\n\t\treturn [chalk.blue(\"─\".repeat(Math.max(1, width)))];\n\t}\n}\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(currentLevel: ThinkingLevel, onSelect: (level: ThinkingLevel) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = [\n\t\t\t{ value: \"off\", label: \"off\", description: \"No reasoning\" },\n\t\t\t{ value: \"minimal\", label: \"minimal\", description: \"Very brief reasoning (~1k tokens)\" },\n\t\t\t{ value: \"low\", label: \"low\", description: \"Light reasoning (~2k tokens)\" },\n\t\t\t{ value: \"medium\", label: \"medium\", description: \"Moderate reasoning (~8k tokens)\" },\n\t\t\t{ value: \"high\", label: \"high\", description: \"Deep reasoning (~16k tokens)\" },\n\t\t];\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(thinkingLevels, 5);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Container, SelectList } from "@mariozechner/pi-tui";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
/**
|
|
4
|
+
* Dynamic border component that adjusts to viewport width
|
|
5
|
+
*/
|
|
6
|
+
class DynamicBorder {
|
|
7
|
+
render(width) {
|
|
8
|
+
return [chalk.blue("─".repeat(Math.max(1, width)))];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Component that renders a thinking level selector with borders
|
|
13
|
+
*/
|
|
14
|
+
export class ThinkingSelectorComponent extends Container {
|
|
15
|
+
selectList;
|
|
16
|
+
constructor(currentLevel, onSelect, onCancel) {
|
|
17
|
+
super();
|
|
18
|
+
const thinkingLevels = [
|
|
19
|
+
{ value: "off", label: "off", description: "No reasoning" },
|
|
20
|
+
{ value: "minimal", label: "minimal", description: "Very brief reasoning (~1k tokens)" },
|
|
21
|
+
{ value: "low", label: "low", description: "Light reasoning (~2k tokens)" },
|
|
22
|
+
{ value: "medium", label: "medium", description: "Moderate reasoning (~8k tokens)" },
|
|
23
|
+
{ value: "high", label: "high", description: "Deep reasoning (~16k tokens)" },
|
|
24
|
+
];
|
|
25
|
+
// Add top border
|
|
26
|
+
this.addChild(new DynamicBorder());
|
|
27
|
+
// Create selector
|
|
28
|
+
this.selectList = new SelectList(thinkingLevels, 5);
|
|
29
|
+
// Preselect current level
|
|
30
|
+
const currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);
|
|
31
|
+
if (currentIndex !== -1) {
|
|
32
|
+
this.selectList.setSelectedIndex(currentIndex);
|
|
33
|
+
}
|
|
34
|
+
this.selectList.onSelect = (item) => {
|
|
35
|
+
onSelect(item.value);
|
|
36
|
+
};
|
|
37
|
+
this.selectList.onCancel = () => {
|
|
38
|
+
onCancel();
|
|
39
|
+
};
|
|
40
|
+
this.addChild(this.selectList);
|
|
41
|
+
// Add bottom border
|
|
42
|
+
this.addChild(new DynamicBorder());
|
|
43
|
+
}
|
|
44
|
+
getSelectList() {
|
|
45
|
+
return this.selectList;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=thinking-selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking-selector.js","sourceRoot":"","sources":["../../src/tui/thinking-selector.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,SAAS,EAAmB,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC9F,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;GAEG;AACH,MAAM,aAAa;IAClB,MAAM,CAAC,KAAa,EAAY;QAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAAA,CACpD;CACD;AAED;;GAEG;AACH,MAAM,OAAO,yBAA0B,SAAQ,SAAS;IAC/C,UAAU,CAAa;IAE/B,YAAY,YAA2B,EAAE,QAAwC,EAAE,QAAoB,EAAE;QACxG,KAAK,EAAE,CAAC;QAER,MAAM,cAAc,GAAiB;YACpC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE;YAC3D,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,mCAAmC,EAAE;YACxF,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,8BAA8B,EAAE;YAC3E,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,iCAAiC,EAAE;YACpF,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,8BAA8B,EAAE;SAC7E,CAAC;QAEF,iBAAiB;QACjB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAEnC,kBAAkB;QAClB,IAAI,CAAC,UAAU,GAAG,IAAI,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;QAEpD,0BAA0B;QAC1B,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC;QACrF,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CAAC,KAAsB,CAAC,CAAC;QAAA,CACtC,CAAC;QAEF,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YAChC,QAAQ,EAAE,CAAC;QAAA,CACX,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/B,oBAAoB;QACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;IAAA,CACnC;IAED,aAAa,GAAe;QAC3B,OAAO,IAAI,CAAC,UAAU,CAAC;IAAA,CACvB;CACD","sourcesContent":["import type { ThinkingLevel } from \"@mariozechner/pi-agent\";\nimport { type Component, Container, type SelectItem, SelectList } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\n\n/**\n * Dynamic border component that adjusts to viewport width\n */\nclass DynamicBorder implements Component {\n\trender(width: number): string[] {\n\t\treturn [chalk.blue(\"─\".repeat(Math.max(1, width)))];\n\t}\n}\n\n/**\n * Component that renders a thinking level selector with borders\n */\nexport class ThinkingSelectorComponent extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(currentLevel: ThinkingLevel, onSelect: (level: ThinkingLevel) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst thinkingLevels: SelectItem[] = [\n\t\t\t{ value: \"off\", label: \"off\", description: \"No reasoning\" },\n\t\t\t{ value: \"minimal\", label: \"minimal\", description: \"Very brief reasoning (~1k tokens)\" },\n\t\t\t{ value: \"low\", label: \"low\", description: \"Light reasoning (~2k tokens)\" },\n\t\t\t{ value: \"medium\", label: \"medium\", description: \"Moderate reasoning (~8k tokens)\" },\n\t\t\t{ value: \"high\", label: \"high\", description: \"Deep reasoning (~16k tokens)\" },\n\t\t];\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Create selector\n\t\tthis.selectList = new SelectList(thinkingLevels, 5);\n\n\t\t// Preselect current level\n\t\tconst currentIndex = thinkingLevels.findIndex((item) => item.value === currentLevel);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value as ThinkingLevel);\n\t\t};\n\n\t\tthis.selectList.onCancel = () => {\n\t\t\tonCancel();\n\t\t};\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSelectList(): SelectList {\n\t\treturn this.selectList;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Container } from "@mariozechner/pi-tui";
|
|
2
|
+
/**
|
|
3
|
+
* Component that renders a tool call with its result (updateable)
|
|
4
|
+
*/
|
|
5
|
+
export declare class ToolExecutionComponent extends Container {
|
|
6
|
+
private contentText;
|
|
7
|
+
private toolName;
|
|
8
|
+
private args;
|
|
9
|
+
private result?;
|
|
10
|
+
constructor(toolName: string, args: any);
|
|
11
|
+
updateArgs(args: any): void;
|
|
12
|
+
updateResult(result: {
|
|
13
|
+
content: Array<{
|
|
14
|
+
type: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
data?: string;
|
|
17
|
+
mimeType?: string;
|
|
18
|
+
}>;
|
|
19
|
+
details?: any;
|
|
20
|
+
isError: boolean;
|
|
21
|
+
}): void;
|
|
22
|
+
private updateDisplay;
|
|
23
|
+
private getTextOutput;
|
|
24
|
+
private formatToolExecution;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=tool-execution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-execution.d.ts","sourceRoot":"","sources":["../../src/tui/tool-execution.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAwH/D;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,MAAM,CAAC,CAIb;IAEF,YAAY,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAStC;IAED,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAG1B;IAED,YAAY,CAAC,MAAM,EAAE;QACpB,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClF,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;KACjB,GAAG,IAAI,CAGP;IAED,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,mBAAmB;CA8F3B","sourcesContent":["import * as os from \"node:os\";\nimport { Container, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport * as Diff from \"diff\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Generate a unified diff with line numbers and context\n */\nfunction generateDiff(oldStr: string, newStr: string): string {\n\tconst parts = Diff.diffLines(oldStr, newStr);\n\tconst output: string[] = [];\n\n\t// Calculate max line number for padding\n\tconst oldLines = oldStr.split(\"\\n\");\n\tconst newLines = newStr.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tconst CONTEXT_LINES = 2; // Show 2 lines of context around changes\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.green(`${lineNum} ${line}`));\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.red(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst isFirstPart = i === 0;\n\t\t\tconst isLastPart = i === parts.length - 1;\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!isFirstPart && !lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - CONTEXT_LINES);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - CONTEXT_LINES;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, CONTEXT_LINES);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.dim(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\t// Update line numbers for skipped lines\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.addChild(new Spacer(1));\n\t\t// Content with colored background and padding\n\t\tthis.contentText = new Text(\"\", 1, 1, { r: 40, g: 40, b: 50 });\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(result: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: any;\n\t\tisError: boolean;\n\t}): void {\n\t\tthis.result = result;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgColor = this.result\n\t\t\t? this.result.isError\n\t\t\t\t? { r: 60, g: 40, b: 40 }\n\t\t\t\t: { r: 40, g: 50, b: 40 }\n\t\t\t: { r: 40, g: 40, b: 50 };\n\n\t\tthis.contentText.setCustomBgRgb(bgColor);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\t// Extract text from content blocks\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\tlet output = textBlocks.map((c: any) => c.text).join(\"\\n\");\n\n\t\t// Add indicator for images\n\t\tif (imageBlocks.length > 0) {\n\t\t\tconst imageIndicators = imageBlocks.map((img: any) => `[Image: ${img.mimeType}]`).join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = chalk.bold(`$ ${command || chalk.dim(\"...\")}`);\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show output without code fences - more minimal\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = 5;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"read\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext = chalk.bold(\"write\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"edit\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\t// Show diff if available\n\t\t\tif (this.result?.details?.diff) {\n\t\t\t\t// Parse the diff string and apply colors\n\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\treturn chalk.green(line);\n\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\treturn chalk.red(line);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn chalk.dim(line);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = chalk.bold(this.toolName);\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import * as Diff from "diff";
|
|
5
|
+
/**
|
|
6
|
+
* Convert absolute path to tilde notation if it's in home directory
|
|
7
|
+
*/
|
|
8
|
+
function shortenPath(path) {
|
|
9
|
+
const home = os.homedir();
|
|
10
|
+
if (path.startsWith(home)) {
|
|
11
|
+
return "~" + path.slice(home.length);
|
|
12
|
+
}
|
|
13
|
+
return path;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Replace tabs with spaces for consistent rendering
|
|
17
|
+
*/
|
|
18
|
+
function replaceTabs(text) {
|
|
19
|
+
return text.replace(/\t/g, " ");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate a unified diff with line numbers and context
|
|
23
|
+
*/
|
|
24
|
+
function generateDiff(oldStr, newStr) {
|
|
25
|
+
const parts = Diff.diffLines(oldStr, newStr);
|
|
26
|
+
const output = [];
|
|
27
|
+
// Calculate max line number for padding
|
|
28
|
+
const oldLines = oldStr.split("\n");
|
|
29
|
+
const newLines = newStr.split("\n");
|
|
30
|
+
const maxLineNum = Math.max(oldLines.length, newLines.length);
|
|
31
|
+
const lineNumWidth = String(maxLineNum).length;
|
|
32
|
+
const CONTEXT_LINES = 2; // Show 2 lines of context around changes
|
|
33
|
+
let oldLineNum = 1;
|
|
34
|
+
let newLineNum = 1;
|
|
35
|
+
let lastWasChange = false;
|
|
36
|
+
for (let i = 0; i < parts.length; i++) {
|
|
37
|
+
const part = parts[i];
|
|
38
|
+
const raw = part.value.split("\n");
|
|
39
|
+
if (raw[raw.length - 1] === "") {
|
|
40
|
+
raw.pop();
|
|
41
|
+
}
|
|
42
|
+
if (part.added || part.removed) {
|
|
43
|
+
// Show the change
|
|
44
|
+
for (const line of raw) {
|
|
45
|
+
if (part.added) {
|
|
46
|
+
const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
|
|
47
|
+
output.push(chalk.green(`${lineNum} ${line}`));
|
|
48
|
+
newLineNum++;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// removed
|
|
52
|
+
const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
|
|
53
|
+
output.push(chalk.red(`${lineNum} ${line}`));
|
|
54
|
+
oldLineNum++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
lastWasChange = true;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Context lines - only show a few before/after changes
|
|
61
|
+
const isFirstPart = i === 0;
|
|
62
|
+
const isLastPart = i === parts.length - 1;
|
|
63
|
+
const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
|
|
64
|
+
if (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {
|
|
65
|
+
// Show context
|
|
66
|
+
let linesToShow = raw;
|
|
67
|
+
let skipStart = 0;
|
|
68
|
+
let skipEnd = 0;
|
|
69
|
+
if (!isFirstPart && !lastWasChange) {
|
|
70
|
+
// Show only last N lines as leading context
|
|
71
|
+
skipStart = Math.max(0, raw.length - CONTEXT_LINES);
|
|
72
|
+
linesToShow = raw.slice(skipStart);
|
|
73
|
+
}
|
|
74
|
+
if (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {
|
|
75
|
+
// Show only first N lines as trailing context
|
|
76
|
+
skipEnd = linesToShow.length - CONTEXT_LINES;
|
|
77
|
+
linesToShow = linesToShow.slice(0, CONTEXT_LINES);
|
|
78
|
+
}
|
|
79
|
+
// Add ellipsis if we skipped lines at start
|
|
80
|
+
if (skipStart > 0) {
|
|
81
|
+
output.push(chalk.dim(`${"".padStart(lineNumWidth, " ")} ...`));
|
|
82
|
+
}
|
|
83
|
+
for (const line of linesToShow) {
|
|
84
|
+
const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
|
|
85
|
+
output.push(chalk.dim(`${lineNum} ${line}`));
|
|
86
|
+
oldLineNum++;
|
|
87
|
+
newLineNum++;
|
|
88
|
+
}
|
|
89
|
+
// Add ellipsis if we skipped lines at end
|
|
90
|
+
if (skipEnd > 0) {
|
|
91
|
+
output.push(chalk.dim(`${"".padStart(lineNumWidth, " ")} ...`));
|
|
92
|
+
}
|
|
93
|
+
// Update line numbers for skipped lines
|
|
94
|
+
oldLineNum += skipStart + skipEnd;
|
|
95
|
+
newLineNum += skipStart + skipEnd;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Skip these context lines entirely
|
|
99
|
+
oldLineNum += raw.length;
|
|
100
|
+
newLineNum += raw.length;
|
|
101
|
+
}
|
|
102
|
+
lastWasChange = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return output.join("\n");
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Component that renders a tool call with its result (updateable)
|
|
109
|
+
*/
|
|
110
|
+
export class ToolExecutionComponent extends Container {
|
|
111
|
+
contentText;
|
|
112
|
+
toolName;
|
|
113
|
+
args;
|
|
114
|
+
result;
|
|
115
|
+
constructor(toolName, args) {
|
|
116
|
+
super();
|
|
117
|
+
this.toolName = toolName;
|
|
118
|
+
this.args = args;
|
|
119
|
+
this.addChild(new Spacer(1));
|
|
120
|
+
// Content with colored background and padding
|
|
121
|
+
this.contentText = new Text("", 1, 1, { r: 40, g: 40, b: 50 });
|
|
122
|
+
this.addChild(this.contentText);
|
|
123
|
+
this.updateDisplay();
|
|
124
|
+
}
|
|
125
|
+
updateArgs(args) {
|
|
126
|
+
this.args = args;
|
|
127
|
+
this.updateDisplay();
|
|
128
|
+
}
|
|
129
|
+
updateResult(result) {
|
|
130
|
+
this.result = result;
|
|
131
|
+
this.updateDisplay();
|
|
132
|
+
}
|
|
133
|
+
updateDisplay() {
|
|
134
|
+
const bgColor = this.result
|
|
135
|
+
? this.result.isError
|
|
136
|
+
? { r: 60, g: 40, b: 40 }
|
|
137
|
+
: { r: 40, g: 50, b: 40 }
|
|
138
|
+
: { r: 40, g: 40, b: 50 };
|
|
139
|
+
this.contentText.setCustomBgRgb(bgColor);
|
|
140
|
+
this.contentText.setText(this.formatToolExecution());
|
|
141
|
+
}
|
|
142
|
+
getTextOutput() {
|
|
143
|
+
if (!this.result)
|
|
144
|
+
return "";
|
|
145
|
+
// Extract text from content blocks
|
|
146
|
+
const textBlocks = this.result.content?.filter((c) => c.type === "text") || [];
|
|
147
|
+
const imageBlocks = this.result.content?.filter((c) => c.type === "image") || [];
|
|
148
|
+
let output = textBlocks.map((c) => c.text).join("\n");
|
|
149
|
+
// Add indicator for images
|
|
150
|
+
if (imageBlocks.length > 0) {
|
|
151
|
+
const imageIndicators = imageBlocks.map((img) => `[Image: ${img.mimeType}]`).join("\n");
|
|
152
|
+
output = output ? `${output}\n${imageIndicators}` : imageIndicators;
|
|
153
|
+
}
|
|
154
|
+
return output;
|
|
155
|
+
}
|
|
156
|
+
formatToolExecution() {
|
|
157
|
+
let text = "";
|
|
158
|
+
// Format based on tool type
|
|
159
|
+
if (this.toolName === "bash") {
|
|
160
|
+
const command = this.args?.command || "";
|
|
161
|
+
text = chalk.bold(`$ ${command || chalk.dim("...")}`);
|
|
162
|
+
if (this.result) {
|
|
163
|
+
// Show output without code fences - more minimal
|
|
164
|
+
const output = this.getTextOutput().trim();
|
|
165
|
+
if (output) {
|
|
166
|
+
const lines = output.split("\n");
|
|
167
|
+
const maxLines = 5;
|
|
168
|
+
const displayLines = lines.slice(0, maxLines);
|
|
169
|
+
const remaining = lines.length - maxLines;
|
|
170
|
+
text += "\n\n" + displayLines.map((line) => chalk.dim(line)).join("\n");
|
|
171
|
+
if (remaining > 0) {
|
|
172
|
+
text += chalk.dim(`\n... (${remaining} more lines)`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (this.toolName === "read") {
|
|
178
|
+
const path = shortenPath(this.args?.file_path || this.args?.path || "");
|
|
179
|
+
text = chalk.bold("read") + " " + (path ? chalk.cyan(path) : chalk.dim("..."));
|
|
180
|
+
if (this.result) {
|
|
181
|
+
const output = this.getTextOutput();
|
|
182
|
+
const lines = output.split("\n");
|
|
183
|
+
const maxLines = 10;
|
|
184
|
+
const displayLines = lines.slice(0, maxLines);
|
|
185
|
+
const remaining = lines.length - maxLines;
|
|
186
|
+
text += "\n\n" + displayLines.map((line) => chalk.dim(replaceTabs(line))).join("\n");
|
|
187
|
+
if (remaining > 0) {
|
|
188
|
+
text += chalk.dim(`\n... (${remaining} more lines)`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (this.toolName === "write") {
|
|
193
|
+
const path = shortenPath(this.args?.file_path || this.args?.path || "");
|
|
194
|
+
const fileContent = this.args?.content || "";
|
|
195
|
+
const lines = fileContent ? fileContent.split("\n") : [];
|
|
196
|
+
const totalLines = lines.length;
|
|
197
|
+
text = chalk.bold("write") + " " + (path ? chalk.cyan(path) : chalk.dim("..."));
|
|
198
|
+
if (totalLines > 10) {
|
|
199
|
+
text += ` (${totalLines} lines)`;
|
|
200
|
+
}
|
|
201
|
+
// Show first 10 lines of content if available
|
|
202
|
+
if (fileContent) {
|
|
203
|
+
const maxLines = 10;
|
|
204
|
+
const displayLines = lines.slice(0, maxLines);
|
|
205
|
+
const remaining = lines.length - maxLines;
|
|
206
|
+
text += "\n\n" + displayLines.map((line) => chalk.dim(replaceTabs(line))).join("\n");
|
|
207
|
+
if (remaining > 0) {
|
|
208
|
+
text += chalk.dim(`\n... (${remaining} more lines)`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else if (this.toolName === "edit") {
|
|
213
|
+
const path = shortenPath(this.args?.file_path || this.args?.path || "");
|
|
214
|
+
text = chalk.bold("edit") + " " + (path ? chalk.cyan(path) : chalk.dim("..."));
|
|
215
|
+
// Show diff if available
|
|
216
|
+
if (this.result?.details?.diff) {
|
|
217
|
+
// Parse the diff string and apply colors
|
|
218
|
+
const diffLines = this.result.details.diff.split("\n");
|
|
219
|
+
const coloredLines = diffLines.map((line) => {
|
|
220
|
+
if (line.startsWith("+")) {
|
|
221
|
+
return chalk.green(line);
|
|
222
|
+
}
|
|
223
|
+
else if (line.startsWith("-")) {
|
|
224
|
+
return chalk.red(line);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
return chalk.dim(line);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
text += "\n\n" + coloredLines.join("\n");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Generic tool
|
|
235
|
+
text = chalk.bold(this.toolName);
|
|
236
|
+
const content = JSON.stringify(this.args, null, 2);
|
|
237
|
+
text += "\n\n" + content;
|
|
238
|
+
const output = this.getTextOutput();
|
|
239
|
+
if (output) {
|
|
240
|
+
text += "\n" + output;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return text;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=tool-execution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-execution.js","sourceRoot":"","sources":["../../src/tui/tool-execution.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { Container, Spacer, Text } from \"@mariozechner/pi-tui\";\nimport chalk from \"chalk\";\nimport * as Diff from \"diff\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\n/**\n * Generate a unified diff with line numbers and context\n */\nfunction generateDiff(oldStr: string, newStr: string): string {\n\tconst parts = Diff.diffLines(oldStr, newStr);\n\tconst output: string[] = [];\n\n\t// Calculate max line number for padding\n\tconst oldLines = oldStr.split(\"\\n\");\n\tconst newLines = newStr.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tconst CONTEXT_LINES = 2; // Show 2 lines of context around changes\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.green(`${lineNum} ${line}`));\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.red(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst isFirstPart = i === 0;\n\t\t\tconst isLastPart = i === parts.length - 1;\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange || isFirstPart || isLastPart) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!isFirstPart && !lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - CONTEXT_LINES);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!isLastPart && !nextPartIsChange && linesToShow.length > CONTEXT_LINES) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - CONTEXT_LINES;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, CONTEXT_LINES);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(chalk.dim(`${lineNum} ${line}`));\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(chalk.dim(`${\"\".padStart(lineNumWidth, \" \")} ...`));\n\t\t\t\t}\n\n\t\t\t\t// Update line numbers for skipped lines\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.addChild(new Spacer(1));\n\t\t// Content with colored background and padding\n\t\tthis.contentText = new Text(\"\", 1, 1, { r: 40, g: 40, b: 50 });\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(result: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: any;\n\t\tisError: boolean;\n\t}): void {\n\t\tthis.result = result;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgColor = this.result\n\t\t\t? this.result.isError\n\t\t\t\t? { r: 60, g: 40, b: 40 }\n\t\t\t\t: { r: 40, g: 50, b: 40 }\n\t\t\t: { r: 40, g: 40, b: 50 };\n\n\t\tthis.contentText.setCustomBgRgb(bgColor);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\t// Extract text from content blocks\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\tlet output = textBlocks.map((c: any) => c.text).join(\"\\n\");\n\n\t\t// Add indicator for images\n\t\tif (imageBlocks.length > 0) {\n\t\t\tconst imageIndicators = imageBlocks.map((img: any) => `[Image: ${img.mimeType}]`).join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = chalk.bold(`$ ${command || chalk.dim(\"...\")}`);\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show output without code fences - more minimal\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = 5;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"read\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext = chalk.bold(\"write\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => chalk.dim(replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += chalk.dim(`\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext = chalk.bold(\"edit\") + \" \" + (path ? chalk.cyan(path) : chalk.dim(\"...\"));\n\n\t\t\t// Show diff if available\n\t\t\tif (this.result?.details?.diff) {\n\t\t\t\t// Parse the diff string and apply colors\n\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\treturn chalk.green(line);\n\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\treturn chalk.red(line);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn chalk.dim(line);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = chalk.bold(this.toolName);\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|