@nedleeds/pi-compact-ui 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 +35 -4
- package/examples/compact-tools.json +1 -0
- package/extensions/compact-tools.ts +75 -17
- package/package.json +2 -2
- package/schemas/compact-tools.schema.json +10 -0
package/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# pi-compact-ui
|
|
2
2
|
|
|
3
|
-
Compact, expandable rendering for
|
|
3
|
+
Compact, expandable rendering for all of Pi's built-in tools, bundled with a polished GitHub Dark theme.
|
|
4
4
|
|
|
5
5
|
Designed for a focused, low-noise terminal workflow.
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
## Demo
|
|
10
10
|
|
|
11
|
+
**Spinner and status** – track active execution and see duration-aware completion indicators.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
11
15
|
**Keyboard expansion** – press `Ctrl+O` to cycle through detail levels.
|
|
12
16
|
|
|
13
17
|

|
|
@@ -18,7 +22,8 @@ Designed for a focused, low-noise terminal workflow.
|
|
|
18
22
|
|
|
19
23
|
## Features
|
|
20
24
|
|
|
21
|
-
- Compact one-line
|
|
25
|
+
- Compact one-line rendering for configurable built-in tools
|
|
26
|
+
- Supports `read`, `write`, `edit`, `bash`, `powershell`, `grep`, `find`, and `ls`
|
|
22
27
|
- Click or `Ctrl+O` to cycle through available detail levels
|
|
23
28
|
- Skips argument and output levels that contain no additional information
|
|
24
29
|
- Animated, configurable tool spinner
|
|
@@ -28,6 +33,21 @@ Designed for a focused, low-noise terminal workflow.
|
|
|
28
33
|
- GitHub Dark theme with distinct tool, output, success, and error colors
|
|
29
34
|
- No Nerd Font requirement
|
|
30
35
|
|
|
36
|
+
## Supported tools
|
|
37
|
+
|
|
38
|
+
| Tool | Purpose | Compact by default |
|
|
39
|
+
| --- | --- | :---: |
|
|
40
|
+
| `read` | Read files and images | Yes |
|
|
41
|
+
| `write` | Create or overwrite files | Yes |
|
|
42
|
+
| `edit` | Apply exact text replacements with clean diffs | Yes |
|
|
43
|
+
| `bash` | Run shell commands | Yes |
|
|
44
|
+
| `grep` | Search file contents | Opt-in |
|
|
45
|
+
| `find` | Find files by glob pattern | Opt-in |
|
|
46
|
+
| `ls` | List directory contents | Opt-in |
|
|
47
|
+
| `powershell` | Run PowerShell commands | Opt-in |
|
|
48
|
+
|
|
49
|
+
Enable opt-in tools with the [`tools` configuration](#configuration). PowerShell is intended for Windows; the other seven tools are demonstrated above on macOS.
|
|
50
|
+
|
|
31
51
|
## Requirements
|
|
32
52
|
|
|
33
53
|
- pi `0.85.1` or newer is recommended
|
|
@@ -85,6 +105,7 @@ Default configuration:
|
|
|
85
105
|
```json
|
|
86
106
|
{
|
|
87
107
|
"$schema": "https://raw.githubusercontent.com/nedleeds/pi-compact-ui/main/schemas/compact-tools.schema.json",
|
|
108
|
+
"tools": ["read", "write", "edit", "bash"],
|
|
88
109
|
"previewLines": 10,
|
|
89
110
|
"spinner": {
|
|
90
111
|
"frames": ["◐", "◓", "◑", "◒"],
|
|
@@ -99,6 +120,16 @@ Default configuration:
|
|
|
99
120
|
}
|
|
100
121
|
```
|
|
101
122
|
|
|
123
|
+
`tools` selects which built-in definitions receive compact rendering. Omitted tools retain Pi's default renderer. The array replaces, rather than extends, the previous configuration layer. To enable every Unix-compatible tool:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"tools": ["read", "write", "edit", "bash", "grep", "find", "ls"]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`powershell` is also supported and can be selected explicitly on Windows. Unsupported names are ignored with a warning.
|
|
132
|
+
|
|
102
133
|
`durationIndicators` must use ascending `underMs` values. The final entry must omit `underMs` and acts as the fallback.
|
|
103
134
|
|
|
104
135
|
## Expansion levels
|
|
@@ -119,7 +150,7 @@ summary → diff → summary
|
|
|
119
150
|
|
|
120
151
|
## Compatibility and limitations
|
|
121
152
|
|
|
122
|
-
- The package overrides
|
|
153
|
+
- The package overrides only the built-in definitions selected by `tools`, while preserving their execution behavior and metadata.
|
|
123
154
|
- Another extension overriding the same tool names may conflict depending on extension load order.
|
|
124
155
|
- Tools registered by other extensions are not modified.
|
|
125
156
|
- Duration is measured from the first execution render and is intended as a UI estimate.
|
|
@@ -15,6 +15,10 @@ import {
|
|
|
15
15
|
CONFIG_DIR_NAME,
|
|
16
16
|
createBashToolDefinition,
|
|
17
17
|
createEditToolDefinition,
|
|
18
|
+
createFindToolDefinition,
|
|
19
|
+
createGrepToolDefinition,
|
|
20
|
+
createLsToolDefinition,
|
|
21
|
+
createPowerShellToolDefinition,
|
|
18
22
|
createReadToolDefinition,
|
|
19
23
|
createWriteToolDefinition,
|
|
20
24
|
getAgentDir,
|
|
@@ -24,6 +28,10 @@ import { Container, Text, visibleWidth } from "@earendil-works/pi-tui";
|
|
|
24
28
|
|
|
25
29
|
const MAX_LEVEL = 3;
|
|
26
30
|
const CONFIG_FILE = "compact-tools.json";
|
|
31
|
+
const SUPPORTED_TOOLS = ["read", "write", "edit", "bash", "powershell", "grep", "find", "ls"] as const;
|
|
32
|
+
const SUPPORTED_TOOL_SET = new Set<string>(SUPPORTED_TOOLS);
|
|
33
|
+
|
|
34
|
+
type CompactToolName = (typeof SUPPORTED_TOOLS)[number];
|
|
27
35
|
|
|
28
36
|
export interface DurationIndicatorConfig {
|
|
29
37
|
underMs?: number;
|
|
@@ -31,6 +39,7 @@ export interface DurationIndicatorConfig {
|
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
export interface CompactToolsConfig {
|
|
42
|
+
tools: CompactToolName[];
|
|
34
43
|
previewLines: number;
|
|
35
44
|
spinner: {
|
|
36
45
|
frames: string[];
|
|
@@ -40,6 +49,7 @@ export interface CompactToolsConfig {
|
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
export const DEFAULT_CONFIG: CompactToolsConfig = {
|
|
52
|
+
tools: ["read", "write", "edit", "bash"],
|
|
43
53
|
previewLines: 10,
|
|
44
54
|
spinner: {
|
|
45
55
|
frames: ["◐", "◓", "◑", "◒"],
|
|
@@ -49,11 +59,12 @@ export const DEFAULT_CONFIG: CompactToolsConfig = {
|
|
|
49
59
|
{ underMs: 1_000, icon: "⚡️" },
|
|
50
60
|
{ underMs: 10_000, icon: "🚀" },
|
|
51
61
|
{ underMs: 30_000, icon: "🔥" },
|
|
52
|
-
{ icon: "
|
|
62
|
+
{ icon: "⏳" },
|
|
53
63
|
],
|
|
54
64
|
};
|
|
55
65
|
|
|
56
66
|
let config = DEFAULT_CONFIG;
|
|
67
|
+
const registeredCompactTools = new Set<CompactToolName>();
|
|
57
68
|
|
|
58
69
|
interface RowState {
|
|
59
70
|
level?: number;
|
|
@@ -92,6 +103,17 @@ function isNonEmptyStringArray(value: unknown): value is string[] {
|
|
|
92
103
|
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0);
|
|
93
104
|
}
|
|
94
105
|
|
|
106
|
+
function parseTools(value: unknown, path: string): CompactToolName[] | undefined {
|
|
107
|
+
if (!Array.isArray(value)) {
|
|
108
|
+
warnConfig(path, "tools must be an array; using previous values");
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const valid = value.filter((item): item is CompactToolName => typeof item === "string" && SUPPORTED_TOOL_SET.has(item));
|
|
112
|
+
const invalid = value.filter((item) => typeof item !== "string" || !SUPPORTED_TOOL_SET.has(item));
|
|
113
|
+
if (invalid.length > 0) warnConfig(path, `ignoring unsupported tools: ${invalid.map(String).join(", ")}`);
|
|
114
|
+
return [...new Set(valid)];
|
|
115
|
+
}
|
|
116
|
+
|
|
95
117
|
function parseDurationIndicators(value: unknown): DurationIndicatorConfig[] | undefined {
|
|
96
118
|
if (!Array.isArray(value) || value.length === 0) return undefined;
|
|
97
119
|
const rules: DurationIndicatorConfig[] = [];
|
|
@@ -114,6 +136,7 @@ function mergeConfig(base: CompactToolsConfig, value: unknown, path: string): Co
|
|
|
114
136
|
warnConfig(path, "expected a JSON object; using previous values");
|
|
115
137
|
return base;
|
|
116
138
|
}
|
|
139
|
+
const tools = value.tools === undefined ? base.tools : (parseTools(value.tools, path) ?? base.tools);
|
|
117
140
|
const previewValue = value.previewLines;
|
|
118
141
|
const validPreviewLines = isIntegerInRange(previewValue, 1, 1_000);
|
|
119
142
|
const previewLines = validPreviewLines ? previewValue : base.previewLines;
|
|
@@ -125,7 +148,7 @@ function mergeConfig(base: CompactToolsConfig, value: unknown, path: string): Co
|
|
|
125
148
|
if (value.durationIndicators !== undefined && durationIndicators === base.durationIndicators) {
|
|
126
149
|
warnConfig(path, "invalid durationIndicators; using previous values");
|
|
127
150
|
}
|
|
128
|
-
return { previewLines, spinner, durationIndicators };
|
|
151
|
+
return { tools, previewLines, spinner, durationIndicators };
|
|
129
152
|
}
|
|
130
153
|
|
|
131
154
|
function parseSpinnerConfig(
|
|
@@ -339,10 +362,19 @@ function getPathArg(args: ToolArgs): string {
|
|
|
339
362
|
return typeof value === "string" ? firstLine(value) : "";
|
|
340
363
|
}
|
|
341
364
|
|
|
365
|
+
function getCallDetails(name: string, args: ToolArgs): string {
|
|
366
|
+
const path = getPathArg(args) || (name === "grep" || name === "find" || name === "ls" ? "." : "");
|
|
367
|
+
const pattern = typeof args.pattern === "string" ? firstLine(args.pattern) : "…";
|
|
368
|
+
if (name === "grep") return `/${pattern}/ in ${path}`;
|
|
369
|
+
if (name === "find") return `${pattern} in ${path}`;
|
|
370
|
+
return path;
|
|
371
|
+
}
|
|
372
|
+
|
|
342
373
|
function getFileArgumentDetails(name: string, args: ToolArgs): ToolArgs {
|
|
343
374
|
if (name === "edit") return {};
|
|
344
375
|
const omitted = new Set(["path", "file_path"]);
|
|
345
376
|
if (name === "write") omitted.add("content");
|
|
377
|
+
if (name === "grep" || name === "find") omitted.add("pattern");
|
|
346
378
|
return Object.fromEntries(Object.entries(args).filter(([key, value]) => !omitted.has(key) && value !== undefined));
|
|
347
379
|
}
|
|
348
380
|
|
|
@@ -374,7 +406,9 @@ function callOriginalEditResult(
|
|
|
374
406
|
}
|
|
375
407
|
|
|
376
408
|
function getFileOutput(name: string, args: ToolArgs, result: AgentToolResult<unknown>, isError: boolean): string {
|
|
377
|
-
if (isError || name === "read"
|
|
409
|
+
if (isError || name === "read" || name === "grep" || name === "find" || name === "ls") {
|
|
410
|
+
return getTextResult(result);
|
|
411
|
+
}
|
|
378
412
|
if (name === "write") return String(args.content ?? "");
|
|
379
413
|
return "";
|
|
380
414
|
}
|
|
@@ -388,11 +422,11 @@ function renderFileCall(
|
|
|
388
422
|
const running = ctx.executionStarted && ctx.isPartial;
|
|
389
423
|
const state = syncRow(ctx, running);
|
|
390
424
|
const level = advanceLevel(state, ctx.expanded);
|
|
391
|
-
const
|
|
425
|
+
const callDetails = getCallDetails(definition.name, args);
|
|
392
426
|
const argumentDetails = getFileArgumentDetails(definition.name, args);
|
|
393
427
|
let text = `${renderIndicator(theme, state, running, ctx.isError, ctx.isPartial && !ctx.executionStarted)} `;
|
|
394
428
|
text += theme.fg("toolTitle", theme.bold(definition.name));
|
|
395
|
-
if (
|
|
429
|
+
if (callDetails) text += ` ${theme.fg("toolOutput", callDetails)}`;
|
|
396
430
|
|
|
397
431
|
const container = new Container();
|
|
398
432
|
container.addChild(new Text(text, 1, 0));
|
|
@@ -463,14 +497,19 @@ function registerFileTool(pi: ExtensionAPI, definition: BuiltInDefinition): void
|
|
|
463
497
|
pi.registerTool(tool as ToolDefinition<any, any, RowState>);
|
|
464
498
|
}
|
|
465
499
|
|
|
466
|
-
function
|
|
500
|
+
function renderShellCall(
|
|
501
|
+
name: "bash" | "powershell",
|
|
502
|
+
args: BashToolInput,
|
|
503
|
+
theme: Theme,
|
|
504
|
+
ctx: RenderContext<BashToolInput>,
|
|
505
|
+
): Text {
|
|
467
506
|
const running = ctx.executionStarted && ctx.isPartial;
|
|
468
507
|
const state = syncRow(ctx, running);
|
|
469
508
|
const level = advanceLevel(state, ctx.expanded);
|
|
470
509
|
const command = args.command ?? "";
|
|
471
510
|
const displayedCommand = (level >= 1 ? command : firstLine(command)) || "…";
|
|
472
511
|
let text = `${renderIndicator(theme, state, running, ctx.isError, ctx.isPartial && !ctx.executionStarted)} `;
|
|
473
|
-
text += `${theme.fg("toolTitle", theme.bold(
|
|
512
|
+
text += `${theme.fg("toolTitle", theme.bold(name))} ${theme.fg("toolOutput", displayedCommand)}`;
|
|
474
513
|
if (level >= 1 && args.timeout) text += theme.fg("dim", ` (timeout: ${args.timeout}s)`);
|
|
475
514
|
return new Text(text, 1, 0);
|
|
476
515
|
}
|
|
@@ -496,21 +535,40 @@ function renderBashResult(
|
|
|
496
535
|
return container;
|
|
497
536
|
}
|
|
498
537
|
|
|
499
|
-
function
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
538
|
+
function createBuiltInDefinition(name: CompactToolName, cwd: string): BuiltInDefinition {
|
|
539
|
+
switch (name) {
|
|
540
|
+
case "read": return createReadToolDefinition(cwd);
|
|
541
|
+
case "write": return createWriteToolDefinition(cwd);
|
|
542
|
+
case "edit": return createEditToolDefinition(cwd);
|
|
543
|
+
case "bash": return createBashToolDefinition(cwd);
|
|
544
|
+
case "powershell": return createPowerShellToolDefinition(cwd);
|
|
545
|
+
case "grep": return createGrepToolDefinition(cwd);
|
|
546
|
+
case "find": return createFindToolDefinition(cwd);
|
|
547
|
+
case "ls": return createLsToolDefinition(cwd);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function registerShellTool(pi: ExtensionAPI, definition: BuiltInDefinition): void {
|
|
552
|
+
pi.registerTool({
|
|
553
|
+
...definition,
|
|
503
554
|
renderShell: "self",
|
|
504
|
-
renderCall:
|
|
555
|
+
renderCall: (args: BashToolInput, theme: Theme, ctx: RenderContext<BashToolInput>) =>
|
|
556
|
+
renderShellCall(definition.name as "bash" | "powershell", args, theme, ctx),
|
|
505
557
|
renderResult: renderBashResult,
|
|
506
|
-
});
|
|
558
|
+
} as ToolDefinition<any, BashToolDetails | undefined, RowState>);
|
|
507
559
|
}
|
|
508
560
|
|
|
509
561
|
function registerBuiltInTools(pi: ExtensionAPI, cwd: string): void {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
562
|
+
for (const name of registeredCompactTools) {
|
|
563
|
+
if (!config.tools.includes(name)) pi.registerTool(createBuiltInDefinition(name, cwd));
|
|
564
|
+
}
|
|
565
|
+
registeredCompactTools.clear();
|
|
566
|
+
for (const name of config.tools) {
|
|
567
|
+
const definition = createBuiltInDefinition(name, cwd);
|
|
568
|
+
if (name === "bash" || name === "powershell") registerShellTool(pi, definition);
|
|
569
|
+
else registerFileTool(pi, definition);
|
|
570
|
+
registeredCompactTools.add(name);
|
|
571
|
+
}
|
|
514
572
|
}
|
|
515
573
|
|
|
516
574
|
function applyConfig(pi: ExtensionAPI, cwd: string, projectTrusted: boolean): void {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nedleeds/pi-compact-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Compact, expandable UI for pi's built-in tools, bundled with a polished GitHub Dark theme.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"themes": [
|
|
42
42
|
"./themes/github-dark-pro.json"
|
|
43
43
|
],
|
|
44
|
-
"image": "https://raw.githubusercontent.com/nedleeds/pi-compact-ui/main/assets/
|
|
44
|
+
"image": "https://raw.githubusercontent.com/nedleeds/pi-compact-ui/main/assets/built-in-tool-support.png"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@earendil-works/pi-coding-agent": "*",
|
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
"$schema": {
|
|
9
9
|
"type": "string"
|
|
10
10
|
},
|
|
11
|
+
"tools": {
|
|
12
|
+
"description": "Built-in tools that use compact rendering. Omitted tools keep Pi's default renderer.",
|
|
13
|
+
"type": "array",
|
|
14
|
+
"uniqueItems": true,
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["read", "write", "edit", "bash", "powershell", "grep", "find", "ls"]
|
|
18
|
+
},
|
|
19
|
+
"default": ["read", "write", "edit", "bash"]
|
|
20
|
+
},
|
|
11
21
|
"previewLines": {
|
|
12
22
|
"description": "Number of output lines shown before the full-output expansion level.",
|
|
13
23
|
"type": "integer",
|