pi-git-graph-sidebar 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/extensions/git-graph-sidebar.ts +309 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yuxiang-gao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# pi-git-graph-sidebar
|
|
2
|
+
|
|
3
|
+
A VS Code Git Graph-style sidebar overlay for the [Pi coding agent](https://github.com/earendil-works/pi-coding-agent) TUI.
|
|
4
|
+
|
|
5
|
+
It opens an interactive right-side graph of your repository history using `git log --graph --decorate --oneline --all`.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Right-side responsive TUI overlay/sidebar
|
|
10
|
+
- ASCII commit graph across all refs
|
|
11
|
+
- Branch/decorator display from Git
|
|
12
|
+
- Keyboard navigation
|
|
13
|
+
- Refresh without closing the sidebar
|
|
14
|
+
- Works as a Pi package installed from npm or GitHub
|
|
15
|
+
|
|
16
|
+
## Preview
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
╭──────────────── Git Graph ────────────────╮
|
|
20
|
+
│branch main • 120/120 │
|
|
21
|
+
│cwd /path/to/repo │
|
|
22
|
+
├───────────────────────────────────────────┤
|
|
23
|
+
│› * a1b2c3d (HEAD -> main, origin/main) ...│
|
|
24
|
+
│ * d4e5f6a Add feature │
|
|
25
|
+
│ | * 123abcd (feature/x) Try variant │
|
|
26
|
+
│ |/ │
|
|
27
|
+
│ * 987fedc Initial commit │
|
|
28
|
+
├───────────────────────────────────────────┤
|
|
29
|
+
│↑↓/jk scroll • r refresh • q/esc close │
|
|
30
|
+
╰───────────────────────────────────────────╯
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### From npm
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pi install npm:pi-git-graph-sidebar
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then restart Pi or run `/reload` in an existing Pi TUI session.
|
|
42
|
+
|
|
43
|
+
### From GitHub
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pi install git:github.com/yuxiang-gao/pi-git-graph-sidebar
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Try without installing
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pi -e npm:pi-git-graph-sidebar
|
|
53
|
+
# or
|
|
54
|
+
pi -e git:github.com/yuxiang-gao/pi-git-graph-sidebar
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Manual local install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
mkdir -p ~/.pi/agent/extensions
|
|
61
|
+
curl -L \
|
|
62
|
+
https://raw.githubusercontent.com/yuxiang-gao/pi-git-graph-sidebar/main/extensions/git-graph-sidebar.ts \
|
|
63
|
+
-o ~/.pi/agent/extensions/git-graph-sidebar.ts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Restart Pi or run `/reload`.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
Open the sidebar:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
/git-graph
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Open with a custom commit limit:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
/git-graph 200
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Keyboard shortcut:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
ctrl+shift+g
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Keyboard controls inside the sidebar:
|
|
89
|
+
|
|
90
|
+
| Key | Action |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `↑` / `k` | Move up |
|
|
93
|
+
| `↓` / `j` | Move down |
|
|
94
|
+
| `PgUp` / `ctrl+u` | Page up |
|
|
95
|
+
| `PgDn` / `ctrl+d` | Page down |
|
|
96
|
+
| `g` | Jump to top |
|
|
97
|
+
| `G` | Jump to bottom |
|
|
98
|
+
| `r` | Refresh graph |
|
|
99
|
+
| `q` / `esc` | Close sidebar |
|
|
100
|
+
|
|
101
|
+
## Notes
|
|
102
|
+
|
|
103
|
+
- Requires `git` on `PATH`.
|
|
104
|
+
- The sidebar is visible only when the terminal is at least 90 columns wide.
|
|
105
|
+
- The default commit limit is 120; the maximum accepted limit is 500.
|
|
106
|
+
- Pi packages run extension code with local system access. Review code before installing third-party packages.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
Clone and run Pi with the local package:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/yuxiang-gao/pi-git-graph-sidebar.git
|
|
114
|
+
cd pi-git-graph-sidebar
|
|
115
|
+
pi -e .
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or install the local checkout globally:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pi install /absolute/path/to/pi-git-graph-sidebar
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
|
|
5
|
+
|
|
6
|
+
function visibleWidth(text: string): number {
|
|
7
|
+
return Array.from(text.replace(ANSI_PATTERN, "")).length;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function truncateToWidth(text: string, width: number, ellipsis = "…"): string {
|
|
11
|
+
if (width <= 0) return "";
|
|
12
|
+
if (visibleWidth(text) <= width) return text;
|
|
13
|
+
const plain = text.replace(ANSI_PATTERN, "");
|
|
14
|
+
const suffix = visibleWidth(ellipsis) < width ? ellipsis : "";
|
|
15
|
+
return `${Array.from(plain).slice(0, Math.max(0, width - visibleWidth(suffix))).join("")}${suffix}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isKey(data: string, key: string): boolean {
|
|
19
|
+
const aliases: Record<string, string[]> = {
|
|
20
|
+
escape: ["\x1b"],
|
|
21
|
+
up: ["\x1b[A", "\x1bOA"],
|
|
22
|
+
down: ["\x1b[B", "\x1bOB"],
|
|
23
|
+
pageUp: ["\x1b[5~"],
|
|
24
|
+
pageDown: ["\x1b[6~"],
|
|
25
|
+
home: ["\x1b[H", "\x1bOH", "\x1b[1~"],
|
|
26
|
+
end: ["\x1b[F", "\x1bOF", "\x1b[4~"],
|
|
27
|
+
ctrlC: ["\x03"],
|
|
28
|
+
ctrlD: ["\x04"],
|
|
29
|
+
ctrlU: ["\x15"],
|
|
30
|
+
};
|
|
31
|
+
return aliases[key]?.includes(data) ?? data === key;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type GitGraphData = {
|
|
35
|
+
cwd: string;
|
|
36
|
+
branch: string;
|
|
37
|
+
lines: string[];
|
|
38
|
+
error?: string;
|
|
39
|
+
loadedAt: Date;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DEFAULT_LIMIT = 120;
|
|
43
|
+
const MAX_LIMIT = 500;
|
|
44
|
+
|
|
45
|
+
function parseLimit(args: string): number {
|
|
46
|
+
const trimmed = args.trim();
|
|
47
|
+
if (!trimmed) return DEFAULT_LIMIT;
|
|
48
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
49
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LIMIT;
|
|
50
|
+
return Math.min(parsed, MAX_LIMIT);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function loadGitGraph(pi: ExtensionAPI, cwd: string, limit: number): Promise<GitGraphData> {
|
|
54
|
+
const common = { cwd, loadedAt: new Date() };
|
|
55
|
+
const root = await pi.exec("git", ["rev-parse", "--show-toplevel"], { cwd, timeout: 3000 });
|
|
56
|
+
if (root.code !== 0) {
|
|
57
|
+
return {
|
|
58
|
+
...common,
|
|
59
|
+
branch: "",
|
|
60
|
+
lines: [],
|
|
61
|
+
error: "Not inside a git repository.",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const branchResult = await pi.exec("git", ["branch", "--show-current"], { cwd, timeout: 3000 });
|
|
66
|
+
const branch = branchResult.stdout.trim() || "detached";
|
|
67
|
+
const log = await pi.exec(
|
|
68
|
+
"git",
|
|
69
|
+
[
|
|
70
|
+
"log",
|
|
71
|
+
"--graph",
|
|
72
|
+
"--decorate=short",
|
|
73
|
+
"--oneline",
|
|
74
|
+
"--abbrev-commit",
|
|
75
|
+
"--date-order",
|
|
76
|
+
"--all",
|
|
77
|
+
`-n${limit}`,
|
|
78
|
+
],
|
|
79
|
+
{ cwd, timeout: 8000 },
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (log.code !== 0) {
|
|
83
|
+
return {
|
|
84
|
+
...common,
|
|
85
|
+
branch,
|
|
86
|
+
lines: [],
|
|
87
|
+
error: log.stderr.trim() || "git log failed.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...common,
|
|
93
|
+
branch,
|
|
94
|
+
lines: log.stdout.split("\n").filter((line) => line.trim().length > 0),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class GitGraphSidebar implements Component {
|
|
99
|
+
private offset = 0;
|
|
100
|
+
private selected = 0;
|
|
101
|
+
private loading = false;
|
|
102
|
+
private message: string | undefined;
|
|
103
|
+
private readonly pi: ExtensionAPI;
|
|
104
|
+
private readonly tui: TUI;
|
|
105
|
+
private readonly theme: Theme;
|
|
106
|
+
private readonly cwd: string;
|
|
107
|
+
private readonly limit: number;
|
|
108
|
+
private data: GitGraphData;
|
|
109
|
+
private readonly done: () => void;
|
|
110
|
+
|
|
111
|
+
constructor(
|
|
112
|
+
pi: ExtensionAPI,
|
|
113
|
+
tui: TUI,
|
|
114
|
+
theme: Theme,
|
|
115
|
+
cwd: string,
|
|
116
|
+
limit: number,
|
|
117
|
+
data: GitGraphData,
|
|
118
|
+
done: () => void,
|
|
119
|
+
) {
|
|
120
|
+
this.pi = pi;
|
|
121
|
+
this.tui = tui;
|
|
122
|
+
this.theme = theme;
|
|
123
|
+
this.cwd = cwd;
|
|
124
|
+
this.limit = limit;
|
|
125
|
+
this.data = data;
|
|
126
|
+
this.done = done;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
handleInput(data: string): void {
|
|
130
|
+
if (isKey(data, "escape") || isKey(data, "q") || isKey(data, "ctrlC")) {
|
|
131
|
+
this.done();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (isKey(data, "up") || isKey(data, "k")) {
|
|
135
|
+
this.selected = Math.max(0, this.selected - 1);
|
|
136
|
+
this.ensureSelectedVisible();
|
|
137
|
+
this.tui.requestRender();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (isKey(data, "down") || isKey(data, "j")) {
|
|
141
|
+
this.selected = Math.min(Math.max(0, this.data.lines.length - 1), this.selected + 1);
|
|
142
|
+
this.ensureSelectedVisible();
|
|
143
|
+
this.tui.requestRender();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (isKey(data, "pageUp") || isKey(data, "ctrlU")) {
|
|
147
|
+
this.selected = Math.max(0, this.selected - 10);
|
|
148
|
+
this.ensureSelectedVisible();
|
|
149
|
+
this.tui.requestRender();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (isKey(data, "pageDown") || isKey(data, "ctrlD")) {
|
|
153
|
+
this.selected = Math.min(Math.max(0, this.data.lines.length - 1), this.selected + 10);
|
|
154
|
+
this.ensureSelectedVisible();
|
|
155
|
+
this.tui.requestRender();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (isKey(data, "home") || isKey(data, "g")) {
|
|
159
|
+
this.selected = 0;
|
|
160
|
+
this.offset = 0;
|
|
161
|
+
this.tui.requestRender();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (isKey(data, "end") || isKey(data, "G")) {
|
|
165
|
+
this.selected = Math.max(0, this.data.lines.length - 1);
|
|
166
|
+
this.ensureSelectedVisible();
|
|
167
|
+
this.tui.requestRender();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (isKey(data, "r")) {
|
|
171
|
+
void this.refresh();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
render(width: number): string[] {
|
|
176
|
+
const innerWidth = Math.max(1, width - 2);
|
|
177
|
+
const border = (text: string) => this.theme.fg("border", text);
|
|
178
|
+
const pad = (text: string) => {
|
|
179
|
+
const truncated = truncateToWidth(text, innerWidth, "…");
|
|
180
|
+
return `${truncated}${" ".repeat(Math.max(0, innerWidth - visibleWidth(truncated)))}`;
|
|
181
|
+
};
|
|
182
|
+
const row = (text: string) => `${border("│")}${pad(text)}${border("│")}`;
|
|
183
|
+
const lines: string[] = [];
|
|
184
|
+
|
|
185
|
+
const title = this.theme.fg("accent", this.theme.bold(" Git Graph "));
|
|
186
|
+
const titleWidth = visibleWidth(" Git Graph ");
|
|
187
|
+
const left = "─".repeat(Math.max(0, Math.floor((innerWidth - titleWidth) / 2)));
|
|
188
|
+
const right = "─".repeat(Math.max(0, innerWidth - titleWidth - left.length));
|
|
189
|
+
lines.push(`${border("╭" + left)}${title}${border(right + "╮")}`);
|
|
190
|
+
lines.push(row(`${this.theme.fg("dim", "branch")} ${this.theme.fg("success", this.data.branch || "-")} ${this.theme.fg("dim", `• ${this.data.lines.length}/${this.limit}`)}`));
|
|
191
|
+
lines.push(row(this.theme.fg("dim", `cwd ${this.cwd}`)));
|
|
192
|
+
lines.push(border("├") + border("─".repeat(innerWidth)) + border("┤"));
|
|
193
|
+
|
|
194
|
+
if (this.loading) {
|
|
195
|
+
lines.push(row(`${this.theme.fg("accent", "⟳")} refreshing…`));
|
|
196
|
+
}
|
|
197
|
+
if (this.message) {
|
|
198
|
+
lines.push(row(this.theme.fg("warning", this.message)));
|
|
199
|
+
}
|
|
200
|
+
if (this.data.error) {
|
|
201
|
+
lines.push(row(this.theme.fg("error", this.data.error)));
|
|
202
|
+
} else if (this.data.lines.length === 0) {
|
|
203
|
+
lines.push(row(this.theme.fg("muted", "No commits found.")));
|
|
204
|
+
} else {
|
|
205
|
+
const headerLines = lines.length;
|
|
206
|
+
const footerLines = 3;
|
|
207
|
+
const visibleRows = Math.max(4, 28 - headerLines - footerLines);
|
|
208
|
+
this.clampScroll(visibleRows);
|
|
209
|
+
const shown = this.data.lines.slice(this.offset, this.offset + visibleRows);
|
|
210
|
+
for (let i = 0; i < shown.length; i++) {
|
|
211
|
+
const absoluteIndex = this.offset + i;
|
|
212
|
+
const marker = absoluteIndex === this.selected ? this.theme.fg("accent", "› ") : " ";
|
|
213
|
+
const content = this.colorizeCommitLine(shown[i]!);
|
|
214
|
+
const text = absoluteIndex === this.selected ? this.theme.bg("selectedBg", `${marker}${content}`) : `${marker}${content}`;
|
|
215
|
+
lines.push(row(text));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
lines.push(border("├") + border("─".repeat(innerWidth)) + border("┤"));
|
|
220
|
+
lines.push(row(this.theme.fg("dim", "↑↓/jk scroll • r refresh • q/esc close")));
|
|
221
|
+
lines.push(border("╰") + border("─".repeat(innerWidth)) + border("╯"));
|
|
222
|
+
return lines;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
invalidate(): void {}
|
|
226
|
+
|
|
227
|
+
private async refresh(): Promise<void> {
|
|
228
|
+
if (this.loading) return;
|
|
229
|
+
this.loading = true;
|
|
230
|
+
this.message = undefined;
|
|
231
|
+
this.tui.requestRender();
|
|
232
|
+
try {
|
|
233
|
+
this.data = await loadGitGraph(this.pi, this.cwd, this.limit);
|
|
234
|
+
this.selected = Math.min(this.selected, Math.max(0, this.data.lines.length - 1));
|
|
235
|
+
this.message = `refreshed ${this.data.loadedAt.toLocaleTimeString()}`;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
238
|
+
} finally {
|
|
239
|
+
this.loading = false;
|
|
240
|
+
this.tui.requestRender();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private ensureSelectedVisible(): void {
|
|
245
|
+
const visibleRows = 20;
|
|
246
|
+
if (this.selected < this.offset) this.offset = this.selected;
|
|
247
|
+
if (this.selected >= this.offset + visibleRows) this.offset = this.selected - visibleRows + 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private clampScroll(visibleRows: number): void {
|
|
251
|
+
if (this.selected < this.offset) this.offset = this.selected;
|
|
252
|
+
if (this.selected >= this.offset + visibleRows) this.offset = this.selected - visibleRows + 1;
|
|
253
|
+
const maxOffset = Math.max(0, this.data.lines.length - visibleRows);
|
|
254
|
+
this.offset = Math.min(Math.max(0, this.offset), maxOffset);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private colorizeCommitLine(line: string): string {
|
|
258
|
+
const match = line.match(/^([*|\\/ ]*)([0-9a-f]{5,})(.*)$/);
|
|
259
|
+
if (!match) return this.theme.fg("toolOutput", line);
|
|
260
|
+
const graph = this.theme.fg("accent", match[1] ?? "");
|
|
261
|
+
const hash = this.theme.fg("warning", match[2] ?? "");
|
|
262
|
+
const rest = this.colorizeRefs(match[3] ?? "");
|
|
263
|
+
return `${graph}${hash}${rest}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private colorizeRefs(text: string): string {
|
|
267
|
+
return text.replace(/\(([^)]+)\)/g, (_match, refs: string) => this.theme.fg("success", `(${refs})`));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function showGitGraph(pi: ExtensionAPI, ctx: ExtensionCommandContext, args: string): Promise<void> {
|
|
272
|
+
if (!ctx.hasUI) {
|
|
273
|
+
ctx.ui.notify("Git graph sidebar requires interactive TUI mode.", "warning");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const limit = parseLimit(args);
|
|
277
|
+
const data = await loadGitGraph(pi, ctx.cwd, limit);
|
|
278
|
+
await ctx.ui.custom<void>((tui, theme, _keybindings, done) => new GitGraphSidebar(pi, tui, theme, ctx.cwd, limit, data, done), {
|
|
279
|
+
overlay: true,
|
|
280
|
+
overlayOptions: {
|
|
281
|
+
anchor: "right-center",
|
|
282
|
+
width: "42%",
|
|
283
|
+
minWidth: 48,
|
|
284
|
+
maxHeight: "95%",
|
|
285
|
+
margin: { right: 1 },
|
|
286
|
+
visible: (termWidth) => termWidth >= 90,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default function registerGitGraphSidebar(pi: ExtensionAPI): void {
|
|
292
|
+
pi.registerCommand("git-graph", {
|
|
293
|
+
description: "Open a VS Code Git Graph-style sidebar for the current repository",
|
|
294
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
295
|
+
await showGitGraph(pi, ctx, args);
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
pi.registerShortcut("ctrl+shift+g", {
|
|
300
|
+
description: "Open git graph sidebar",
|
|
301
|
+
handler: async (ctx: ExtensionCommandContext) => {
|
|
302
|
+
await showGitGraph(pi, ctx, "");
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
pi.on("session_start", (_event, ctx) => {
|
|
307
|
+
if (ctx.hasUI) ctx.ui.setStatus("git-graph", ctx.ui.theme.fg("dim", "⇧⌃G graph"));
|
|
308
|
+
});
|
|
309
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-git-graph-sidebar",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A VS Code Git Graph-style sidebar overlay for the Pi coding agent TUI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"git",
|
|
10
|
+
"git-graph",
|
|
11
|
+
"tui"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "yuxiang-gao",
|
|
15
|
+
"pi": {
|
|
16
|
+
"extensions": [
|
|
17
|
+
"./extensions"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
22
|
+
"@earendil-works/pi-tui": "*"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/yuxiang-gao/pi-git-graph-sidebar.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/yuxiang-gao/pi-git-graph-sidebar/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/yuxiang-gao/pi-git-graph-sidebar#readme",
|
|
32
|
+
"files": [
|
|
33
|
+
"extensions",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
]
|
|
37
|
+
}
|