pi-compact-tools 0.6.1 → 0.7.7
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/LICENSE +21 -21
- package/README.md +29 -61
- package/examples/compact-tools-2.json +1 -4
- package/examples/compact-tools.json +1 -4
- package/extensions/compact-tools-color.ts +65 -0
- package/extensions/compact-tools-config.ts +7 -42
- package/extensions/compact-tools-core.ts +26 -0
- package/extensions/compact-tools-invocation.ts +166 -31
- package/extensions/compact-tools-layout.ts +23 -22
- package/extensions/compact-tools-progress.ts +158 -0
- package/extensions/compact-tools-runtime.ts +44 -70
- package/extensions/compact-tools-thinking.ts +53 -92
- package/extensions/compact-tools-types.ts +11 -8
- package/extensions/compact-tools.ts +126 -94
- package/package.json +1 -1
- package/schemas/compact-tools.schema.json +8 -24
- package/scripts/check-resources.mjs +13 -16
- package/extensions/compact-tools-input.ts +0 -8
|
@@ -3,7 +3,6 @@ import type {
|
|
|
3
3
|
BashToolDetails,
|
|
4
4
|
BashToolInput,
|
|
5
5
|
ExtensionAPI,
|
|
6
|
-
ExtensionContext,
|
|
7
6
|
Theme,
|
|
8
7
|
ToolDefinition,
|
|
9
8
|
ToolRenderResultOptions,
|
|
@@ -22,28 +21,34 @@ import type { Component, Container } from "@earendil-works/pi-tui";
|
|
|
22
21
|
import {
|
|
23
22
|
classifyCallStatus,
|
|
24
23
|
formatDurationMs,
|
|
24
|
+
indicatorGlyph,
|
|
25
|
+
indicatorStrength,
|
|
26
|
+
indicatorTone,
|
|
25
27
|
normalizeLineEndings,
|
|
26
28
|
type RowStatus,
|
|
27
29
|
} from "./compact-tools-core.ts";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
+
import { colorizeRgb, interpolateRgb, themeColorRgb } from "./compact-tools-color.ts";
|
|
31
|
+
import { loadConfig } from "./compact-tools-config.ts";
|
|
30
32
|
import {
|
|
31
|
-
|
|
32
|
-
formatReadResultSummary,
|
|
33
|
+
formatResultLineSummary,
|
|
33
34
|
getArgumentDetails,
|
|
34
35
|
getCallDetails,
|
|
36
|
+
getEditChanges,
|
|
35
37
|
getFileOutput,
|
|
36
38
|
getTextResult,
|
|
39
|
+
summarizeShellCommand,
|
|
37
40
|
} from "./compact-tools-invocation.ts";
|
|
38
41
|
import {
|
|
39
42
|
CachedContainer,
|
|
43
|
+
limitComponentLines,
|
|
40
44
|
prefixedText,
|
|
41
45
|
renderArguments,
|
|
46
|
+
renderEditChanges,
|
|
42
47
|
renderOutput,
|
|
43
48
|
renderToolCall,
|
|
44
49
|
styleMultiline,
|
|
45
|
-
wrapEditResult,
|
|
46
50
|
} from "./compact-tools-layout.ts";
|
|
51
|
+
import { ProgressController } from "./compact-tools-progress.ts";
|
|
47
52
|
import { ToolRuntime } from "./compact-tools-runtime.ts";
|
|
48
53
|
import { ThinkingCycleController } from "./compact-tools-thinking.ts";
|
|
49
54
|
import type {
|
|
@@ -57,37 +62,27 @@ import type {
|
|
|
57
62
|
|
|
58
63
|
const runtime = new ToolRuntime();
|
|
59
64
|
const registeredTools = new Set<CompactToolName>();
|
|
60
|
-
let
|
|
61
|
-
|
|
62
|
-
function bindTerminalInput(ctx: ExtensionContext): void {
|
|
63
|
-
unsubscribeTerminalInput?.();
|
|
64
|
-
unsubscribeTerminalInput = ctx.ui.onTerminalInput((data) => {
|
|
65
|
-
const input = classifyToggleInput(data);
|
|
66
|
-
if (!input) return undefined;
|
|
67
|
-
if (input === "release") return { consume: true };
|
|
68
|
-
const result = runtime.toggleTrackedRows();
|
|
69
|
-
if (result) ctx.ui.notify(`Compact tool rows: ${result}`, "info");
|
|
70
|
-
return { consume: true };
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function spinnerTone(frameIndex: number, frameCount: number): "muted" | "dim" | "border" {
|
|
75
|
-
if (frameCount < 3) return "muted";
|
|
76
|
-
const distanceFromCenter = Math.abs(frameIndex / (frameCount - 1) - 0.5) * 2;
|
|
77
|
-
if (distanceFromCenter < 0.34) return "border";
|
|
78
|
-
return distanceFromCenter < 0.75 ? "dim" : "muted";
|
|
79
|
-
}
|
|
65
|
+
let registeredConfiguration: string | undefined;
|
|
80
66
|
|
|
81
67
|
function callStatus(ctx: RenderContext, state: RowState): RowStatus {
|
|
82
|
-
|
|
68
|
+
// write/edit arguments can stream for much longer than their eventual filesystem
|
|
69
|
+
// operation. Treat that active tool-call phase as running so every built-in row
|
|
70
|
+
// animates consistently instead of waiting for execute() to begin.
|
|
71
|
+
const active = ctx.executionStarted || !ctx.argsComplete;
|
|
72
|
+
return classifyCallStatus(ctx.isError, active, state.endedAt !== undefined);
|
|
83
73
|
}
|
|
84
74
|
|
|
85
|
-
function renderIndicator(theme: Theme,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
function renderIndicator(theme: Theme, ctx: RenderContext, status: RowStatus): string {
|
|
76
|
+
const frame = runtime.syncIndicator(ctx.toolCallId, status === "running", () => ctx.invalidate());
|
|
77
|
+
const glyph = indicatorGlyph(status, frame);
|
|
78
|
+
if (status === "running") {
|
|
79
|
+
const bright = themeColorRgb(theme, "borderAccent");
|
|
80
|
+
const dim = themeColorRgb(theme, "borderMuted");
|
|
81
|
+
if (bright && dim) {
|
|
82
|
+
return colorizeRgb(theme, interpolateRgb(dim, bright, indicatorStrength(status, frame)), glyph);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return theme.fg(indicatorTone(status, frame), glyph);
|
|
91
86
|
}
|
|
92
87
|
|
|
93
88
|
function formatDuration(state: RowState): string | undefined {
|
|
@@ -95,33 +90,55 @@ function formatDuration(state: RowState): string | undefined {
|
|
|
95
90
|
return formatDurationMs((state.endedAt ?? Date.now()) - state.startedAt);
|
|
96
91
|
}
|
|
97
92
|
|
|
93
|
+
function canReuseResult<TDetails, TArgs>(
|
|
94
|
+
state: RowState,
|
|
95
|
+
result: AgentToolResult<TDetails>,
|
|
96
|
+
options: ToolRenderResultOptions,
|
|
97
|
+
ctx: RenderContext<TArgs>,
|
|
98
|
+
): ctx is RenderContext<TArgs> & { lastComponent: Component } {
|
|
99
|
+
return ctx.lastComponent !== undefined
|
|
100
|
+
&& state.lastResultContent === result.content
|
|
101
|
+
&& state.lastResultDetails === result.details
|
|
102
|
+
&& state.lastResultPartial === options.isPartial
|
|
103
|
+
&& state.lastResultExpanded === state.expanded
|
|
104
|
+
&& state.lastResultPreview === state.preview
|
|
105
|
+
&& state.lastResultError === ctx.isError
|
|
106
|
+
&& state.lastResultConfigRevision === state.configRevision;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function rememberResult<TDetails, TArgs>(
|
|
110
|
+
state: RowState,
|
|
111
|
+
result: AgentToolResult<TDetails>,
|
|
112
|
+
options: ToolRenderResultOptions,
|
|
113
|
+
ctx: RenderContext<TArgs>,
|
|
114
|
+
): void {
|
|
115
|
+
state.lastResultContent = result.content;
|
|
116
|
+
state.lastResultDetails = result.details;
|
|
117
|
+
state.lastResultPartial = options.isPartial;
|
|
118
|
+
state.lastResultExpanded = state.expanded;
|
|
119
|
+
state.lastResultPreview = state.preview;
|
|
120
|
+
state.lastResultError = ctx.isError;
|
|
121
|
+
state.lastResultConfigRevision = state.configRevision;
|
|
122
|
+
}
|
|
123
|
+
|
|
98
124
|
function renderControls(
|
|
99
125
|
theme: Theme,
|
|
100
126
|
state: RowState,
|
|
101
127
|
running: boolean,
|
|
102
128
|
isError: boolean,
|
|
103
|
-
|
|
104
|
-
argumentSummary?: string,
|
|
129
|
+
lineSummary?: string,
|
|
105
130
|
): Component {
|
|
106
|
-
const duration = running
|
|
131
|
+
const duration = running ? undefined : formatDuration(state);
|
|
107
132
|
const status = running
|
|
108
133
|
? (duration ?? "Running")
|
|
109
134
|
: `${isError ? "Failed" : "Done"}${duration ? ` in ${duration}` : ""}`;
|
|
110
135
|
let details = theme.fg("borderAccent", status);
|
|
111
|
-
if (
|
|
112
|
-
if (summary) details += theme.fg("borderAccent", ` • ${summary}`);
|
|
113
|
-
if (state.hasResult) {
|
|
114
|
-
const clickAction = state.expanded ? "to hide" : "for result";
|
|
115
|
-
details += theme.fg(
|
|
116
|
-
"borderAccent",
|
|
117
|
-
`, ${theme.italic("ctrl+o")} toggle all • ${theme.italic("click")} ${clickAction}`,
|
|
118
|
-
);
|
|
119
|
-
}
|
|
136
|
+
if (lineSummary && !running) details += theme.fg("borderAccent", ` (${lineSummary})`);
|
|
120
137
|
return prefixedText(details, theme.fg("border", " └─ "), " ");
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
function renderCallTitle(name: string, theme: Theme, ctx: RenderContext, state: RowState): string {
|
|
124
|
-
return `${renderIndicator(theme,
|
|
141
|
+
return `${renderIndicator(theme, ctx, callStatus(ctx, state))} ${theme.fg("toolTitle", theme.bold(name))}`;
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
function renderFileCall(
|
|
@@ -132,7 +149,6 @@ function renderFileCall(
|
|
|
132
149
|
): Container {
|
|
133
150
|
const name = definition.name as CompactToolName;
|
|
134
151
|
const state = runtime.syncRow(ctx, ctx.state.endedAt === undefined);
|
|
135
|
-
runtime.track(ctx, name, state);
|
|
136
152
|
runtime.syncExpansion(state, ctx.expanded, name);
|
|
137
153
|
const callDetails = getCallDetails(name, args);
|
|
138
154
|
const details = callDetails
|
|
@@ -145,38 +161,21 @@ function renderFileCall(
|
|
|
145
161
|
return container;
|
|
146
162
|
}
|
|
147
163
|
|
|
148
|
-
function updateEditResult(
|
|
149
|
-
definition: BuiltInDefinition,
|
|
150
|
-
result: AgentToolResult<unknown>,
|
|
151
|
-
options: ToolRenderResultOptions,
|
|
152
|
-
theme: Theme,
|
|
153
|
-
ctx: RenderContext,
|
|
154
|
-
state: RowState,
|
|
155
|
-
): void {
|
|
156
|
-
if (definition.name !== "edit" || !definition.renderResult) return;
|
|
157
|
-
state.originalResultComponent = definition.renderResult(
|
|
158
|
-
result,
|
|
159
|
-
{ ...options, expanded: state.expanded ?? false },
|
|
160
|
-
theme,
|
|
161
|
-
{ ...ctx, state: {}, lastComponent: state.originalResultComponent },
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
164
|
function appendFileResult(
|
|
166
165
|
container: Container,
|
|
167
166
|
definition: BuiltInDefinition,
|
|
168
167
|
state: RowState,
|
|
169
168
|
output: string,
|
|
169
|
+
editChanges: string,
|
|
170
170
|
theme: Theme,
|
|
171
171
|
isError: boolean,
|
|
172
172
|
): void {
|
|
173
|
-
if (!state.expanded) return;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (component) container.addChild(component);
|
|
173
|
+
if (!state.expanded && !state.preview) return;
|
|
174
|
+
const component = definition.name === "edit" && editChanges
|
|
175
|
+
? renderEditChanges(editChanges, theme)
|
|
176
|
+
: renderOutput(output, theme, isError);
|
|
177
|
+
if (!component) return;
|
|
178
|
+
container.addChild(state.expanded ? component : limitComponentLines(component, runtime.config.previewLines, theme));
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
function renderFileResult(
|
|
@@ -185,18 +184,22 @@ function renderFileResult(
|
|
|
185
184
|
options: ToolRenderResultOptions,
|
|
186
185
|
theme: Theme,
|
|
187
186
|
ctx: RenderContext,
|
|
188
|
-
):
|
|
187
|
+
): Component {
|
|
189
188
|
const name = definition.name as CompactToolName;
|
|
190
189
|
const state = runtime.syncRow(ctx, options.isPartial, !options.isPartial);
|
|
191
|
-
runtime.
|
|
190
|
+
runtime.syncExpansion(state, ctx.expanded, name);
|
|
191
|
+
if (canReuseResult(state, result, options, ctx)) return ctx.lastComponent;
|
|
192
192
|
const output = getFileOutput(name, ctx.args, result, ctx.isError);
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
const editChanges = name === "edit" ? getEditChanges(result) : "";
|
|
194
|
+
runtime.setResultAvailable(state, name, editChanges.length > 0 || output.length > 0);
|
|
195
195
|
const container = new CachedContainer();
|
|
196
|
-
appendFileResult(container, definition, state, output, theme, ctx.isError);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
appendFileResult(container, definition, state, output, editChanges, theme, ctx.isError);
|
|
197
|
+
if (!options.isPartial && !state.resultLineSummaryComputed) {
|
|
198
|
+
state.resultLineSummary = formatResultLineSummary(name, ctx.args, result, output);
|
|
199
|
+
state.resultLineSummaryComputed = true;
|
|
200
|
+
}
|
|
201
|
+
container.addChild(renderControls(theme, state, options.isPartial, ctx.isError, state.resultLineSummary));
|
|
202
|
+
rememberResult(state, result, options, ctx);
|
|
200
203
|
return container;
|
|
201
204
|
}
|
|
202
205
|
|
|
@@ -207,10 +210,11 @@ function renderShellCall(
|
|
|
207
210
|
ctx: RenderContext<BashToolInput>,
|
|
208
211
|
): Component {
|
|
209
212
|
const state = runtime.syncRow(ctx, ctx.state.endedAt === undefined);
|
|
210
|
-
runtime.track(ctx, name, state);
|
|
211
213
|
runtime.syncExpansion(state, ctx.expanded, name);
|
|
212
|
-
const command = normalizeLineEndings(args.command ?? "")
|
|
213
|
-
const details =
|
|
214
|
+
const command = normalizeLineEndings(args.command ?? "");
|
|
215
|
+
const details = state.expanded
|
|
216
|
+
? styleMultiline(command || "…", (line) => theme.fg("toolOutput", line))
|
|
217
|
+
: theme.fg("toolOutput", summarizeShellCommand(name, command));
|
|
214
218
|
return renderToolCall(renderCallTitle(name, theme, ctx, state), details, theme);
|
|
215
219
|
}
|
|
216
220
|
|
|
@@ -222,14 +226,26 @@ function renderShellResult(
|
|
|
222
226
|
ctx: RenderContext<BashToolInput>,
|
|
223
227
|
): Component {
|
|
224
228
|
const state = runtime.syncRow(ctx, options.isPartial, !options.isPartial);
|
|
225
|
-
runtime.
|
|
229
|
+
runtime.syncExpansion(state, ctx.expanded, name);
|
|
230
|
+
if (canReuseResult(state, result, options, ctx)) return ctx.lastComponent;
|
|
226
231
|
const output = getTextResult(result);
|
|
227
232
|
runtime.setResultAvailable(state, name, output.length > 0);
|
|
228
|
-
if (!
|
|
233
|
+
if (!options.isPartial && !state.resultLineSummaryComputed) {
|
|
234
|
+
state.resultLineSummary = formatResultLineSummary(name, ctx.args, result, output);
|
|
235
|
+
state.resultLineSummaryComputed = true;
|
|
236
|
+
}
|
|
237
|
+
if (!state.expanded && !state.preview) {
|
|
238
|
+
const controls = renderControls(theme, state, options.isPartial, ctx.isError, state.resultLineSummary);
|
|
239
|
+
rememberResult(state, result, options, ctx);
|
|
240
|
+
return controls;
|
|
241
|
+
}
|
|
229
242
|
const container = new CachedContainer();
|
|
230
243
|
const component = renderOutput(output, theme, ctx.isError);
|
|
231
|
-
if (component)
|
|
232
|
-
|
|
244
|
+
if (component) {
|
|
245
|
+
container.addChild(state.expanded ? component : limitComponentLines(component, runtime.config.previewLines, theme));
|
|
246
|
+
}
|
|
247
|
+
container.addChild(renderControls(theme, state, options.isPartial, ctx.isError, state.resultLineSummary));
|
|
248
|
+
rememberResult(state, result, options, ctx);
|
|
233
249
|
return container;
|
|
234
250
|
}
|
|
235
251
|
|
|
@@ -283,6 +299,8 @@ function registerShellTool(pi: ExtensionAPI, definition: BuiltInDefinition): voi
|
|
|
283
299
|
function registerTools(pi: ExtensionAPI, cwd: string): void {
|
|
284
300
|
const enabledTools = new Set(runtime.config.tools);
|
|
285
301
|
for (const name of registeredTools) {
|
|
302
|
+
// Pi exposes no unregister API, so restoring a disabled tool means re-registering
|
|
303
|
+
// the built-in definition to release this extension's renderers.
|
|
286
304
|
if (!enabledTools.has(name)) pi.registerTool(createBuiltInDefinition(name, cwd));
|
|
287
305
|
}
|
|
288
306
|
registeredTools.clear();
|
|
@@ -295,24 +313,38 @@ function registerTools(pi: ExtensionAPI, cwd: string): void {
|
|
|
295
313
|
}
|
|
296
314
|
|
|
297
315
|
function configure(pi: ExtensionAPI, cwd?: string, projectTrusted = false): void {
|
|
298
|
-
|
|
299
|
-
|
|
316
|
+
const resolvedCwd = cwd ?? process.cwd();
|
|
317
|
+
const config = loadConfig(resolvedCwd, projectTrusted);
|
|
318
|
+
const signature = JSON.stringify([resolvedCwd, config]);
|
|
319
|
+
if (signature === registeredConfiguration) return;
|
|
320
|
+
runtime.configure(config);
|
|
321
|
+
registerTools(pi, resolvedCwd);
|
|
322
|
+
registeredConfiguration = signature;
|
|
300
323
|
}
|
|
301
324
|
|
|
302
325
|
export default function compactTools(pi: ExtensionAPI): void {
|
|
303
326
|
const thinkingCycle = new ThinkingCycleController(pi);
|
|
304
|
-
|
|
327
|
+
const progress = new ProgressController(pi);
|
|
328
|
+
// Register once while the extension runtime is being built. In particular, this
|
|
329
|
+
// makes the overrides available before Pi restores the active tool set on /reload.
|
|
330
|
+
configure(pi, process.cwd());
|
|
305
331
|
pi.on("session_start", (event, ctx) => {
|
|
306
332
|
if (event.reason !== "reload") runtime.clearTimings();
|
|
307
333
|
configure(pi, ctx.cwd, ctx.isProjectTrusted());
|
|
308
|
-
|
|
309
|
-
|
|
334
|
+
if (ctx.mode === "tui") {
|
|
335
|
+
progress.bind(ctx);
|
|
336
|
+
thinkingCycle.bind(ctx);
|
|
337
|
+
} else {
|
|
338
|
+
progress.dispose();
|
|
339
|
+
thinkingCycle.dispose();
|
|
340
|
+
}
|
|
310
341
|
});
|
|
311
|
-
pi.on("resources_discover", (_event, ctx) => configure(pi, ctx.cwd, ctx.isProjectTrusted()));
|
|
312
342
|
pi.on("session_shutdown", (event) => {
|
|
313
343
|
thinkingCycle.dispose();
|
|
314
|
-
|
|
315
|
-
unsubscribeTerminalInput = undefined;
|
|
344
|
+
progress.dispose();
|
|
316
345
|
runtime.reset(event.reason !== "reload");
|
|
346
|
+
// Some Pi hosts rebuild the active tool registry during reload while keeping
|
|
347
|
+
// this module instance. Force the next session to restore our renderers.
|
|
348
|
+
if (event.reason === "reload") registeredConfiguration = undefined;
|
|
317
349
|
});
|
|
318
350
|
}
|
package/package.json
CHANGED
|
@@ -18,8 +18,15 @@
|
|
|
18
18
|
},
|
|
19
19
|
"default": ["read", "write", "edit", "bash"]
|
|
20
20
|
},
|
|
21
|
+
"previewLines": {
|
|
22
|
+
"description": "Maximum rendered lines shown initially when auto_compact is false. Explicit expansion always shows the complete result.",
|
|
23
|
+
"type": "integer",
|
|
24
|
+
"minimum": 1,
|
|
25
|
+
"maximum": 100,
|
|
26
|
+
"default": 10
|
|
27
|
+
},
|
|
21
28
|
"auto_compact": {
|
|
22
|
-
"description": "Whether each compact-rendered tool starts
|
|
29
|
+
"description": "Whether each compact-rendered tool starts hidden. False starts with a bounded preview; Pi's normal click and Ctrl+O flow can expand either state.",
|
|
23
30
|
"type": "object",
|
|
24
31
|
"additionalProperties": false,
|
|
25
32
|
"properties": {
|
|
@@ -38,29 +45,6 @@
|
|
|
38
45
|
"edit": false,
|
|
39
46
|
"bash": true
|
|
40
47
|
}
|
|
41
|
-
},
|
|
42
|
-
"spinner": {
|
|
43
|
-
"type": "object",
|
|
44
|
-
"additionalProperties": false,
|
|
45
|
-
"properties": {
|
|
46
|
-
"frames": {
|
|
47
|
-
"description": "Animation frames. Use terminal-safe single-width characters for consistent alignment.",
|
|
48
|
-
"type": "array",
|
|
49
|
-
"minItems": 1,
|
|
50
|
-
"items": {
|
|
51
|
-
"type": "string",
|
|
52
|
-
"minLength": 1
|
|
53
|
-
},
|
|
54
|
-
"default": ["◐", "◓", "◑", "◒"]
|
|
55
|
-
},
|
|
56
|
-
"intervalMs": {
|
|
57
|
-
"description": "Milliseconds between spinner frames.",
|
|
58
|
-
"type": "integer",
|
|
59
|
-
"minimum": 40,
|
|
60
|
-
"maximum": 5000,
|
|
61
|
-
"default": 120
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
48
|
}
|
|
65
49
|
}
|
|
66
50
|
}
|
|
@@ -21,7 +21,7 @@ if (theme.name !== "github-dark-pro" || !theme.colors || typeof theme.colors !==
|
|
|
21
21
|
throw new Error("themes/github-dark-pro.json is not a valid pi theme document");
|
|
22
22
|
}
|
|
23
23
|
if (!theme.colors.border || !theme.colors.dim || !theme.colors.muted) {
|
|
24
|
-
throw new Error("github-dark-pro must define
|
|
24
|
+
throw new Error("github-dark-pro must define separator colors");
|
|
25
25
|
}
|
|
26
26
|
if (!theme.vars?.thinkingText || !theme.colors.thinkingMax) {
|
|
27
27
|
throw new Error("github-dark-pro must define thinking text and summary colors");
|
|
@@ -33,31 +33,25 @@ if (!theme.colors.mdQuote || !theme.colors.mdCodeBlockBorder) {
|
|
|
33
33
|
const example = JSON.parse(
|
|
34
34
|
await readFile(new URL("../examples/compact-tools.json", import.meta.url), "utf8"),
|
|
35
35
|
);
|
|
36
|
-
if (!Array.isArray(example.spinner?.frames) || example.spinner.frames.length === 0) {
|
|
37
|
-
throw new Error("example spinner.frames must be a non-empty array");
|
|
38
|
-
}
|
|
39
|
-
if (!Number.isInteger(example.spinner.intervalMs)) {
|
|
40
|
-
throw new Error("example spinner.intervalMs must be an integer");
|
|
41
|
-
}
|
|
42
36
|
if (!example.auto_compact || example.auto_compact.edit !== false) {
|
|
43
37
|
throw new Error("example auto_compact policy must keep edit diffs visible by default");
|
|
44
38
|
}
|
|
45
39
|
if (Object.values(example.auto_compact).some((enabled) => typeof enabled !== "boolean")) {
|
|
46
40
|
throw new Error("example auto_compact values must be booleans");
|
|
47
41
|
}
|
|
48
|
-
if (
|
|
49
|
-
throw new Error("example must
|
|
42
|
+
if (!Number.isInteger(example.previewLines) || example.previewLines < 1 || example.previewLines > 100) {
|
|
43
|
+
throw new Error("example previewLines must be an integer from 1 to 100");
|
|
50
44
|
}
|
|
51
|
-
const
|
|
45
|
+
const alternateExample = JSON.parse(
|
|
52
46
|
await readFile(new URL("../examples/compact-tools-2.json", import.meta.url), "utf8"),
|
|
53
47
|
);
|
|
54
|
-
|
|
55
|
-
throw new Error("compact-tools-2 spinner must fade through repeated circles and a blank frame");
|
|
56
|
-
}
|
|
57
|
-
for (const [name, config] of [["compact-tools", example], ["compact-tools-2", blinkExample]]) {
|
|
48
|
+
for (const [name, config] of [["compact-tools", example], ["compact-tools-2", alternateExample]]) {
|
|
58
49
|
if ("durationIndicators" in config) {
|
|
59
50
|
throw new Error(`${name} must not contain the removed durationIndicators setting`);
|
|
60
51
|
}
|
|
52
|
+
if ("spinner" in config) {
|
|
53
|
+
throw new Error(`${name} must not contain the removed spinner setting`);
|
|
54
|
+
}
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
const schema = JSON.parse(
|
|
@@ -66,11 +60,14 @@ const schema = JSON.parse(
|
|
|
66
60
|
if (schema.properties?.durationIndicators) {
|
|
67
61
|
throw new Error("schema must not expose the removed durationIndicators setting");
|
|
68
62
|
}
|
|
63
|
+
if (schema.properties?.spinner) {
|
|
64
|
+
throw new Error("schema must not expose the removed spinner setting");
|
|
65
|
+
}
|
|
69
66
|
if (!schema.properties?.auto_compact?.properties?.edit) {
|
|
70
67
|
throw new Error("schema must describe per-tool auto_compact policies");
|
|
71
68
|
}
|
|
72
|
-
if (schema.properties?.previewLines) {
|
|
73
|
-
throw new Error("schema must
|
|
69
|
+
if (schema.properties?.previewLines?.default !== 10) {
|
|
70
|
+
throw new Error("schema must expose the bounded previewLines setting");
|
|
74
71
|
}
|
|
75
72
|
|
|
76
73
|
console.log("Resource checks passed");
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { isKeyRelease, Key, matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
-
|
|
3
|
-
export type ToggleInput = "toggle" | "release";
|
|
4
|
-
|
|
5
|
-
export function classifyToggleInput(data: string): ToggleInput | undefined {
|
|
6
|
-
if (!matchesKey(data, Key.ctrl("o"))) return undefined;
|
|
7
|
-
return isKeyRelease(data) ? "release" : "toggle";
|
|
8
|
-
}
|