@ogulcancelik/pi-minimal-footer 0.1.3 → 0.1.4
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 +6 -1
- package/index.ts +56 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Minimal footer for [pi](https://github.com/badlogic/pi-mono) that replaces the d
|
|
|
11
11
|
- **Context gauge** — working directory, git branch, model, thinking level, and context window usage with token counts
|
|
12
12
|
- **Subscription usage bars** — rolling window quotas with reset timers for supported providers
|
|
13
13
|
- **Auto-refresh** — fetches usage on startup and model switch, then every 5 minutes
|
|
14
|
-
- **Git integration** — branch name, dirty state, ahead/behind counts
|
|
14
|
+
- **Git integration** — branch name, dirty state, ahead/behind counts
|
|
15
15
|
|
|
16
16
|
## Supported providers
|
|
17
17
|
|
|
@@ -39,6 +39,11 @@ Usage is fetched:
|
|
|
39
39
|
- Immediately on model switch (Ctrl+P)
|
|
40
40
|
- Every 5 minutes after that
|
|
41
41
|
|
|
42
|
+
Git state is refreshed:
|
|
43
|
+
- Once on startup
|
|
44
|
+
- When pi reports a branch change
|
|
45
|
+
- At the end of each turn
|
|
46
|
+
|
|
42
47
|
The footer adapts to narrow terminals by stacking lines vertically instead of the single-line wide layout.
|
|
43
48
|
|
|
44
49
|
## Known issues
|
package/index.ts
CHANGED
|
@@ -35,7 +35,6 @@ interface GitCache {
|
|
|
35
35
|
dirty: boolean;
|
|
36
36
|
ahead: number;
|
|
37
37
|
behind: number;
|
|
38
|
-
isJj: boolean;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
// ============ Usage Cache ============
|
|
@@ -51,63 +50,58 @@ let codexEmailPrefix: string | null | undefined; // undefined = not yet resolved
|
|
|
51
50
|
|
|
52
51
|
let gitCache: GitCache | null = null;
|
|
53
52
|
|
|
54
|
-
function
|
|
55
|
-
|
|
53
|
+
function parseGitStatus(output: string): GitCache {
|
|
54
|
+
let branch: string | null = null;
|
|
55
|
+
let dirty = false;
|
|
56
|
+
let ahead = 0;
|
|
57
|
+
let behind = 0;
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
encoding: "utf8",
|
|
60
|
-
timeout: 500,
|
|
61
|
-
}).trim();
|
|
62
|
-
|
|
63
|
-
if (!gitRoot) return;
|
|
64
|
-
|
|
65
|
-
const isJj = existsSync(`${gitRoot}/.jj`);
|
|
66
|
-
let branch: string | null = null;
|
|
67
|
-
|
|
68
|
-
if (isJj) {
|
|
69
|
-
try {
|
|
70
|
-
const jjBranch = execSync(
|
|
71
|
-
`jj log -r 'heads(ancestors(@) & bookmarks())' --no-graph -T 'bookmarks.map(|b| b.name()).join(" ")' --limit 1 2>/dev/null`,
|
|
72
|
-
{ encoding: "utf8", timeout: 1000 }
|
|
73
|
-
).trim();
|
|
74
|
-
branch = jjBranch ? jjBranch.split(" ")[0] : null;
|
|
75
|
-
} catch {}
|
|
76
|
-
} else {
|
|
77
|
-
try {
|
|
78
|
-
const gitBranch = execSync("git rev-parse --abbrev-ref HEAD 2>/dev/null", {
|
|
79
|
-
encoding: "utf8",
|
|
80
|
-
timeout: 500,
|
|
81
|
-
}).trim();
|
|
82
|
-
branch = gitBranch && gitBranch !== "HEAD" ? gitBranch : null;
|
|
83
|
-
} catch {}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let dirty = false;
|
|
87
|
-
try {
|
|
88
|
-
const status = execSync("git status --porcelain 2>/dev/null", {
|
|
89
|
-
encoding: "utf8",
|
|
90
|
-
timeout: 500,
|
|
91
|
-
});
|
|
92
|
-
dirty = status.trim().length > 0;
|
|
93
|
-
} catch {}
|
|
59
|
+
for (const line of output.split("\n")) {
|
|
60
|
+
if (!line) continue;
|
|
94
61
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
timeout: 500,
|
|
101
|
-
}).trim();
|
|
102
|
-
const [a, b] = counts.split(/\s+/);
|
|
103
|
-
ahead = parseInt(a, 10) || 0;
|
|
104
|
-
behind = parseInt(b, 10) || 0;
|
|
105
|
-
} catch {}
|
|
62
|
+
if (line.startsWith("# branch.head ")) {
|
|
63
|
+
const head = line.slice("# branch.head ".length).trim();
|
|
64
|
+
branch = head && head !== "(detached)" ? head : null;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
106
67
|
|
|
107
|
-
|
|
68
|
+
if (line.startsWith("# branch.ab ")) {
|
|
69
|
+
const match = line.match(/^# branch\.ab \+(\d+) -(\d+)$/);
|
|
70
|
+
if (match) {
|
|
71
|
+
ahead = parseInt(match[1], 10) || 0;
|
|
72
|
+
behind = parseInt(match[2], 10) || 0;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!line.startsWith("# ")) dirty = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { branch, dirty, ahead, behind };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sameGitCache(a: GitCache | null, b: GitCache | null): boolean {
|
|
84
|
+
if (a === b) return true;
|
|
85
|
+
if (!a || !b) return false;
|
|
86
|
+
return a.branch === b.branch && a.dirty === b.dirty && a.ahead === b.ahead && a.behind === b.behind;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function refreshGitCache(): boolean {
|
|
90
|
+
let next: GitCache | null = null;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const status = execSync("git status --porcelain=v2 --branch 2>/dev/null", {
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
timeout: 1000,
|
|
96
|
+
});
|
|
97
|
+
next = parseGitStatus(status.trimEnd());
|
|
108
98
|
} catch {
|
|
109
|
-
|
|
99
|
+
next = null;
|
|
110
100
|
}
|
|
101
|
+
|
|
102
|
+
const changed = !sameGitCache(gitCache, next);
|
|
103
|
+
gitCache = next;
|
|
104
|
+
return changed;
|
|
111
105
|
}
|
|
112
106
|
|
|
113
107
|
// ============ JWT Helpers ============
|
|
@@ -778,6 +772,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
778
772
|
// Store tui reference for triggering re-renders from event handlers
|
|
779
773
|
let tuiRef: { requestRender: () => void } | null = null;
|
|
780
774
|
|
|
775
|
+
function refreshGitFooter(): void {
|
|
776
|
+
if (refreshGitCache()) tuiRef?.requestRender();
|
|
777
|
+
}
|
|
778
|
+
|
|
781
779
|
/** Fetch usage for the active provider. Shows cached data immediately,
|
|
782
780
|
* then fetches fresh in the background. Discards results if provider
|
|
783
781
|
* changed while the fetch was in flight. */
|
|
@@ -848,8 +846,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
848
846
|
tuiRef = tui;
|
|
849
847
|
|
|
850
848
|
const unsub = footerData.onBranchChange(() => {
|
|
851
|
-
|
|
852
|
-
tui.requestRender();
|
|
849
|
+
refreshGitFooter();
|
|
853
850
|
});
|
|
854
851
|
|
|
855
852
|
// Initial fetch inside factory — tui is guaranteed available here,
|
|
@@ -936,6 +933,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
936
933
|
|
|
937
934
|
});
|
|
938
935
|
|
|
936
|
+
pi.on("turn_end", async () => {
|
|
937
|
+
refreshGitFooter();
|
|
938
|
+
});
|
|
939
|
+
|
|
939
940
|
// Refresh when model changes — fetch immediately, restart timer
|
|
940
941
|
pi.on("model_select", (event, _ctx) => {
|
|
941
942
|
if (!event.model?.provider) return;
|