@diegopetrucci/pi-extensions 0.1.39 → 0.1.40
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/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A collection of [pi](https://github.com/earendil-works/pi-mono) agent extensions
|
|
|
9
9
|
- [`context-cap`](./extensions/context-cap): Caps effective model context windows at 200k tokens by default so pi avoids the `dumb zone`; toggle temporarily with `/context-cap`.
|
|
10
10
|
- [`context-inspector`](./extensions/context-inspector): Adds `/context`, a local self-contained HTML dashboard that breaks down where the current session context is going, with category overview, top offenders, and drilldown search.
|
|
11
11
|
- [`dirty-repo-guard`](./extensions/dirty-repo-guard): Prompts before new sessions, session switches, or forks when the current git repo has uncommitted changes.
|
|
12
|
-
- [`git-footer`](./extensions/git-footer): Standalone
|
|
12
|
+
- [`git-footer`](./extensions/git-footer): Standalone extension that adds TLH-style git dirty counts, ahead/behind, and optional PR number to pi's built-in footer status area.
|
|
13
13
|
- [`gnosis`](./extensions/gnosis): Exposes the `gn` repo-local knowledge base CLI as an agent tool for searching and recording durable project decisions, constraints, and intent.
|
|
14
14
|
- [`inline-bash`](./extensions/inline-bash): Expands `!{command}` snippets in user prompts by running them through bash before the prompt reaches the agent.
|
|
15
15
|
- [`librarian`](./extensions/librarian): Adds a GitHub research scout with a local repo checkout cache enabled by default under the OS user cache directory, toggleable with `/librarian-cache`, with cached repos expiring after 7 days of non-use.
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# git-footer
|
|
2
2
|
|
|
3
|
-
A TLH-style git status
|
|
3
|
+
A TLH-style git status add-on for pi's built-in footer.
|
|
4
4
|
|
|
5
|
-
This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package
|
|
5
|
+
This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package.
|
|
6
6
|
|
|
7
|
-
It
|
|
7
|
+
It keeps pi's default footer intact and adds a compact git status segment through pi's extension status API:
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
~/repo (main) • session-name
|
|
11
|
+
↑12k ↓3k 44.1%/200k model
|
|
12
|
+
+2 ~1 ?3 ↑1 • PR #123
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
Git status indicators:
|
|
@@ -20,7 +21,7 @@ Git status indicators:
|
|
|
20
21
|
- `↑N`: commits ahead of upstream
|
|
21
22
|
- `↓N`: commits behind upstream
|
|
22
23
|
|
|
23
|
-
The extension polls git status in the background
|
|
24
|
+
The extension polls git status in the background and caches the latest snapshot. It also performs a best-effort `gh pr view` lookup for the current branch; if `gh` is unavailable or the branch has no PR, the PR segment is omitted.
|
|
24
25
|
|
|
25
26
|
## Install
|
|
26
27
|
|
|
@@ -36,6 +37,7 @@ Then reload pi:
|
|
|
36
37
|
|
|
37
38
|
## Notes
|
|
38
39
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
40
|
+
- Does not replace pi's built-in footer.
|
|
41
|
+
- Uses `ctx.ui.setStatus()`, so pi renders the git summary with other extension statuses.
|
|
42
|
+
- The current pi extension API does not support literally appending text inside the built-in footer's first `cwd (branch)` line without replacing the footer.
|
|
43
|
+
- Git and GitHub CLI lookups run on a short background interval with timeouts.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { basename } from "node:path";
|
|
3
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
5
3
|
|
|
6
4
|
type GitStatusSnapshot = {
|
|
7
5
|
branch?: string;
|
|
@@ -48,12 +46,12 @@ type GitFooterCacheOptions = {
|
|
|
48
46
|
gitTimeoutMs?: number;
|
|
49
47
|
ghTimeoutMs?: number;
|
|
50
48
|
onChange?: () => void;
|
|
51
|
-
onBranchChangeSource?: (callback: () => void) => () => void;
|
|
52
49
|
};
|
|
53
50
|
|
|
54
51
|
const BRANCH_HEAD_PREFIX = "# branch.head ";
|
|
55
52
|
const BRANCH_AB_PREFIX = "# branch.ab ";
|
|
56
|
-
const
|
|
53
|
+
const STATUS_KEY = "git-footer";
|
|
54
|
+
const STATUS_SEPARATOR = " • ";
|
|
57
55
|
const DEFAULT_REFRESH_INTERVAL_MS = 8_000;
|
|
58
56
|
const DEFAULT_GIT_TIMEOUT_MS = 1_500;
|
|
59
57
|
const DEFAULT_GH_TIMEOUT_MS = 3_000;
|
|
@@ -157,21 +155,15 @@ function formatPullRequestFooterSegment(pullRequest: PullRequestSnapshot | undef
|
|
|
157
155
|
return undefined;
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
function
|
|
158
|
+
function formatGitFooterStatus(
|
|
161
159
|
status: GitStatusSnapshot | undefined,
|
|
162
160
|
pullRequest: PullRequestSnapshot | undefined,
|
|
163
|
-
): string
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (statusSegment) segments.push(statusSegment);
|
|
170
|
-
|
|
171
|
-
const pullRequestSegment = formatPullRequestFooterSegment(pullRequest);
|
|
172
|
-
if (pullRequestSegment) segments.push(pullRequestSegment);
|
|
173
|
-
|
|
174
|
-
return segments;
|
|
161
|
+
): string | undefined {
|
|
162
|
+
const parts = [
|
|
163
|
+
formatGitStatusFooterSegment(status),
|
|
164
|
+
formatPullRequestFooterSegment(pullRequest),
|
|
165
|
+
].filter((part): part is string => !!part);
|
|
166
|
+
return parts.length > 0 ? parts.join(STATUS_SEPARATOR) : undefined;
|
|
175
167
|
}
|
|
176
168
|
|
|
177
169
|
function parsePullRequestJson(stdout: string): PullRequestSnapshot | undefined {
|
|
@@ -307,7 +299,6 @@ class GitFooterCache {
|
|
|
307
299
|
private readonly inflightControllers = new Set<AbortController>();
|
|
308
300
|
private disposed = false;
|
|
309
301
|
private refreshInFlight: Promise<void> | undefined;
|
|
310
|
-
private branchChangeUnsubscribe: (() => void) | undefined;
|
|
311
302
|
private statusSnapshot: GitStatusSnapshot | undefined;
|
|
312
303
|
private pullRequestSnapshot: PullRequestSnapshot | undefined;
|
|
313
304
|
private lastSeenBranch: string | undefined;
|
|
@@ -325,12 +316,6 @@ class GitFooterCache {
|
|
|
325
316
|
void this.refresh();
|
|
326
317
|
}, this.refreshIntervalMs);
|
|
327
318
|
void this.refresh();
|
|
328
|
-
|
|
329
|
-
if (options.onBranchChangeSource) {
|
|
330
|
-
this.branchChangeUnsubscribe = options.onBranchChangeSource(() => {
|
|
331
|
-
void this.refresh();
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
319
|
}
|
|
335
320
|
|
|
336
321
|
getStatusSnapshot(): GitStatusSnapshot | undefined {
|
|
@@ -453,81 +438,43 @@ class GitFooterCache {
|
|
|
453
438
|
this.clock.clearInterval(this.intervalHandle);
|
|
454
439
|
this.intervalHandle = undefined;
|
|
455
440
|
}
|
|
456
|
-
if (this.branchChangeUnsubscribe) {
|
|
457
|
-
try {
|
|
458
|
-
this.branchChangeUnsubscribe();
|
|
459
|
-
} catch {
|
|
460
|
-
// Ignore misbehaving notifier.
|
|
461
|
-
}
|
|
462
|
-
this.branchChangeUnsubscribe = undefined;
|
|
463
|
-
}
|
|
464
441
|
for (const controller of this.inflightControllers) controller.abort();
|
|
465
442
|
this.inflightControllers.clear();
|
|
466
443
|
}
|
|
467
444
|
}
|
|
468
445
|
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
sessionName?: string | null;
|
|
472
|
-
status?: GitStatusSnapshot;
|
|
473
|
-
pullRequest?: PullRequestSnapshot;
|
|
474
|
-
}): string {
|
|
475
|
-
const segments = [input.cwd];
|
|
476
|
-
if (input.status !== undefined) {
|
|
477
|
-
segments.push(...formatGitFooterSegments(input.status, input.pullRequest));
|
|
478
|
-
}
|
|
479
|
-
if (input.sessionName) segments.push(input.sessionName);
|
|
480
|
-
return segments.join(FOOTER_SEPARATOR);
|
|
481
|
-
}
|
|
446
|
+
export default function (pi: ExtensionAPI) {
|
|
447
|
+
let cache: GitFooterCache | undefined;
|
|
482
448
|
|
|
483
|
-
function
|
|
484
|
-
|
|
485
|
-
|
|
449
|
+
function disposeCache(): void {
|
|
450
|
+
cache?.dispose();
|
|
451
|
+
cache = undefined;
|
|
452
|
+
}
|
|
486
453
|
|
|
487
|
-
export default function (pi: ExtensionAPI) {
|
|
488
454
|
pi.on("session_start", (_event, ctx) => {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
455
|
+
disposeCache();
|
|
456
|
+
|
|
457
|
+
const updateStatus = () => {
|
|
458
|
+
const text = formatGitFooterStatus(
|
|
459
|
+
cache?.getStatusSnapshot(),
|
|
460
|
+
cache?.getPullRequestSnapshot(),
|
|
461
|
+
);
|
|
462
|
+
ctx.ui.setStatus(STATUS_KEY, text ? ctx.ui.theme.fg("dim", text) : undefined);
|
|
463
|
+
};
|
|
497
464
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
cache = undefined;
|
|
502
|
-
},
|
|
503
|
-
invalidate() {},
|
|
504
|
-
render(width: number): string[] {
|
|
505
|
-
const status = cache?.getStatusSnapshot();
|
|
506
|
-
const pullRequest = cache?.getPullRequestSnapshot();
|
|
507
|
-
const firstLine = composeFooterFirstLine({
|
|
508
|
-
cwd: basename(ctx.cwd),
|
|
509
|
-
status,
|
|
510
|
-
pullRequest,
|
|
511
|
-
sessionName: pi.getSessionName(),
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
const usage = ctx.getContextUsage();
|
|
515
|
-
const context = usage?.percent == null ? "ctx ?" : `ctx ${usage.percent.toFixed(1)}%`;
|
|
516
|
-
const thinking = pi.getThinkingLevel();
|
|
517
|
-
const model = ctx.model?.id ?? "no-model";
|
|
518
|
-
const modelText = thinking === "off" ? model : `${model} ${thinking}`;
|
|
519
|
-
const statuses = [...footerData.getExtensionStatuses().values()]
|
|
520
|
-
.map(sanitizeFooterSegment)
|
|
521
|
-
.filter(Boolean);
|
|
522
|
-
const secondLine = [theme.fg("dim", context), theme.fg("dim", modelText), ...statuses]
|
|
523
|
-
.join(theme.fg("dim", FOOTER_SEPARATOR));
|
|
524
|
-
|
|
525
|
-
return [
|
|
526
|
-
truncateToWidth(theme.fg("dim", firstLine), width),
|
|
527
|
-
truncateToWidth(secondLine, width),
|
|
528
|
-
];
|
|
529
|
-
},
|
|
530
|
-
};
|
|
465
|
+
cache = new GitFooterCache({
|
|
466
|
+
cwd: () => ctx.cwd,
|
|
467
|
+
onChange: updateStatus,
|
|
531
468
|
});
|
|
469
|
+
updateStatus();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
pi.on("turn_end", () => {
|
|
473
|
+
void cache?.refresh();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
477
|
+
disposeCache();
|
|
478
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
532
479
|
});
|
|
533
480
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"description": "A collection of pi extensions for context management, workflow audits, review-comment triage, notifications, brrr push alerts, safety guards, GitHub research, repo-local knowledge, todos, tool rendering, and model/provider helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|