pi-compact-tools 0.3.3 → 0.4.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 +23 -12
- package/examples/compact-tools.json +6 -0
- package/extensions/compact-tools.ts +58 -11
- package/package.json +2 -2
- package/schemas/compact-tools.schema.json +21 -0
- package/scripts/check-resources.mjs +9 -0
package/README.md
CHANGED
|
@@ -12,27 +12,28 @@ The recordings below use the optional [Nerd Font](#nerd-font-preset) preset. **T
|
|
|
12
12
|
|
|
13
13
|
Active, successful, and failed tool calls stay compact while preserving useful status and timing information.
|
|
14
14
|
|
|
15
|
-

|
|
15
|
+

|
|
16
16
|
|
|
17
17
|
### Spinner and duration indicators
|
|
18
18
|
|
|
19
19
|
The spinner animates during execution, then changes to a duration-aware completion icon.
|
|
20
20
|
|
|
21
|
-

|
|
21
|
+

|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### Initially visible edit diffs
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Completed `edit` rows show their full diff initially by default. The per-tool `auto_compact` setting controls which tools start in their compact summary state without disabling click or `Ctrl+O` interaction.
|
|
26
26
|
|
|
27
|
-

|
|
28
28
|
|
|
29
29
|
## Features
|
|
30
30
|
|
|
31
31
|
- Compact rendering for `read`, `write`, `edit`, `bash`, `powershell`, `grep`, `find`, and `ls`
|
|
32
32
|
- Animated configurable spinner in fullscreen TUI mode
|
|
33
33
|
- Millisecond-precision duration and configurable completion icons
|
|
34
|
-
-
|
|
35
|
-
-
|
|
34
|
+
- Per-tool initial compaction with `auto_compact: true` or `false`
|
|
35
|
+
- Click or `Ctrl+O` always cycles through meaningful detail levels
|
|
36
|
+
- Short previews, full output, and initially visible edit diffs by default
|
|
36
37
|
- Dark `│` and `└─` visual grouping
|
|
37
38
|
- Windows, Unix, and classic Mac line-ending support
|
|
38
39
|
- No Nerd Font requirement
|
|
@@ -78,6 +79,12 @@ Default configuration:
|
|
|
78
79
|
{
|
|
79
80
|
"$schema": "https://raw.githubusercontent.com/nedleeds/pi-compact-tools/main/schemas/compact-tools.schema.json",
|
|
80
81
|
"tools": ["read", "write", "edit", "bash"],
|
|
82
|
+
"auto_compact": {
|
|
83
|
+
"read": true,
|
|
84
|
+
"write": true,
|
|
85
|
+
"edit": false,
|
|
86
|
+
"bash": true
|
|
87
|
+
},
|
|
81
88
|
"previewLines": 10,
|
|
82
89
|
"spinner": {
|
|
83
90
|
"frames": ["◐", "◓", "◑", "◒"],
|
|
@@ -94,6 +101,14 @@ Default configuration:
|
|
|
94
101
|
|
|
95
102
|
These defaults use emoji and standard Unicode, so they work without a Nerd Font. Fast tools finish immediately; no artificial spinner delay is added.
|
|
96
103
|
|
|
104
|
+
`auto_compact` controls each tool's initial state independently:
|
|
105
|
+
|
|
106
|
+
- `true` starts at the compact summary.
|
|
107
|
+
- `false` starts at the most detailed available level.
|
|
108
|
+
- Click or `Ctrl+O` continues to cycle through available levels in either case.
|
|
109
|
+
|
|
110
|
+
Unspecified entries inherit the previous configuration layer. By default, `read`, `write`, and `bash` start compact, while `edit` starts expanded so code diffs are immediately visible.
|
|
111
|
+
|
|
97
112
|
`durationIndicators` must have ascending `underMs` values, with a final fallback entry that omits `underMs`. `color` is optional and accepts a supported theme color or six-digit hex value. Existing icon-only configurations remain compatible.
|
|
98
113
|
|
|
99
114
|
To enable all Unix-compatible tools:
|
|
@@ -138,11 +153,7 @@ The escaped code points remain readable on GitHub and are decoded to Nerd Font i
|
|
|
138
153
|
summary → arguments → output preview → full output → summary
|
|
139
154
|
```
|
|
140
155
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```text
|
|
144
|
-
summary → diff → summary
|
|
145
|
-
```
|
|
156
|
+
The `auto_compact` boolean only chooses where that cycle starts. With the default `edit: false`, the diff starts visible and the next click collapses it.
|
|
146
157
|
|
|
147
158
|
## Notes
|
|
148
159
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/nedleeds/pi-compact-tools/main/schemas/compact-tools.schema.json",
|
|
3
3
|
"tools": ["read", "write", "edit", "bash", "grep", "find", "ls"],
|
|
4
|
+
"auto_compact": {
|
|
5
|
+
"read": true,
|
|
6
|
+
"write": true,
|
|
7
|
+
"edit": false,
|
|
8
|
+
"bash": true
|
|
9
|
+
},
|
|
4
10
|
"previewLines": 10,
|
|
5
11
|
"spinner": {
|
|
6
12
|
"frames": ["◐", "◓", "◑", "◒"],
|
|
@@ -47,6 +47,7 @@ type CompactToolName = (typeof SUPPORTED_TOOLS)[number];
|
|
|
47
47
|
|
|
48
48
|
export interface CompactToolsConfig {
|
|
49
49
|
tools: CompactToolName[];
|
|
50
|
+
auto_compact: Record<CompactToolName, boolean>;
|
|
50
51
|
previewLines: number;
|
|
51
52
|
spinner: {
|
|
52
53
|
frames: string[];
|
|
@@ -57,6 +58,16 @@ export interface CompactToolsConfig {
|
|
|
57
58
|
|
|
58
59
|
export const DEFAULT_CONFIG: CompactToolsConfig = {
|
|
59
60
|
tools: ["read", "write", "edit", "bash"],
|
|
61
|
+
auto_compact: {
|
|
62
|
+
read: true,
|
|
63
|
+
write: true,
|
|
64
|
+
edit: false,
|
|
65
|
+
bash: true,
|
|
66
|
+
powershell: true,
|
|
67
|
+
grep: true,
|
|
68
|
+
find: true,
|
|
69
|
+
ls: true,
|
|
70
|
+
},
|
|
60
71
|
previewLines: 10,
|
|
61
72
|
spinner: {
|
|
62
73
|
frames: ["◐", "◓", "◑", "◒"],
|
|
@@ -122,6 +133,31 @@ function parseTools(value: unknown, path: string): CompactToolName[] | undefined
|
|
|
122
133
|
return [...new Set(valid)];
|
|
123
134
|
}
|
|
124
135
|
|
|
136
|
+
function parseAutoCompact(
|
|
137
|
+
base: CompactToolsConfig["auto_compact"],
|
|
138
|
+
value: unknown,
|
|
139
|
+
path: string,
|
|
140
|
+
): CompactToolsConfig["auto_compact"] {
|
|
141
|
+
if (value === undefined) return base;
|
|
142
|
+
if (!isObject(value)) {
|
|
143
|
+
warnConfig(path, "auto_compact must be an object; using previous values");
|
|
144
|
+
return base;
|
|
145
|
+
}
|
|
146
|
+
const autoCompact = { ...base };
|
|
147
|
+
for (const [name, enabled] of Object.entries(value)) {
|
|
148
|
+
if (!SUPPORTED_TOOL_SET.has(name)) {
|
|
149
|
+
warnConfig(path, `ignoring unsupported auto_compact tool: ${name}`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (typeof enabled !== "boolean") {
|
|
153
|
+
warnConfig(path, `auto_compact.${name} must be true or false; using previous value`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
autoCompact[name as CompactToolName] = enabled;
|
|
157
|
+
}
|
|
158
|
+
return autoCompact;
|
|
159
|
+
}
|
|
160
|
+
|
|
125
161
|
function mergeConfig(base: CompactToolsConfig, value: unknown, path: string): CompactToolsConfig {
|
|
126
162
|
if (value === undefined) return base;
|
|
127
163
|
if (!isObject(value)) {
|
|
@@ -129,6 +165,7 @@ function mergeConfig(base: CompactToolsConfig, value: unknown, path: string): Co
|
|
|
129
165
|
return base;
|
|
130
166
|
}
|
|
131
167
|
const tools = value.tools === undefined ? base.tools : (parseTools(value.tools, path) ?? base.tools);
|
|
168
|
+
const auto_compact = parseAutoCompact(base.auto_compact, value.auto_compact, path);
|
|
132
169
|
const previewValue = value.previewLines;
|
|
133
170
|
const validPreviewLines = isIntegerInRange(previewValue, 1, 1_000);
|
|
134
171
|
const previewLines = validPreviewLines ? previewValue : base.previewLines;
|
|
@@ -140,7 +177,7 @@ function mergeConfig(base: CompactToolsConfig, value: unknown, path: string): Co
|
|
|
140
177
|
if (value.durationIndicators !== undefined && durationIndicators === base.durationIndicators) {
|
|
141
178
|
warnConfig(path, "invalid durationIndicators; using previous values");
|
|
142
179
|
}
|
|
143
|
-
return { tools, previewLines, spinner, durationIndicators };
|
|
180
|
+
return { tools, auto_compact, previewLines, spinner, durationIndicators };
|
|
144
181
|
}
|
|
145
182
|
|
|
146
183
|
function parseSpinnerConfig(
|
|
@@ -216,8 +253,8 @@ const executionTimings = globalState[EXECUTION_TIMINGS_KEY] ?? new Map<string, E
|
|
|
216
253
|
globalState[SPINNING_ROWS_KEY] = spinningRows;
|
|
217
254
|
globalState[EXECUTION_TIMINGS_KEY] = executionTimings;
|
|
218
255
|
|
|
219
|
-
function advanceLevel(state: RowState, expanded: boolean): number {
|
|
220
|
-
state.level ??= 0;
|
|
256
|
+
function advanceLevel(state: RowState, expanded: boolean, name: CompactToolName): number {
|
|
257
|
+
state.level ??= config.auto_compact[name] ? 0 : (state.availableLevels?.at(-1) ?? MAX_LEVEL);
|
|
221
258
|
if (state.lastExpanded === undefined) {
|
|
222
259
|
state.lastExpanded = expanded;
|
|
223
260
|
return state.level;
|
|
@@ -231,9 +268,13 @@ function advanceLevel(state: RowState, expanded: boolean): number {
|
|
|
231
268
|
return state.level;
|
|
232
269
|
}
|
|
233
270
|
|
|
234
|
-
function setAvailableLevels(state: RowState, levels: number[]): void {
|
|
271
|
+
function setAvailableLevels(state: RowState, levels: number[], name: CompactToolName): void {
|
|
235
272
|
state.availableLevels = levels;
|
|
236
|
-
if (
|
|
273
|
+
if (state.level === undefined) {
|
|
274
|
+
state.level = config.auto_compact[name] ? 0 : (levels.at(-1) ?? 0);
|
|
275
|
+
} else if (!levels.includes(state.level)) {
|
|
276
|
+
state.level = !config.auto_compact[name] && state.level === MAX_LEVEL ? (levels.at(-1) ?? 0) : 0;
|
|
277
|
+
}
|
|
237
278
|
}
|
|
238
279
|
|
|
239
280
|
function getOutputLevels(output: string): number[] {
|
|
@@ -509,7 +550,7 @@ function renderFileCall(
|
|
|
509
550
|
// syncRow may restore a completed timing after /reload. Recompute from the
|
|
510
551
|
// synchronized state so a stale local value cannot leave the spinner visible.
|
|
511
552
|
const status = resolveCallStatus(ctx, state);
|
|
512
|
-
const level = advanceLevel(state, ctx.expanded);
|
|
553
|
+
const level = advanceLevel(state, ctx.expanded, definition.name as CompactToolName);
|
|
513
554
|
const callDetails = getCallDetails(definition.name, args);
|
|
514
555
|
const argumentDetails = getFileArgumentDetails(definition.name, args);
|
|
515
556
|
let text = `${renderIndicator(theme, state, status)} `;
|
|
@@ -554,8 +595,8 @@ function renderFileResult(
|
|
|
554
595
|
const state = syncRow(ctx, options.isPartial, !options.isPartial);
|
|
555
596
|
const output = getFileOutput(definition.name, ctx.args, result, ctx.isError);
|
|
556
597
|
const hasArguments = Object.keys(getFileArgumentDetails(definition.name, ctx.args)).length > 0;
|
|
557
|
-
const levels = definition.name === "edit" ? [0,
|
|
558
|
-
setAvailableLevels(state, levels);
|
|
598
|
+
const levels = definition.name === "edit" ? [0, MAX_LEVEL] : [0, ...(hasArguments ? [1] : []), ...getOutputLevels(output)];
|
|
599
|
+
setAvailableLevels(state, levels, definition.name as CompactToolName);
|
|
559
600
|
|
|
560
601
|
const level = state.level ?? 0;
|
|
561
602
|
callOriginalEditResult(definition, result, options, theme, ctx, state, level);
|
|
@@ -614,7 +655,7 @@ function renderShellCall(
|
|
|
614
655
|
// A completed timing can be restored inside syncRow. Derive the rendered
|
|
615
656
|
// status afterward so completion immediately replaces the spinner.
|
|
616
657
|
const status = resolveCallStatus(ctx, state);
|
|
617
|
-
const level = advanceLevel(state, ctx.expanded);
|
|
658
|
+
const level = advanceLevel(state, ctx.expanded, name);
|
|
618
659
|
const command = normalizeLineEndings(args.command ?? "");
|
|
619
660
|
const displayedCommand = (level >= 1 ? command : firstLine(command)) || "…";
|
|
620
661
|
let text = `${renderIndicator(theme, state, status)} `;
|
|
@@ -623,6 +664,7 @@ function renderShellCall(
|
|
|
623
664
|
}
|
|
624
665
|
|
|
625
666
|
function renderShellResult(
|
|
667
|
+
name: "bash" | "powershell",
|
|
626
668
|
result: AgentToolResult<BashToolDetails | undefined>,
|
|
627
669
|
options: ToolRenderResultOptions,
|
|
628
670
|
theme: Theme,
|
|
@@ -631,7 +673,7 @@ function renderShellResult(
|
|
|
631
673
|
const state = syncRow(ctx, options.isPartial, !options.isPartial);
|
|
632
674
|
const output = getTextResult(result);
|
|
633
675
|
const hasDetailedCall = /\r\n?|\n/.test(ctx.args.command ?? "");
|
|
634
|
-
setAvailableLevels(state, [0, ...(hasDetailedCall ? [1] : []), ...getOutputLevels(output)]);
|
|
676
|
+
setAvailableLevels(state, [0, ...(hasDetailedCall ? [1] : []), ...getOutputLevels(output)], name);
|
|
635
677
|
const level = state.level ?? 0;
|
|
636
678
|
if (level < 2) return renderControls(theme, state, options.isPartial, ctx.isError);
|
|
637
679
|
|
|
@@ -667,7 +709,12 @@ function registerShellTool(pi: ExtensionAPI, definition: BuiltInDefinition): voi
|
|
|
667
709
|
renderShell: "self",
|
|
668
710
|
renderCall: (args: BashToolInput, theme: Theme, ctx: RenderContext<BashToolInput>) =>
|
|
669
711
|
renderShellCall(definition.name as "bash" | "powershell", args, theme, ctx),
|
|
670
|
-
renderResult:
|
|
712
|
+
renderResult: (
|
|
713
|
+
result: AgentToolResult<BashToolDetails | undefined>,
|
|
714
|
+
options: ToolRenderResultOptions,
|
|
715
|
+
theme: Theme,
|
|
716
|
+
ctx: RenderContext<BashToolInput>,
|
|
717
|
+
) => renderShellResult(definition.name as "bash" | "powershell", result, options, theme, ctx),
|
|
671
718
|
} as ToolDefinition<any, BashToolDetails | undefined, RowState>);
|
|
672
719
|
}
|
|
673
720
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-compact-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"themes": [
|
|
43
43
|
"./themes/github-dark-pro.json"
|
|
44
44
|
],
|
|
45
|
-
"image": "https://raw.githubusercontent.com/nedleeds/pi-compact-tools/main/assets/nerd-font-workflow.gif"
|
|
45
|
+
"image": "https://raw.githubusercontent.com/nedleeds/pi-compact-tools/main/assets/nerd-font-workflow-optimized.gif"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@earendil-works/pi-coding-agent": "*",
|
|
@@ -18,6 +18,27 @@
|
|
|
18
18
|
},
|
|
19
19
|
"default": ["read", "write", "edit", "bash"]
|
|
20
20
|
},
|
|
21
|
+
"auto_compact": {
|
|
22
|
+
"description": "Whether each compact-rendered tool starts in its compact summary state. Click/Ctrl+O remains available regardless of this value.",
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"read": { "type": "boolean", "default": true },
|
|
27
|
+
"write": { "type": "boolean", "default": true },
|
|
28
|
+
"edit": { "type": "boolean", "default": false },
|
|
29
|
+
"bash": { "type": "boolean", "default": true },
|
|
30
|
+
"powershell": { "type": "boolean", "default": true },
|
|
31
|
+
"grep": { "type": "boolean", "default": true },
|
|
32
|
+
"find": { "type": "boolean", "default": true },
|
|
33
|
+
"ls": { "type": "boolean", "default": true }
|
|
34
|
+
},
|
|
35
|
+
"default": {
|
|
36
|
+
"read": true,
|
|
37
|
+
"write": true,
|
|
38
|
+
"edit": false,
|
|
39
|
+
"bash": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
21
42
|
"previewLines": {
|
|
22
43
|
"description": "Number of output lines shown before the full-output expansion level.",
|
|
23
44
|
"type": "integer",
|
|
@@ -32,6 +32,12 @@ if (!Array.isArray(example.spinner?.frames) || example.spinner.frames.length ===
|
|
|
32
32
|
if (!Number.isInteger(example.spinner.intervalMs)) {
|
|
33
33
|
throw new Error("example spinner.intervalMs must be an integer");
|
|
34
34
|
}
|
|
35
|
+
if (!example.auto_compact || example.auto_compact.edit !== false) {
|
|
36
|
+
throw new Error("example auto_compact policy must keep edit diffs visible by default");
|
|
37
|
+
}
|
|
38
|
+
if (Object.values(example.auto_compact).some((enabled) => typeof enabled !== "boolean")) {
|
|
39
|
+
throw new Error("example auto_compact values must be booleans");
|
|
40
|
+
}
|
|
35
41
|
const indicators = example.durationIndicators;
|
|
36
42
|
if (!Array.isArray(indicators) || indicators.length === 0 || indicators.at(-1)?.underMs !== undefined) {
|
|
37
43
|
throw new Error("example durationIndicators must end with a fallback rule");
|
|
@@ -51,5 +57,8 @@ const schema = JSON.parse(
|
|
|
51
57
|
if (!schema.properties?.durationIndicators?.items?.properties?.color) {
|
|
52
58
|
throw new Error("schema must describe durationIndicators.color");
|
|
53
59
|
}
|
|
60
|
+
if (!schema.properties?.auto_compact?.properties?.edit) {
|
|
61
|
+
throw new Error("schema must describe per-tool auto_compact policies");
|
|
62
|
+
}
|
|
54
63
|
|
|
55
64
|
console.log("Resource checks passed");
|