pi-extensions 0.1.9

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 (135) hide show
  1. package/.ralph/import-cc-codex.md +31 -0
  2. package/.ralph/import-cc-codex.state.json +14 -0
  3. package/.ralph/mario-not-impl.md +69 -0
  4. package/.ralph/mario-not-impl.state.json +14 -0
  5. package/.ralph/mario-not-spec.md +163 -0
  6. package/.ralph/mario-not-spec.state.json +14 -0
  7. package/LICENSE +21 -0
  8. package/README.md +65 -0
  9. package/RELEASING.md +34 -0
  10. package/agent-guidance/CHANGELOG.md +4 -0
  11. package/agent-guidance/README.md +102 -0
  12. package/agent-guidance/agent-guidance.ts +147 -0
  13. package/agent-guidance/package.json +22 -0
  14. package/agent-guidance/setup.sh +75 -0
  15. package/agent-guidance/templates/CLAUDE.md +5 -0
  16. package/agent-guidance/templates/CODEX.md +92 -0
  17. package/agent-guidance/templates/GEMINI.md +5 -0
  18. package/arcade/CHANGELOG.md +4 -0
  19. package/arcade/README.md +85 -0
  20. package/arcade/assets/picman.png +0 -0
  21. package/arcade/assets/ping.png +0 -0
  22. package/arcade/assets/spice-invaders.png +0 -0
  23. package/arcade/assets/tetris.png +0 -0
  24. package/arcade/mario-not/README.md +30 -0
  25. package/arcade/mario-not/boss.js +103 -0
  26. package/arcade/mario-not/camera.js +59 -0
  27. package/arcade/mario-not/collision.js +91 -0
  28. package/arcade/mario-not/colors.js +36 -0
  29. package/arcade/mario-not/constants.js +97 -0
  30. package/arcade/mario-not/core.js +39 -0
  31. package/arcade/mario-not/death.js +77 -0
  32. package/arcade/mario-not/effects.js +84 -0
  33. package/arcade/mario-not/enemies.js +31 -0
  34. package/arcade/mario-not/engine.js +171 -0
  35. package/arcade/mario-not/fireballs.js +98 -0
  36. package/arcade/mario-not/items.js +24 -0
  37. package/arcade/mario-not/levels.js +403 -0
  38. package/arcade/mario-not/logic.js +104 -0
  39. package/arcade/mario-not/mario-not.ts +297 -0
  40. package/arcade/mario-not/player.js +244 -0
  41. package/arcade/mario-not/render.js +257 -0
  42. package/arcade/mario-not/spec.md +548 -0
  43. package/arcade/mario-not/state.js +246 -0
  44. package/arcade/mario-not/tests/e2e.test.js +855 -0
  45. package/arcade/mario-not/tests/engine.test.js +888 -0
  46. package/arcade/mario-not/tests/fixtures/story0-frame.txt +4 -0
  47. package/arcade/mario-not/tests/fixtures/story1-camera.txt +4 -0
  48. package/arcade/mario-not/tests/fixtures/story1-glyphs.txt +4 -0
  49. package/arcade/mario-not/tests/fixtures/story10-item.txt +4 -0
  50. package/arcade/mario-not/tests/fixtures/story11-hazards.txt +4 -0
  51. package/arcade/mario-not/tests/fixtures/story12-used-block.txt +4 -0
  52. package/arcade/mario-not/tests/fixtures/story13-pipes.txt +4 -0
  53. package/arcade/mario-not/tests/fixtures/story14-goal.txt +4 -0
  54. package/arcade/mario-not/tests/fixtures/story15-hud-narrow.txt +2 -0
  55. package/arcade/mario-not/tests/fixtures/story16-unknown-tile.txt +4 -0
  56. package/arcade/mario-not/tests/fixtures/story17-mix.txt +4 -0
  57. package/arcade/mario-not/tests/fixtures/story18-hud-score.txt +2 -0
  58. package/arcade/mario-not/tests/fixtures/story19-cue.txt +4 -0
  59. package/arcade/mario-not/tests/fixtures/story2-enemy.txt +4 -0
  60. package/arcade/mario-not/tests/fixtures/story20-camera-offset.txt +4 -0
  61. package/arcade/mario-not/tests/fixtures/story21-hud-zero.txt +2 -0
  62. package/arcade/mario-not/tests/fixtures/story22-big-viewport.txt +4 -0
  63. package/arcade/mario-not/tests/fixtures/story23-camera-negative.txt +4 -0
  64. package/arcade/mario-not/tests/fixtures/story24-camera-width.txt +4 -0
  65. package/arcade/mario-not/tests/fixtures/story25-camera-positive.txt +4 -0
  66. package/arcade/mario-not/tests/fixtures/story26-hud-lives.txt +2 -0
  67. package/arcade/mario-not/tests/fixtures/story27-hud-coins.txt +2 -0
  68. package/arcade/mario-not/tests/fixtures/story28-item-viewport.txt +4 -0
  69. package/arcade/mario-not/tests/fixtures/story29-enemy-viewport.txt +4 -0
  70. package/arcade/mario-not/tests/fixtures/story3-hud.txt +2 -0
  71. package/arcade/mario-not/tests/fixtures/story30-hud-score.txt +2 -0
  72. package/arcade/mario-not/tests/fixtures/story31-particles-viewport.txt +4 -0
  73. package/arcade/mario-not/tests/fixtures/story32-paused-frame.txt +4 -0
  74. package/arcade/mario-not/tests/fixtures/story4-big.txt +4 -0
  75. package/arcade/mario-not/tests/fixtures/story5-resume-hud.txt +2 -0
  76. package/arcade/mario-not/tests/fixtures/story6-particles.txt +4 -0
  77. package/arcade/mario-not/tests/fixtures/story6-paused.txt +4 -0
  78. package/arcade/mario-not/tests/fixtures/story7-powerup.txt +4 -0
  79. package/arcade/mario-not/tests/fixtures/story8-hud-time.txt +2 -0
  80. package/arcade/mario-not/tests/fixtures/story9-hud-level.txt +2 -0
  81. package/arcade/mario-not/tiles.js +79 -0
  82. package/arcade/mario-not/tsconfig.json +14 -0
  83. package/arcade/mario-not/types.js +225 -0
  84. package/arcade/package.json +26 -0
  85. package/arcade/picman.ts +328 -0
  86. package/arcade/ping.ts +594 -0
  87. package/arcade/spice-invaders.ts +1104 -0
  88. package/arcade/tetris.ts +662 -0
  89. package/code-actions/CHANGELOG.md +4 -0
  90. package/code-actions/README.md +65 -0
  91. package/code-actions/actions.ts +107 -0
  92. package/code-actions/index.ts +148 -0
  93. package/code-actions/package.json +22 -0
  94. package/code-actions/search.ts +79 -0
  95. package/code-actions/snippets.ts +179 -0
  96. package/code-actions/ui.ts +120 -0
  97. package/files-widget/CHANGELOG.md +90 -0
  98. package/files-widget/DESIGN.md +452 -0
  99. package/files-widget/README.md +122 -0
  100. package/files-widget/TODO.md +141 -0
  101. package/files-widget/browser.ts +922 -0
  102. package/files-widget/comment.ts +5 -0
  103. package/files-widget/constants.ts +18 -0
  104. package/files-widget/demo.svg +1 -0
  105. package/files-widget/file-tree.ts +224 -0
  106. package/files-widget/file-viewer.ts +93 -0
  107. package/files-widget/git.ts +107 -0
  108. package/files-widget/index.ts +140 -0
  109. package/files-widget/input-utils.ts +3 -0
  110. package/files-widget/package.json +22 -0
  111. package/files-widget/types.ts +28 -0
  112. package/files-widget/utils.ts +26 -0
  113. package/files-widget/viewer.ts +424 -0
  114. package/import-cc-codex/research/import-chats-from-other-agents.md +135 -0
  115. package/import-cc-codex/spec.md +79 -0
  116. package/package.json +29 -0
  117. package/ralph-wiggum/CHANGELOG.md +7 -0
  118. package/ralph-wiggum/README.md +96 -0
  119. package/ralph-wiggum/SKILL.md +73 -0
  120. package/ralph-wiggum/index.ts +792 -0
  121. package/ralph-wiggum/package.json +25 -0
  122. package/raw-paste/CHANGELOG.md +7 -0
  123. package/raw-paste/README.md +52 -0
  124. package/raw-paste/index.ts +112 -0
  125. package/raw-paste/package.json +22 -0
  126. package/tab-status/CHANGELOG.md +4 -0
  127. package/tab-status/README.md +61 -0
  128. package/tab-status/assets/tab-status.png +0 -0
  129. package/tab-status/package.json +22 -0
  130. package/tab-status/tab-status.ts +179 -0
  131. package/usage-extension/CHANGELOG.md +17 -0
  132. package/usage-extension/README.md +120 -0
  133. package/usage-extension/index.ts +628 -0
  134. package/usage-extension/package.json +22 -0
  135. package/usage-extension/screenshot.png +0 -0
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@tmustier/pi-ralph-wiggum",
3
+ "version": "0.1.1",
4
+ "description": "Long-running agent loops for iterative development in Pi.",
5
+ "license": "MIT",
6
+ "author": "Thomas Mustier",
7
+ "keywords": [
8
+ "pi-package"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tmustier/pi-extensions.git",
13
+ "directory": "ralph-wiggum"
14
+ },
15
+ "bugs": "https://github.com/tmustier/pi-extensions/issues",
16
+ "homepage": "https://github.com/tmustier/pi-extensions/tree/main/ralph-wiggum",
17
+ "pi": {
18
+ "extensions": [
19
+ "index.ts"
20
+ ],
21
+ "skills": [
22
+ "SKILL.md"
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 - 2026-01-16
4
+ - Fix crash on Pi v0.47+ caused by CustomEditor signature change.
5
+
6
+ ## 0.1.0 - 2026-01-13
7
+ - Initial release.
@@ -0,0 +1,52 @@
1
+ # Raw Paste Extension
2
+
3
+ /paste to arm a one-shot raw paste so large clipboard content stays fully editable in the editor (no paste markers).
4
+ Useful for template prompts that need slight edits or when you need to refer to the text you pasted.
5
+
6
+ ## Without /paste -> the text is condensed
7
+ <img width="1487" height="104" alt="Screenshot 2026-01-09 at 17 38 31" src="https://github.com/user-attachments/assets/d6a0793e-3ef3-4c3b-83c8-f6ca10f0db51" />
8
+
9
+ ## With /paste -> you can see raw text
10
+ <img width="1487" height="387" alt="Screenshot 2026-01-09 at 17 39 35" src="https://github.com/user-attachments/assets/292c059a-8b06-40c2-abdd-795c0699336a" />
11
+
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pi install npm:@tmustier/pi-raw-paste
17
+ ```
18
+
19
+ ```bash
20
+ pi install git:github.com/tmustier/pi-extensions
21
+ ```
22
+
23
+ Then filter to just this extension in `~/.pi/agent/settings.json`:
24
+
25
+ ```json
26
+ {
27
+ "packages": [
28
+ {
29
+ "source": "git:github.com/tmustier/pi-extensions",
30
+ "extensions": ["raw-paste/index.ts"]
31
+ }
32
+ ]
33
+ }
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ - Command: `/paste`
39
+ - No default keybinding
40
+
41
+ After arming, paste normally (for example `Cmd+V` on macOS).
42
+
43
+ Optional: add your own keybinding in `raw-paste/index.ts` if your terminal supports it.
44
+
45
+ ## Notes
46
+
47
+ - Terminal.app and Warp intercept `Cmd+V` and `Cmd+Shift+V`, so the extension cannot see those keys.
48
+ - Use `/paste` before pasting in those terminals.
49
+
50
+ ## Changelog
51
+
52
+ See `CHANGELOG.md`.
@@ -0,0 +1,112 @@
1
+ import { CustomEditor, type ExtensionAPI, type ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+
3
+ const PASTE_START = "\x1b[200~";
4
+ const PASTE_END = "\x1b[201~";
5
+ const PASTE_END_LEN = PASTE_END.length;
6
+
7
+ class RawPasteEditor extends CustomEditor {
8
+ private rawPasteArmed = false;
9
+ private rawPasteBuffer = "";
10
+ private isInRawPaste = false;
11
+ private onArm?: () => void;
12
+
13
+ constructor(
14
+ tui: ConstructorParameters<typeof CustomEditor>[0],
15
+ theme: ConstructorParameters<typeof CustomEditor>[1],
16
+ keybindings: ConstructorParameters<typeof CustomEditor>[2],
17
+ onArm?: () => void,
18
+ ) {
19
+ super(tui, theme, keybindings);
20
+ this.onArm = onArm;
21
+ }
22
+
23
+ armRawPaste(): void {
24
+ this.rawPasteArmed = true;
25
+ this.onArm?.();
26
+ }
27
+
28
+ private flushRawPaste(content: string): void {
29
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
30
+ for (const char of normalized) {
31
+ super.handleInput(char);
32
+ }
33
+ }
34
+
35
+ private handleRawPasteInput(data: string): boolean {
36
+ let handled = false;
37
+
38
+ if (data.includes(PASTE_START)) {
39
+ this.isInRawPaste = true;
40
+ this.rawPasteBuffer = "";
41
+ data = data.replace(PASTE_START, "");
42
+ handled = true;
43
+ }
44
+
45
+ if (this.isInRawPaste) {
46
+ this.rawPasteBuffer += data;
47
+ const endIndex = this.rawPasteBuffer.indexOf(PASTE_END);
48
+ if (endIndex !== -1) {
49
+ const pasteContent = this.rawPasteBuffer.substring(0, endIndex);
50
+ const remaining = this.rawPasteBuffer.substring(endIndex + PASTE_END_LEN);
51
+ this.rawPasteBuffer = "";
52
+ this.isInRawPaste = false;
53
+ this.rawPasteArmed = false;
54
+
55
+ if (pasteContent.length > 0) {
56
+ this.flushRawPaste(pasteContent);
57
+ }
58
+ if (remaining.length > 0) {
59
+ this.handleInput(remaining);
60
+ }
61
+ }
62
+ return true;
63
+ }
64
+
65
+ return handled;
66
+ }
67
+
68
+ handleInput(data: string): void {
69
+ if (this.rawPasteArmed || this.isInRawPaste) {
70
+ if (this.handleRawPasteInput(data)) {
71
+ return;
72
+ }
73
+ }
74
+
75
+ super.handleInput(data);
76
+ }
77
+ }
78
+
79
+ export default function (pi: ExtensionAPI) {
80
+ let editor: RawPasteEditor | null = null;
81
+
82
+ const notifyArmed = (ctx: ExtensionContext): void => {
83
+ if (!ctx.hasUI) return;
84
+ ctx.ui.notify("Raw paste armed. Paste now.", "info");
85
+ };
86
+
87
+ const armRawPaste = (ctx: ExtensionContext): void => {
88
+ if (!editor) {
89
+ if (ctx.hasUI) ctx.ui.notify("Raw paste editor not ready.", "warning");
90
+ return;
91
+ }
92
+
93
+ editor.armRawPaste();
94
+ };
95
+
96
+ pi.on("session_start", (_event, ctx) => {
97
+ if (!ctx.hasUI) return;
98
+
99
+ ctx.ui.setEditorComponent((tui, theme, keybindings) => {
100
+ editor = new RawPasteEditor(tui, theme, keybindings, () => notifyArmed(ctx));
101
+ return editor;
102
+ });
103
+ });
104
+
105
+ pi.registerCommand("paste", {
106
+ description: "Arm raw paste for the next paste operation",
107
+ handler: async (_args, ctx) => {
108
+ armRawPaste(ctx);
109
+ },
110
+ });
111
+
112
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@tmustier/pi-raw-paste",
3
+ "version": "0.1.0",
4
+ "description": "One-shot raw paste support for Pi (/paste).",
5
+ "license": "MIT",
6
+ "author": "Thomas Mustier",
7
+ "keywords": [
8
+ "pi-package"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tmustier/pi-extensions.git",
13
+ "directory": "raw-paste"
14
+ },
15
+ "bugs": "https://github.com/tmustier/pi-extensions/issues",
16
+ "homepage": "https://github.com/tmustier/pi-extensions/tree/main/raw-paste",
17
+ "pi": {
18
+ "extensions": [
19
+ "index.ts"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-01-13
4
+ - Initial release.
@@ -0,0 +1,61 @@
1
+ # tab-status
2
+
3
+ Manage as many parallel Pis as your mind can handle without forgetting any of them.
4
+
5
+ Adds little modifiable indicators to tab titles:
6
+ - ✅ means done and committed
7
+ - 🚧 means done without a commit - Pi might need you to answer a question
8
+ - 🛑 means the agent is blocked (no messages or tool calls for 180s while running) or stopped due to an error
9
+ - `running...` means agent turn in progress - you can check back later
10
+
11
+ The more tabs you have open, the better it is.
12
+
13
+ ![tab-status](assets/tab-status.png)
14
+
15
+ ## Install
16
+
17
+ ### Pi package manager
18
+
19
+ ```bash
20
+ pi install npm:@tmustier/pi-tab-status
21
+ ```
22
+
23
+ ```bash
24
+ pi install git:github.com/tmustier/pi-extensions
25
+ ```
26
+
27
+ Then filter to just this extension in `~/.pi/agent/settings.json`:
28
+
29
+ ```json
30
+ {
31
+ "packages": [
32
+ {
33
+ "source": "git:github.com/tmustier/pi-extensions",
34
+ "extensions": ["tab-status/tab-status.ts"]
35
+ }
36
+ ]
37
+ }
38
+ ```
39
+
40
+ ### Local clone
41
+
42
+ ```bash
43
+ ln -s ~/pi-extensions/tab-status/tab-status.ts ~/.pi/agent/extensions/
44
+ ```
45
+
46
+ Or add to `~/.pi/agent/settings.json`:
47
+
48
+ ```json
49
+ {
50
+ "extensions": ["~/pi-extensions/tab-status/tab-status.ts"]
51
+ }
52
+ ```
53
+
54
+ ## Todo
55
+
56
+ - [x] Status indicators in terminal tabs
57
+ - [ ] Central location to view and navigate to specific tabs across terminal windows
58
+
59
+ ## Changelog
60
+
61
+ See `CHANGELOG.md`.
Binary file
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@tmustier/pi-tab-status",
3
+ "version": "0.1.0",
4
+ "description": "Terminal tab status indicators for Pi sessions.",
5
+ "license": "MIT",
6
+ "author": "Thomas Mustier",
7
+ "keywords": [
8
+ "pi-package"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/tmustier/pi-extensions.git",
13
+ "directory": "tab-status"
14
+ },
15
+ "bugs": "https://github.com/tmustier/pi-extensions/issues",
16
+ "homepage": "https://github.com/tmustier/pi-extensions/tree/main/tab-status",
17
+ "pi": {
18
+ "extensions": [
19
+ "tab-status.ts"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Update the terminal tab title with Pi run status (:new/:running/:✅/:🚧/:🛑).
3
+ */
4
+ import type {
5
+ ExtensionAPI,
6
+ ExtensionContext,
7
+ SessionStartEvent,
8
+ SessionSwitchEvent,
9
+ BeforeAgentStartEvent,
10
+ AgentStartEvent,
11
+ AgentEndEvent,
12
+ TurnStartEvent,
13
+ ToolCallEvent,
14
+ ToolResultEvent,
15
+ SessionShutdownEvent,
16
+ } from "@mariozechner/pi-coding-agent";
17
+ import type { AgentMessage } from "@mariozechner/pi-agent-core";
18
+ import type { AssistantMessage, StopReason } from "@mariozechner/pi-ai";
19
+ import { basename } from "node:path";
20
+
21
+ type StatusState = "new" | "running" | "doneCommitted" | "doneNoCommit" | "timeout";
22
+
23
+ type StatusTracker = {
24
+ state: StatusState;
25
+ running: boolean;
26
+ sawCommit: boolean;
27
+ };
28
+
29
+ const STATUS_TEXT: Record<StatusState, string> = {
30
+ new: ":new",
31
+ running: ":running...",
32
+ doneCommitted: ":✅",
33
+ doneNoCommit: ":🚧",
34
+ timeout: ":🛑",
35
+ };
36
+
37
+ const INACTIVE_TIMEOUT_MS = 180_000;
38
+ const GIT_COMMIT_RE = /\bgit\b[^\n]*\bcommit\b/;
39
+
40
+ export default function (pi: ExtensionAPI) {
41
+ const status: StatusTracker = {
42
+ state: "new",
43
+ running: false,
44
+ sawCommit: false,
45
+ };
46
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
47
+ const nativeClearTimeout = globalThis.clearTimeout;
48
+
49
+ const cwdBase = (ctx: ExtensionContext): string => basename(ctx.cwd || "pi");
50
+
51
+ const setTitle = (ctx: ExtensionContext, next: StatusState): void => {
52
+ status.state = next;
53
+ if (!ctx.hasUI) return;
54
+ ctx.ui.setTitle(`pi - ${cwdBase(ctx)}${STATUS_TEXT[next]}`);
55
+ };
56
+
57
+ const clearTabTimeout = (): void => {
58
+ if (timeoutId === undefined) return;
59
+ nativeClearTimeout(timeoutId);
60
+ timeoutId = undefined;
61
+ };
62
+
63
+ const resetTimeout = (ctx: ExtensionContext): void => {
64
+ clearTabTimeout();
65
+ timeoutId = setTimeout(() => {
66
+ if (status.running && status.state === "running") {
67
+ setTitle(ctx, "timeout");
68
+ }
69
+ }, INACTIVE_TIMEOUT_MS);
70
+ };
71
+
72
+ const markActivity = (ctx: ExtensionContext): void => {
73
+ if (status.state === "timeout") {
74
+ setTitle(ctx, "running");
75
+ }
76
+ if (!status.running) return;
77
+ resetTimeout(ctx);
78
+ };
79
+
80
+ const resetState = (ctx: ExtensionContext, next: StatusState): void => {
81
+ status.running = false;
82
+ status.sawCommit = false;
83
+ clearTabTimeout();
84
+ setTitle(ctx, next);
85
+ };
86
+
87
+ const beginRun = (ctx: ExtensionContext): void => {
88
+ status.running = true;
89
+ status.sawCommit = false;
90
+ setTitle(ctx, "running");
91
+ resetTimeout(ctx);
92
+ };
93
+
94
+ const getStopReason = (messages: AgentMessage[]): StopReason | undefined => {
95
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
96
+ const message = messages[i];
97
+ if (message.role === "assistant") {
98
+ return (message as AssistantMessage).stopReason;
99
+ }
100
+ }
101
+ return undefined;
102
+ };
103
+
104
+ const handlers = [
105
+ [
106
+ "session_start",
107
+ async (_event: SessionStartEvent, ctx: ExtensionContext) => {
108
+ resetState(ctx, "new");
109
+ },
110
+ ],
111
+ [
112
+ "session_switch",
113
+ async (event: SessionSwitchEvent, ctx: ExtensionContext) => {
114
+ resetState(ctx, event.reason === "new" ? "new" : "doneCommitted");
115
+ },
116
+ ],
117
+ [
118
+ "before_agent_start",
119
+ async (_event: BeforeAgentStartEvent, ctx: ExtensionContext) => {
120
+ markActivity(ctx);
121
+ },
122
+ ],
123
+ [
124
+ "agent_start",
125
+ async (_event: AgentStartEvent, ctx: ExtensionContext) => {
126
+ beginRun(ctx);
127
+ },
128
+ ],
129
+ [
130
+ "turn_start",
131
+ async (_event: TurnStartEvent, ctx: ExtensionContext) => {
132
+ markActivity(ctx);
133
+ },
134
+ ],
135
+ [
136
+ "tool_call",
137
+ async (event: ToolCallEvent, ctx: ExtensionContext) => {
138
+ if (event.toolName === "bash") {
139
+ const command = typeof event.input.command === "string" ? event.input.command : "";
140
+ if (command && GIT_COMMIT_RE.test(command)) {
141
+ status.sawCommit = true;
142
+ }
143
+ }
144
+ markActivity(ctx);
145
+ },
146
+ ],
147
+ [
148
+ "tool_result",
149
+ async (_event: ToolResultEvent, ctx: ExtensionContext) => {
150
+ markActivity(ctx);
151
+ },
152
+ ],
153
+ [
154
+ "agent_end",
155
+ async (event: AgentEndEvent, ctx: ExtensionContext) => {
156
+ status.running = false;
157
+ clearTabTimeout();
158
+ const stopReason = getStopReason(event.messages);
159
+ if (stopReason === "error") {
160
+ setTitle(ctx, "timeout");
161
+ return;
162
+ }
163
+ setTitle(ctx, status.sawCommit ? "doneCommitted" : "doneNoCommit");
164
+ },
165
+ ],
166
+ [
167
+ "session_shutdown",
168
+ async (_event: SessionShutdownEvent, ctx: ExtensionContext) => {
169
+ clearTabTimeout();
170
+ if (!ctx.hasUI) return;
171
+ ctx.ui.setTitle(`pi - ${cwdBase(ctx)}`);
172
+ },
173
+ ],
174
+ ] as const;
175
+
176
+ for (const [event, handler] of handlers) {
177
+ pi.on(event, handler as (event: unknown, ctx: ExtensionContext) => void);
178
+ }
179
+ }
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.1.2 - 2026-01-13
4
+ - Add loading spinner while parsing session files (Esc to cancel)
5
+ - Make data loading async to keep UI responsive
6
+ - Thanks @nicobailon
7
+
8
+ ## 0.1.1 - 2026-01-12
9
+ - Deduplicate assistant messages across branched sessions to avoid double-counting
10
+ - Tokens total now excludes cache read/write tokens (cache remains in Cache column)
11
+ - Thanks @nicobailon
12
+
13
+ ## 0.1.0 - 2026-01-10
14
+ - Initial release
15
+ - Collapsible provider/model view
16
+ - Three time periods: Today, This Week, All Time
17
+ - Token breakdown columns (dimmed for de-emphasis)
@@ -0,0 +1,120 @@
1
+ # /usage - Usage Statistics Dashboard
2
+
3
+ A Pi extension that displays aggregated usage statistics across all sessions.
4
+
5
+ ![Usage Statistics Screenshot](screenshot.png)
6
+
7
+ ## Compatibility
8
+
9
+ - **Pi version:** 0.42.4+
10
+ - **Last updated:** 2026-01-13
11
+
12
+ ## Installation
13
+
14
+ ### Pi package manager
15
+
16
+ ```bash
17
+ pi install npm:@tmustier/pi-usage-extension
18
+ ```
19
+
20
+ ```bash
21
+ pi install git:github.com/tmustier/pi-extensions
22
+ ```
23
+
24
+ Then filter to just this extension in `~/.pi/agent/settings.json`:
25
+
26
+ ```json
27
+ {
28
+ "packages": [
29
+ {
30
+ "source": "git:github.com/tmustier/pi-extensions",
31
+ "extensions": ["usage-extension/index.ts"]
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ ### Local clone
38
+
39
+ Add to your `~/.pi/agent/settings.json`:
40
+
41
+ ```json
42
+ {
43
+ "extensions": [
44
+ "~/pi-extensions/usage-extension"
45
+ ]
46
+ }
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ In Pi, run:
52
+ ```
53
+ /usage
54
+ ```
55
+
56
+ ## Features
57
+
58
+ ### Time Periods
59
+
60
+ | Period | Definition |
61
+ |--------|------------|
62
+ | **Today** | From midnight (00:00) today |
63
+ | **This Week** | From Monday 00:00 of the current week |
64
+ | **All Time** | All recorded sessions |
65
+
66
+ Use `Tab` or `←`/`→` to switch between periods.
67
+
68
+ ### Timezone
69
+
70
+ Time periods are calculated in the local timezone where Pi runs. If you want to override it, set the `TZ` environment variable (IANA timezone, e.g. `TZ=UTC` or `TZ=America/New_York`) before launching Pi.
71
+
72
+ ### Columns
73
+
74
+ | Column | Description |
75
+ |--------|-------------|
76
+ | **Provider / Model** | Provider name, expandable to show models |
77
+ | **Sessions** | Number of unique sessions |
78
+ | **Msgs** | Number of assistant messages |
79
+ | **Cost** | Total cost in USD (from API response) |
80
+ | **Tokens** | Total tokens (input + output) |
81
+ | **↑In** | Input tokens *(dimmed)* |
82
+ | **↓Out** | Output tokens *(dimmed)* |
83
+ | **Cache** | Cache read + write tokens *(dimmed)* |
84
+
85
+ ### Navigation
86
+
87
+ | Key | Action |
88
+ |-----|--------|
89
+ | `Tab` / `←` `→` | Switch time period |
90
+ | `↑` `↓` | Select provider |
91
+ | `Enter` / `Space` | Expand/collapse provider to show models |
92
+ | `q` / `Esc` | Close |
93
+
94
+ ## Provider Notes
95
+
96
+ ### Cost Tracking
97
+
98
+ Cost data comes directly from the API response (`usage.cost.total`). Accuracy depends on the provider reporting costs.
99
+
100
+ ### Cache Tokens
101
+
102
+ Cache token support varies by provider:
103
+
104
+ | Provider | Cache Read | Cache Write |
105
+ |----------|------------|-------------|
106
+ | Anthropic | ✓ | ✓ |
107
+ | Google | ✓ | ✗ |
108
+ | OpenAI Codex | ✓ | ✗ |
109
+
110
+ The "Cache" column combines both read and write tokens.
111
+
112
+ ## Data Source
113
+
114
+ Statistics are parsed from session files in `~/.pi/agent/sessions/`. Each session is a JSONL file containing message entries with usage data. Assistant messages duplicated across branched session files are deduplicated by timestamp + total tokens (matching ccusage).
115
+
116
+ Respects the `PI_CODING_AGENT_DIR` environment variable if set.
117
+
118
+ ## Changelog
119
+
120
+ See `CHANGELOG.md`.