pi-minimalist 0.1.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/LICENSE +21 -0
- package/README.md +24 -0
- package/index.ts +132 -0
- package/package.json +21 -0
- package/src/bridge.ts +124 -0
- package/src/components.ts +173 -0
- package/src/config-file.ts +277 -0
- package/src/config-ui.ts +208 -0
- package/src/config.ts +246 -0
- package/src/core-patch.ts +510 -0
- package/src/row.ts +193 -0
- package/src/run-grouping.ts +378 -0
- package/src/thinking.ts +100 -0
- package/src/tool-renderer.ts +260 -0
- package/src/tools.ts +195 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The setting schema and the live settings object.
|
|
3
|
+
*
|
|
4
|
+
* This file has NO filesystem access: reading and writing settings.json lives in
|
|
5
|
+
* `config-file.ts`. Keeping them apart means the schema can be unit-tested with
|
|
6
|
+
* no temp directories, and the I/O rules (comment handling, refusal to clobber)
|
|
7
|
+
* are readable on their own.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
// ThemeBg is not exported publicly, so recover it from Theme.bg's own signature
|
|
13
|
+
// rather than importing a deep dist path (which is not a package export).
|
|
14
|
+
type ThemeBg = Parameters<Theme["bg"]>[0];
|
|
15
|
+
|
|
16
|
+
/** Top-level key inside Pi's settings.json. */
|
|
17
|
+
export const SETTINGS_KEY = "minimalist";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Glyphs
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Status and decoration characters.
|
|
25
|
+
*
|
|
26
|
+
* Configurable for fonts or terminals that lack one of the ordinary Unicode
|
|
27
|
+
* characters below, or measure its width differently. A Nerd Font is not
|
|
28
|
+
* required; the ASCII fallback keeps each glyph within one column.
|
|
29
|
+
*/
|
|
30
|
+
export type Glyphs = {
|
|
31
|
+
done: string;
|
|
32
|
+
failed: string;
|
|
33
|
+
running: string;
|
|
34
|
+
queued: string;
|
|
35
|
+
gutter: string;
|
|
36
|
+
/** Prefix inside the elapsed badge, e.g. `⏱` in `[⏱ 3s]`. May be empty. */
|
|
37
|
+
timer: string;
|
|
38
|
+
/** Multiplier in run summaries, e.g. `×` in `read ×2`. */
|
|
39
|
+
count: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type GlyphStyle = "unicode" | "ascii";
|
|
43
|
+
|
|
44
|
+
export const GLYPH_PRESETS: Record<GlyphStyle, Glyphs> = {
|
|
45
|
+
unicode: { done: "✓", failed: "✗", running: "•", queued: "›", gutter: "▌", timer: "⏱", count: "×" },
|
|
46
|
+
// Every ASCII glyph is exactly one column wide, so rows stay aligned in fonts
|
|
47
|
+
// that render the Unicode set as double-width boxes.
|
|
48
|
+
ascii: { done: "+", failed: "x", running: "*", queued: ">", gutter: "|", timer: "", count: "x" },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Theme tokens
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Which Pi theme token paints each part of a row.
|
|
57
|
+
*
|
|
58
|
+
* Overridable, but the defaults are deliberate: the action word is a LABEL, not
|
|
59
|
+
* a warning, so it uses `success` green even for mutating tools. An earlier
|
|
60
|
+
* version used warning orange, which collided with orange meaning "highlighted
|
|
61
|
+
* prose".
|
|
62
|
+
*/
|
|
63
|
+
export type Tokens = {
|
|
64
|
+
label: ThemeColor;
|
|
65
|
+
details: ThemeColor;
|
|
66
|
+
thinking: ThemeColor;
|
|
67
|
+
error: ThemeColor;
|
|
68
|
+
outputGutter: ThemeColor;
|
|
69
|
+
activeBg: ThemeBg;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const DEFAULT_TOKENS: Tokens = {
|
|
73
|
+
label: "success",
|
|
74
|
+
details: "toolTitle",
|
|
75
|
+
thinking: "thinkingText",
|
|
76
|
+
error: "error",
|
|
77
|
+
outputGutter: "borderMuted",
|
|
78
|
+
activeBg: "toolPendingBg",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Settings
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
/** Settings `/minimalist` can edit: visible effect, no vocabulary needed. */
|
|
86
|
+
export type ActivitySummary = "elapsed" | "tools";
|
|
87
|
+
|
|
88
|
+
export type BasicSettings = {
|
|
89
|
+
/** Master switch. Off restores Pi's native tool cards entirely. */
|
|
90
|
+
compactToolRows: boolean;
|
|
91
|
+
/** Fold adjacent finished rows into one summary line. */
|
|
92
|
+
groupToolRuns: boolean;
|
|
93
|
+
/** Replace all but the latest assistant prose block with one summary row. */
|
|
94
|
+
foldIntermediateActivity: boolean;
|
|
95
|
+
/** Keep commentary visible until OpenAI starts its final-answer message. */
|
|
96
|
+
foldActivityOnFinalAnswer: boolean;
|
|
97
|
+
/** What replaces the activity hidden before the latest prose block. */
|
|
98
|
+
activitySummary: ActivitySummary;
|
|
99
|
+
/** Draw the left gutter bar. */
|
|
100
|
+
gutter: boolean;
|
|
101
|
+
/** Show the elapsed badge while a tool runs. */
|
|
102
|
+
timer: boolean;
|
|
103
|
+
/** Render collapsed thinking like a tool row. Off restores Pi's native thinking. */
|
|
104
|
+
thinkingAsToolCall: boolean;
|
|
105
|
+
/** Keep a RUNNING tool out of a fold so its progress stays visible. */
|
|
106
|
+
keepActiveToolsExpanded: boolean;
|
|
107
|
+
/** Keep a STREAMING thinking block expanded instead of collapsed. */
|
|
108
|
+
keepActiveThinkingExpanded: boolean;
|
|
109
|
+
/** Choose ordinary Unicode symbols or a one-column ASCII fallback. */
|
|
110
|
+
glyphStyle: GlyphStyle;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** Settings only reachable by editing settings.json by hand. */
|
|
114
|
+
export type AdvancedSettings = {
|
|
115
|
+
/** Tool names left to their own renderer. Everything else is compacted. */
|
|
116
|
+
excludeTools: string[];
|
|
117
|
+
glyphs: Partial<Glyphs>;
|
|
118
|
+
tokens: Partial<Tokens>;
|
|
119
|
+
/**
|
|
120
|
+
* Sanity cap on detail text length. NOT a display limit — rows truncate at the
|
|
121
|
+
* real viewport width. This only stops a multi-megabyte heredoc from being
|
|
122
|
+
* whitespace-collapsed, colored and measured on every repaint.
|
|
123
|
+
*/
|
|
124
|
+
maxDetailChars: number;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type Settings = BasicSettings & AdvancedSettings;
|
|
128
|
+
|
|
129
|
+
export const DEFAULT_BASIC: BasicSettings = {
|
|
130
|
+
compactToolRows: true,
|
|
131
|
+
groupToolRuns: false,
|
|
132
|
+
foldIntermediateActivity: false,
|
|
133
|
+
foldActivityOnFinalAnswer: false,
|
|
134
|
+
activitySummary: "elapsed",
|
|
135
|
+
gutter: true,
|
|
136
|
+
timer: true,
|
|
137
|
+
thinkingAsToolCall: false,
|
|
138
|
+
keepActiveToolsExpanded: false,
|
|
139
|
+
keepActiveThinkingExpanded: false,
|
|
140
|
+
glyphStyle: "unicode",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const DEFAULT_ADVANCED: AdvancedSettings = {
|
|
144
|
+
// `subagent` ships a renderer showing run id, state and a ctrl+o hint that one
|
|
145
|
+
// line cannot carry. Excluded by NAME rather than by the old "does this tool
|
|
146
|
+
// have its own renderer?" rule, which silently exempted every third-party tool
|
|
147
|
+
// and made the behavior impossible to discover from the UI.
|
|
148
|
+
excludeTools: ["subagent"],
|
|
149
|
+
glyphs: {},
|
|
150
|
+
tokens: {},
|
|
151
|
+
maxDetailChars: 4000,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const DEFAULTS: Settings = { ...DEFAULT_BASIC, ...DEFAULT_ADVANCED };
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Keys `/minimalist` may write.
|
|
158
|
+
*
|
|
159
|
+
* Derived from DEFAULT_BASIC, so adding a basic setting cannot forget to expose
|
|
160
|
+
* it, and the command can never clobber a hand-written advanced key.
|
|
161
|
+
*/
|
|
162
|
+
export const BASIC_KEYS = Object.keys(DEFAULT_BASIC) as (keyof BasicSettings)[];
|
|
163
|
+
|
|
164
|
+
export type BasicKey = keyof BasicSettings;
|
|
165
|
+
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Live configuration
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The live settings object.
|
|
172
|
+
*
|
|
173
|
+
* Shared and mutable on purpose: transcript rows resolve their appearance at
|
|
174
|
+
* RENDER time, so flipping a value re-renders existing history with no rebuild
|
|
175
|
+
* and no restart.
|
|
176
|
+
*/
|
|
177
|
+
export class Config {
|
|
178
|
+
private settings: Settings;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Values changed in this session that are NOT in settings.json.
|
|
182
|
+
*
|
|
183
|
+
* Settings are re-read every turn so an external edit applies without a
|
|
184
|
+
* restart. Without this, a change that could not be persisted (a commented
|
|
185
|
+
* settings.json, a read-only file) or the legacy migration would be silently
|
|
186
|
+
* reverted on the next turn — while the UI still claimed it was applied.
|
|
187
|
+
*/
|
|
188
|
+
private overrides = new Map<keyof Settings, Settings[keyof Settings]>();
|
|
189
|
+
|
|
190
|
+
constructor(settings: Partial<Settings> = {}) {
|
|
191
|
+
this.settings = { ...DEFAULTS, ...settings };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get<K extends keyof Settings>(key: K): Settings[K] {
|
|
195
|
+
return this.settings[key];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
set<K extends keyof Settings>(key: K, value: Settings[K]): void {
|
|
199
|
+
this.settings = { ...this.settings, [key]: value };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Set a value that must survive a re-read of settings.json. */
|
|
203
|
+
setSessionOverride<K extends keyof Settings>(key: K, value: Settings[K]): void {
|
|
204
|
+
this.overrides.set(key, value);
|
|
205
|
+
this.set(key, value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Drop an override once the file agrees with it (i.e. a later save worked). */
|
|
209
|
+
clearSessionOverride(key: keyof Settings): void {
|
|
210
|
+
this.overrides.delete(key);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
all(): Settings {
|
|
214
|
+
return this.settings;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Adopt freshly read settings, re-applying anything held only in memory. */
|
|
218
|
+
replace(settings: Settings): void {
|
|
219
|
+
this.settings = { ...settings, ...Object.fromEntries(this.overrides) } as Settings;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Preset chosen by `glyphStyle`, then per-glyph overrides. */
|
|
223
|
+
glyphs(): Glyphs {
|
|
224
|
+
return { ...GLYPH_PRESETS[this.settings.glyphStyle], ...this.settings.glyphs };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Default token map plus overrides. */
|
|
228
|
+
tokens(): Tokens {
|
|
229
|
+
return { ...DEFAULT_TOKENS, ...this.settings.tokens };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Should this tool keep Pi's own rendering?
|
|
234
|
+
*
|
|
235
|
+
* The exclusion list is the ONLY exemption, so the master switch is checked
|
|
236
|
+
* alongside it wherever claiming happens.
|
|
237
|
+
*/
|
|
238
|
+
isExcluded(toolName: string): boolean {
|
|
239
|
+
return this.settings.excludeTools.includes(toolName);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** True when this tool should be drawn as a compact row. */
|
|
243
|
+
compacts(toolName: string): boolean {
|
|
244
|
+
return this.settings.compactToolRows && !this.isExcluded(toolName);
|
|
245
|
+
}
|
|
246
|
+
}
|