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,120 @@
1
+ import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
+ import { DynamicBorder } from "@mariozechner/pi-coding-agent";
3
+ import { Container, type SelectItem, SelectList, Text, matchesKey } from "@mariozechner/pi-tui";
4
+ import type { Snippet } from "./snippets";
5
+ import { getSnippetPreview, truncatePreview } from "./snippets";
6
+ import { buildSearchIndex, rankedFilterItems } from "./search";
7
+
8
+ const PREVIEW_WIDTH = 52;
9
+
10
+ function buildSnippetLabel(snippet: Snippet, index: number, indexWidth: number, timeWidth: number): string {
11
+ const preview = truncatePreview(getSnippetPreview(snippet), PREVIEW_WIDTH).padEnd(PREVIEW_WIDTH, " ");
12
+ const number = String(index + 1).padStart(indexWidth, " ");
13
+ const type = snippet.type === "block" ? "Block" : "Inline";
14
+ const lang = snippet.type === "block" && snippet.language ? ` (${snippet.language})` : "";
15
+ const time = snippet.sourceLabel.padEnd(timeWidth, " ");
16
+ return `${number}. ${preview} ${time} ${type}${lang}`;
17
+ }
18
+
19
+ export type PickResult = {
20
+ snippet: Snippet;
21
+ action?: "copy" | "insert";
22
+ };
23
+
24
+ export async function pickSnippet(ctx: ExtensionCommandContext, snippets: Snippet[]): Promise<PickResult | undefined> {
25
+ const indexWidth = String(snippets.length).length;
26
+ const timeWidth = Math.max(...snippets.map((snippet) => snippet.sourceLabel.length));
27
+ const items: SelectItem[] = snippets.map((snippet, idx) => ({
28
+ value: String(idx),
29
+ label: buildSnippetLabel(snippet, idx, indexWidth, timeWidth),
30
+ description: "",
31
+ }));
32
+ const searchIndex = buildSearchIndex(snippets, items);
33
+
34
+ const selectedIndex = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
35
+ const container = new Container();
36
+
37
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
38
+ const title = new Text(theme.fg("accent", theme.bold("Select Code Snippet")), 1, 0);
39
+ container.addChild(title);
40
+
41
+ const list = new SelectList(items, Math.min(items.length, 12), {
42
+ selectedPrefix: (t) => theme.fg("accent", t),
43
+ selectedText: (t) => theme.fg("accent", t),
44
+ description: (t) => theme.fg("muted", t),
45
+ scrollInfo: (t) => theme.fg("dim", t),
46
+ noMatch: (t) => theme.fg("warning", t),
47
+ });
48
+
49
+ list.onSelect = (item) => done(`copy:${item.value}`);
50
+ list.onCancel = () => done(null);
51
+ container.addChild(list);
52
+
53
+ const help = new Text(
54
+ theme.fg("dim", "Filter: (none) Enter copy Right/Tab insert Up/Down navigate Esc cancel"),
55
+ 1,
56
+ 0,
57
+ );
58
+ container.addChild(help);
59
+ container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
60
+
61
+ let filter = "";
62
+ const updateFilter = (next: string) => {
63
+ filter = next;
64
+ const listAny = list as unknown as { filteredItems: SelectItem[]; selectedIndex: number };
65
+ listAny.filteredItems = rankedFilterItems(filter, items, searchIndex);
66
+ listAny.selectedIndex = 0;
67
+
68
+ help.setText(
69
+ theme.fg(
70
+ "dim",
71
+ `Filter: ${filter.length > 0 ? filter : "(none)"} Enter copy Right/Tab insert Up/Down navigate Esc cancel`,
72
+ ),
73
+ );
74
+ list.invalidate();
75
+ tui.requestRender();
76
+ };
77
+
78
+ return {
79
+ render: (width: number) => container.render(width),
80
+ invalidate: () => container.invalidate(),
81
+ handleInput: (data: string) => {
82
+ if (matchesKey(data, "backspace")) {
83
+ if (filter.length > 0) updateFilter(filter.slice(0, -1));
84
+ return;
85
+ }
86
+
87
+ if (matchesKey(data, "right") || matchesKey(data, "tab")) {
88
+ const selected = list.getSelectedItem();
89
+ if (selected) done(`insert:${selected.value}`);
90
+ return;
91
+ }
92
+
93
+ if (data.length === 1 && data >= " " && data <= "~") {
94
+ updateFilter(filter + data);
95
+ return;
96
+ }
97
+
98
+ list.handleInput?.(data);
99
+ tui.requestRender();
100
+ },
101
+ };
102
+ });
103
+
104
+ if (selectedIndex === null || selectedIndex === undefined) return undefined;
105
+ const [action, rawIndex] = selectedIndex.split(":");
106
+ const index = Number.parseInt(rawIndex ?? "", 10);
107
+ if (Number.isNaN(index)) return undefined;
108
+ const snippet = snippets[index];
109
+ if (!snippet) return undefined;
110
+ if (action === "copy" || action === "insert") {
111
+ return { snippet, action };
112
+ }
113
+ return { snippet };
114
+ }
115
+
116
+ export async function pickAction(ctx: ExtensionCommandContext): Promise<"copy" | "insert" | "run" | undefined> {
117
+ const action = await ctx.ui.select("Action", ["Copy", "Insert", "Run"]);
118
+ if (!action) return undefined;
119
+ return action.toLowerCase() as "copy" | "insert" | "run";
120
+ }
@@ -0,0 +1,90 @@
1
+ # Changelog
2
+
3
+ All notable changes to this extension will be documented in this file.
4
+
5
+ ## [0.1.9] - 2026-01-26
6
+
7
+ ### Changed
8
+ - Bind render requests to avoid undefined context with the latest pi-tui
9
+
10
+ ## [0.1.8] - 2026-01-26
11
+
12
+ ### Changed
13
+ - Compute line counts asynchronously with loading indicators
14
+ - Build git repo trees from git file lists to avoid filesystem scans
15
+ - Add progressive filesystem scanning with safe mode for large folders
16
+ - Reduce refresh work to git metadata updates
17
+
18
+ ## [0.1.7] - 2026-01-26
19
+
20
+ ### Changed
21
+ - Cache line counts and skip large files to avoid freezes in big folders
22
+ - Avoid recomputing tree stats on every render
23
+ - Preserve line counts for open files across refreshes
24
+
25
+ ## [0.1.6] - 2026-01-24
26
+
27
+ ### Added
28
+ - Clearer install instructions and dependency notes in README
29
+
30
+ ## [0.1.5] - 2026-01-24
31
+
32
+ ### Added
33
+ - Demo recording embedded in README
34
+
35
+ ### Changed
36
+ - Comment sending now queues with follow-up delivery in streaming sessions
37
+ - Split viewer logic into `viewer.ts` and shared helpers
38
+ - Reduced browser render duplication with node format helpers
39
+
40
+ ## [0.1.4] - 2026-01-24
41
+
42
+ ### Changed
43
+ - Split viewer logic into `viewer.ts` and shared helpers
44
+ - Reduced browser render duplication with node format helpers
45
+
46
+ ## [0.1.3] - 2026-01-24
47
+
48
+ ### Changed
49
+ - `c` in viewer now opens an inline comment prompt and sends a follow-up message
50
+
51
+ ## [0.1.2] - 2026-01-24
52
+
53
+ ### Changed
54
+ - `c` in viewer now appends selection to editor input instead of sending immediately
55
+
56
+ ## [0.1.1] - 2026-01-24
57
+
58
+ ### Added
59
+ - README with install steps, dependencies, and keybindings
60
+
61
+ ### Changed
62
+ - Refactored into modular files (browser, git, tree, viewer, utils)
63
+
64
+ ## [0.1.0] - 2026-01-24
65
+
66
+ ### Added
67
+ - `/files` command opens full-screen file browser
68
+ - File tree with j/k navigation, Enter to open, h/l to collapse/expand
69
+ - File viewer with syntax highlighting via `bat`
70
+ - Markdown rendering via `glow`
71
+ - Git diff view via `delta` with line numbers
72
+ - Git status indicators (M, A, D, ?) on files
73
+ - Agent-modified file tracking (🤖 indicator)
74
+ - Changed files filter (`c` to toggle)
75
+ - Jump to next/prev changed file (`]`/`[`)
76
+ - Search in file tree (`/` then type)
77
+ - Search in file viewer (`/` then type, `n`/`N` for next/prev match)
78
+ - Select mode (`v`) to select lines and comment (`c`) to send to agent
79
+ - Line counts and diff stats (+/-) on files and collapsed folders
80
+ - Auto-refresh git status every 3 seconds (preserves expansion state)
81
+ - PageUp/PageDown support in browser and viewer
82
+ - Height adjustment (`+`/`-`)
83
+ - Works in non-git directories (git features gracefully disabled)
84
+
85
+ ### Dependencies
86
+ - `bat` - syntax highlighting (recommended)
87
+ - `glow` - markdown rendering (recommended)
88
+ - `delta` - diff formatting (recommended)
89
+
90
+ Install with: `brew install bat git-delta glow`
@@ -0,0 +1,452 @@
1
+ # Pi Editor Extension
2
+
3
+ A Pi extension that provides an in-terminal file browser, viewer, and review workflow - enabling you to navigate, view, and comment on files without leaving Pi or interrupting the agent.
4
+
5
+ ## Problem Statement
6
+
7
+ When working with coding agents in Pi:
8
+ - Viewing files requires either putting full content into context (`cat`) or leaving the terminal
9
+ - Reviewing diffs and agent changes means context-switching to an external editor
10
+ - No way to easily browse the project structure while the agent works
11
+ - No feedback loop for "I see this line, let me comment on it and send to agent"
12
+
13
+ ## Goals
14
+
15
+ ### MVP (v0.1)
16
+ 1. **File Tree Widget** - Toggleable file browser showing project structure
17
+ 2. **File Viewer** - View files with syntax highlighting, scroll, search
18
+ 3. **Diff View** - Toggle diff vs HEAD for modified files
19
+ 4. **Markdown Rendering** - Render .md files nicely (via `glow` or similar)
20
+ 5. **Select + Comment + Send** - Select text, add comment, send to agent as steering message
21
+
22
+ ### Future Enhancements
23
+ - Agent vs human edit attribution (Cursor Blame style)
24
+ - Word-level diff for prose (not just line-level git diff)
25
+ - Multiple comments before sending (queue workflow)
26
+ - Per-model attribution for agent edits
27
+ - Side-by-side diff view
28
+
29
+ ## Architecture
30
+
31
+ ### Extension Type
32
+ Pi extension (`~/.pi/agent/extensions/editor/index.ts`) using:
33
+ - `ctx.ui.setWidget()` for the file browser panel
34
+ - `ctx.ui.custom()` for the file viewer modal
35
+ - `pi.registerShortcut()` for hotkeys
36
+ - `pi.on("tool_result")` to track agent file modifications
37
+
38
+ ### External Dependencies
39
+ - `bat` - Syntax highlighting for code files (falls back to `cat`)
40
+ - `delta` - Pretty git diff output (falls back to `git diff`)
41
+ - `glow` - Markdown rendering (falls back to plain text)
42
+ - `fd` or `find` - Fast file discovery
43
+ - `git` - Status, diff operations
44
+
45
+ ### UI Layout
46
+
47
+ ```
48
+ ┌─────────────────────────────────────────────────────────────┐
49
+ │ [Agent output / streaming...] │
50
+ │ │
51
+ ├─────────────────────────────────────────────────────────────┤
52
+ │ 📁 File Browser (widget, toggleable with Ctrl+E) │
53
+ │ ┌─────────────────────────────────────────────────────────┐ │
54
+ │ │ > src/ │ │
55
+ │ │ ├── index.ts M │ │
56
+ │ │ ├── utils.ts M │ │
57
+ │ │ └── types.ts │ │
58
+ │ │ > tests/ │ │
59
+ │ │ README.md ? │ │
60
+ │ └─────────────────────────────────────────────────────────┘ │
61
+ ├─────────────────────────────────────────────────────────────┤
62
+ │ [Your input editor] │
63
+ └─────────────────────────────────────────────────────────────┘
64
+ ```
65
+
66
+ When you press Enter on a file, it opens the **File Viewer** (full-screen via `ctx.ui.custom()`):
67
+
68
+ ```
69
+ ┌─────────────────────────────────────────────────────────────┐
70
+ │ src/index.ts [Diff: ON] [q]uit │
71
+ ├─────────────────────────────────────────────────────────────┤
72
+ │ 1 │ import { foo } from './utils'; │
73
+ │ 2 │ import { Bar } from './types'; │
74
+ │ 3 │ │
75
+ │ 4 │+export function main() { <- added line │
76
+ │ 5 │+ console.log('hello'); │
77
+ │ 6 │+} │
78
+ │ 7 │ │
79
+ │ │
80
+ ├─────────────────────────────────────────────────────────────┤
81
+ │ j/k: scroll v: select d: toggle diff g: glow (md) q: quit│
82
+ └─────────────────────────────────────────────────────────────┘
83
+ ```
84
+
85
+ Selection mode (`v` to enter):
86
+
87
+ ```
88
+ ┌─────────────────────────────────────────────────────────────┐
89
+ │ src/index.ts [SELECT MODE] lines 4-6 │
90
+ ├─────────────────────────────────────────────────────────────┤
91
+ │ 1 │ import { foo } from './utils'; │
92
+ │ 2 │ import { Bar } from './types'; │
93
+ │ 3 │ │
94
+ │ ▌ 4 │ export function main() { <- selected │
95
+ │ ▌ 5 │ console.log('hello'); <- selected │
96
+ │ ▌ 6 │ } <- selected │
97
+ │ 7 │ │
98
+ │ │
99
+ ├─────────────────────────────────────────────────────────────┤
100
+ │ j/k: extend c: comment Esc: cancel │
101
+ └─────────────────────────────────────────────────────────────┘
102
+ ```
103
+
104
+ After pressing `c`, comment dialog appears, then sends to agent:
105
+
106
+ ```
107
+ Steering message sent to agent:
108
+
109
+ In `src/index.ts` (lines 4-6):
110
+ ```ts
111
+ export function main() {
112
+ console.log('hello');
113
+ }
114
+ ```
115
+
116
+ User comment: "This should take a name parameter and greet by name"
117
+ ```
118
+
119
+ ## Keybindings
120
+
121
+ | Key | Context | Action |
122
+ |-----|---------|--------|
123
+ | `Ctrl+E` | Global | Toggle file browser widget |
124
+ | `j/k` | File browser | Navigate files |
125
+ | `Enter` | File browser | Open file viewer |
126
+ | `/` | File browser | Fuzzy search |
127
+ | `q` | File viewer | Close viewer, return to pi |
128
+ | `j/k` | File viewer | Scroll up/down |
129
+ | `g/G` | File viewer | Go to top/bottom |
130
+ | `d` | File viewer | Toggle diff view |
131
+ | `v` | File viewer | Enter selection mode |
132
+ | `j/k` | Selection mode | Extend selection |
133
+ | `c` | Selection mode | Add comment to selection |
134
+ | `Esc` | Selection mode | Cancel selection |
135
+
136
+ ## Implementation Plan
137
+
138
+ ### Phase 1: File Browser Widget
139
+ - [ ] Create extension scaffold
140
+ - [ ] Implement file tree data structure (respect .gitignore)
141
+ - [ ] Git status integration (modified, untracked, staged indicators)
142
+ - [ ] Widget rendering with `ctx.ui.setWidget()`
143
+ - [ ] Toggle hotkey `Ctrl+E`
144
+ - [ ] Navigation (j/k, Enter to open)
145
+ - [ ] Fuzzy search (/)
146
+
147
+ ### Phase 2: File Viewer
148
+ - [ ] Full-screen viewer via `ctx.ui.custom()`
149
+ - [ ] Syntax highlighting via `bat` (with fallback)
150
+ - [ ] Scroll, go to line
151
+ - [ ] Markdown rendering via `glow` (with fallback)
152
+ - [ ] Diff toggle via `delta`/`git diff`
153
+
154
+ ### Phase 3: Select + Comment + Send
155
+ - [ ] Selection mode (v to enter, j/k to extend)
156
+ - [ ] Comment input dialog
157
+ - [ ] Format and send as steering message via `pi.sendUserMessage()`
158
+ - [ ] Handle mid-stream delivery (`deliverAs: "steer"`)
159
+
160
+ ### Phase 4: Agent Awareness (Future)
161
+ - [ ] Track agent file modifications via `tool_result` events
162
+ - [ ] Badge files in tree with "agent modified" indicator
163
+ - [ ] Store modification metadata in session via `pi.appendEntry()`
164
+ - [ ] Per-line attribution (which tool call modified which lines)
165
+
166
+ ## Technical Notes
167
+
168
+ ### File Tree Performance
169
+ - Use `fd` if available (respects .gitignore by default, fast)
170
+ - Fall back to `find` + manual .gitignore parsing
171
+ - Cache tree, invalidate on file system events or manual refresh
172
+
173
+ ### Syntax Highlighting
174
+ ```bash
175
+ # Check for bat
176
+ bat --style=numbers,changes --color=always "$file"
177
+
178
+ # Fallback
179
+ cat -n "$file"
180
+ ```
181
+
182
+ ### Diff Display
183
+ ```bash
184
+ # Check for delta
185
+ git diff HEAD -- "$file" | delta --side-by-side
186
+
187
+ # Fallback
188
+ git diff HEAD -- "$file"
189
+ ```
190
+
191
+ ### Markdown Rendering
192
+ ```bash
193
+ # Check for glow
194
+ glow "$file"
195
+
196
+ # Fallback: just show raw markdown with bat
197
+ bat "$file"
198
+ ```
199
+
200
+ ### Git Status
201
+ ```bash
202
+ git status --porcelain
203
+ # M = modified (staged)
204
+ # _M = modified (unstaged)
205
+ # ?? = untracked
206
+ # A = added
207
+ # D = deleted
208
+ ```
209
+
210
+ ## Research Notes
211
+
212
+ ### Cursor Blame (Cursor 2.4, Jan 2025)
213
+
214
+ Cursor introduced "Cursor Blame" which provides per-line attribution:
215
+ - Distinguishes code from **Tab completions**, **agent runs** (broken down by model), and **human edits**
216
+ - Gutter annotations showing who/what wrote each line
217
+ - Addresses the "which model made this change?" problem
218
+ - Users on Reddit specifically requested this to compare model performance
219
+
220
+ **Relevance**: The "agent vs human" distinction we want for Phase 4 is similar. We can track this via `tool_result` events for `write` and `edit` tools.
221
+
222
+ ### tuicr (Rust, `cargo install tuicr`)
223
+
224
+ GitHub: https://github.com/agavra/tuicr
225
+
226
+ A TUI for reviewing AI-generated diffs like a GitHub PR:
227
+ - **Infinite scroll diff view** - all changed files in one continuous scroll
228
+ - **Vim keybindings** - j/k, Ctrl-d/u, g/G, {/} for file jumping
229
+ - **Comments** - file-level or line-level with types (Note, Suggestion, Issue, Praise)
230
+ - **Visual mode** - select line ranges with v/V, comment on multiple lines
231
+ - **Session persistence** - reviews auto-save and reload
232
+ - **Clipboard export** - structured Markdown optimized for LLM consumption
233
+ - Supports git, jujutsu, mercurial
234
+
235
+ **Sample export format**:
236
+ ```markdown
237
+ In `src/index.ts` (lines 4-6):
238
+ ```ts
239
+ export function main() {
240
+ console.log('hello');
241
+ }
242
+ ```
243
+ [Issue] This should take a name parameter
244
+ ```
245
+
246
+ **Relevance**: The "select + comment + export for LLM" workflow is exactly what we want. Could either integrate tuicr or replicate the pattern.
247
+
248
+ ### critique (Bun/TypeScript, `bunx critique`)
249
+
250
+ GitHub: https://github.com/remorses/critique
251
+
252
+ Beautiful terminal diff viewer:
253
+ - **Word-level diff** - not just line-level, shows exactly what changed within lines
254
+ - **Split view** - side-by-side comparison
255
+ - **Syntax highlighting** - via tree-sitter
256
+ - **Watch mode** - `critique --watch` auto-refreshes on file changes
257
+ - **AI-powered explanation** - `critique review` uses Claude Code or OpenCode to explain changes
258
+ - **Web preview** - `critique web` generates shareable URL
259
+ - **Lazygit integration** - can be used as custom pager
260
+ - **Branch comparison** - `critique main feature-branch` for PR-style diffs
261
+
262
+ Commands:
263
+ ```bash
264
+ critique # View unstaged changes
265
+ critique --staged # View staged changes
266
+ critique HEAD # View last commit
267
+ critique --watch # Auto-refresh on changes
268
+ critique review # AI-powered explanation
269
+ critique web # Generate shareable web preview
270
+ ```
271
+
272
+ **Relevance**: Word-level diff is important for prose/markdown review. Could shell out to critique for diff viewing, or adopt their approach.
273
+
274
+ ### Integration Options Considered
275
+
276
+ | Approach | Pros | Cons |
277
+ |----------|------|------|
278
+ | Shell out to tuicr/critique | Battle-tested, feature-rich | Context switch, lose pi output visibility |
279
+ | Embed in pi widget | No context switch, agent-aware | More work, rebuild existing features |
280
+ | Hybrid (tree in pi, diff in external) | Best of both | Still some context switch |
281
+
282
+ **Decision**: Build file browser in pi (stays visible), use external tools for complex diff viewing if user wants, but provide basic in-pi diff view for quick checks.
283
+
284
+ ### tuicr Integration Analysis
285
+
286
+ **Value**: High - tuicr was built specifically for the "review AI changes and send feedback" workflow we want.
287
+
288
+ **Key Features**:
289
+ - Clipboard export in LLM-optimized markdown format
290
+ - Comment types (Note, Suggestion, Issue, Praise)
291
+ - Session persistence (reviews survive restarts)
292
+ - Visual mode for multi-line selection
293
+
294
+ **Integration Approach**:
295
+ ```bash
296
+ # User triggers review mode from pi
297
+ # 1. Pi extension spawns tuicr
298
+ tuicr
299
+
300
+ # 2. User reviews, adds comments, presses :wq or y (copy to clipboard)
301
+ # 3. tuicr exits, clipboard contains structured review
302
+
303
+ # 4. Pi extension reads clipboard and sends to agent
304
+ pbpaste | pi.sendUserMessage(...)
305
+ ```
306
+
307
+ **Feasibility**: Medium
308
+ - tuicr is a full TUI that takes over the terminal (can't see pi output while reviewing)
309
+ - No stdout/file output mode - only clipboard (would need to read clipboard after exit)
310
+ - User must quit tuicr to return to pi
311
+ - But: the workflow is natural - "let me review these changes" → review → return with feedback
312
+
313
+ **Proposed UX**:
314
+ ```
315
+ You: [working with agent]
316
+ Agent: [makes changes to multiple files]
317
+ You: /review # or Ctrl+R
318
+ [tuicr opens, shows all unstaged changes]
319
+ [user navigates, adds comments]
320
+ [user presses :wq]
321
+ [tuicr exits, review copied to clipboard]
322
+ [pi reads clipboard, sends as steering message]
323
+ Agent: [receives structured feedback, addresses comments]
324
+ ```
325
+
326
+ **Implementation**:
327
+ ```typescript
328
+ pi.registerCommand("review", {
329
+ description: "Open tuicr to review changes, send feedback to agent",
330
+ handler: async (args, ctx) => {
331
+ // Check if tuicr is installed
332
+ if (!hasCommand("tuicr")) {
333
+ ctx.ui.notify("Install tuicr: brew install agavra/tap/tuicr", "error");
334
+ return;
335
+ }
336
+
337
+ // Spawn tuicr (takes over terminal)
338
+ const result = await ctx.exec("tuicr", [], { stdio: "inherit" });
339
+
340
+ // Read clipboard after tuicr exits
341
+ const review = execSync("pbpaste", { encoding: "utf-8" });
342
+
343
+ if (review.includes("## Review Summary") || review.includes("```")) {
344
+ // Looks like a tuicr export, send to agent
345
+ pi.sendUserMessage(review, { deliverAs: "steer" });
346
+ ctx.ui.notify("Review sent to agent", "success");
347
+ }
348
+ },
349
+ });
350
+ ```
351
+
352
+ ### critique Integration Analysis
353
+
354
+ **Value**: High for diff viewing, medium for review workflow (no built-in comment system).
355
+
356
+ **Key Features**:
357
+ - Word-level diff (great for prose/markdown)
358
+ - `--watch` mode for live updates
359
+ - `--stdin` for piping (lazygit integration)
360
+ - `critique review` uses AI to explain changes
361
+ - `critique web` for shareable URLs
362
+
363
+ **Integration Approaches**:
364
+
365
+ 1. **As diff viewer only** (simple):
366
+ ```bash
367
+ # View specific file diff
368
+ critique --filter "src/index.ts"
369
+
370
+ # Watch mode while agent works
371
+ critique --watch
372
+ ```
373
+
374
+ 2. **As lazygit pager** (if user has lazygit):
375
+ ```yaml
376
+ # ~/.config/lazygit/config.yml
377
+ git:
378
+ paging:
379
+ pager: critique --stdin
380
+ ```
381
+
382
+ 3. **Web preview for sharing** (useful for async review):
383
+ ```bash
384
+ critique web --title "Agent changes for review"
385
+ # Returns URL like critique.work/v/abc123
386
+ ```
387
+
388
+ **Feasibility**: High
389
+ - `--watch` mode is ideal for "keep an eye on changes while agent works"
390
+ - No comment system, but pairs well with our in-pi select+comment flow
391
+ - Requires Bun (not Node.js)
392
+
393
+ **Proposed UX**:
394
+ ```
395
+ You: /diff # Quick diff view
396
+ [critique opens showing current changes]
397
+ [user reviews, presses q to quit]
398
+ [returns to pi]
399
+
400
+ You: /diff --watch # Live watch mode
401
+ [critique opens in watch mode]
402
+ [updates as agent modifies files]
403
+ [user presses q when done]
404
+ ```
405
+
406
+ ### Recommended Integration Strategy
407
+
408
+ **Phase 1 (MVP)**: Built-in basic diff
409
+ - Simple `git diff` via `delta` in pi's file viewer
410
+ - No external dependencies required
411
+ - Good enough for quick checks
412
+
413
+ **Phase 2**: Optional tuicr integration
414
+ - `/review` command spawns tuicr
415
+ - Clipboard capture sends review to agent
416
+ - Requires `brew install agavra/tap/tuicr`
417
+
418
+ **Phase 3**: Optional critique integration
419
+ - `/diff` command spawns critique
420
+ - `/diff --watch` for live monitoring
421
+ - Requires Bun + `bun install -g critique`
422
+
423
+ **Phase 4**: Deep integration
424
+ - Track which tool calls modified which files
425
+ - Pass this metadata to tuicr/critique
426
+ - Show "agent modified" vs "human modified" in diff view
427
+
428
+ ### Tools for Syntax Highlighting / Rendering
429
+
430
+ | Tool | Purpose | Install |
431
+ |------|---------|---------|
432
+ | `bat` | Syntax highlighting, line numbers | `brew install bat` |
433
+ | `delta` | Git diff beautifier, side-by-side | `brew install git-delta` |
434
+ | `glow` | Markdown rendering | `brew install glow` |
435
+ | `fd` | Fast file finder (respects .gitignore) | `brew install fd` |
436
+
437
+ These are standalone tools. This extension aims to provide similar functionality *inside* Pi without context switching, while optionally leveraging these tools for rendering.
438
+
439
+ ### Pi Extension Capabilities Used
440
+ - `ctx.ui.setWidget()` - Persistent widget above/below editor
441
+ - `ctx.ui.custom()` - Full-screen custom TUI component
442
+ - `pi.registerShortcut()` - Custom keybindings
443
+ - `pi.sendUserMessage()` - Send steering messages to agent
444
+ - `pi.on("tool_result")` - Track file modifications
445
+ - `pi.appendEntry()` - Persist state in session
446
+
447
+ ## Open Questions
448
+
449
+ 1. **Widget height** - Fixed height or percentage of terminal? Collapsible?
450
+ 2. **Multi-file comments** - Queue comments across files before sending, or send immediately?
451
+ 3. **Diff base** - Always vs HEAD, or configurable (vs staged, vs specific commit)?
452
+ 4. **File watcher** - Auto-refresh tree when files change, or manual refresh?