@fyeeme/pi-peon-ping 1.0.0 → 1.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.
- package/CHANGELOG.md +22 -0
- package/README.md +37 -19
- package/index.ts +188 -19
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [1.1.1] - 2026-08-08
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Tab 标题改用 provided API:删除手写 ANSI `setTabTitle`(`process.stdout.write(\x1b]0;...\x07)`),四处调用点(`session_start`/`before_agent_start`/`agent_settled`/`tool_result`,均在 `ctx.hasUI` 守卫下)改为 `ctx.ui.setTitle(...)`——mode 感知,print/json 模式自动 no-op,不再向 stdout 写裸 ANSI。
|
|
10
|
+
- `Stop` 事件映射从 `agent_end` 改为 `agent_settled`(与 ExtensionAPI 实际事件名一致)。
|
|
11
|
+
|
|
12
|
+
## [1.1.0] - 2026-07-26
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- New lifecycle event mappings: `session_before_compact` → `PreCompact` (context compaction) and `tool_call` for `ask_user_question` → `PermissionRequest` (input required)
|
|
17
|
+
- Category-aware event firing — reads peon-ping `config.json` and skips spawning `peon.sh` for events whose category is toggled off. New helpers: `EVENT_CATEGORY`, `resolveConfigPath`, `readPeonConfig`, `isCategoryEnabled`
|
|
18
|
+
- `findPeonCli` / `execPeonCli` utility functions for CLI invocation of peon.sh
|
|
19
|
+
- `peon-ping-toggle` and `peon-ping-use` commands
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Map `UserPromptSubmit` and `Stop` to `before_agent_start` and `agent_end` (once per prompt) instead of `turn_start` / `turn_end` (once per turn), so the completion sound fires once per agent run rather than on every turn
|
|
24
|
+
|
|
3
25
|
## [1.0.0] - 2025-07-25
|
|
4
26
|
|
|
5
27
|
### Added
|
package/README.md
CHANGED
|
@@ -49,13 +49,30 @@ ln -s $(pwd)/packages/extensions/pi-peon-ping ~/.pi/agent/extensions/pi-peon-pin
|
|
|
49
49
|
|
|
50
50
|
## Event Mapping
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
| `
|
|
52
|
+
Each pi event is mapped to a peon-ping event, which peon-ping routes to a sound
|
|
53
|
+
**category** — the keys under `categories` in `~/.openpeon/config.json`. Toggle a
|
|
54
|
+
category off there to silence just that event without touching the others.
|
|
55
|
+
|
|
56
|
+
| pi event | peon-ping event | category | Occurs when |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `session_start` | `SessionStart` | `session.start` | Pi starts or resumes a session |
|
|
59
|
+
| `before_agent_start` | `UserPromptSubmit` | `task.acknowledge` | You submit a prompt (once per agent run) |
|
|
60
|
+
| `agent_end` | `Stop` | `task.complete` | An agent run finishes (once per prompt) |
|
|
61
|
+
| `tool_result` (isError) | `PostToolUseFailure` | `task.error` | A tool call fails |
|
|
62
|
+
| `tool_call` (`ask_user_question`) | `PermissionRequest` | `input.required` | Agent asks you a question |
|
|
63
|
+
| `session_before_compact` | `PreCompact` | `resource.limit` | Context is about to be compacted |
|
|
64
|
+
| `session_shutdown` | `SessionEnd` | — | Session ends (always fires) |
|
|
65
|
+
|
|
66
|
+
> **Why `agent_end` / `before_agent_start` instead of `turn_*`?**
|
|
67
|
+
> A single agent run spans multiple turns (e.g. several tool calls in a row).
|
|
68
|
+
> Mapping `turn_end` → `Stop` would fire the completion sound on every turn;
|
|
69
|
+
> `agent_end` fires once per prompt, which is what the completion sound should
|
|
70
|
+
> mean. Same reasoning for `before_agent_start` vs `turn_start`.
|
|
71
|
+
|
|
72
|
+
> **`input.required` needs the `ask_user_question` tool**, provided by an
|
|
73
|
+
> extension such as [@juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rpiv-ask-user-question).
|
|
74
|
+
> It listens for the `tool_call` event with `toolName === "ask_user_question"`.
|
|
75
|
+
> Without such a tool installed, this category never fires.
|
|
59
76
|
|
|
60
77
|
## Configuration
|
|
61
78
|
|
|
@@ -67,28 +84,29 @@ ln -s $(pwd)/packages/extensions/pi-peon-ping ~/.pi/agent/extensions/pi-peon-pin
|
|
|
67
84
|
|
|
68
85
|
### peon.sh Discovery
|
|
69
86
|
|
|
70
|
-
pi-peon-ping
|
|
87
|
+
pi-peon-ping auto-discovers the installed `peon.sh` with the following priority (high → low):
|
|
71
88
|
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
| 1 | `$PEON_SH`
|
|
89
|
+
| Priority | Source | Path |
|
|
90
|
+
|----------|--------|------|
|
|
91
|
+
| 1 | `$PEON_SH` env var | User-specified |
|
|
75
92
|
| 2 | Homebrew (macOS) | `$(brew --prefix peon-ping)/libexec/peon.sh` |
|
|
76
|
-
| 3 | curl
|
|
77
|
-
| 4 | `--openpeon`
|
|
78
|
-
| 5 | OpenClaw
|
|
79
|
-
| 6 | Windows/Git Bash WSL2
|
|
93
|
+
| 3 | curl install (Linux/macOS) | `~/.claude/hooks/peon-ping/peon.sh` |
|
|
94
|
+
| 4 | `--openpeon` mode | `~/.openpeon/hooks/peon-ping/peon.sh` |
|
|
95
|
+
| 5 | OpenClaw install | `~/.openclaw/hooks/peon-ping/peon.sh` |
|
|
96
|
+
| 6 | Windows/Git Bash WSL2 fallback | `~/.claude/hooks/peon-ping/peon.ps1` |
|
|
80
97
|
|
|
81
|
-
|
|
98
|
+
When a `.ps1` file is found, it is automatically run with `powershell` instead of `bash`.
|
|
82
99
|
|
|
83
|
-
### peon-ping
|
|
100
|
+
### peon-ping configuration
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
All peon-ping feature configuration is done through the `peon` CLI:
|
|
86
103
|
|
|
87
104
|
```bash
|
|
88
105
|
peon packs use glados # Switch sound pack
|
|
89
106
|
peon packs install --all # Install all packs
|
|
90
107
|
peon trainer on # Enable trainer mode
|
|
91
108
|
peon notifications off # Mute popups but keep sounds
|
|
109
|
+
peon mute # Mute all sounds (toggle back with `peon toggle`)
|
|
92
110
|
peon mobile ntfy my-topic # Push notifications to phone
|
|
93
111
|
peon ssh-audio relay # Route audio for remote sessions
|
|
94
112
|
```
|
|
@@ -103,7 +121,7 @@ rm -rf ~/.pi/agent/extensions/pi-peon-ping
|
|
|
103
121
|
|
|
104
122
|
This is a thin adapter — it does not play sounds or send notifications directly. Instead, it finds `peon.sh` (from the peon-ping install) and spawns it with a JSON payload on each lifecycle event. `peon.sh` handles all sound selection, playback, notifications, and trainer logic.
|
|
105
123
|
|
|
106
|
-
The tab title
|
|
124
|
+
The tab title reflects agent state: `● working...` when an agent run starts, `✓ done` when it finishes, `✗ error` on tool failures.
|
|
107
125
|
|
|
108
126
|
## Credits
|
|
109
127
|
|
package/index.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Event mapping (pi ExtensionAPI → peon.sh hook_event_name):
|
|
15
15
|
* session_start → SessionStart
|
|
16
16
|
* turn_start → UserPromptSubmit
|
|
17
|
-
*
|
|
17
|
+
* agent_settled → Stop
|
|
18
18
|
* tool_result (event.isError === true) → PostToolUseFailure
|
|
19
19
|
* session_shutdown → SessionEnd
|
|
20
20
|
*
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import { spawn, execFileSync } from "node:child_process";
|
|
38
|
-
import { existsSync } from "node:fs";
|
|
38
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
39
39
|
import { homedir, platform } from "node:os";
|
|
40
|
-
import { basename, join } from "node:path";
|
|
40
|
+
import { basename, dirname, join } from "node:path";
|
|
41
41
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
42
42
|
|
|
43
43
|
// ============================================================================
|
|
@@ -131,13 +131,9 @@ export function resolveShellAndScript(
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// ============================================================================
|
|
134
|
-
// Terminal tab title
|
|
134
|
+
// Terminal tab title (via pi's mode-aware ctx.ui.setTitle)
|
|
135
135
|
// ============================================================================
|
|
136
136
|
|
|
137
|
-
export function setTabTitle(title: string): void {
|
|
138
|
-
process.stdout.write(`\x1b]0;${title}\x07`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
137
|
// ============================================================================
|
|
142
138
|
// Session ID helpers
|
|
143
139
|
// ============================================================================
|
|
@@ -153,16 +149,98 @@ export function getSessionId(ctx: ExtensionContext): string {
|
|
|
153
149
|
return `pi-${Date.now()}`;
|
|
154
150
|
}
|
|
155
151
|
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Peon-ping config (categories)
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Event → peon-ping category key mapping (mirrors peon.sh's routing).
|
|
158
|
+
* SessionEnd is intentionally unmapped so it always fires regardless of
|
|
159
|
+
* category toggles (peon.sh has no matching category for session teardown).
|
|
160
|
+
*/
|
|
161
|
+
export const EVENT_CATEGORY: Record<string, string> = {
|
|
162
|
+
SessionStart: "session.start",
|
|
163
|
+
UserPromptSubmit: "task.acknowledge",
|
|
164
|
+
Stop: "task.complete",
|
|
165
|
+
PostToolUseFailure: "task.error",
|
|
166
|
+
PreCompact: "resource.limit",
|
|
167
|
+
PermissionRequest: "input.required",
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolve the peon-ping config.json path, following the same priority as
|
|
172
|
+
* peon.sh:
|
|
173
|
+
* 1. $PWD/.claude/hooks/peon-ping/config.json (project-local)
|
|
174
|
+
* 2. ~/.openpeon/config.json (user data dir)
|
|
175
|
+
* 3. $PEON_DIR/config.json (install-level)
|
|
176
|
+
*/
|
|
177
|
+
export function resolveConfigPath(peonPath: string): string | null {
|
|
178
|
+
// 1. Project-local config
|
|
179
|
+
const localCfg = join(process.cwd(), ".claude", "hooks", "peon-ping", "config.json");
|
|
180
|
+
if (existsSync(localCfg)) return localCfg;
|
|
181
|
+
|
|
182
|
+
// 2. ~/.openpeon/config.json
|
|
183
|
+
const openpeonCfg = join(homedir(), ".openpeon", "config.json");
|
|
184
|
+
if (existsSync(openpeonCfg)) return openpeonCfg;
|
|
185
|
+
|
|
186
|
+
// 3. Install-level (same directory as peon.sh)
|
|
187
|
+
const peonDir = dirname(peonPath);
|
|
188
|
+
const installCfg = join(peonDir, "config.json");
|
|
189
|
+
if (existsSync(installCfg)) return installCfg;
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface PeonConfig {
|
|
195
|
+
categories?: Record<string, boolean>;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Read the peon-ping config.json and return the parsed object,
|
|
200
|
+
* or null if the config file cannot be read.
|
|
201
|
+
*/
|
|
202
|
+
export function readPeonConfig(peonPath: string): PeonConfig | null {
|
|
203
|
+
const cfgPath = resolveConfigPath(peonPath);
|
|
204
|
+
if (!cfgPath) return null;
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(readFileSync(cfgPath, "utf-8")) as PeonConfig;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check whether a peon-ping category is enabled in the config.
|
|
214
|
+
* If no config is found, returns true (allow — mimic peon.sh default).
|
|
215
|
+
*/
|
|
216
|
+
export function isCategoryEnabled(config: PeonConfig | null, category: string): boolean {
|
|
217
|
+
if (!config?.categories) return true;
|
|
218
|
+
const val = config.categories[category];
|
|
219
|
+
// peon.sh defaults: task.acknowledge → false, everything else → true
|
|
220
|
+
if (val === undefined) return category !== "task.acknowledge";
|
|
221
|
+
return val === true;
|
|
222
|
+
}
|
|
223
|
+
|
|
156
224
|
// ============================================================================
|
|
157
225
|
// peon.sh invocation
|
|
158
226
|
// ============================================================================
|
|
159
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Fire a peon-ping event. Spawns peon.sh with the given hook_event_name,
|
|
230
|
+
* but only if the corresponding category is enabled in the peon-ping config.
|
|
231
|
+
* This avoids spawning a process for suppressed events.
|
|
232
|
+
*/
|
|
160
233
|
export function firePeon(
|
|
161
234
|
peonPath: string,
|
|
162
235
|
event: string,
|
|
163
236
|
cwd: string,
|
|
164
237
|
sessionId: string,
|
|
238
|
+
config?: PeonConfig | null,
|
|
165
239
|
): void {
|
|
240
|
+
const category = EVENT_CATEGORY[event];
|
|
241
|
+
const enabled = !category || isCategoryEnabled(config ?? null, category);
|
|
242
|
+
if (!enabled) return;
|
|
243
|
+
|
|
166
244
|
const payload = JSON.stringify({
|
|
167
245
|
hook_event_name: event,
|
|
168
246
|
notification_type: "",
|
|
@@ -185,6 +263,35 @@ export function firePeon(
|
|
|
185
263
|
}
|
|
186
264
|
}
|
|
187
265
|
|
|
266
|
+
// ============================================================================
|
|
267
|
+
// peon CLI invocation (for commands)
|
|
268
|
+
// ============================================================================
|
|
269
|
+
|
|
270
|
+
export function findPeonCli(peonPath?: string | null): string | null {
|
|
271
|
+
// peon.sh accepts CLI args too — just use it directly
|
|
272
|
+
return peonPath ?? findPeonSh();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function execPeonCli(
|
|
276
|
+
cliPath: string,
|
|
277
|
+
args: string[],
|
|
278
|
+
): Promise<{ stdout: string; exitCode: number | null }> {
|
|
279
|
+
return new Promise((resolve) => {
|
|
280
|
+
const { shell, script } = resolveShellAndScript(cliPath);
|
|
281
|
+
const proc = spawn(shell, [script, ...args], {
|
|
282
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
let stdout = "";
|
|
286
|
+
proc.stdout?.on("data", (chunk: Buffer) => { stdout += chunk.toString(); });
|
|
287
|
+
|
|
288
|
+
proc.on("close", (code) => {
|
|
289
|
+
resolve({ stdout: stdout.trim(), exitCode: code });
|
|
290
|
+
});
|
|
291
|
+
proc.on("error", () => resolve({ stdout: "", exitCode: -1 }));
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
188
295
|
// ============================================================================
|
|
189
296
|
// Extension
|
|
190
297
|
// ============================================================================
|
|
@@ -199,36 +306,98 @@ export default function peonPingExtension(pi: ExtensionAPI): void {
|
|
|
199
306
|
return;
|
|
200
307
|
}
|
|
201
308
|
|
|
309
|
+
const cliPath = findPeonCli(peonPath);
|
|
202
310
|
const cwd = process.cwd();
|
|
203
311
|
const projectName = basename(cwd) || "pi";
|
|
204
312
|
|
|
313
|
+
// -----------------------------------------------------------------------
|
|
314
|
+
// Lifecycle event forwarding (respect peon-ping category toggles)
|
|
315
|
+
// Config is re-read on each event so runtime category changes (via
|
|
316
|
+
// `peon notifications`) take effect without a pi restart.
|
|
317
|
+
// -----------------------------------------------------------------------
|
|
318
|
+
|
|
205
319
|
pi.on("session_start", async (_event, ctx) => {
|
|
320
|
+
const config = readPeonConfig(peonPath);
|
|
206
321
|
const sessionId = getSessionId(ctx);
|
|
207
|
-
if (ctx.hasUI)
|
|
208
|
-
firePeon(peonPath, "SessionStart", cwd, sessionId);
|
|
322
|
+
if (ctx.hasUI) ctx.ui.setTitle(`● ${projectName}: ready`);
|
|
323
|
+
firePeon(peonPath, "SessionStart", cwd, sessionId, config);
|
|
209
324
|
});
|
|
210
325
|
|
|
211
|
-
pi.on("
|
|
326
|
+
pi.on("before_agent_start", async (_event, ctx) => {
|
|
327
|
+
const config = readPeonConfig(peonPath);
|
|
212
328
|
const sessionId = getSessionId(ctx);
|
|
213
|
-
if (ctx.hasUI)
|
|
214
|
-
firePeon(peonPath, "UserPromptSubmit", cwd, sessionId);
|
|
329
|
+
if (ctx.hasUI) ctx.ui.setTitle(`● ${projectName}: working...`);
|
|
330
|
+
firePeon(peonPath, "UserPromptSubmit", cwd, sessionId, config);
|
|
215
331
|
});
|
|
216
332
|
|
|
217
|
-
pi.on("
|
|
333
|
+
pi.on("session_before_compact", async (_event, ctx) => {
|
|
334
|
+
const config = readPeonConfig(peonPath);
|
|
218
335
|
const sessionId = getSessionId(ctx);
|
|
219
|
-
|
|
220
|
-
|
|
336
|
+
firePeon(peonPath, "PreCompact", cwd, sessionId, config);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
340
|
+
const config = readPeonConfig(peonPath);
|
|
341
|
+
const sessionId = getSessionId(ctx);
|
|
342
|
+
if (ctx.hasUI) ctx.ui.setTitle(`✓ ${projectName}: done`);
|
|
343
|
+
firePeon(peonPath, "Stop", cwd, sessionId, config);
|
|
221
344
|
});
|
|
222
345
|
|
|
223
346
|
pi.on("tool_result", async (event, ctx) => {
|
|
224
347
|
if (!event.isError) return;
|
|
348
|
+
const config = readPeonConfig(peonPath);
|
|
349
|
+
const sessionId = getSessionId(ctx);
|
|
350
|
+
if (ctx.hasUI) ctx.ui.setTitle(`✗ ${projectName}: error`);
|
|
351
|
+
firePeon(peonPath, "PostToolUseFailure", cwd, sessionId, config);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Agent is asking the user a question (e.g. ask_user_question tool) →
|
|
355
|
+
// peon-ping "input.required" category. Fires before the tool runs.
|
|
356
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
357
|
+
if (event.toolName !== "ask_user_question") return;
|
|
358
|
+
const config = readPeonConfig(peonPath);
|
|
225
359
|
const sessionId = getSessionId(ctx);
|
|
226
|
-
|
|
227
|
-
firePeon(peonPath, "PostToolUseFailure", cwd, sessionId);
|
|
360
|
+
firePeon(peonPath, "PermissionRequest", cwd, sessionId, config);
|
|
228
361
|
});
|
|
229
362
|
|
|
230
363
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
364
|
+
const config = readPeonConfig(peonPath);
|
|
231
365
|
const sessionId = getSessionId(ctx);
|
|
232
|
-
firePeon(peonPath, "SessionEnd", cwd, sessionId);
|
|
366
|
+
firePeon(peonPath, "SessionEnd", cwd, sessionId, config);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// -----------------------------------------------------------------------
|
|
370
|
+
// Commands
|
|
371
|
+
// -----------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
if (!cliPath) return;
|
|
374
|
+
|
|
375
|
+
pi.registerCommand("peon-ping-toggle", {
|
|
376
|
+
description: "Toggle peon-ping mute on/off",
|
|
377
|
+
handler: async (_args, ctx) => {
|
|
378
|
+
const result = await execPeonCli(cliPath, ["toggle"]);
|
|
379
|
+
if (result.exitCode !== 0) {
|
|
380
|
+
ctx.ui.notify(result.stdout || "toggle failed", "error");
|
|
381
|
+
} else {
|
|
382
|
+
ctx.ui.notify(result.stdout || "toggled", "info");
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
pi.registerCommand("peon-ping-use", {
|
|
388
|
+
description: "Switch sound pack: /peon-ping-use <name>",
|
|
389
|
+
handler: async (args, ctx) => {
|
|
390
|
+
const name = args.trim();
|
|
391
|
+
if (!name) {
|
|
392
|
+
ctx.ui.notify("Usage: /peon-ping-use <pack-name>", "warning");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const result = await execPeonCli(cliPath, ["packs", "use", "--install", name]);
|
|
396
|
+
if (result.exitCode !== 0) {
|
|
397
|
+
ctx.ui.notify(result.stdout || `Failed to switch to ${name}`, "error");
|
|
398
|
+
} else {
|
|
399
|
+
ctx.ui.notify(result.stdout || `Switched to ${name}`, "info");
|
|
400
|
+
}
|
|
401
|
+
},
|
|
233
402
|
});
|
|
234
403
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fyeeme/pi-peon-ping",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "peon-ping adapter for pi — routes lifecycle events through peon.sh for sound packs, notifications, and trainer reminders.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"typecheck": "tsc"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
47
|
+
"@earendil-works/pi-coding-agent": ">=0.84.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
50
|
+
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
51
51
|
"@types/node": "22.19.19",
|
|
52
52
|
"typescript": "5.9.3"
|
|
53
53
|
}
|