@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.
- package/LICENSE +2 -0
- package/README.md +17 -12
- package/extensions/agent-workflow-audit/README.md +1 -1
- package/extensions/agent-workflow-audit/index.ts +1631 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +846 -768
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/index.html +3 -3
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +6 -6
- package/extensions/code-reviewer/index.ts +1825 -1373
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +50 -52
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/README.md +9 -9
- package/extensions/context-cap/index.ts +313 -305
- package/extensions/context-cap/package.json +2 -2
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/README.md +4 -3
- package/extensions/contrarian/index.ts +1863 -1737
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +37 -41
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +11 -43
- package/extensions/dynamic-context-pruning/index.ts +2131 -2105
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +52 -14
- package/extensions/fast/index.ts +513 -317
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +87 -103
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +1877 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +987 -1012
- package/extensions/minimal-footer/minimal-footer.example.json +1 -1
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/README.md +4 -3
- package/extensions/oracle/index.ts +1740 -1627
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +3 -0
- package/extensions/permission-gate/index.ts +785 -612
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/README.md +30 -19
- package/extensions/quiet-tools/index.ts +376 -291
- package/extensions/quiet-tools/package.json +2 -2
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +280 -280
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/README.md +6 -4
- package/extensions/triage-comments/index.ts +2620 -1888
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +9 -6
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
package/extensions/brrr/index.ts
CHANGED
|
@@ -13,271 +13,293 @@ import { execFile } from "node:child_process";
|
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
14
14
|
import { basename, join } from "node:path";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
CONFIG_DIR_NAME,
|
|
18
|
+
getAgentDir,
|
|
19
|
+
type ExtensionAPI,
|
|
20
|
+
type ExtensionCommandContext,
|
|
21
|
+
type SessionEntry,
|
|
22
|
+
} from "@earendil-works/pi-coding-agent";
|
|
17
23
|
|
|
18
24
|
const execFileAsync = promisify(execFile);
|
|
19
25
|
|
|
20
26
|
interface BrrrConfig {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
onlyWhenInteractive: boolean;
|
|
29
|
+
webhook: string;
|
|
30
|
+
idleSeconds: number | null;
|
|
31
|
+
title: string;
|
|
32
|
+
message: string;
|
|
33
|
+
includeLastAssistantMessage: boolean;
|
|
34
|
+
sound: string;
|
|
35
|
+
openUrl: string;
|
|
36
|
+
imageUrl: string;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
interface BrrrPayload {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
open_url?: string;
|
|
40
|
-
image_url?: string;
|
|
40
|
+
message: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
sound?: string;
|
|
43
|
+
open_url?: string;
|
|
44
|
+
image_url?: string;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
type ProjectConfigContext = {
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
cwd: string;
|
|
49
|
+
isProjectTrusted?: () => boolean;
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
const DEFAULT_CONFIG: BrrrConfig = {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
enabled: true,
|
|
54
|
+
onlyWhenInteractive: true,
|
|
55
|
+
webhook: "$BRRR_WEBHOOK_URL",
|
|
56
|
+
idleSeconds: 20,
|
|
57
|
+
title: "Pi finished",
|
|
58
|
+
message: "Pi finished working in '{project}'.",
|
|
59
|
+
includeLastAssistantMessage: true,
|
|
60
|
+
sound: "",
|
|
61
|
+
openUrl: "",
|
|
62
|
+
imageUrl: "",
|
|
59
63
|
};
|
|
60
64
|
|
|
61
65
|
function readConfigFile(path: string): Partial<BrrrConfig> {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
if (!existsSync(path)) return {};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(readFileSync(path, "utf-8")) as Partial<BrrrConfig>;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(`Warning: Could not parse ${path}: ${error}`);
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
function mergeConfig(base: BrrrConfig, overrides: Partial<BrrrConfig>): BrrrConfig {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
...overrides,
|
|
80
|
+
};
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
|
|
80
|
-
|
|
84
|
+
return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
function loadConfig(ctx: ProjectConfigContext): BrrrConfig {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "brrr.json"));
|
|
89
|
+
const projectConfig = canReadProjectConfig(ctx)
|
|
90
|
+
? readConfigFile(join(ctx.cwd, CONFIG_DIR_NAME, "brrr.json"))
|
|
91
|
+
: {};
|
|
92
|
+
return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
function resolveWebhook(raw: string): string | undefined {
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
const value = raw.trim();
|
|
97
|
+
if (!value) return undefined;
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
const envMatch = value.match(/^\$([A-Z0-9_]+)$/) ?? value.match(/^\$\{([A-Z0-9_]+)\}$/);
|
|
100
|
+
if (envMatch) return process.env[envMatch[1]]?.trim() || undefined;
|
|
97
101
|
|
|
98
|
-
|
|
102
|
+
return value;
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
function isBrrrWebhookUrl(value: string): boolean {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
try {
|
|
107
|
+
const url = new URL(value);
|
|
108
|
+
return (
|
|
109
|
+
url.protocol === "https:" &&
|
|
110
|
+
(url.hostname === "api.brrr.now" || url.hostname === "dev.api.brrr.now") &&
|
|
111
|
+
/^\/v1\/br_[A-Za-z0-9_]+$/.test(url.pathname)
|
|
112
|
+
);
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
async function getMacOsIdleSeconds(): Promise<number | null> {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
if (process.platform !== "darwin") return null;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const { stdout } = await execFileAsync("ioreg", ["-c", "IOHIDSystem"], {
|
|
123
|
+
timeout: 1000,
|
|
124
|
+
maxBuffer: 1024 * 1024,
|
|
125
|
+
});
|
|
126
|
+
const match =
|
|
127
|
+
stdout.match(/HIDIdleTime"\s*=\s*(\d+)/) ?? stdout.match(/HIDIdleTime\s+=\s+(\d+)/);
|
|
128
|
+
if (!match) return null;
|
|
129
|
+
|
|
130
|
+
const idleNanoseconds = Number(match[1]);
|
|
131
|
+
if (!Number.isFinite(idleNanoseconds)) return null;
|
|
132
|
+
|
|
133
|
+
return Math.floor(idleNanoseconds / 1_000_000_000);
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
async function shouldSkipForIdleThreshold(idleSeconds: number | null): Promise<boolean> {
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
if (idleSeconds === null) return false;
|
|
141
|
+
if (!Number.isFinite(idleSeconds) || idleSeconds < 0) return false;
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
const currentIdleSeconds = await getMacOsIdleSeconds();
|
|
144
|
+
if (currentIdleSeconds === null) return false;
|
|
140
145
|
|
|
141
|
-
|
|
146
|
+
return currentIdleSeconds < idleSeconds;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
function formatTemplate(template: string, cwd: string): string {
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
const project = basename(cwd || process.cwd());
|
|
151
|
+
return template.replaceAll("{project}", project).replaceAll("{cwd}", cwd);
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
function extractTextContent(content: unknown): string {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
155
|
+
if (typeof content === "string") return content;
|
|
156
|
+
if (!Array.isArray(content)) return "";
|
|
157
|
+
|
|
158
|
+
const parts: string[] = [];
|
|
159
|
+
for (const part of content) {
|
|
160
|
+
if (typeof part === "string") {
|
|
161
|
+
parts.push(part);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (typeof part !== "object" || part === null) continue;
|
|
165
|
+
|
|
166
|
+
const typedPart = part as { type?: unknown; text?: unknown };
|
|
167
|
+
if (typedPart.type !== undefined && typedPart.type !== "text") continue;
|
|
168
|
+
if (typeof typedPart.text === "string") parts.push(typedPart.text);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return parts.join("\n");
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
function lastAssistantMessage(messages: readonly unknown[]): string | undefined {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
176
|
+
const message = messages[index];
|
|
177
|
+
if (typeof message !== "object" || message === null) continue;
|
|
178
|
+
if ((message as { role?: unknown }).role !== "assistant") continue;
|
|
174
179
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
const text = extractTextContent((message as { content?: unknown }).content).trim();
|
|
181
|
+
return text || undefined;
|
|
182
|
+
}
|
|
178
183
|
|
|
179
|
-
|
|
184
|
+
return undefined;
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
type SessionMessageSource = {
|
|
183
|
-
|
|
188
|
+
buildContextEntries(): SessionEntry[];
|
|
184
189
|
};
|
|
185
190
|
|
|
186
191
|
function sessionMessages(sessionManager: SessionMessageSource | undefined): readonly unknown[] {
|
|
187
|
-
|
|
192
|
+
if (!sessionManager) return [];
|
|
188
193
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
194
|
+
return sessionManager
|
|
195
|
+
.buildContextEntries()
|
|
196
|
+
.flatMap((entry: SessionEntry) => (entry.type === "message" ? [entry.message] : []));
|
|
192
197
|
}
|
|
193
198
|
|
|
194
199
|
function resolveAssistantMessage(
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
sessionManager: SessionMessageSource | undefined,
|
|
201
|
+
fallbackMessages: readonly unknown[],
|
|
197
202
|
): string | undefined {
|
|
198
|
-
|
|
203
|
+
return (
|
|
204
|
+
lastAssistantMessage(sessionMessages(sessionManager)) ?? lastAssistantMessage(fallbackMessages)
|
|
205
|
+
);
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
function truncateMessage(value: string): string {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
const trimmed = value.trim();
|
|
210
|
+
if (trimmed.length <= 800) return trimmed;
|
|
211
|
+
return `${trimmed.slice(0, 797).trimEnd()}...`;
|
|
205
212
|
}
|
|
206
213
|
|
|
207
|
-
async function sendBrrr(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
214
|
+
async function sendBrrr(
|
|
215
|
+
webhook: string,
|
|
216
|
+
payload: BrrrPayload,
|
|
217
|
+
): Promise<{ ok: boolean; error?: string }> {
|
|
218
|
+
const controller = new AbortController();
|
|
219
|
+
const timeout = setTimeout(() => controller.abort(), 2_000);
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const body: Record<string, string> = { message: payload.message };
|
|
223
|
+
if (payload.title) body.title = payload.title;
|
|
224
|
+
if (payload.sound) body.sound = payload.sound;
|
|
225
|
+
if (payload.open_url) body.open_url = payload.open_url;
|
|
226
|
+
if (payload.image_url) body.image_url = payload.image_url;
|
|
227
|
+
|
|
228
|
+
const response = await fetch(webhook, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: {
|
|
231
|
+
"content-type": "application/json",
|
|
232
|
+
accept: "application/json",
|
|
233
|
+
},
|
|
234
|
+
body: JSON.stringify(body),
|
|
235
|
+
signal: controller.signal,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (response.status === 202) return { ok: true };
|
|
239
|
+
return { ok: false, error: `Unexpected response status ${response.status}.` };
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
error: error instanceof Error ? error.message : "Unknown webhook failure.",
|
|
244
|
+
};
|
|
245
|
+
} finally {
|
|
246
|
+
clearTimeout(timeout);
|
|
247
|
+
}
|
|
237
248
|
}
|
|
238
249
|
|
|
239
|
-
function notify(
|
|
240
|
-
|
|
250
|
+
function notify(
|
|
251
|
+
ctx: ExtensionCommandContext,
|
|
252
|
+
message: string,
|
|
253
|
+
type: "info" | "warning" | "error" = "info",
|
|
254
|
+
): void {
|
|
255
|
+
if (ctx.hasUI) ctx.ui.notify(message, type);
|
|
241
256
|
}
|
|
242
257
|
|
|
243
258
|
function describeConfig(config: BrrrConfig, webhook: string | undefined): string {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
259
|
+
const webhookStatus = webhook
|
|
260
|
+
? isBrrrWebhookUrl(webhook)
|
|
261
|
+
? "configured"
|
|
262
|
+
: "invalid"
|
|
263
|
+
: "missing";
|
|
264
|
+
const idle = config.idleSeconds === null ? "off" : `${config.idleSeconds}s`;
|
|
265
|
+
return `brrr is ${config.enabled ? "enabled" : "disabled"}; webhook ${webhookStatus}; idle threshold ${idle}.`;
|
|
247
266
|
}
|
|
248
267
|
|
|
249
268
|
export default function brrrExtension(pi: ExtensionAPI) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
269
|
+
pi.registerCommand("brrr", {
|
|
270
|
+
description: "Show brrr notification status",
|
|
271
|
+
handler: async (_args, ctx) => {
|
|
272
|
+
const config = loadConfig(ctx);
|
|
273
|
+
notify(ctx, describeConfig(config, resolveWebhook(config.webhook)));
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
pi.on("agent_settled", async (event, ctx) => {
|
|
278
|
+
const config = loadConfig(ctx);
|
|
279
|
+
if (!config.enabled) return;
|
|
280
|
+
if (config.onlyWhenInteractive && !ctx.hasUI) return;
|
|
281
|
+
|
|
282
|
+
const webhook = resolveWebhook(config.webhook);
|
|
283
|
+
if (!webhook || !isBrrrWebhookUrl(webhook)) return;
|
|
284
|
+
if (await shouldSkipForIdleThreshold(config.idleSeconds)) return;
|
|
285
|
+
|
|
286
|
+
const assistantMessage = config.includeLastAssistantMessage
|
|
287
|
+
? resolveAssistantMessage(
|
|
288
|
+
ctx.sessionManager,
|
|
289
|
+
(event as { messages?: readonly unknown[] }).messages ?? [],
|
|
290
|
+
)
|
|
291
|
+
: undefined;
|
|
292
|
+
const message = truncateMessage(assistantMessage || formatTemplate(config.message, ctx.cwd));
|
|
293
|
+
const result = await sendBrrr(webhook, {
|
|
294
|
+
title: formatTemplate(config.title, ctx.cwd),
|
|
295
|
+
message,
|
|
296
|
+
sound: config.sound.trim() || undefined,
|
|
297
|
+
open_url: config.openUrl.trim() || undefined,
|
|
298
|
+
image_url: config.imageUrl.trim() || undefined,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (!result.ok && result.error) {
|
|
302
|
+
console.error(`brrr notification failed: ${result.error}`);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
283
305
|
}
|
|
@@ -1,88 +1,21 @@
|
|
|
1
1
|
# claude-fast
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Deprecated standalone leftover. This package no longer enables Claude Fast mode, registers `/claude-fast`, or reads `claude-fast.json`. On startup it clears a leftover `claude-fast` footer status and removes `fast-mode-2026-02-01` from the current model header. It does not inject Fast fields into requests.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Fast mode lives in the unified [`fast`](../fast) extension. Install `@diegopetrucci/pi-fast`, or install the collection, which loads `extensions/fast` only.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"speed": "fast"
|
|
12
|
-
}
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
It also adds the required Anthropic beta header value to the current model before requests:
|
|
16
|
-
|
|
17
|
-
```text
|
|
18
|
-
anthropic-beta: fast-mode-2026-02-01
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Eligibility
|
|
22
|
-
|
|
23
|
-
Fast mode is only injected when all of these are true:
|
|
24
|
-
|
|
25
|
-
- The current provider is `anthropic`.
|
|
26
|
-
- The current API is `anthropic-messages`.
|
|
27
|
-
- The current model is `claude-opus-4-8` or `claude-opus-5`.
|
|
28
|
-
- The request payload does not already include `speed`.
|
|
29
|
-
|
|
30
|
-
Claude Fast mode is available for API-key access and Claude Code subscription/OAuth access when the account has access to Anthropic's Fast mode research preview. For Claude Code subscription users, Anthropic documents this as extra usage credits, not included subscription usage.
|
|
31
|
-
|
|
32
|
-
## Commands
|
|
33
|
-
|
|
34
|
-
```text
|
|
35
|
-
/claude-fast
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Run `/claude-fast` to toggle Fast mode on or off for the current session/runtime. The command reports the new state in chat, and the footer shows `fast` while Fast mode is active for an eligible model.
|
|
39
|
-
|
|
40
|
-
The extension defaults to off so installing the full collection does not accidentally spend Fast-mode credits.
|
|
41
|
-
|
|
42
|
-
## Config
|
|
43
|
-
|
|
44
|
-
Optional global config:
|
|
45
|
-
|
|
46
|
-
```text
|
|
47
|
-
~/<pi-config-dir>/agent/extensions/claude-fast.json
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Optional project config:
|
|
51
|
-
|
|
52
|
-
```text
|
|
53
|
-
<project>/<pi-config-dir>/claude-fast.json
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Here `<pi-config-dir>` is Pi's runtime config directory name (`CONFIG_DIR_NAME`; `.pi` by default). Project config overrides global config after Pi reports that the project is trusted.
|
|
57
|
-
|
|
58
|
-
```json
|
|
59
|
-
{
|
|
60
|
-
"enabled": false,
|
|
61
|
-
"showStatus": true
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
- `enabled`: default Fast-mode state when there is no session override.
|
|
66
|
-
- `showStatus`: show a compact `fast` status when Fast mode is active for the current model.
|
|
7
|
+
This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package.
|
|
67
8
|
|
|
68
9
|
## Install
|
|
69
10
|
|
|
70
|
-
### Standalone npm package
|
|
71
|
-
|
|
72
11
|
```bash
|
|
73
12
|
pi install npm:@diegopetrucci/pi-claude-fast
|
|
74
13
|
```
|
|
75
14
|
|
|
76
|
-
|
|
15
|
+
Installing this package does not enable Fast mode. For the working extension:
|
|
77
16
|
|
|
78
17
|
```bash
|
|
79
|
-
pi install npm:@diegopetrucci/pi-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### GitHub package
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
pi install git:github.com/diegopetrucci/pi-extensions
|
|
18
|
+
pi install npm:@diegopetrucci/pi-fast
|
|
86
19
|
```
|
|
87
20
|
|
|
88
21
|
Then reload pi:
|
|
@@ -90,10 +23,3 @@ Then reload pi:
|
|
|
90
23
|
```text
|
|
91
24
|
/reload
|
|
92
25
|
```
|
|
93
|
-
|
|
94
|
-
## Notes
|
|
95
|
-
|
|
96
|
-
- Anthropic Fast mode has separate rate limits and costs more than standard speed.
|
|
97
|
-
- Requests at different speeds do not share prompt-cache prefixes.
|
|
98
|
-
- Anthropic documents Fast mode as unavailable with Priority Tier.
|
|
99
|
-
- If pi adds first-class speed support later, this extension skips payloads that already contain `speed`.
|