@oh-my-pi/pi-coding-agent 3.4.1337 → 3.6.1337
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 +22 -0
- package/package.json +5 -4
- package/src/core/sdk.ts +14 -1
- package/src/core/session-manager.ts +98 -69
- package/src/core/settings-manager.ts +33 -0
- package/src/core/system-prompt.ts +15 -0
- package/src/core/title-generator.ts +28 -6
- package/src/core/tools/index.ts +6 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +297 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +477 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/settings-defs.ts +2 -31
- package/src/modes/interactive/components/settings-selector.ts +0 -1
- package/src/modes/interactive/interactive-mode.ts +24 -296
- package/src/modes/print-mode.ts +34 -0
- package/src/modes/rpc/rpc-mode.ts +8 -7
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InspectorPanel - Detail view for selected extension.
|
|
3
|
+
*
|
|
4
|
+
* Shows name, description, origin, status, and kind-specific preview.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { type Component, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { theme } from "../../theme/theme";
|
|
11
|
+
import type { Extension, ExtensionState } from "./types";
|
|
12
|
+
|
|
13
|
+
export class InspectorPanel implements Component {
|
|
14
|
+
private extension: Extension | null = null;
|
|
15
|
+
|
|
16
|
+
setExtension(extension: Extension | null): void {
|
|
17
|
+
this.extension = extension;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
invalidate(): void {}
|
|
21
|
+
|
|
22
|
+
render(width: number): string[] {
|
|
23
|
+
if (!this.extension) {
|
|
24
|
+
return [theme.fg("muted", "Select an extension"), theme.fg("dim", "to view details")];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ext = this.extension;
|
|
28
|
+
const lines: string[] = [];
|
|
29
|
+
|
|
30
|
+
// Name header
|
|
31
|
+
lines.push(theme.bold(theme.fg("accent", ext.displayName)));
|
|
32
|
+
lines.push("");
|
|
33
|
+
|
|
34
|
+
// Kind badge
|
|
35
|
+
lines.push(theme.fg("muted", "Type: ") + this.getKindBadge(ext.kind));
|
|
36
|
+
lines.push("");
|
|
37
|
+
|
|
38
|
+
// Description (wrapped)
|
|
39
|
+
if (ext.description) {
|
|
40
|
+
const wrapped = wrapTextWithAnsi(ext.description, width - 2);
|
|
41
|
+
for (const line of wrapped) {
|
|
42
|
+
lines.push(truncateToWidth(line, width));
|
|
43
|
+
}
|
|
44
|
+
lines.push("");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Origin
|
|
48
|
+
lines.push(theme.fg("muted", "Origin:"));
|
|
49
|
+
const levelLabel = ext.source.level === "user" ? "User" : ext.source.level === "project" ? "Project" : "Native";
|
|
50
|
+
lines.push(` ${theme.italic(`via ${ext.source.providerName} (${levelLabel})`)}`);
|
|
51
|
+
lines.push(` ${theme.fg("dim", this.shortenPath(ext.path))}`);
|
|
52
|
+
lines.push("");
|
|
53
|
+
|
|
54
|
+
// Status badge
|
|
55
|
+
lines.push(theme.fg("muted", "Status:"));
|
|
56
|
+
lines.push(` ${this.getStatusBadge(ext.state, ext.disabledReason, ext.shadowedBy)}`);
|
|
57
|
+
lines.push("");
|
|
58
|
+
|
|
59
|
+
// Preview section (routed based on kind)
|
|
60
|
+
const previewLines = this.renderPreview(ext, width);
|
|
61
|
+
lines.push(...previewLines);
|
|
62
|
+
|
|
63
|
+
return lines;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private renderPreview(ext: Extension, width: number): string[] {
|
|
67
|
+
const lines: string[] = [];
|
|
68
|
+
let content: string[] = [];
|
|
69
|
+
|
|
70
|
+
switch (ext.kind) {
|
|
71
|
+
case "context-file":
|
|
72
|
+
content = this.renderFilePreview(ext.path, width);
|
|
73
|
+
break;
|
|
74
|
+
case "tool":
|
|
75
|
+
content = this.renderToolArgs(ext.raw, width);
|
|
76
|
+
break;
|
|
77
|
+
case "skill":
|
|
78
|
+
content = this.renderSkillContent(ext.raw, width);
|
|
79
|
+
break;
|
|
80
|
+
case "mcp":
|
|
81
|
+
content = this.renderMcpDetails(ext.raw, width);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
content = this.renderDefaultPreview(ext, width);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (content.length > 0) {
|
|
89
|
+
lines.push(...content);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private renderFilePreview(path: string, width: number): string[] {
|
|
96
|
+
const lines: string[] = [];
|
|
97
|
+
lines.push(theme.fg("muted", "Preview:"));
|
|
98
|
+
lines.push(theme.fg("dim", "─".repeat(Math.min(width - 2, 40))));
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const content = readFileSync(path, "utf-8");
|
|
102
|
+
const fileLines = content.split("\n").slice(0, 20);
|
|
103
|
+
|
|
104
|
+
for (const line of fileLines) {
|
|
105
|
+
const highlighted = this.highlightMarkdown(line);
|
|
106
|
+
lines.push(truncateToWidth(highlighted, width - 2));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (content.split("\n").length > 20) {
|
|
110
|
+
lines.push(theme.fg("dim", "(truncated at line 20)"));
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
lines.push(theme.fg("error", `Failed to read file: ${err instanceof Error ? err.message : String(err)}`));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
lines.push("");
|
|
117
|
+
return lines;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private highlightMarkdown(line: string): string {
|
|
121
|
+
// Basic markdown syntax highlighting
|
|
122
|
+
let highlighted = line;
|
|
123
|
+
|
|
124
|
+
// Headers
|
|
125
|
+
if (/^#{1,6}\s/.test(highlighted)) {
|
|
126
|
+
highlighted = theme.bold(theme.fg("accent", highlighted));
|
|
127
|
+
}
|
|
128
|
+
// Code blocks
|
|
129
|
+
else if (/^```/.test(highlighted)) {
|
|
130
|
+
highlighted = theme.fg("dim", highlighted);
|
|
131
|
+
}
|
|
132
|
+
// Lists
|
|
133
|
+
else if (/^[\s]*[-*+]\s/.test(highlighted)) {
|
|
134
|
+
highlighted = highlighted.replace(/^([\s]*[-*+]\s)/, theme.fg("accent", "$1"));
|
|
135
|
+
}
|
|
136
|
+
// Numbered lists
|
|
137
|
+
else if (/^[\s]*\d+\.\s/.test(highlighted)) {
|
|
138
|
+
highlighted = highlighted.replace(/^([\s]*\d+\.\s)/, theme.fg("accent", "$1"));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return highlighted;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private renderToolArgs(raw: unknown, width: number): string[] {
|
|
145
|
+
const lines: string[] = [];
|
|
146
|
+
lines.push(theme.fg("muted", "Arguments:"));
|
|
147
|
+
lines.push(theme.fg("dim", "─".repeat(Math.min(width - 2, 40))));
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const tool = raw as any;
|
|
151
|
+
const params = tool?.parameters?.properties || tool?.inputSchema?.properties || {};
|
|
152
|
+
|
|
153
|
+
if (Object.keys(params).length === 0) {
|
|
154
|
+
lines.push(theme.fg("dim", " (no arguments)"));
|
|
155
|
+
} else {
|
|
156
|
+
const required = new Set(tool?.parameters?.required || tool?.inputSchema?.required || []);
|
|
157
|
+
|
|
158
|
+
for (const [name, spec] of Object.entries(params)) {
|
|
159
|
+
const param = spec as any;
|
|
160
|
+
const type = param.type || "any";
|
|
161
|
+
const isRequired = required.has(name);
|
|
162
|
+
const defaultVal = param.default !== undefined ? `Default: ${param.default}` : null;
|
|
163
|
+
|
|
164
|
+
const nameCol = theme.fg("accent", name.padEnd(12));
|
|
165
|
+
const typeCol = theme.fg("muted", type.padEnd(10));
|
|
166
|
+
const reqCol = isRequired
|
|
167
|
+
? theme.fg("warning", "Required")
|
|
168
|
+
: defaultVal
|
|
169
|
+
? theme.fg("dim", defaultVal)
|
|
170
|
+
: theme.fg("dim", "Optional");
|
|
171
|
+
|
|
172
|
+
lines.push(` ${nameCol} ${typeCol} ${reqCol}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
lines.push(theme.fg("dim", " (unable to parse tool definition)"));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
lines.push("");
|
|
180
|
+
return lines;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private renderSkillContent(raw: unknown, width: number): string[] {
|
|
184
|
+
const lines: string[] = [];
|
|
185
|
+
lines.push(theme.fg("muted", "Instruction:"));
|
|
186
|
+
lines.push(theme.fg("dim", "─".repeat(Math.min(width - 2, 40))));
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const skill = raw as any;
|
|
190
|
+
const instruction = skill?.prompt || skill?.instruction || skill?.content || "";
|
|
191
|
+
|
|
192
|
+
if (!instruction) {
|
|
193
|
+
lines.push(theme.fg("dim", " (no instruction text)"));
|
|
194
|
+
} else {
|
|
195
|
+
const instructionLines = instruction.split("\n").slice(0, 15);
|
|
196
|
+
for (const line of instructionLines) {
|
|
197
|
+
lines.push(truncateToWidth(line, width - 2));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (instruction.split("\n").length > 15) {
|
|
201
|
+
lines.push(theme.fg("dim", "(truncated at line 15)"));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
lines.push(theme.fg("dim", " (unable to parse skill content)"));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
lines.push("");
|
|
209
|
+
return lines;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private renderMcpDetails(raw: unknown, width: number): string[] {
|
|
213
|
+
const lines: string[] = [];
|
|
214
|
+
lines.push(theme.fg("muted", "Connection:"));
|
|
215
|
+
lines.push(theme.fg("dim", "─".repeat(Math.min(width - 2, 40))));
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const mcp = raw as any;
|
|
219
|
+
const transport = mcp?.transport || mcp?.type || "unknown";
|
|
220
|
+
const command = mcp?.command || mcp?.cmd || "";
|
|
221
|
+
const args = mcp?.args || mcp?.arguments || [];
|
|
222
|
+
|
|
223
|
+
lines.push(` ${theme.fg("muted", "Transport:")} ${theme.fg("accent", transport)}`);
|
|
224
|
+
|
|
225
|
+
if (command) {
|
|
226
|
+
lines.push(` ${theme.fg("muted", "Command:")} ${theme.fg("success", command)}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (Array.isArray(args) && args.length > 0) {
|
|
230
|
+
lines.push(` ${theme.fg("muted", "Args:")} ${theme.fg("dim", args.join(" "))}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Environment variables if present
|
|
234
|
+
if (mcp?.env && typeof mcp.env === "object") {
|
|
235
|
+
const envCount = Object.keys(mcp.env).length;
|
|
236
|
+
if (envCount > 0) {
|
|
237
|
+
lines.push(` ${theme.fg("muted", "Env vars:")} ${theme.fg("dim", `${envCount} defined`)}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch {
|
|
241
|
+
lines.push(theme.fg("dim", " (unable to parse MCP configuration)"));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
lines.push("");
|
|
245
|
+
return lines;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private renderDefaultPreview(ext: Extension, width: number): string[] {
|
|
249
|
+
const lines: string[] = [];
|
|
250
|
+
|
|
251
|
+
// Show trigger pattern if present
|
|
252
|
+
if (ext.trigger) {
|
|
253
|
+
lines.push(theme.fg("muted", "Trigger:"));
|
|
254
|
+
lines.push(theme.fg("dim", "─".repeat(Math.min(width - 2, 40))));
|
|
255
|
+
lines.push(` ${theme.fg("accent", ext.trigger)}`);
|
|
256
|
+
lines.push("");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return lines;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private getKindBadge(kind: string): string {
|
|
263
|
+
const kindColors: Record<string, string> = {
|
|
264
|
+
skill: "accent",
|
|
265
|
+
rule: "success",
|
|
266
|
+
tool: "warning",
|
|
267
|
+
mcp: "accent",
|
|
268
|
+
prompt: "muted",
|
|
269
|
+
hook: "warning",
|
|
270
|
+
"context-file": "dim",
|
|
271
|
+
instruction: "muted",
|
|
272
|
+
"slash-command": "accent",
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const color = kindColors[kind] || "muted";
|
|
276
|
+
return theme.fg(color as any, kind);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private getStatusBadge(state: ExtensionState, reason?: string, shadowedBy?: string): string {
|
|
280
|
+
switch (state) {
|
|
281
|
+
case "active":
|
|
282
|
+
return theme.fg("success", "● Active");
|
|
283
|
+
case "disabled": {
|
|
284
|
+
const reasonText =
|
|
285
|
+
reason === "provider-disabled"
|
|
286
|
+
? "provider disabled"
|
|
287
|
+
: reason === "item-disabled"
|
|
288
|
+
? "manually disabled"
|
|
289
|
+
: "unknown";
|
|
290
|
+
return theme.fg("dim", `○ Disabled (${reasonText})`);
|
|
291
|
+
}
|
|
292
|
+
case "shadowed":
|
|
293
|
+
return theme.fg("warning", `◐ Shadowed${shadowedBy ? ` by ${shadowedBy}` : ""}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private shortenPath(path: string): string {
|
|
298
|
+
const home = homedir();
|
|
299
|
+
if (path.startsWith(home)) {
|
|
300
|
+
return `~${path.slice(home.length)}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// If path is very long, show just the last parts
|
|
304
|
+
if (path.length > 40) {
|
|
305
|
+
const parts = path.split("/");
|
|
306
|
+
if (parts.length > 3) {
|
|
307
|
+
return `.../${parts.slice(-3).join("/")}`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return path;
|
|
312
|
+
}
|
|
313
|
+
}
|