pi-observability 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # 🔭 pi-observability
2
+
3
+ A [pi](https://github.com/mariozechner/pi) extension that replaces the default footer with a live observability bar and provides a full dashboard command.
4
+
5
+ ## Features
6
+
7
+ - **Live footer bar** showing:
8
+ - Session input/output tokens & estimated cost
9
+ - Live TPS (tokens per second) during streaming
10
+ - Session runtime
11
+ - Current model & git branch
12
+ - Git diff stats (added/removed lines)
13
+ - Context usage (current / max)
14
+
15
+ - **`/obs` command** — Print a full observability dashboard with per-turn breakdowns and last 10 session history
16
+
17
+ - **`/obs-toggle` command** — Toggle the live footer on/off
18
+
19
+ ## Preview
20
+
21
+ ```
22
+ ~/projects/my-app (main) +42 -7
23
+ ⏱ 12:34 ctx 4.2k/200k ↑1.2k ↓3.4k $0.0042 ⚡ 45.2 tok/s claude-sonnet-4
24
+ ```
25
+
26
+ ## Install
27
+
28
+ ### Via npm
29
+
30
+ ```bash
31
+ pi install npm:pi-observability
32
+ ```
33
+
34
+ ### Via git
35
+
36
+ ```bash
37
+ pi install git:github.com/imran-vz/pi-observability
38
+ ```
39
+
40
+ ### Manual
41
+
42
+ Copy `extensions/observability.ts` to `~/.pi/agent/extensions/observability.ts` (or `.pi/extensions/observability.ts` for project-local).
43
+
44
+ ## Commands
45
+
46
+ | Command | Description |
47
+ |---------|-------------|
48
+ | `/obs` | Print full observability dashboard + last 10 sessions history |
49
+ | `/obs-toggle` | Toggle the observability footer on/off |
50
+
51
+ ## Dashboard Output
52
+
53
+ ```
54
+ ╔══════════════════════════════════════════════════════════════╗
55
+ ║ 🕵️ Agent Observability Dashboard ║
56
+ ╠══════════════════════════════════════════════════════════════╣
57
+ ║ Runtime: 12:34 ║
58
+ ║ Dir: ~/projects/my-app ║
59
+ ║ Branch: main ║
60
+ ║ Model: claude-sonnet-4 ║
61
+ ╠══════════════════════════════════════════════════════════════╣
62
+ ║ Tokens: ↑1.2k ↓3.4k ║
63
+ ║ Cost: $0.004200 ║
64
+ ╠══════════════════════════════════════════════════════════════╣
65
+ ║ Turns: ║
66
+ ║ #1 ↑450 ↓1200 0:45 26.7/s $0.0015 claude-sonne ║
67
+ ║ #2 ↑320 ↓900 0:32 28.1/s $0.0012 claude-sonne ║
68
+ ╚══════════════════════════════════════════════════════════════╝
69
+ ```
70
+
71
+ ## Requirements
72
+
73
+ - [pi](https://github.com/mariozechner/pi) coding agent
74
+ - Git (for branch & diff stats)
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Agent Observability Extension
3
+ *
4
+ * Replaces the default footer with a live observability bar showing:
5
+ * - Session input/output tokens & cost
6
+ * - Live TPS during streaming (chunk-based estimate)
7
+ * - Session runtime
8
+ * - Current model & git branch
9
+ * - Git diff stats (added/removed lines)
10
+ * - Context usage (current/max)
11
+ *
12
+ * Commands:
13
+ * /obs - Print full observability dashboard + last 10 sessions
14
+ * /obs-toggle - Toggle the observability footer on/off
15
+ */
16
+
17
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
18
+ import type { AssistantMessage } from "@mariozechner/pi-ai";
19
+ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
20
+ import { homedir } from "node:os";
21
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
22
+ import { join } from "node:path";
23
+
24
+ /* ───── Types ───── */
25
+
26
+ interface TurnRecord {
27
+ turnIndex: number;
28
+ inputTokens: number;
29
+ outputTokens: number;
30
+ cost: number;
31
+ durationMs: number;
32
+ tps: number;
33
+ model: string;
34
+ }
35
+
36
+ interface PersistedTurn {
37
+ customType: "obs-turn";
38
+ data: TurnRecord;
39
+ }
40
+
41
+ interface SessionState {
42
+ startTime: number;
43
+ turns: TurnRecord[];
44
+ currentTurnStartTime: number | null;
45
+ currentTurnUpdateCount: number;
46
+ isStreaming: boolean;
47
+ footerEnabled: boolean;
48
+ }
49
+
50
+ interface SessionSummary {
51
+ endedAt: number;
52
+ runtimeMs: number;
53
+ turns: number;
54
+ inputTokens: number;
55
+ outputTokens: number;
56
+ cost: number;
57
+ model: string;
58
+ cwd: string;
59
+ branch: string | null;
60
+ }
61
+
62
+ /* ───── Helpers ───── */
63
+
64
+ function fmtDuration(ms: number): string {
65
+ if (!Number.isFinite(ms) || ms < 0) ms = 0;
66
+ const s = Math.floor(ms / 1000);
67
+ const h = Math.floor(s / 3600);
68
+ const m = Math.floor((s % 3600) / 60);
69
+ const sec = s % 60;
70
+ if (h > 0) return `${h}:${m.toString().padStart(2, "0")}:${sec.toString().padStart(2, "0")}`;
71
+ return `${m}:${sec.toString().padStart(2, "0")}`;
72
+ }
73
+
74
+ function fmtTokens(n: number): string {
75
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
76
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
77
+ return `${n}`;
78
+ }
79
+
80
+ function shortenPath(p: string): string {
81
+ const home = homedir();
82
+ if (home && p.startsWith(home)) return p.replace(home, "~");
83
+ return p;
84
+ }
85
+
86
+ function scanHistoricalTurns(ctx: ExtensionContext): TurnRecord[] {
87
+ const turns: TurnRecord[] = [];
88
+ for (const entry of ctx.sessionManager.getBranch()) {
89
+ if (entry.type === "custom" && entry.customType === "obs-turn") {
90
+ turns.push((entry as unknown as PersistedTurn).data);
91
+ }
92
+ }
93
+ return turns;
94
+ }
95
+
96
+ function getSessionStartTime(ctx: ExtensionContext): number {
97
+ const entries = ctx.sessionManager.getBranch();
98
+ for (const e of entries) {
99
+ if (typeof e.timestamp === "number" && Number.isFinite(e.timestamp)) {
100
+ return e.timestamp;
101
+ }
102
+ }
103
+ return Date.now();
104
+ }
105
+
106
+ /* ───── History persistence ───── */
107
+
108
+ const HISTORY_DIR = join(homedir(), ".pi", "agent", "observability");
109
+ const HISTORY_FILE = join(HISTORY_DIR, "history.jsonl");
110
+
111
+ async function loadHistory(): Promise<SessionSummary[]> {
112
+ try {
113
+ const text = await readFile(HISTORY_FILE, "utf8");
114
+ const lines = text.split("\n").filter((l) => l.trim());
115
+ return lines.map((l) => JSON.parse(l));
116
+ } catch {
117
+ return [];
118
+ }
119
+ }
120
+
121
+ async function saveHistory(sessions: SessionSummary[]): Promise<void> {
122
+ await mkdir(HISTORY_DIR, { recursive: true });
123
+ const text = sessions.map((s) => JSON.stringify(s)).join("\n") + "\n";
124
+ await writeFile(HISTORY_FILE, text, "utf8");
125
+ }
126
+
127
+ /* ───── Dashboard formatting ───── */
128
+
129
+ const BOX_W = 64; // total outer width including ║ borders
130
+ const IN_W = BOX_W - 4; // inner width: "║ " + content + " ║"
131
+
132
+ function boxTop(): string {
133
+ return "╔" + "═".repeat(BOX_W - 2) + "╗";
134
+ }
135
+ function boxMid(): string {
136
+ return "╠" + "═".repeat(BOX_W - 2) + "╣";
137
+ }
138
+ function boxBot(): string {
139
+ return "╚" + "═".repeat(BOX_W - 2) + "╝";
140
+ }
141
+ function boxLine(text: string): string {
142
+ const visible = visibleWidth(text);
143
+ let pad = IN_W - visible;
144
+ if (pad < 0) pad = 0;
145
+ return "║ " + text + " ".repeat(pad) + " ║";
146
+ }
147
+
148
+ /* ───── Extension ───── */
149
+
150
+ export default function (pi: ExtensionAPI) {
151
+ const state: SessionState = {
152
+ startTime: Date.now(),
153
+ turns: [],
154
+ currentTurnStartTime: null,
155
+ currentTurnUpdateCount: 0,
156
+ isStreaming: false,
157
+ footerEnabled: true,
158
+ };
159
+
160
+ /* ─── Lifecycle ─── */
161
+
162
+ pi.on("session_start", async (_event, ctx) => {
163
+ state.startTime = getSessionStartTime(ctx);
164
+ state.turns = scanHistoricalTurns(ctx);
165
+ state.currentTurnStartTime = null;
166
+ state.currentTurnUpdateCount = 0;
167
+ state.isStreaming = false;
168
+
169
+ if (state.footerEnabled && ctx.hasUI) {
170
+ setupFooter(ctx);
171
+ }
172
+ });
173
+
174
+ pi.on("turn_start", async (_event, _ctx) => {
175
+ state.currentTurnStartTime = Date.now();
176
+ state.currentTurnUpdateCount = 0;
177
+ state.isStreaming = true;
178
+ });
179
+
180
+ pi.on("message_update", async (_event, _ctx) => {
181
+ state.currentTurnUpdateCount++;
182
+ });
183
+
184
+ pi.on("turn_end", async (event, ctx) => {
185
+ const duration = state.currentTurnStartTime
186
+ ? Date.now() - state.currentTurnStartTime
187
+ : 0;
188
+
189
+ let inputTokens = 0;
190
+ let outputTokens = 0;
191
+ let cost = 0;
192
+
193
+ const branch = ctx.sessionManager.getBranch();
194
+ for (let i = branch.length - 1; i >= 0; i--) {
195
+ const entry = branch[i];
196
+ if (entry.type === "message" && entry.message.role === "assistant") {
197
+ const m = entry.message as AssistantMessage;
198
+ inputTokens = m.usage?.input ?? 0;
199
+ outputTokens = m.usage?.output ?? 0;
200
+ cost = m.usage?.cost?.total ?? 0;
201
+ break;
202
+ }
203
+ }
204
+
205
+ const tps = duration > 0 && outputTokens >= 0 ? outputTokens / (duration / 1000) : 0;
206
+
207
+ const record: TurnRecord = {
208
+ turnIndex: event.turnIndex,
209
+ inputTokens,
210
+ outputTokens,
211
+ cost,
212
+ durationMs: duration,
213
+ tps,
214
+ model: ctx.model?.id ?? "unknown",
215
+ };
216
+
217
+ state.turns.push(record);
218
+ state.isStreaming = false;
219
+ state.currentTurnStartTime = null;
220
+ state.currentTurnUpdateCount = 0;
221
+
222
+ pi.appendEntry("obs-turn", record);
223
+ });
224
+
225
+ pi.on("agent_end", async (_event, _ctx) => {
226
+ state.isStreaming = false;
227
+ });
228
+
229
+ pi.on("session_shutdown", async (_event, ctx) => {
230
+ const totalIn = state.turns.reduce((s, t) => s + t.inputTokens, 0);
231
+ const totalOut = state.turns.reduce((s, t) => s + t.outputTokens, 0);
232
+ const totalCost = state.turns.reduce((s, t) => s + t.cost, 0);
233
+ const runtime = Date.now() - state.startTime;
234
+
235
+ let branch: string | null = null;
236
+ try {
237
+ const result = await pi.exec("git", ["branch", "--show-current"], {
238
+ cwd: ctx.cwd,
239
+ throwOnError: false,
240
+ });
241
+ branch = result.stdout?.trim() || null;
242
+ } catch {
243
+ branch = null;
244
+ }
245
+
246
+ const summary: SessionSummary = {
247
+ endedAt: Date.now(),
248
+ runtimeMs: runtime,
249
+ turns: state.turns.length,
250
+ inputTokens: totalIn,
251
+ outputTokens: totalOut,
252
+ cost: totalCost,
253
+ model: ctx.model?.id ?? "unknown",
254
+ cwd: ctx.cwd,
255
+ branch,
256
+ };
257
+
258
+ const history = await loadHistory();
259
+ history.push(summary);
260
+ if (history.length > 10) history.splice(0, history.length - 10);
261
+ await saveHistory(history);
262
+ });
263
+
264
+ /* ─── Footer ─── */
265
+
266
+ function setupFooter(ctx: ExtensionContext) {
267
+ ctx.ui.setFooter((tui, theme, footerData) => {
268
+ let diffAdded = 0;
269
+ let diffRemoved = 0;
270
+
271
+ async function refreshDiff() {
272
+ try {
273
+ const result = await pi.exec("git", ["diff", "--numstat"], {
274
+ cwd: ctx.cwd,
275
+ throwOnError: false,
276
+ });
277
+ if (result.code !== 0 || !result.stdout) {
278
+ diffAdded = 0;
279
+ diffRemoved = 0;
280
+ return;
281
+ }
282
+ let added = 0;
283
+ let removed = 0;
284
+ for (const line of result.stdout.split("\n")) {
285
+ const parts = line.trim().split(/\s+/);
286
+ if (parts.length >= 2) {
287
+ const a = parseInt(parts[0], 10);
288
+ const b = parseInt(parts[1], 10);
289
+ if (!Number.isNaN(a)) added += a;
290
+ if (!Number.isNaN(b)) removed += b;
291
+ }
292
+ }
293
+ diffAdded = added;
294
+ diffRemoved = removed;
295
+ } catch {
296
+ diffAdded = 0;
297
+ diffRemoved = 0;
298
+ }
299
+ }
300
+
301
+ refreshDiff();
302
+
303
+ const unsubBranch = footerData.onBranchChange(() => {
304
+ refreshDiff();
305
+ tui.requestRender();
306
+ });
307
+
308
+ const timer = setInterval(() => {
309
+ refreshDiff();
310
+ tui.requestRender();
311
+ }, 1000);
312
+
313
+ return {
314
+ dispose() {
315
+ unsubBranch();
316
+ clearInterval(timer);
317
+ },
318
+ invalidate() {},
319
+ render(width: number): string[] {
320
+ const totalIn = state.turns.reduce((s, t) => s + t.inputTokens, 0);
321
+ const totalOut = state.turns.reduce((s, t) => s + t.outputTokens, 0);
322
+ const totalCost = state.turns.reduce((s, t) => s + t.cost, 0);
323
+ let runtime = Date.now() - state.startTime;
324
+ if (!Number.isFinite(runtime) || runtime < 0) runtime = 0;
325
+
326
+ const branch = footerData.getGitBranch();
327
+ const model = ctx.model?.id ?? "no-model";
328
+ const cwd = shortenPath(ctx.cwd);
329
+
330
+ // ── Line 1: folder + branch + git diff stats ──
331
+ const branchPart = branch ? ` (${branch})` : "";
332
+ const diffPart =
333
+ diffAdded > 0 || diffRemoved > 0
334
+ ? ` ${theme.fg("success", `+${diffAdded}`)} ${theme.fg("error", `-${diffRemoved}`)}`
335
+ : "";
336
+ const line1Raw = theme.fg("dim", `${cwd}${branchPart}`) + diffPart;
337
+ const line1 = truncateToWidth(line1Raw, width);
338
+
339
+ // ── Line 2: runtime, context, tokens, cost, TPS, model ──
340
+ const segRuntime = theme.fg("dim", `⏱ ${fmtDuration(runtime)}`);
341
+
342
+ const ctxUsage = ctx.getContextUsage();
343
+ const segCtx = ctxUsage
344
+ ? theme.fg("dim", `ctx ${fmtTokens(ctxUsage.tokens)}/${fmtTokens(ctxUsage.contextWindow)}`)
345
+ : "";
346
+
347
+ const segTokens = theme.fg("dim", `↑${fmtTokens(totalIn)} ↓${fmtTokens(totalOut)}`);
348
+ const segCost = theme.fg("dim", `$${totalCost.toFixed(4)}`);
349
+
350
+ let segTps = "";
351
+ if (state.isStreaming && state.currentTurnStartTime) {
352
+ const elapsed = (Date.now() - state.currentTurnStartTime) / 1000;
353
+ const liveTps = elapsed > 0 ? state.currentTurnUpdateCount / elapsed : 0;
354
+ segTps = theme.fg("accent", `⚡ ${liveTps.toFixed(1)} tok/s`);
355
+ } else if (state.turns.length > 0) {
356
+ const last = state.turns[state.turns.length - 1];
357
+ segTps = theme.fg("accent", `⚡ ${last.tps.toFixed(1)} tok/s`);
358
+ }
359
+
360
+ const segModel = theme.fg("dim", model);
361
+
362
+ const leftRaw = [segRuntime, segCtx, segTokens, segCost, segTps].filter(Boolean).join(" ");
363
+ const leftW = visibleWidth(leftRaw);
364
+ const rightW = visibleWidth(segModel);
365
+
366
+ const gap = width - leftW - rightW;
367
+ let line2: string;
368
+ if (gap >= 1) {
369
+ line2 = leftRaw + " ".repeat(gap) + segModel;
370
+ } else {
371
+ line2 = leftRaw + " " + segModel;
372
+ }
373
+ line2 = truncateToWidth(line2, width);
374
+
375
+ return [line1, line2];
376
+ },
377
+ };
378
+ });
379
+ }
380
+
381
+ function teardownFooter(ctx: ExtensionContext) {
382
+ ctx.ui.setFooter(undefined);
383
+ }
384
+
385
+ /* ─── Commands ─── */
386
+
387
+ pi.registerCommand("obs", {
388
+ description: "Show observability dashboard (tokens, cost, TPS, runtime, history)",
389
+ handler: async (_args, ctx) => {
390
+ const lines: string[] = [];
391
+ const runtime = Date.now() - state.startTime;
392
+
393
+ const branchResult = await pi.exec("git", ["branch", "--show-current"], {
394
+ cwd: ctx.cwd,
395
+ throwOnError: false,
396
+ });
397
+ const branch = branchResult.stdout?.trim() || null;
398
+
399
+ const totalIn = state.turns.reduce((s, t) => s + t.inputTokens, 0);
400
+ const totalOut = state.turns.reduce((s, t) => s + t.outputTokens, 0);
401
+ const totalCost = state.turns.reduce((s, t) => s + t.cost, 0);
402
+
403
+ // ── Current Session ──
404
+ lines.push("");
405
+ lines.push(boxTop());
406
+ lines.push(boxLine("🕵️ Agent Observability Dashboard"));
407
+ lines.push(boxMid());
408
+ lines.push(boxLine(`Runtime: ${fmtDuration(runtime)}`));
409
+ lines.push(boxLine(`Dir: ${shortenPath(ctx.cwd)}`));
410
+ if (branch) lines.push(boxLine(`Branch: ${branch}`));
411
+ lines.push(boxLine(`Model: ${ctx.model?.id ?? "none"}`));
412
+ lines.push(boxMid());
413
+ lines.push(boxLine(`Tokens: ↑${fmtTokens(totalIn)} ↓${fmtTokens(totalOut)}`));
414
+ lines.push(boxLine(`Cost: $${totalCost.toFixed(6)}`));
415
+
416
+ if (state.turns.length > 0) {
417
+ lines.push(boxMid());
418
+ lines.push(boxLine("Turns:"));
419
+ for (let i = 0; i < state.turns.length; i++) {
420
+ const t = state.turns[i];
421
+ const parts = [
422
+ `#${i + 1}`,
423
+ `↑${fmtTokens(t.inputTokens)}`,
424
+ `↓${fmtTokens(t.outputTokens)}`,
425
+ fmtDuration(t.durationMs),
426
+ `${t.tps.toFixed(1)}/s`,
427
+ `$${t.cost.toFixed(2)}`,
428
+ t.model.slice(0, 14),
429
+ ];
430
+ lines.push(boxLine(parts.join(" ")));
431
+ }
432
+ }
433
+ lines.push(boxBot());
434
+
435
+ // ── History ──
436
+ const history = await loadHistory();
437
+ if (history.length > 0) {
438
+ lines.push("");
439
+ lines.push(boxTop());
440
+ lines.push(boxLine("📜 Last 10 Sessions"));
441
+ lines.push(boxMid());
442
+ for (const h of history.slice().reverse()) {
443
+ const date = new Date(h.endedAt).toLocaleDateString("en-US", {
444
+ month: "short",
445
+ day: "numeric",
446
+ hour: "2-digit",
447
+ minute: "2-digit",
448
+ });
449
+ const parts = [
450
+ date,
451
+ fmtDuration(h.runtimeMs),
452
+ `${h.turns}t`,
453
+ `↑${fmtTokens(h.inputTokens)}`,
454
+ `↓${fmtTokens(h.outputTokens)}`,
455
+ `$${h.cost.toFixed(2)}`,
456
+ h.model.slice(0, 10),
457
+ ];
458
+ lines.push(boxLine(parts.join(" ")));
459
+ }
460
+ lines.push(boxBot());
461
+ }
462
+
463
+ lines.push("");
464
+ console.log(lines.join("\n"));
465
+ ctx.ui.notify("Observability dashboard printed to console", "info");
466
+ },
467
+ });
468
+
469
+ pi.registerCommand("obs-toggle", {
470
+ description: "Toggle the observability footer on/off",
471
+ handler: async (_args, ctx) => {
472
+ state.footerEnabled = !state.footerEnabled;
473
+ if (state.footerEnabled) {
474
+ setupFooter(ctx);
475
+ ctx.ui.notify("Observability footer enabled", "success");
476
+ } else {
477
+ teardownFooter(ctx);
478
+ ctx.ui.notify("Observability footer disabled", "info");
479
+ }
480
+ },
481
+ });
482
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "pi-observability",
3
+ "version": "1.0.0",
4
+ "description": "Live observability dashboard for pi coding agent sessions — tokens, cost, TPS, runtime, git stats, and context usage",
5
+ "keywords": [
6
+ "pi-package",
7
+ "pi-extension",
8
+ "observability",
9
+ "dashboard",
10
+ "tokens",
11
+ "cost-tracking",
12
+ "tps",
13
+ "cli"
14
+ ],
15
+ "author": "",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/imran-vz/pi-observability.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/imran-vz/pi-observability/issues"
23
+ },
24
+ "homepage": "https://github.com/imran-vz/pi-observability#readme",
25
+ "pi": {
26
+ "extensions": [
27
+ "./extensions/observability.ts"
28
+ ]
29
+ },
30
+ "peerDependencies": {
31
+ "@mariozechner/pi-coding-agent": "*",
32
+ "@mariozechner/pi-ai": "*",
33
+ "@mariozechner/pi-tui": "*"
34
+ },
35
+ "devDependencies": {
36
+ "@mariozechner/pi-coding-agent": "^0.1.0",
37
+ "@mariozechner/pi-ai": "^0.1.0",
38
+ "@mariozechner/pi-tui": "^0.1.0",
39
+ "typescript": "^5.4.0"
40
+ }
41
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "noEmit": true,
11
+ "types": ["node"]
12
+ },
13
+ "include": ["extensions/**/*.ts"]
14
+ }