@diegopetrucci/pi-extensions 0.1.74 → 0.1.78

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 (108) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +17 -12
  3. package/extensions/agent-workflow-audit/README.md +1 -1
  4. package/extensions/agent-workflow-audit/index.ts +1631 -1009
  5. package/extensions/agent-workflow-audit/package.json +1 -1
  6. package/extensions/annotate-git-diff/README.md +3 -1
  7. package/extensions/annotate-git-diff/clipboard.ts +109 -100
  8. package/extensions/annotate-git-diff/git.ts +846 -768
  9. package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
  10. package/extensions/annotate-git-diff/index.ts +712 -671
  11. package/extensions/annotate-git-diff/package.json +1 -1
  12. package/extensions/annotate-git-diff/prompt.ts +45 -45
  13. package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
  14. package/extensions/annotate-git-diff/review-server.ts +337 -311
  15. package/extensions/annotate-git-diff/types.ts +132 -132
  16. package/extensions/annotate-git-diff/ui.ts +27 -27
  17. package/extensions/annotate-git-diff/watch.ts +74 -74
  18. package/extensions/annotate-git-diff/web/app.js +1947 -1884
  19. package/extensions/annotate-git-diff/web/index.html +5 -6
  20. package/extensions/annotate-last-message/README.md +1 -1
  21. package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
  22. package/extensions/annotate-last-message/index.ts +210 -206
  23. package/extensions/annotate-last-message/package.json +1 -1
  24. package/extensions/annotate-last-message/prompt.ts +87 -66
  25. package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
  26. package/extensions/annotate-last-message/session.ts +94 -89
  27. package/extensions/annotate-last-message/theme.ts +199 -193
  28. package/extensions/annotate-last-message/types.ts +23 -23
  29. package/extensions/annotate-last-message/ui.ts +27 -25
  30. package/extensions/annotate-last-message/web/app.js +4 -7
  31. package/extensions/annotate-last-message/web/index.html +3 -3
  32. package/extensions/annotate-last-message/web/md-renderer.js +1 -1
  33. package/extensions/brrr/README.md +1 -1
  34. package/extensions/brrr/index.ts +213 -191
  35. package/extensions/brrr/package.json +1 -1
  36. package/extensions/claude-fast/README.md +5 -79
  37. package/extensions/claude-fast/index.ts +31 -289
  38. package/extensions/claude-fast/package.json +2 -3
  39. package/extensions/code-reviewer/README.md +6 -6
  40. package/extensions/code-reviewer/index.ts +1825 -1373
  41. package/extensions/code-reviewer/package.json +1 -1
  42. package/extensions/confirm-destructive/README.md +1 -1
  43. package/extensions/confirm-destructive/index.ts +50 -52
  44. package/extensions/confirm-destructive/package.json +1 -1
  45. package/extensions/context-cap/README.md +9 -9
  46. package/extensions/context-cap/index.ts +313 -305
  47. package/extensions/context-cap/package.json +2 -2
  48. package/extensions/context-inspector/index.ts +1211 -1064
  49. package/extensions/context-inspector/package.json +1 -1
  50. package/extensions/contrarian/README.md +4 -3
  51. package/extensions/contrarian/index.ts +1863 -1737
  52. package/extensions/contrarian/package.json +1 -1
  53. package/extensions/dirty-repo-guard/index.ts +37 -41
  54. package/extensions/dirty-repo-guard/package.json +1 -1
  55. package/extensions/dynamic-context-pruning/README.md +11 -43
  56. package/extensions/dynamic-context-pruning/index.ts +2131 -2105
  57. package/extensions/dynamic-context-pruning/package.json +1 -1
  58. package/extensions/fast/README.md +52 -14
  59. package/extensions/fast/index.ts +513 -317
  60. package/extensions/fast/package.json +1 -1
  61. package/extensions/git-footer/README.md +1 -1
  62. package/extensions/git-footer/index.ts +458 -411
  63. package/extensions/git-footer/package.json +2 -3
  64. package/extensions/gnosis/index.ts +180 -130
  65. package/extensions/gnosis/package.json +1 -1
  66. package/extensions/inline-bash/index.ts +87 -103
  67. package/extensions/inline-bash/package.json +1 -1
  68. package/extensions/librarian/README.md +1 -1
  69. package/extensions/librarian/index.ts +1877 -1419
  70. package/extensions/librarian/package.json +1 -1
  71. package/extensions/minimal-footer/README.md +5 -5
  72. package/extensions/minimal-footer/index.ts +987 -1012
  73. package/extensions/minimal-footer/minimal-footer.example.json +1 -1
  74. package/extensions/minimal-footer/openai-usage.ts +195 -190
  75. package/extensions/minimal-footer/package.json +1 -1
  76. package/extensions/notify/index.ts +222 -230
  77. package/extensions/notify/package.json +1 -1
  78. package/extensions/openai-fast/README.md +5 -73
  79. package/extensions/openai-fast/index.ts +32 -272
  80. package/extensions/openai-fast/package.json +2 -3
  81. package/extensions/oracle/README.md +4 -3
  82. package/extensions/oracle/index.ts +1740 -1627
  83. package/extensions/oracle/package.json +1 -1
  84. package/extensions/permission-gate/README.md +3 -0
  85. package/extensions/permission-gate/index.ts +785 -612
  86. package/extensions/permission-gate/package.json +1 -1
  87. package/extensions/permission-gate/powershell-safety.ts +840 -793
  88. package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
  89. package/extensions/project-mcp-json/README.md +199 -0
  90. package/extensions/project-mcp-json/config.ts +564 -0
  91. package/extensions/project-mcp-json/index.ts +119 -0
  92. package/extensions/project-mcp-json/package.json +38 -0
  93. package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
  94. package/extensions/quiet-tools/README.md +30 -19
  95. package/extensions/quiet-tools/index.ts +376 -291
  96. package/extensions/quiet-tools/package.json +2 -2
  97. package/extensions/review/README.md +2 -2
  98. package/extensions/review/index.ts +1745 -1872
  99. package/extensions/review/package.json +1 -1
  100. package/extensions/todo/index.ts +280 -280
  101. package/extensions/todo/package.json +1 -1
  102. package/extensions/triage-comments/README.md +6 -4
  103. package/extensions/triage-comments/index.ts +2620 -1888
  104. package/extensions/triage-comments/package.json +1 -1
  105. package/package.json +9 -6
  106. package/assets/social-preview.png +0 -0
  107. package/extensions/claude-fast/claude-fast.example.json +0 -4
  108. package/extensions/openai-fast/openai-fast.example.json +0 -4
@@ -1,8 +1,4 @@
1
- import type {
2
- Theme,
3
- ThemeBg,
4
- ThemeColor,
5
- } from "@earendil-works/pi-coding-agent";
1
+ import type { Theme, ThemeBg, ThemeColor } from "@earendil-works/pi-coding-agent";
6
2
 
7
3
  /**
8
4
  * The small public part of Pi's active theme used by the native annotation UI.
@@ -11,209 +7,219 @@ import type {
11
7
  export type AnnotateTheme = Pick<Theme, "appearance" | "colors" | "fg" | "bg">;
12
8
 
13
9
  const DARK_FALLBACK: Readonly<Record<string, string>> = {
14
- "--color-scheme": "dark",
15
- "--bg": "#0d1117",
16
- "--panel": "#151b23",
17
- "--panel-hover": "#1f242c",
18
- "--inset": "#0b1118",
19
- "--border": "#30363d",
20
- "--border-muted": "#21262d",
21
- "--text": "#f0f6fc",
22
- "--muted": "#8a8a8a",
23
- "--dim": "#585858",
24
- "--accent": "#f4c95d",
25
- "--success": "#7ee787",
26
- "--error": "#ffb3b3",
27
- "--warning": "#f4c95d",
28
- "--mdHeading": "#f4c95d",
29
- "--mdLink": "#7dd3fc",
30
- "--mdLinkUrl": "#8a8a8a",
31
- "--mdCode": "#9b7bff",
32
- "--mdCodeBlock": "inherit",
33
- "--mdCodeBlockBorder": "#6f42c1",
34
- "--mdQuote": "#8a8a8a",
35
- "--mdQuoteBorder": "#6f42c1",
36
- "--mdHr": "#585858",
37
- "--mdListBullet": "#f4c95d",
38
- "--mdBold": "inherit",
39
- "--mdItalic": "inherit",
10
+ "--color-scheme": "dark",
11
+ "--bg": "#0d1117",
12
+ "--panel": "#151b23",
13
+ "--panel-hover": "#1f242c",
14
+ "--inset": "#0b1118",
15
+ "--border": "#30363d",
16
+ "--border-muted": "#21262d",
17
+ "--text": "#f0f6fc",
18
+ "--muted": "#8a8a8a",
19
+ "--dim": "#585858",
20
+ "--accent": "#f4c95d",
21
+ "--success": "#7ee787",
22
+ "--error": "#ffb3b3",
23
+ "--warning": "#f4c95d",
24
+ "--mdHeading": "#f4c95d",
25
+ "--mdLink": "#7dd3fc",
26
+ "--mdLinkUrl": "#8a8a8a",
27
+ "--mdCode": "#9b7bff",
28
+ "--mdCodeBlock": "inherit",
29
+ "--mdCodeBlockBorder": "#6f42c1",
30
+ "--mdQuote": "#8a8a8a",
31
+ "--mdQuoteBorder": "#6f42c1",
32
+ "--mdHr": "#585858",
33
+ "--mdListBullet": "#f4c95d",
34
+ "--mdBold": "inherit",
35
+ "--mdItalic": "inherit",
40
36
  };
41
37
 
42
38
  const LIGHT_FALLBACK: Readonly<Record<string, string>> = {
43
- "--color-scheme": "light",
44
- "--bg": "#f6f8fa",
45
- "--panel": "#ffffff",
46
- "--panel-hover": "#f3f4f6",
47
- "--inset": "#f6f8fa",
48
- "--border": "#d0d7de",
49
- "--border-muted": "#afb8c1",
50
- "--text": "#1f2328",
51
- "--muted": "#57606a",
52
- "--dim": "#6e7781",
53
- "--accent": "#8250df",
54
- "--success": "#1a7f37",
55
- "--error": "#cf222e",
56
- "--warning": "#9a6700",
57
- "--mdHeading": "#9a6700",
58
- "--mdLink": "#0969da",
59
- "--mdLinkUrl": "#57606a",
60
- "--mdCode": "#8250df",
61
- "--mdCodeBlock": "#1a7f37",
62
- "--mdCodeBlockBorder": "#6e7781",
63
- "--mdQuote": "#57606a",
64
- "--mdQuoteBorder": "#6e7781",
65
- "--mdHr": "#8c959f",
66
- "--mdListBullet": "#8250df",
67
- "--mdBold": "inherit",
68
- "--mdItalic": "inherit",
39
+ "--color-scheme": "light",
40
+ "--bg": "#f6f8fa",
41
+ "--panel": "#ffffff",
42
+ "--panel-hover": "#f3f4f6",
43
+ "--inset": "#f6f8fa",
44
+ "--border": "#d0d7de",
45
+ "--border-muted": "#afb8c1",
46
+ "--text": "#1f2328",
47
+ "--muted": "#57606a",
48
+ "--dim": "#6e7781",
49
+ "--accent": "#8250df",
50
+ "--success": "#1a7f37",
51
+ "--error": "#cf222e",
52
+ "--warning": "#9a6700",
53
+ "--mdHeading": "#9a6700",
54
+ "--mdLink": "#0969da",
55
+ "--mdLinkUrl": "#57606a",
56
+ "--mdCode": "#8250df",
57
+ "--mdCodeBlock": "#1a7f37",
58
+ "--mdCodeBlockBorder": "#6e7781",
59
+ "--mdQuote": "#57606a",
60
+ "--mdQuoteBorder": "#6e7781",
61
+ "--mdHr": "#8c959f",
62
+ "--mdListBullet": "#8250df",
63
+ "--mdBold": "inherit",
64
+ "--mdItalic": "inherit",
69
65
  };
70
66
 
71
67
  const BASIC_ANSI_COLORS: Readonly<Record<number, string>> = {
72
- 0: "#000000",
73
- 1: "#800000",
74
- 2: "#008000",
75
- 3: "#808000",
76
- 4: "#000080",
77
- 5: "#800080",
78
- 6: "#008080",
79
- 7: "#c0c0c0",
80
- 8: "#808080",
81
- 9: "#ff0000",
82
- 10: "#00ff00",
83
- 11: "#ffff00",
84
- 12: "#0000ff",
85
- 13: "#ff00ff",
86
- 14: "#00ffff",
87
- 15: "#ffffff",
68
+ 0: "#000000",
69
+ 1: "#800000",
70
+ 2: "#008000",
71
+ 3: "#808000",
72
+ 4: "#000080",
73
+ 5: "#800080",
74
+ 6: "#008080",
75
+ 7: "#c0c0c0",
76
+ 8: "#808080",
77
+ 9: "#ff0000",
78
+ 10: "#00ff00",
79
+ 11: "#ffff00",
80
+ 12: "#0000ff",
81
+ 13: "#ff00ff",
82
+ 14: "#00ffff",
83
+ 15: "#ffffff",
88
84
  };
89
85
 
90
86
  const CUBE_LEVELS = [0, 95, 135, 175, 215, 255] as const;
91
87
  const SENTINEL = "X";
92
88
 
93
89
  const FOREGROUND_THEME_VARS: ReadonlyArray<{ token: ThemeColor; cssVar: string }> = [
94
- { token: "text", cssVar: "--text" },
95
- { token: "muted", cssVar: "--muted" },
96
- { token: "dim", cssVar: "--dim" },
97
- { token: "accent", cssVar: "--accent" },
98
- { token: "success", cssVar: "--success" },
99
- { token: "error", cssVar: "--error" },
100
- { token: "warning", cssVar: "--warning" },
101
- { token: "mdHeading", cssVar: "--mdHeading" },
102
- { token: "mdLink", cssVar: "--mdLink" },
103
- { token: "mdLinkUrl", cssVar: "--mdLinkUrl" },
104
- { token: "mdCode", cssVar: "--mdCode" },
105
- { token: "mdCodeBlock", cssVar: "--mdCodeBlock" },
106
- { token: "mdCodeBlockBorder", cssVar: "--mdCodeBlockBorder" },
107
- { token: "mdQuote", cssVar: "--mdQuote" },
108
- { token: "mdQuoteBorder", cssVar: "--mdQuoteBorder" },
109
- { token: "mdHr", cssVar: "--mdHr" },
110
- { token: "mdListBullet", cssVar: "--mdListBullet" },
111
- { token: "border", cssVar: "--border" },
112
- { token: "borderMuted", cssVar: "--border-muted" },
90
+ { token: "text", cssVar: "--text" },
91
+ { token: "muted", cssVar: "--muted" },
92
+ { token: "dim", cssVar: "--dim" },
93
+ { token: "accent", cssVar: "--accent" },
94
+ { token: "success", cssVar: "--success" },
95
+ { token: "error", cssVar: "--error" },
96
+ { token: "warning", cssVar: "--warning" },
97
+ { token: "mdHeading", cssVar: "--mdHeading" },
98
+ { token: "mdLink", cssVar: "--mdLink" },
99
+ { token: "mdLinkUrl", cssVar: "--mdLinkUrl" },
100
+ { token: "mdCode", cssVar: "--mdCode" },
101
+ { token: "mdCodeBlock", cssVar: "--mdCodeBlock" },
102
+ { token: "mdCodeBlockBorder", cssVar: "--mdCodeBlockBorder" },
103
+ { token: "mdQuote", cssVar: "--mdQuote" },
104
+ { token: "mdQuoteBorder", cssVar: "--mdQuoteBorder" },
105
+ { token: "mdHr", cssVar: "--mdHr" },
106
+ { token: "mdListBullet", cssVar: "--mdListBullet" },
107
+ { token: "border", cssVar: "--border" },
108
+ { token: "borderMuted", cssVar: "--border-muted" },
113
109
  ];
114
110
 
115
111
  const BACKGROUND_THEME_VARS: ReadonlyArray<{ token: ThemeBg; cssVar: string }> = [
116
- { token: "customMessageBg", cssVar: "--panel" },
117
- { token: "selectedBg", cssVar: "--panel-hover" },
118
- { token: "toolPendingBg", cssVar: "--inset" },
112
+ { token: "customMessageBg", cssVar: "--panel" },
113
+ { token: "selectedBg", cssVar: "--panel-hover" },
114
+ { token: "toolPendingBg", cssVar: "--inset" },
119
115
  ];
120
116
 
121
117
  function toHex(value: number): string {
122
- const clamped = Math.min(255, Math.max(0, Math.round(value)));
123
- return clamped.toString(16).padStart(2, "0");
118
+ const clamped = Math.min(255, Math.max(0, Math.round(value)));
119
+ return clamped.toString(16).padStart(2, "0");
124
120
  }
125
121
 
126
122
  function xterm256ToHex(index: number): string | null {
127
- if (!Number.isInteger(index) || index < 0 || index > 255) return null;
128
- if (index < 16) return BASIC_ANSI_COLORS[index] ?? null;
129
- if (index >= 232) {
130
- const value = 8 + (index - 232) * 10;
131
- const hex = toHex(value);
132
- return `#${hex}${hex}${hex}`;
133
- }
134
- const cubeIndex = index - 16;
135
- const blue = cubeIndex % 6;
136
- const green = Math.floor(cubeIndex / 6) % 6;
137
- const red = Math.floor(cubeIndex / 36) % 6;
138
- return `#${toHex(CUBE_LEVELS[red])}${toHex(CUBE_LEVELS[green])}${toHex(CUBE_LEVELS[blue])}`;
123
+ if (!Number.isInteger(index) || index < 0 || index > 255) return null;
124
+ if (index < 16) return BASIC_ANSI_COLORS[index] ?? null;
125
+ if (index >= 232) {
126
+ const value = 8 + (index - 232) * 10;
127
+ const hex = toHex(value);
128
+ return `#${hex}${hex}${hex}`;
129
+ }
130
+ const cubeIndex = index - 16;
131
+ const blue = cubeIndex % 6;
132
+ const green = Math.floor(cubeIndex / 6) % 6;
133
+ const red = Math.floor(cubeIndex / 36) % 6;
134
+ return `#${toHex(CUBE_LEVELS[red])}${toHex(CUBE_LEVELS[green])}${toHex(CUBE_LEVELS[blue])}`;
139
135
  }
140
136
 
141
137
  function colorValueToHex(value: unknown): string | null {
142
- if (typeof value !== "object" || value == null) return null;
143
- const color = value as { kind?: unknown; r?: unknown; g?: unknown; b?: unknown; index?: unknown };
144
- if (color.kind === "rgb") {
145
- const channels = [color.r, color.g, color.b];
146
- if (!channels.every((channel) => typeof channel === "number" && Number.isFinite(channel) && channel >= 0 && channel <= 255)) {
147
- return null;
148
- }
149
- return `#${toHex(color.r as number)}${toHex(color.g as number)}${toHex(color.b as number)}`;
150
- }
151
- if (color.kind === "indexed" && typeof color.index === "number") {
152
- return xterm256ToHex(color.index);
153
- }
154
- // OKLCH values are intentionally handled through theme.fg/theme.bg below.
155
- // Those public helpers account for Pi's configured terminal color mode.
156
- return null;
138
+ if (typeof value !== "object" || value == null) return null;
139
+ const color = value as { kind?: unknown; r?: unknown; g?: unknown; b?: unknown; index?: unknown };
140
+ if (color.kind === "rgb") {
141
+ const channels = [color.r, color.g, color.b];
142
+ if (
143
+ !channels.every(
144
+ (channel) =>
145
+ typeof channel === "number" && Number.isFinite(channel) && channel >= 0 && channel <= 255,
146
+ )
147
+ ) {
148
+ return null;
149
+ }
150
+ return `#${toHex(color.r as number)}${toHex(color.g as number)}${toHex(color.b as number)}`;
151
+ }
152
+ if (color.kind === "indexed" && typeof color.index === "number") {
153
+ return xterm256ToHex(color.index);
154
+ }
155
+ // OKLCH values are intentionally handled through theme.fg/theme.bg below.
156
+ // Those public helpers account for Pi's configured terminal color mode.
157
+ return null;
157
158
  }
158
159
 
159
160
  function ansiColorToHex(value: unknown, background: boolean): string | null {
160
- if (typeof value !== "string") return null;
161
- const match = new RegExp(`^\\u001b\\[([0-9;]+)m`).exec(value);
162
- if (!match) return null;
163
- const params = match[1].split(";").map(Number);
164
- const [mode, kind, first, second, third] = params;
165
- const expectedMode = background ? 48 : 38;
166
- if (mode !== expectedMode) {
167
- if (!background && ((mode >= 30 && mode <= 37) || (mode >= 90 && mode <= 97))) {
168
- return xterm256ToHex(mode >= 90 ? mode - 82 : mode - 30);
169
- }
170
- if (background && ((mode >= 40 && mode <= 47) || (mode >= 100 && mode <= 107))) {
171
- return xterm256ToHex(mode >= 100 ? mode - 92 : mode - 40);
172
- }
173
- return null;
174
- }
175
- if (kind === 2 && params.length >= 5) {
176
- const channels = [first, second, third];
177
- if (!channels.every((channel) => Number.isInteger(channel) && channel >= 0 && channel <= 255)) return null;
178
- return `#${toHex(first)}${toHex(second)}${toHex(third)}`;
179
- }
180
- if (kind === 5 && params.length >= 3) return xterm256ToHex(first);
181
- return null;
161
+ if (typeof value !== "string") return null;
162
+ const match = new RegExp(`^\\u001b\\[([0-9;]+)m`).exec(value);
163
+ if (!match) return null;
164
+ const params = match[1].split(";").map(Number);
165
+ const [mode, kind, first, second, third] = params;
166
+ const expectedMode = background ? 48 : 38;
167
+ if (mode !== expectedMode) {
168
+ if (!background && ((mode >= 30 && mode <= 37) || (mode >= 90 && mode <= 97))) {
169
+ return xterm256ToHex(mode >= 90 ? mode - 82 : mode - 30);
170
+ }
171
+ if (background && ((mode >= 40 && mode <= 47) || (mode >= 100 && mode <= 107))) {
172
+ return xterm256ToHex(mode >= 100 ? mode - 92 : mode - 40);
173
+ }
174
+ return null;
175
+ }
176
+ if (kind === 2 && params.length >= 5) {
177
+ const channels = [first, second, third];
178
+ if (!channels.every((channel) => Number.isInteger(channel) && channel >= 0 && channel <= 255))
179
+ return null;
180
+ return `#${toHex(first)}${toHex(second)}${toHex(third)}`;
181
+ }
182
+ if (kind === 5 && params.length >= 3) return xterm256ToHex(first);
183
+ return null;
182
184
  }
183
185
 
184
- function readThemeColor(theme: AnnotateTheme, token: ThemeColor | ThemeBg, background: boolean): string | null {
185
- let colors: Readonly<Record<string, unknown>> | undefined;
186
- try {
187
- colors = theme.colors as Readonly<Record<string, unknown>>;
188
- } catch {
189
- colors = undefined;
190
- }
191
-
192
- let colorValue: unknown;
193
- try {
194
- colorValue = colors?.[token];
195
- } catch {
196
- colorValue = undefined;
197
- }
198
- const fromColors = colorValueToHex(colorValue);
199
- if (fromColors != null) return fromColors;
200
-
201
- try {
202
- const styled = background
203
- ? theme.bg(token as ThemeBg, SENTINEL)
204
- : theme.fg(token as ThemeColor, SENTINEL);
205
- return ansiColorToHex(styled, background);
206
- } catch {
207
- return null;
208
- }
186
+ function readThemeColor(
187
+ theme: AnnotateTheme,
188
+ token: ThemeColor | ThemeBg,
189
+ background: boolean,
190
+ ): string | null {
191
+ let colors: Readonly<Record<string, unknown>> | undefined;
192
+ try {
193
+ colors = theme.colors as Readonly<Record<string, unknown>>;
194
+ } catch {
195
+ colors = undefined;
196
+ }
197
+
198
+ let colorValue: unknown;
199
+ try {
200
+ colorValue = colors?.[token];
201
+ } catch {
202
+ colorValue = undefined;
203
+ }
204
+ const fromColors = colorValueToHex(colorValue);
205
+ if (fromColors != null) return fromColors;
206
+
207
+ try {
208
+ const styled = background
209
+ ? theme.bg(token as ThemeBg, SENTINEL)
210
+ : theme.fg(token as ThemeColor, SENTINEL);
211
+ return ansiColorToHex(styled, background);
212
+ } catch {
213
+ return null;
214
+ }
209
215
  }
210
216
 
211
217
  function readAppearance(theme: AnnotateTheme | null | undefined): "dark" | "light" {
212
- try {
213
- return theme?.appearance === "light" ? "light" : "dark";
214
- } catch {
215
- return "dark";
216
- }
218
+ try {
219
+ return theme?.appearance === "light" ? "light" : "dark";
220
+ } catch {
221
+ return "dark";
222
+ }
217
223
  }
218
224
 
219
225
  /**
@@ -224,24 +230,24 @@ function readAppearance(theme: AnnotateTheme | null | undefined): "dark" | "ligh
224
230
  * native annotation window unreadable or prevent it from opening.
225
231
  */
226
232
  export function getThemeCssVars(theme?: AnnotateTheme | null): Record<string, string> {
227
- const vars: Record<string, string> = {
228
- ...(readAppearance(theme) === "light" ? LIGHT_FALLBACK : DARK_FALLBACK),
229
- };
230
- if (theme == null) return vars;
231
-
232
- for (const { token, cssVar } of FOREGROUND_THEME_VARS) {
233
- const color = readThemeColor(theme, token, false);
234
- if (color != null) vars[cssVar] = color;
235
- }
236
- for (const { token, cssVar } of BACKGROUND_THEME_VARS) {
237
- const color = readThemeColor(theme, token, true);
238
- if (color != null) vars[cssVar] = color;
239
- }
240
- return vars;
233
+ const vars: Record<string, string> = {
234
+ ...(readAppearance(theme) === "light" ? LIGHT_FALLBACK : DARK_FALLBACK),
235
+ };
236
+ if (theme == null) return vars;
237
+
238
+ for (const { token, cssVar } of FOREGROUND_THEME_VARS) {
239
+ const color = readThemeColor(theme, token, false);
240
+ if (color != null) vars[cssVar] = color;
241
+ }
242
+ for (const { token, cssVar } of BACKGROUND_THEME_VARS) {
243
+ const color = readThemeColor(theme, token, true);
244
+ if (color != null) vars[cssVar] = color;
245
+ }
246
+ return vars;
241
247
  }
242
248
 
243
249
  export const __testing = {
244
- ansiColorToHex,
245
- colorValueToHex,
246
- xterm256ToHex,
250
+ ansiColorToHex,
251
+ colorValueToHex,
252
+ xterm256ToHex,
247
253
  };
@@ -1,46 +1,46 @@
1
1
  export interface LastAssistantMessageLine {
2
- number: number;
3
- text: string;
2
+ number: number;
3
+ text: string;
4
4
  }
5
5
 
6
6
  export interface LastAssistantMessageSection {
7
- id: string;
8
- index: number;
9
- startLine: number;
10
- endLine: number;
11
- preview: string;
12
- text: string;
7
+ id: string;
8
+ index: number;
9
+ startLine: number;
10
+ endLine: number;
11
+ preview: string;
13
12
  }
14
13
 
15
14
  export interface LastAssistantMessageData {
16
- text: string;
17
- lines: LastAssistantMessageLine[];
18
- sections: LastAssistantMessageSection[];
15
+ lines: LastAssistantMessageLine[];
16
+ sections: LastAssistantMessageSection[];
19
17
  }
20
18
 
21
19
  export type LastAssistantMessageLookupResult =
22
- | { ok: true; data: LastAssistantMessageData }
23
- | { ok: false; code: "missing" | "incomplete" | "empty"; message: string };
20
+ | { ok: true; data: LastAssistantMessageData }
21
+ | { ok: false; code: "missing" | "incomplete" | "empty"; message: string };
24
22
 
25
23
  export interface AnnotateLastMessageInlineComment {
26
- line: number;
27
- body: string;
24
+ line: number;
25
+ body: string;
28
26
  }
29
27
 
30
28
  export interface AnnotateLastMessageSectionComment {
31
- sectionId: string;
32
- body: string;
29
+ sectionId: string;
30
+ body: string;
33
31
  }
34
32
 
35
33
  export interface AnnotateLastMessageSubmitPayload {
36
- type: "submit";
37
- overallComment: string;
38
- inlineComments: AnnotateLastMessageInlineComment[];
39
- sectionComments: AnnotateLastMessageSectionComment[];
34
+ type: "submit";
35
+ overallComment: string;
36
+ inlineComments: AnnotateLastMessageInlineComment[];
37
+ sectionComments: AnnotateLastMessageSectionComment[];
40
38
  }
41
39
 
42
40
  export interface AnnotateLastMessageCancelPayload {
43
- type: "cancel";
41
+ type: "cancel";
44
42
  }
45
43
 
46
- export type AnnotateLastMessageWindowMessage = AnnotateLastMessageSubmitPayload | AnnotateLastMessageCancelPayload;
44
+ export type AnnotateLastMessageWindowMessage =
45
+ | AnnotateLastMessageSubmitPayload
46
+ | AnnotateLastMessageCancelPayload;
@@ -10,40 +10,42 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
10
10
  const webDir = join(__dirname, "web");
11
11
 
12
12
  function escapeForInlineScript(value: string): string {
13
- return value.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
13
+ return value.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
14
14
  }
15
15
 
16
16
  function escapeInlineScriptSource(value: string): string {
17
- return value.replace(/<\/(script)/gi, "<\\/$1");
17
+ return value.replace(/<\/(script)/gi, "<\\/$1");
18
18
  }
19
19
 
20
20
  function buildThemeCssBlock(vars: Record<string, string>): string {
21
- const declarations = Object.entries(vars)
22
- .map(([key, value]) => ` ${key}: ${value};`)
23
- .join("\n");
24
- return `:root {\n${declarations}\n}`;
21
+ const declarations = Object.entries(vars)
22
+ .map(([key, value]) => ` ${key}: ${value};`)
23
+ .join("\n");
24
+ return `:root {\n${declarations}\n}`;
25
25
  }
26
26
 
27
27
  export function buildAnnotateLastMessageHtml(
28
- data: LastAssistantMessageData,
29
- theme?: AnnotateTheme,
28
+ data: LastAssistantMessageData,
29
+ theme?: AnnotateTheme,
30
30
  ): string {
31
- const templateHtml = readFileSync(join(webDir, "index.html"), "utf8");
32
- const mdRendererJs = escapeInlineScriptSource(readFileSync(join(webDir, "md-renderer.js"), "utf8"));
33
- const appJs = escapeInlineScriptSource(readFileSync(join(webDir, "app.js"), "utf8"));
34
- const payload = escapeForInlineScript(JSON.stringify(data));
35
- const themeBlock = buildThemeCssBlock(getThemeCssVars(theme));
36
- const replacements = new Map([
37
- ['"__INLINE_DATA__"', payload],
38
- ["__INLINE_MD_RENDERER_JS__", mdRendererJs],
39
- ["__INLINE_JS__", appJs],
40
- ["__INLINE_THEME__", themeBlock],
41
- ]);
31
+ const templateHtml = readFileSync(join(webDir, "index.html"), "utf8");
32
+ const mdRendererJs = escapeInlineScriptSource(
33
+ readFileSync(join(webDir, "md-renderer.js"), "utf8"),
34
+ );
35
+ const appJs = escapeInlineScriptSource(readFileSync(join(webDir, "app.js"), "utf8"));
36
+ const payload = escapeForInlineScript(JSON.stringify(data));
37
+ const themeBlock = buildThemeCssBlock(getThemeCssVars(theme));
38
+ const replacements = new Map([
39
+ ['"__INLINE_DATA__"', payload],
40
+ ["__INLINE_MD_RENDERER_JS__", mdRendererJs],
41
+ ["__INLINE_JS__", appJs],
42
+ ["__INLINE_THEME__", themeBlock],
43
+ ]);
42
44
 
43
- // Replace the original template in one pass so marker-like message data,
44
- // theme values, or runtime source is never scanned as another placeholder.
45
- return templateHtml.replace(
46
- /"__INLINE_DATA__"|__INLINE_MD_RENDERER_JS__|__INLINE_JS__|__INLINE_THEME__/g,
47
- (marker) => replacements.get(marker) ?? marker,
48
- );
45
+ // Replace the original template in one pass so marker-like message data,
46
+ // theme values, or runtime source is never scanned as another placeholder.
47
+ return templateHtml.replace(
48
+ /"__INLINE_DATA__"|__INLINE_MD_RENDERER_JS__|__INLINE_JS__|__INLINE_THEME__/g,
49
+ (marker) => replacements.get(marker) ?? marker,
50
+ );
49
51
  }
@@ -305,9 +305,6 @@ function createSectionCard(section) {
305
305
  meta.textContent = lineRange;
306
306
  card.append(meta);
307
307
 
308
- // Render the exact source lines in this section. Do not parse section.text
309
- // independently: its opening fence may be in an earlier section separated
310
- // by a blank line.
311
308
  const preview = document.createElement("div");
312
309
  preview.className = "section-preview markdown-content";
313
310
 
@@ -316,9 +313,7 @@ function createSectionCard(section) {
316
313
  const sourceLines =
317
314
  startIndex >= 0 && endIndex >= startIndex && endIndex <= messageData.lines.length
318
315
  ? messageData.lines.slice(startIndex, endIndex).map((line) => line.text)
319
- : typeof section.text === "string"
320
- ? section.text.split("\n")
321
- : [];
316
+ : [];
322
317
  for (let idx = 0; idx < sourceLines.length; idx++) {
323
318
  const lineDiv = document.createElement("div");
324
319
  const sourceIndex = startIndex >= 0 ? startIndex + idx : -1;
@@ -342,7 +337,9 @@ function createSectionCard(section) {
342
337
  function renderMessageLines() {
343
338
  elements.messageLines.replaceChildren();
344
339
  for (let i = 0; i < messageData.lines.length; i++) {
345
- elements.messageLines.append(createLineRow(messageData.lines[i], fullMessageFenceState[i].lineType));
340
+ elements.messageLines.append(
341
+ createLineRow(messageData.lines[i], fullMessageFenceState[i].lineType),
342
+ );
346
343
  }
347
344
  }
348
345
 
@@ -10,8 +10,8 @@
10
10
  * overrides semantic and Markdown colours from ctx.ui.theme. When Pi's theme
11
11
  * cannot provide a token, the matching dark/light fallback remains readable.
12
12
  *
13
- * The --md* fallback values below are kept in sync with the FALLBACK
14
- * object in theme.ts so this template renders correctly when the injected
13
+ * The --md* fallback values below are kept in sync with DARK_FALLBACK
14
+ * in theme.ts so this template renders correctly when the injected
15
15
  * theme block is absent (e.g. opened standalone in a browser for debugging).
16
16
  */
17
17
  :root {
@@ -31,7 +31,7 @@
31
31
  --error: #ffb3b3;
32
32
  --warning: #f4c95d;
33
33
 
34
- /* Markdown token colours — kept in sync with FALLBACK in theme.ts */
34
+ /* Markdown token colours — kept in sync with DARK_FALLBACK in theme.ts */
35
35
  --mdHeading: #f4c95d;
36
36
  --mdLink: #7dd3fc;
37
37
  --mdLinkUrl: #8a8a8a;
@@ -1,7 +1,7 @@
1
1
  /* global globalThis */
2
2
 
3
3
  /**
4
- * TLH annotate-last-message: pure markdown line parser.
4
+ * annotate-last-message: pure markdown line parser.
5
5
  *
6
6
  * Single source of truth for the parsing logic. ui.ts reads this file and
7
7
  * inlines it as a dedicated script before app.js, so globalThis.__tlhMdRenderer
@@ -83,4 +83,4 @@ Example:
83
83
 
84
84
  - Hooks the `agent_settled` event so automatic retries, compaction retries, and queued follow-ups do not trigger intermediate notifications.
85
85
  - The extension sends directly to the brrr webhook; it does not require the `brrr` CLI at runtime.
86
- - By default, notifications are skipped unless the Mac has been idle for at least 20 seconds.
86
+ - Idle skip is macOS-only. By default, notifications are skipped unless the Mac has been idle for at least 20 seconds. Non-macOS always notifies.