@quandev104/pi-style 0.1.1

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +193 -0
  4. package/dist/extensions/pi-style.js +7897 -0
  5. package/dist/extensions/pi-style.js.map +1 -0
  6. package/extension-src/pi-style/app/command-service.ts +244 -0
  7. package/extension-src/pi-style/app/commands.ts +12 -0
  8. package/extension-src/pi-style/app/config-storage.ts +98 -0
  9. package/extension-src/pi-style/app/doctor.ts +53 -0
  10. package/extension-src/pi-style/app/index.ts +322 -0
  11. package/extension-src/pi-style/app/providers.ts +268 -0
  12. package/extension-src/pi-style/app/render-scheduler.ts +48 -0
  13. package/extension-src/pi-style/app/runtime.ts +404 -0
  14. package/extension-src/pi-style/app/snapshot.ts +15 -0
  15. package/extension-src/pi-style/domain/capabilities.ts +34 -0
  16. package/extension-src/pi-style/domain/config-authorization.ts +20 -0
  17. package/extension-src/pi-style/domain/config-diagnostics.ts +20 -0
  18. package/extension-src/pi-style/domain/config-diff.ts +30 -0
  19. package/extension-src/pi-style/domain/config-migrations.ts +52 -0
  20. package/extension-src/pi-style/domain/config-normalization.ts +567 -0
  21. package/extension-src/pi-style/domain/config-presets.ts +51 -0
  22. package/extension-src/pi-style/domain/config-types.ts +115 -0
  23. package/extension-src/pi-style/domain/providers.ts +37 -0
  24. package/extension-src/pi-style/domain/status-presets.ts +60 -0
  25. package/extension-src/pi-style/domain/status-renderer.ts +142 -0
  26. package/extension-src/pi-style/domain/status.ts +430 -0
  27. package/extension-src/pi-style/domain/theme.ts +232 -0
  28. package/extension-src/pi-style/features/.gitkeep +0 -0
  29. package/extension-src/pi-style/features/editor/index.ts +304 -0
  30. package/extension-src/pi-style/features/messages/boxed-block.ts +74 -0
  31. package/extension-src/pi-style/features/messages/index.ts +145 -0
  32. package/extension-src/pi-style/features/messages/special-blocks.ts +281 -0
  33. package/extension-src/pi-style/features/startup/index.ts +564 -0
  34. package/extension-src/pi-style/features/startup/logo.ts +151 -0
  35. package/extension-src/pi-style/features/status-line/index.ts +319 -0
  36. package/extension-src/pi-style/features/tools/boxed/bash.ts +347 -0
  37. package/extension-src/pi-style/features/tools/boxed/edit.ts +113 -0
  38. package/extension-src/pi-style/features/tools/boxed/fallback.ts +67 -0
  39. package/extension-src/pi-style/features/tools/boxed/find.ts +65 -0
  40. package/extension-src/pi-style/features/tools/boxed/grep.ts +108 -0
  41. package/extension-src/pi-style/features/tools/boxed/index.ts +64 -0
  42. package/extension-src/pi-style/features/tools/boxed/ls.ts +65 -0
  43. package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +190 -0
  44. package/extension-src/pi-style/features/tools/boxed/read.ts +204 -0
  45. package/extension-src/pi-style/features/tools/boxed/session-config.ts +45 -0
  46. package/extension-src/pi-style/features/tools/boxed/shared.ts +157 -0
  47. package/extension-src/pi-style/features/tools/boxed/write.ts +89 -0
  48. package/extension-src/pi-style/features/tools/index.ts +480 -0
  49. package/extension-src/pi-style/pi/commands.ts +17 -0
  50. package/extension-src/pi-style/pi/compatibility-coordinator.ts +195 -0
  51. package/extension-src/pi-style/pi/compatibility-probe.ts +645 -0
  52. package/extension-src/pi-style/pi/compatibility-registry.ts +329 -0
  53. package/extension-src/pi-style/pi/config-host.ts +52 -0
  54. package/extension-src/pi-style/pi/config-session.ts +105 -0
  55. package/extension-src/pi-style/pi/index.ts +77 -0
  56. package/extension-src/pi-style/pi/operational-state.ts +29 -0
  57. package/extension-src/pi-style/pi/session-coordinator.ts +187 -0
  58. package/extension-src/pi-style/pi/session-usage.ts +62 -0
  59. package/extension-src/pi-style/pi/startup-resources.ts +70 -0
  60. package/extension-src/pi-style/shared/.gitkeep +0 -0
  61. package/extension-src/pi-style/shared/ansi.ts +386 -0
  62. package/extension-src/pi-style/shared/box.ts +805 -0
  63. package/extension-src/pi-style/shared/disposable-store.ts +28 -0
  64. package/extension-src/pi-style/shared/elapsed.ts +88 -0
  65. package/extension-src/pi-style/shared/render-budget.ts +367 -0
  66. package/extension-src/pi-style/shared/split-diff.ts +692 -0
  67. package/extension-src/pi-style/shared/theme-extras.ts +292 -0
  68. package/package.json +68 -0
  69. package/themes/.gitkeep +0 -0
@@ -0,0 +1,281 @@
1
+ // Special message-block presentation: compaction, skill invocation, branch
2
+ // summary, and extension custom (MCP) messages, rendered as boxed blocks.
3
+ //
4
+ // These patches are pure delegates: the compatibility probe installs them
5
+ // through its reversible, generation-tracked wrapper and restores the native
6
+ // identity on shutdown. The delegate receives the native method and falls back
7
+ // to it whenever the theme or component shape is unavailable.
8
+
9
+ import { keyText } from "@earendil-works/pi-coding-agent";
10
+ import { Markdown, type MarkdownTheme } from "@earendil-works/pi-tui";
11
+ import type { BoxTheme } from "../../shared/box.js";
12
+ import { setFullTheme } from "../../shared/theme-extras.js";
13
+ import { renderBoxedMessageBlock } from "./boxed-block.js";
14
+
15
+ let cachedTheme: BoxTheme | undefined;
16
+
17
+ export function setSpecialBlockTheme(theme: BoxTheme | undefined): void {
18
+ cachedTheme = theme;
19
+ if (theme) setFullTheme(theme);
20
+ }
21
+
22
+ export type SpecialBlockSubtype =
23
+ | "native-compaction-message"
24
+ | "native-branch-message"
25
+ | "native-skill-message"
26
+ | "native-custom-message";
27
+
28
+ export type SpecialBlockCtor = { prototype?: unknown };
29
+
30
+ export interface SpecialBlockCtors {
31
+ CompactionSummaryMessageComponent?: SpecialBlockCtor;
32
+ SkillInvocationMessageComponent?: SpecialBlockCtor;
33
+ BranchSummaryMessageComponent?: SpecialBlockCtor;
34
+ CustomMessageComponent?: SpecialBlockCtor;
35
+ }
36
+
37
+ /** Structural view of the native message components as used by the patches. */
38
+ interface MessageBlockInstance {
39
+ message?: {
40
+ tokensBefore?: unknown;
41
+ summary?: unknown;
42
+ customType?: unknown;
43
+ content?: unknown;
44
+ };
45
+ skillBlock?: { name?: unknown; content?: unknown };
46
+ expanded?: unknown;
47
+ _expanded?: unknown;
48
+ markdownTheme?: unknown;
49
+ box?: { clear(): void; addChild(child: unknown): void; setBgFn?(fn: (text: string) => string): void };
50
+ customComponent?: unknown;
51
+ customRenderer?: unknown;
52
+ clear?(): void;
53
+ addChild(child: unknown): void;
54
+ removeChild(child: unknown): void;
55
+ setBgFn?(fn: (text: string) => string): void;
56
+ }
57
+
58
+ const EXPAND_HINT = "Ctrl+O to expand";
59
+
60
+ function expandHint(): string {
61
+ try {
62
+ const text = keyText("app.tools.expand");
63
+ return text ? `${text} to expand` : EXPAND_HINT;
64
+ } catch {
65
+ return EXPAND_HINT;
66
+ }
67
+ }
68
+
69
+ function createMarkdownBody(
70
+ text: string,
71
+ markdownTheme: MarkdownTheme | undefined,
72
+ theme: BoxTheme,
73
+ ): (contentWidth: number) => string[] {
74
+ const md = new Markdown(text || "", 0, 0, markdownTheme as MarkdownTheme, {
75
+ color: (t: string) => theme.fg("customMessageText", t),
76
+ });
77
+ return (contentWidth: number) => md.render(contentWidth);
78
+ }
79
+
80
+ /**
81
+ * Neutralize the native customMessageBg fill for boxed blocks: the boxed block
82
+ * owns its visual boundary, so the parent background is removed (the box sits
83
+ * directly on the terminal/page background).
84
+ */
85
+ function neutralizeMessageBlockBackground(target: { setBgFn?(fn: (text: string) => string): void } | undefined): void {
86
+ if (!target) return;
87
+ if (typeof target.setBgFn === "function") target.setBgFn((text) => text);
88
+ }
89
+
90
+ function patchCompaction(instance: MessageBlockInstance, _original: () => void, theme: BoxTheme): boolean {
91
+ const tokensBefore = instance.message?.tokensBefore;
92
+ if (tokensBefore == null) return false;
93
+
94
+ if (typeof instance.clear === "function") instance.clear();
95
+
96
+ const expanded = Boolean(instance.expanded);
97
+ const summary = typeof instance.message?.summary === "string" ? instance.message.summary : "";
98
+ const markdownTheme = instance.markdownTheme as MarkdownTheme | undefined;
99
+
100
+ neutralizeMessageBlockBackground(instance as { setBgFn?(fn: (text: string) => string): void });
101
+
102
+ const body = expanded && summary && markdownTheme ? createMarkdownBody(summary, markdownTheme, theme) : () => [];
103
+
104
+ const tokenStr = Number(tokensBefore).toLocaleString();
105
+ neutralizeMessageBlockBackground(instance as { setBgFn?(fn: (text: string) => string): void });
106
+ const block = renderBoxedMessageBlock(theme, {
107
+ kind: "Compaction",
108
+ title: `${tokenStr} tokens`,
109
+ ...(expanded ? {} : { right: `(${expandHint()})` }),
110
+ body,
111
+ icon: "⊟",
112
+ hasDivider: expanded,
113
+ });
114
+ instance.addChild(block);
115
+ return true;
116
+ }
117
+
118
+ function patchSkill(instance: MessageBlockInstance, _original: () => void, theme: BoxTheme): boolean {
119
+ const skillName = instance.skillBlock?.name;
120
+ if (typeof skillName !== "string" || !skillName) return false;
121
+
122
+ if (typeof instance.clear === "function") instance.clear();
123
+
124
+ const expanded = Boolean(instance.expanded);
125
+ const content = typeof instance.skillBlock?.content === "string" ? instance.skillBlock.content : "";
126
+ const markdownTheme = instance.markdownTheme as MarkdownTheme | undefined;
127
+
128
+ neutralizeMessageBlockBackground(instance as { setBgFn?(fn: (text: string) => string): void });
129
+
130
+ const body = expanded && content && markdownTheme ? createMarkdownBody(content, markdownTheme, theme) : () => [];
131
+
132
+ const block = renderBoxedMessageBlock(theme, {
133
+ kind: "Skill",
134
+ title: skillName,
135
+ ...(expanded ? {} : { right: `(${expandHint()})` }),
136
+ body,
137
+ icon: "⊟",
138
+ hasDivider: expanded,
139
+ });
140
+ instance.addChild(block);
141
+ return true;
142
+ }
143
+
144
+ function patchBranch(instance: MessageBlockInstance, _original: () => void, theme: BoxTheme): boolean {
145
+ if (instance.message == null) return false;
146
+
147
+ if (typeof instance.clear === "function") instance.clear();
148
+
149
+ const expanded = Boolean(instance.expanded);
150
+ const summary = typeof instance.message?.summary === "string" ? instance.message.summary : "";
151
+ const markdownTheme = instance.markdownTheme as MarkdownTheme | undefined;
152
+
153
+ neutralizeMessageBlockBackground(instance as { setBgFn?(fn: (text: string) => string): void });
154
+
155
+ const body = expanded && summary && markdownTheme ? createMarkdownBody(summary, markdownTheme, theme) : () => [];
156
+
157
+ const block = renderBoxedMessageBlock(theme, {
158
+ kind: "Branch",
159
+ ...(expanded ? {} : { right: `(${expandHint()})` }),
160
+ body,
161
+ icon: "⊟",
162
+ hasDivider: expanded,
163
+ });
164
+ instance.addChild(block);
165
+ return true;
166
+ }
167
+
168
+ function attachCustomMessageBlock(instance: MessageBlockInstance, block: unknown): boolean {
169
+ if (instance.box && typeof instance.box.clear === "function" && typeof instance.box.addChild === "function") {
170
+ instance.addChild(instance.box);
171
+ instance.box.clear();
172
+ instance.box.addChild(block);
173
+ return true;
174
+ }
175
+ instance.customComponent = block;
176
+ instance.addChild(instance.customComponent);
177
+ return true;
178
+ }
179
+
180
+ function patchCustomMessage(instance: MessageBlockInstance, _original: () => void, theme: BoxTheme): boolean {
181
+ // Remove previous content component
182
+ if (instance.customComponent) {
183
+ instance.removeChild(instance.customComponent);
184
+ instance.customComponent = undefined;
185
+ }
186
+ if (instance.box) instance.removeChild(instance.box);
187
+
188
+ // The boxed shell owns its boundary; drop the native customMessageBg fill.
189
+ neutralizeMessageBlockBackground(instance.box);
190
+ neutralizeMessageBlockBackground(instance as { setBgFn?(fn: (text: string) => string): void });
191
+
192
+ const rawCustomType = instance.message?.customType;
193
+ const customType = typeof rawCustomType === "string" ? rawCustomType : "Custom";
194
+
195
+ // Try custom renderer first, but keep the special block shell/background owned here.
196
+ if (typeof instance.customRenderer === "function") {
197
+ try {
198
+ const component = (instance.customRenderer as (message: unknown, options: object, theme: unknown) => unknown)(
199
+ instance.message,
200
+ { expanded: instance._expanded },
201
+ theme,
202
+ );
203
+ if (component && typeof (component as { render?: unknown }).render === "function") {
204
+ const block = renderBoxedMessageBlock(theme, {
205
+ kind: "Custom",
206
+ title: customType,
207
+ body: (contentWidth) => (component as { render(width: number): string[] }).render(contentWidth),
208
+ icon: "⊟",
209
+ hasDivider: "auto",
210
+ cache: false,
211
+ });
212
+ attachCustomMessageBlock(instance, block);
213
+ return true;
214
+ }
215
+ } catch {
216
+ // Fall through to default rendering
217
+ }
218
+ }
219
+
220
+ // Default rendering: use boxed message block
221
+
222
+ // Extract text content
223
+ const rawContent = instance.message?.content;
224
+ let text: string;
225
+ if (typeof rawContent === "string") {
226
+ text = rawContent;
227
+ } else if (Array.isArray(rawContent)) {
228
+ text = rawContent
229
+ .filter((c: unknown) => {
230
+ if (!c || typeof c !== "object") return false;
231
+ return (c as { type?: unknown }).type === "text";
232
+ })
233
+ .map((c) => String((c as { text?: unknown }).text ?? ""))
234
+ .join("\n");
235
+ } else {
236
+ text = "";
237
+ }
238
+
239
+ const markdownTheme = instance.markdownTheme as MarkdownTheme | undefined;
240
+
241
+ const body = text && markdownTheme ? createMarkdownBody(text, markdownTheme, theme) : () => [];
242
+
243
+ const block = renderBoxedMessageBlock(theme, {
244
+ kind: "Custom",
245
+ title: customType,
246
+ body,
247
+ icon: "⊟",
248
+ hasDivider: Boolean(text),
249
+ });
250
+
251
+ attachCustomMessageBlock(instance, block);
252
+ return true;
253
+ }
254
+
255
+ /**
256
+ * Delegate invoked by the compatibility probe's wrapper for each special block
257
+ * method. Falls back to the native implementation when the theme cache is
258
+ * empty or the component shape is unsupported.
259
+ */
260
+ export function renderSpecialMessageBlock(
261
+ subtype: SpecialBlockSubtype,
262
+ original: unknown,
263
+ thisArg: object,
264
+ args: unknown[],
265
+ ): unknown {
266
+ const instance = thisArg as unknown as MessageBlockInstance;
267
+ const base = original as (this: object, ...rest: unknown[]) => unknown;
268
+ const applyBase = () => base.apply(thisArg, args);
269
+ const theme = cachedTheme;
270
+ if (!theme) return applyBase();
271
+ try {
272
+ let handled = false;
273
+ if (subtype === "native-compaction-message") handled = patchCompaction(instance, applyBase, theme);
274
+ else if (subtype === "native-skill-message") handled = patchSkill(instance, applyBase, theme);
275
+ else if (subtype === "native-branch-message") handled = patchBranch(instance, applyBase, theme);
276
+ else if (subtype === "native-custom-message") handled = patchCustomMessage(instance, applyBase, theme);
277
+ return handled ? undefined : applyBase();
278
+ } catch {
279
+ return applyBase();
280
+ }
281
+ }