feat: show git branch in footer

- Footer now displays active git branch after directory path (e.g., ~/project (main))
- Branch detected by reading .git/HEAD directly (fast, synchronous)
- Cache refreshed after each assistant message to detect branch changes
- Handles normal branches, detached HEAD, and non-git repos

Closes #55
This commit is contained in:
Mario Zechner 2025-11-27 12:56:45 +01:00
parent f95f41b1c4
commit 318254bff4
3 changed files with 44 additions and 1 deletions

View file

@ -1,6 +1,8 @@
import type { AgentState } from "@mariozechner/pi-agent-core";
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { type Component, visibleWidth } from "@mariozechner/pi-tui";
import { readFileSync } from "fs";
import { join } from "path";
import { theme } from "../theme/theme.js";
/**
@ -8,6 +10,7 @@ import { theme } from "../theme/theme.js";
*/
export class FooterComponent implements Component {
private state: AgentState;
private cachedBranch: string | null | undefined = undefined; // undefined = not checked yet, null = not in git repo, string = branch name
constructor(state: AgentState) {
this.state = state;
@ -18,7 +21,37 @@ export class FooterComponent implements Component {
}
invalidate(): void {
// No cached state to invalidate currently
// Invalidate cached branch so it gets re-read on next render
this.cachedBranch = undefined;
}
/**
* Get current git branch by reading .git/HEAD directly.
* Returns null if not in a git repo, branch name otherwise.
*/
private getCurrentBranch(): string | null {
// Return cached value if available
if (this.cachedBranch !== undefined) {
return this.cachedBranch;
}
try {
const gitHeadPath = join(process.cwd(), ".git", "HEAD");
const content = readFileSync(gitHeadPath, "utf8").trim();
if (content.startsWith("ref: refs/heads/")) {
// Normal branch: extract branch name
this.cachedBranch = content.slice(16);
} else {
// Detached HEAD state
this.cachedBranch = "detached";
}
} catch {
// Not in a git repo or error reading file
this.cachedBranch = null;
}
return this.cachedBranch;
}
render(width: number): string[] {
@ -71,6 +104,12 @@ export class FooterComponent implements Component {
pwd = "~" + pwd.slice(home.length);
}
// Add git branch if available
const branch = this.getCurrentBranch();
if (branch) {
pwd = `${pwd} (${branch})`;
}
// Truncate path if too long to fit width
const maxPathLength = Math.max(20, width - 10); // Leave some margin
if (pwd.length > maxPathLength) {

View file

@ -589,6 +589,9 @@ export class TuiRenderer {
// Keep the streaming component - it's now the final assistant message
this.streamingComponent = null;
// Invalidate footer cache to refresh git branch (in case agent executed git commands)
this.footer.invalidate();
}
this.ui.requestRender();
break;