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.
- package/.ralph/import-cc-codex.md +31 -0
- package/.ralph/import-cc-codex.state.json +14 -0
- package/.ralph/mario-not-impl.md +69 -0
- package/.ralph/mario-not-impl.state.json +14 -0
- package/.ralph/mario-not-spec.md +163 -0
- package/.ralph/mario-not-spec.state.json +14 -0
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/RELEASING.md +34 -0
- package/agent-guidance/CHANGELOG.md +4 -0
- package/agent-guidance/README.md +102 -0
- package/agent-guidance/agent-guidance.ts +147 -0
- package/agent-guidance/package.json +22 -0
- package/agent-guidance/setup.sh +75 -0
- package/agent-guidance/templates/CLAUDE.md +5 -0
- package/agent-guidance/templates/CODEX.md +92 -0
- package/agent-guidance/templates/GEMINI.md +5 -0
- package/arcade/CHANGELOG.md +4 -0
- package/arcade/README.md +85 -0
- package/arcade/assets/picman.png +0 -0
- package/arcade/assets/ping.png +0 -0
- package/arcade/assets/spice-invaders.png +0 -0
- package/arcade/assets/tetris.png +0 -0
- package/arcade/mario-not/README.md +30 -0
- package/arcade/mario-not/boss.js +103 -0
- package/arcade/mario-not/camera.js +59 -0
- package/arcade/mario-not/collision.js +91 -0
- package/arcade/mario-not/colors.js +36 -0
- package/arcade/mario-not/constants.js +97 -0
- package/arcade/mario-not/core.js +39 -0
- package/arcade/mario-not/death.js +77 -0
- package/arcade/mario-not/effects.js +84 -0
- package/arcade/mario-not/enemies.js +31 -0
- package/arcade/mario-not/engine.js +171 -0
- package/arcade/mario-not/fireballs.js +98 -0
- package/arcade/mario-not/items.js +24 -0
- package/arcade/mario-not/levels.js +403 -0
- package/arcade/mario-not/logic.js +104 -0
- package/arcade/mario-not/mario-not.ts +297 -0
- package/arcade/mario-not/player.js +244 -0
- package/arcade/mario-not/render.js +257 -0
- package/arcade/mario-not/spec.md +548 -0
- package/arcade/mario-not/state.js +246 -0
- package/arcade/mario-not/tests/e2e.test.js +855 -0
- package/arcade/mario-not/tests/engine.test.js +888 -0
- package/arcade/mario-not/tests/fixtures/story0-frame.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story1-camera.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story1-glyphs.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story10-item.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story11-hazards.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story12-used-block.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story13-pipes.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story14-goal.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story15-hud-narrow.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story16-unknown-tile.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story17-mix.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story18-hud-score.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story19-cue.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story2-enemy.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story20-camera-offset.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story21-hud-zero.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story22-big-viewport.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story23-camera-negative.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story24-camera-width.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story25-camera-positive.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story26-hud-lives.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story27-hud-coins.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story28-item-viewport.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story29-enemy-viewport.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story3-hud.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story30-hud-score.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story31-particles-viewport.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story32-paused-frame.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story4-big.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story5-resume-hud.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story6-particles.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story6-paused.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story7-powerup.txt +4 -0
- package/arcade/mario-not/tests/fixtures/story8-hud-time.txt +2 -0
- package/arcade/mario-not/tests/fixtures/story9-hud-level.txt +2 -0
- package/arcade/mario-not/tiles.js +79 -0
- package/arcade/mario-not/tsconfig.json +14 -0
- package/arcade/mario-not/types.js +225 -0
- package/arcade/package.json +26 -0
- package/arcade/picman.ts +328 -0
- package/arcade/ping.ts +594 -0
- package/arcade/spice-invaders.ts +1104 -0
- package/arcade/tetris.ts +662 -0
- package/code-actions/CHANGELOG.md +4 -0
- package/code-actions/README.md +65 -0
- package/code-actions/actions.ts +107 -0
- package/code-actions/index.ts +148 -0
- package/code-actions/package.json +22 -0
- package/code-actions/search.ts +79 -0
- package/code-actions/snippets.ts +179 -0
- package/code-actions/ui.ts +120 -0
- package/files-widget/CHANGELOG.md +90 -0
- package/files-widget/DESIGN.md +452 -0
- package/files-widget/README.md +122 -0
- package/files-widget/TODO.md +141 -0
- package/files-widget/browser.ts +922 -0
- package/files-widget/comment.ts +5 -0
- package/files-widget/constants.ts +18 -0
- package/files-widget/demo.svg +1 -0
- package/files-widget/file-tree.ts +224 -0
- package/files-widget/file-viewer.ts +93 -0
- package/files-widget/git.ts +107 -0
- package/files-widget/index.ts +140 -0
- package/files-widget/input-utils.ts +3 -0
- package/files-widget/package.json +22 -0
- package/files-widget/types.ts +28 -0
- package/files-widget/utils.ts +26 -0
- package/files-widget/viewer.ts +424 -0
- package/import-cc-codex/research/import-chats-from-other-agents.md +135 -0
- package/import-cc-codex/spec.md +79 -0
- package/package.json +29 -0
- package/ralph-wiggum/CHANGELOG.md +7 -0
- package/ralph-wiggum/README.md +96 -0
- package/ralph-wiggum/SKILL.md +73 -0
- package/ralph-wiggum/index.ts +792 -0
- package/ralph-wiggum/package.json +25 -0
- package/raw-paste/CHANGELOG.md +7 -0
- package/raw-paste/README.md +52 -0
- package/raw-paste/index.ts +112 -0
- package/raw-paste/package.json +22 -0
- package/tab-status/CHANGELOG.md +4 -0
- package/tab-status/README.md +61 -0
- package/tab-status/assets/tab-status.png +0 -0
- package/tab-status/package.json +22 -0
- package/tab-status/tab-status.ts +179 -0
- package/usage-extension/CHANGELOG.md +17 -0
- package/usage-extension/README.md +120 -0
- package/usage-extension/index.ts +628 -0
- package/usage-extension/package.json +22 -0
- 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?
|