@yagni-app/code-staging 0.2.1-staging.1030.1 → 0.2.1-staging.1033.1
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/dist/extension/branding.d.ts +11 -1
- package/dist/extension/branding.js +49 -4
- package/dist/extension/footer.d.ts +40 -7
- package/dist/extension/footer.js +176 -56
- package/dist/extension/index.js +10 -5
- package/package.json +2 -2
|
@@ -69,11 +69,21 @@ export interface MastheadTheme {
|
|
|
69
69
|
bold(s: string): string;
|
|
70
70
|
fg(color: string, s: string): string;
|
|
71
71
|
}
|
|
72
|
+
export interface MastheadOptions {
|
|
73
|
+
/** CLI version (e.g. "0.2.1") shown next to the title; omitted when absent. */
|
|
74
|
+
version?: string;
|
|
75
|
+
/** Working directory shown on the third line (already home-collapsed). */
|
|
76
|
+
cwd?: string;
|
|
77
|
+
}
|
|
72
78
|
/**
|
|
73
79
|
* Build the YAGNI Code startup masthead string, rendered into a header that
|
|
74
80
|
* REPLACES pi's built-in startup banner (which otherwise shows "pi v<version>"
|
|
75
81
|
* and a "Pi can explain its own features…" onboarding line). Kept here as a
|
|
76
82
|
* pure string builder so its content is unit-testable without a terminal.
|
|
83
|
+
*
|
|
84
|
+
* Layout: the YAGNI art on the left, and to its right a three-line block —
|
|
85
|
+
* bold white "YAGNI Code" + version, the tagline, and the cwd. Art rows are
|
|
86
|
+
* padded to a uniform width so the right column stays straight.
|
|
77
87
|
*/
|
|
78
|
-
export declare function buildMastheadString(theme: MastheadTheme): string;
|
|
88
|
+
export declare function buildMastheadString(theme: MastheadTheme, opts?: MastheadOptions): string;
|
|
79
89
|
//# sourceMappingURL=branding.d.ts.map
|
|
@@ -142,15 +142,60 @@ export function brandSystemPrompt(original, opts = {}) {
|
|
|
142
142
|
const CLOSING_REMINDER = "Reminder: you are YAGNI Code. If any text above names another coding agent, " +
|
|
143
143
|
"assistant, or harness, it is not what you are or what you run on.";
|
|
144
144
|
const BRIEF_HEADER = "=== HOW THIS COMPANY WORKS (live context from the YAGNI app) ===";
|
|
145
|
+
/** The YAGNI brand blue used for the masthead art (truecolor #345cb8). */
|
|
146
|
+
const BRAND_HEX = "#345cb8";
|
|
147
|
+
/**
|
|
148
|
+
* Wrap a string in a 24-bit truecolor foreground. The pi Theme only paints
|
|
149
|
+
* named palette colors and can't express our exact brand hex, so the masthead
|
|
150
|
+
* art emits its own ANSI truecolor escape (matching every modern terminal).
|
|
151
|
+
*/
|
|
152
|
+
function paintBrand(s) {
|
|
153
|
+
const n = BRAND_HEX.replace("#", "");
|
|
154
|
+
const r = parseInt(n.slice(0, 2), 16);
|
|
155
|
+
const g = parseInt(n.slice(2, 4), 16);
|
|
156
|
+
const b = parseInt(n.slice(4, 6), 16);
|
|
157
|
+
return `\u001b[38;2;${r};${g};${b}m${s}\u001b[0m`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The YAGNI wordmark as filled block-art: solid letter interiors (like the
|
|
161
|
+
* Claude robot silhouette), four rows, hand-tuned spacing. Each letter keeps
|
|
162
|
+
* its full form — A's carved counter, G's bowl, N's diagonal.
|
|
163
|
+
*/
|
|
164
|
+
const MASTHEAD_ART = [
|
|
165
|
+
"█ █ ▄▀▀▀▄ ▄▀▀▀▄ █▄ █ ▀█▀",
|
|
166
|
+
" ▀▄▀ █▄▄▄█ █ ▄▄▄ █ █ █ █",
|
|
167
|
+
" █ █ █ ▀▄▄▄▀ █ ██ ▄█▄",
|
|
168
|
+
];
|
|
145
169
|
/**
|
|
146
170
|
* Build the YAGNI Code startup masthead string, rendered into a header that
|
|
147
171
|
* REPLACES pi's built-in startup banner (which otherwise shows "pi v<version>"
|
|
148
172
|
* and a "Pi can explain its own features…" onboarding line). Kept here as a
|
|
149
173
|
* pure string builder so its content is unit-testable without a terminal.
|
|
174
|
+
*
|
|
175
|
+
* Layout: the YAGNI art on the left, and to its right a three-line block —
|
|
176
|
+
* bold white "YAGNI Code" + version, the tagline, and the cwd. Art rows are
|
|
177
|
+
* padded to a uniform width so the right column stays straight.
|
|
150
178
|
*/
|
|
151
|
-
export function buildMastheadString(theme) {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
179
|
+
export function buildMastheadString(theme, opts = {}) {
|
|
180
|
+
const title = theme.bold(theme.fg("text", "YAGNI Code"));
|
|
181
|
+
const version = opts.version?.trim();
|
|
182
|
+
const line1 = version ? `${title} ${theme.fg("dim", `v${version}`)}` : title;
|
|
183
|
+
const right = [
|
|
184
|
+
line1,
|
|
185
|
+
theme.fg("dim", "a terminal coding agent that knows your company"),
|
|
186
|
+
...(opts.cwd ? [theme.fg("dim", opts.cwd)] : []),
|
|
187
|
+
];
|
|
188
|
+
const artW = Math.max(...MASTHEAD_ART.map((l) => [...l].length));
|
|
189
|
+
const gap = " ";
|
|
190
|
+
const rows = Math.max(MASTHEAD_ART.length, right.length);
|
|
191
|
+
const lines = [];
|
|
192
|
+
for (let i = 0; i < rows; i++) {
|
|
193
|
+
const rawArt = MASTHEAD_ART[i] ?? "";
|
|
194
|
+
const pad = " ".repeat(Math.max(0, artW - [...rawArt].length));
|
|
195
|
+
const left = rawArt ? paintBrand(rawArt) + pad : " ".repeat(artW);
|
|
196
|
+
const r = right[i] ?? "";
|
|
197
|
+
lines.push(r ? `${left}${gap}${r}` : left);
|
|
198
|
+
}
|
|
199
|
+
return lines.join("\n");
|
|
155
200
|
}
|
|
156
201
|
//# sourceMappingURL=branding.js.map
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Custom footer for the YAGNI CLI — replaces pi's built-in footer
|
|
3
|
-
* model name from the status bar. The model is locked to `advanced` (see
|
|
4
|
-
* index.ts's catalog filter) and is not a user-facing concern.
|
|
2
|
+
* Custom footer for the YAGNI CLI — replaces pi's built-in footer.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Renders three lines:
|
|
5
|
+
* 1. folder · [worktree] · branch (git context; ~-path fallback off-repo)
|
|
6
|
+
* 2. model · ↑in ↓out $cost · ctx% (session stats; context % is an integer)
|
|
7
|
+
* 3. extension statuses (branding, todo counter, mode) joined by " · "
|
|
9
8
|
*
|
|
10
9
|
* --- How to customize the status bar (for future tickets) ---
|
|
11
10
|
*
|
|
@@ -40,6 +39,7 @@
|
|
|
40
39
|
* — useful reference for what data to replicate and how to format it.
|
|
41
40
|
*/
|
|
42
41
|
import type { ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@earendil-works/pi-coding-agent";
|
|
42
|
+
export declare const BRANCH_MAX_WIDTH = 60;
|
|
43
43
|
/** Format token counts for compact footer display (mirrors pi's formatTokens). */
|
|
44
44
|
export declare function formatTokens(count: number): string;
|
|
45
45
|
/** Shorten cwd relative to home, like pi's built-in footer. */
|
|
@@ -53,6 +53,39 @@ interface UsageTotals {
|
|
|
53
53
|
}
|
|
54
54
|
/** Accumulate usage from all session entries (mirrors pi's built-in footer). */
|
|
55
55
|
export declare function collectUsage(sessionManager: ExtensionContext["sessionManager"]): UsageTotals;
|
|
56
|
+
export interface GitInfo {
|
|
57
|
+
/** Repo-root folder basename (the MAIN repo, stable across worktrees), or ~-path off-repo. */
|
|
58
|
+
folder: string;
|
|
59
|
+
inRepo: boolean;
|
|
60
|
+
/** Current branch, null off-repo. "detached" on detached HEAD. */
|
|
61
|
+
branch: string | null;
|
|
62
|
+
/** Linked-worktree label for [brackets], or null to hide. */
|
|
63
|
+
worktree: string | null;
|
|
64
|
+
}
|
|
65
|
+
/** Git probes `resolveWorktreeLabel` needs, injectable so the rules can be unit-tested. */
|
|
66
|
+
export interface WorktreeProbes {
|
|
67
|
+
isLinkedWorktree(repoRoot: string): boolean;
|
|
68
|
+
worktrees(repoRoot: string): string[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Show [worktree] only on a LINKED worktree (never the main checkout), when the repo has
|
|
72
|
+
* >1 worktree, AND the dir differs from the branch slug. The main checkout is the default
|
|
73
|
+
* context and earns no label; a [bracket] only disambiguates a secondary working tree.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveWorktreeLabel(repoRoot: string, branch: string | null, probes?: WorktreeProbes): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Detect git info for a cwd, gracefully. Never throws: non-git folders, missing git
|
|
78
|
+
* binary, and corrupt repos all degrade to a safe partial/empty result.
|
|
79
|
+
*/
|
|
80
|
+
export declare function detectGitInfo(cwd: string, home: string | undefined): GitInfo;
|
|
81
|
+
/** Pure line-builder, exported for tests. All data injected; colors via theme. */
|
|
82
|
+
export declare function renderFooterLines(input: {
|
|
83
|
+
git: GitInfo;
|
|
84
|
+
model: string;
|
|
85
|
+
usage: UsageTotals;
|
|
86
|
+
contextPercent: number | null;
|
|
87
|
+
statuses: string[];
|
|
88
|
+
}, theme: Pick<Theme, "fg">, width: number): string[];
|
|
56
89
|
/**
|
|
57
90
|
* Create a footer factory that captures the session `ctx` (for session data)
|
|
58
91
|
* and returns the component `setFooter` expects. Called from the
|
|
@@ -61,7 +94,7 @@ export declare function collectUsage(sessionManager: ExtensionContext["sessionMa
|
|
|
61
94
|
export declare function createYagniFooterFactory(ctx: ExtensionContext): (_tui: unknown, theme: Theme, footerData: ReadonlyFooterDataProvider) => {
|
|
62
95
|
render(width: number): string[];
|
|
63
96
|
invalidate(): void;
|
|
64
|
-
dispose
|
|
97
|
+
dispose(): void;
|
|
65
98
|
};
|
|
66
99
|
export {};
|
|
67
100
|
//# sourceMappingURL=footer.d.ts.map
|
package/dist/extension/footer.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Custom footer for the YAGNI CLI — replaces pi's built-in footer
|
|
3
|
-
* model name from the status bar. The model is locked to `advanced` (see
|
|
4
|
-
* index.ts's catalog filter) and is not a user-facing concern.
|
|
2
|
+
* Custom footer for the YAGNI CLI — replaces pi's built-in footer.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Renders three lines:
|
|
5
|
+
* 1. folder · [worktree] · branch (git context; ~-path fallback off-repo)
|
|
6
|
+
* 2. model · ↑in ↓out $cost · ctx% (session stats; context % is an integer)
|
|
7
|
+
* 3. extension statuses (branding, todo counter, mode) joined by " · "
|
|
9
8
|
*
|
|
10
9
|
* --- How to customize the status bar (for future tickets) ---
|
|
11
10
|
*
|
|
@@ -39,7 +38,14 @@
|
|
|
39
38
|
* `node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js`
|
|
40
39
|
* — useful reference for what data to replicate and how to format it.
|
|
41
40
|
*/
|
|
42
|
-
import {
|
|
41
|
+
import { spawnSync } from "node:child_process";
|
|
42
|
+
import { statSync } from "node:fs";
|
|
43
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
44
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
45
|
+
export const BRANCH_MAX_WIDTH = 60;
|
|
46
|
+
const WORKTREE_MAX_WIDTH = 30;
|
|
47
|
+
/** Section separator: single space + middle dot + single space. */
|
|
48
|
+
const SEP = " · ";
|
|
43
49
|
/** Format token counts for compact footer display (mirrors pi's formatTokens). */
|
|
44
50
|
export function formatTokens(count) {
|
|
45
51
|
if (count < 1000)
|
|
@@ -87,6 +93,139 @@ export function collectUsage(sessionManager) {
|
|
|
87
93
|
}
|
|
88
94
|
return totals;
|
|
89
95
|
}
|
|
96
|
+
/** End-cut ellipsis truncation (ANSI-aware) so the branch prefix stays readable. */
|
|
97
|
+
function truncateEnd(text, maxWidth) {
|
|
98
|
+
if (visibleWidth(text) <= maxWidth)
|
|
99
|
+
return text;
|
|
100
|
+
return truncateToWidth(text, maxWidth, "…");
|
|
101
|
+
}
|
|
102
|
+
function runGit(args, cwd) {
|
|
103
|
+
try {
|
|
104
|
+
const r = spawnSync("git", ["--no-optional-locks", ...args], {
|
|
105
|
+
cwd,
|
|
106
|
+
encoding: "utf8",
|
|
107
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
108
|
+
});
|
|
109
|
+
if (r.error || r.status !== 0)
|
|
110
|
+
return null;
|
|
111
|
+
return r.stdout.trim();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function gitRepoRoot(cwd) {
|
|
118
|
+
return runGit(["rev-parse", "--show-toplevel"], cwd) || null;
|
|
119
|
+
}
|
|
120
|
+
function gitBranch(repoRoot) {
|
|
121
|
+
const out = runGit(["symbolic-ref", "--quiet", "--short", "HEAD"], repoRoot);
|
|
122
|
+
if (out)
|
|
123
|
+
return out;
|
|
124
|
+
// Detached HEAD, or repo with no commits yet.
|
|
125
|
+
return runGit(["rev-parse", "--verify", "HEAD"], repoRoot) ? "detached" : null;
|
|
126
|
+
}
|
|
127
|
+
function gitWorktrees(repoRoot) {
|
|
128
|
+
const out = runGit(["worktree", "list", "--porcelain"], repoRoot);
|
|
129
|
+
if (!out)
|
|
130
|
+
return [];
|
|
131
|
+
return out
|
|
132
|
+
.split("\n")
|
|
133
|
+
.filter((l) => l.startsWith("worktree "))
|
|
134
|
+
.map((l) => l.slice("worktree ".length).trim())
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
}
|
|
137
|
+
/** The MAIN repository root (stable across worktrees) — used for the folder name. */
|
|
138
|
+
function gitMainRepoRoot(repoRoot) {
|
|
139
|
+
const common = runGit(["rev-parse", "--git-common-dir"], repoRoot);
|
|
140
|
+
if (!common)
|
|
141
|
+
return repoRoot;
|
|
142
|
+
const abs = isAbsolute(common) ? common : join(repoRoot, common);
|
|
143
|
+
return basename(abs) === ".git" ? dirname(abs) : repoRoot;
|
|
144
|
+
}
|
|
145
|
+
/** Main checkout: .git is a directory. Linked worktree: .git is a "gitdir:" file. */
|
|
146
|
+
function isLinkedWorktree(repoRoot) {
|
|
147
|
+
try {
|
|
148
|
+
return statSync(join(repoRoot, ".git")).isFile();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const liveWorktreeProbes = { isLinkedWorktree, worktrees: gitWorktrees };
|
|
155
|
+
/**
|
|
156
|
+
* Show [worktree] only on a LINKED worktree (never the main checkout), when the repo has
|
|
157
|
+
* >1 worktree, AND the dir differs from the branch slug. The main checkout is the default
|
|
158
|
+
* context and earns no label; a [bracket] only disambiguates a secondary working tree.
|
|
159
|
+
*/
|
|
160
|
+
export function resolveWorktreeLabel(repoRoot, branch, probes = liveWorktreeProbes) {
|
|
161
|
+
if (!probes.isLinkedWorktree(repoRoot))
|
|
162
|
+
return null;
|
|
163
|
+
if (probes.worktrees(repoRoot).length < 2)
|
|
164
|
+
return null;
|
|
165
|
+
const currentDir = basename(repoRoot);
|
|
166
|
+
if (branch && currentDir === branch)
|
|
167
|
+
return null;
|
|
168
|
+
return truncateEnd(currentDir, WORKTREE_MAX_WIDTH);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Detect git info for a cwd, gracefully. Never throws: non-git folders, missing git
|
|
172
|
+
* binary, and corrupt repos all degrade to a safe partial/empty result.
|
|
173
|
+
*/
|
|
174
|
+
export function detectGitInfo(cwd, home) {
|
|
175
|
+
const root = gitRepoRoot(cwd);
|
|
176
|
+
if (!root) {
|
|
177
|
+
return { folder: formatCwd(cwd, home), inRepo: false, branch: null, worktree: null };
|
|
178
|
+
}
|
|
179
|
+
const branch = gitBranch(root);
|
|
180
|
+
const worktree = resolveWorktreeLabel(root, branch);
|
|
181
|
+
return { folder: basename(gitMainRepoRoot(root)), inRepo: true, branch, worktree };
|
|
182
|
+
}
|
|
183
|
+
/** Context color: dim below 70, warning 70-90, error above 90. */
|
|
184
|
+
function contextColor(percent) {
|
|
185
|
+
if (percent === null)
|
|
186
|
+
return "dim";
|
|
187
|
+
if (percent > 90)
|
|
188
|
+
return "error";
|
|
189
|
+
if (percent > 70)
|
|
190
|
+
return "warning";
|
|
191
|
+
return "dim";
|
|
192
|
+
}
|
|
193
|
+
/** Pure line-builder, exported for tests. All data injected; colors via theme. */
|
|
194
|
+
export function renderFooterLines(input, theme, width) {
|
|
195
|
+
const dim = (s) => theme.fg("dim", s);
|
|
196
|
+
const sep = dim(SEP);
|
|
197
|
+
// Line 1: folder · [worktree] · branch
|
|
198
|
+
const line1Parts = [theme.fg("accent", input.git.folder)];
|
|
199
|
+
if (input.git.inRepo) {
|
|
200
|
+
if (input.git.worktree)
|
|
201
|
+
line1Parts.push(theme.fg("warning", `[${input.git.worktree}]`));
|
|
202
|
+
if (input.git.branch)
|
|
203
|
+
line1Parts.push(theme.fg("border", truncateEnd(input.git.branch, BRANCH_MAX_WIDTH)));
|
|
204
|
+
}
|
|
205
|
+
const line1 = truncateToWidth(line1Parts.join(sep), width, dim("…"));
|
|
206
|
+
// Line 2: model · ↑in ↓out $cost · ctx%
|
|
207
|
+
const statParts = [];
|
|
208
|
+
if (input.usage.input)
|
|
209
|
+
statParts.push(`↑${formatTokens(input.usage.input)}`);
|
|
210
|
+
if (input.usage.output)
|
|
211
|
+
statParts.push(`↓${formatTokens(input.usage.output)}`);
|
|
212
|
+
if (input.usage.cost)
|
|
213
|
+
statParts.push(`$${input.usage.cost.toFixed(3)}`);
|
|
214
|
+
const stats = statParts.join(" ");
|
|
215
|
+
const percentText = input.contextPercent === null ? "?" : `${Math.round(input.contextPercent)}%`;
|
|
216
|
+
const line2Parts = [dim(input.model)];
|
|
217
|
+
if (stats)
|
|
218
|
+
line2Parts.push(dim(stats));
|
|
219
|
+
line2Parts.push(theme.fg(contextColor(input.contextPercent), percentText));
|
|
220
|
+
const line2 = truncateToWidth(line2Parts.join(sep), width, dim("…"));
|
|
221
|
+
// Line 3: extension statuses (branding, todo counter, mode), joined by " · ".
|
|
222
|
+
const statuses = input.statuses.map((s) => s.replace(/[\r\n\t]/g, " ").trim()).filter(Boolean);
|
|
223
|
+
const lines = [line1, line2];
|
|
224
|
+
if (statuses.length > 0) {
|
|
225
|
+
lines.push(truncateToWidth(dim(statuses.join(SEP)), width, dim("…")));
|
|
226
|
+
}
|
|
227
|
+
return lines;
|
|
228
|
+
}
|
|
90
229
|
/**
|
|
91
230
|
* Create a footer factory that captures the session `ctx` (for session data)
|
|
92
231
|
* and returns the component `setFooter` expects. Called from the
|
|
@@ -94,58 +233,39 @@ export function collectUsage(sessionManager) {
|
|
|
94
233
|
*/
|
|
95
234
|
export function createYagniFooterFactory(ctx) {
|
|
96
235
|
return (_tui, theme, footerData) => {
|
|
236
|
+
// Recompute git/worktree info only when the branch actually changes. Optional-
|
|
237
|
+
// chained: onBranchChange is typed on ReadonlyFooterDataProvider, but the runtime
|
|
238
|
+
// provider is whatever pi version is installed — guard so a mismatch can't break
|
|
239
|
+
// footer construction (worst case, git info just doesn't auto-invalidate).
|
|
240
|
+
let gitCache;
|
|
241
|
+
const unsubscribeBranch = footerData.onBranchChange?.(() => {
|
|
242
|
+
gitCache = undefined;
|
|
243
|
+
});
|
|
244
|
+
const gitInfo = () => {
|
|
245
|
+
if (!gitCache) {
|
|
246
|
+
gitCache = detectGitInfo(ctx.sessionManager.getCwd(), process.env.HOME || process.env.USERPROFILE);
|
|
247
|
+
}
|
|
248
|
+
return gitCache;
|
|
249
|
+
};
|
|
97
250
|
return {
|
|
98
251
|
render(width) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (usage.cacheRead)
|
|
116
|
-
statsParts.push(`R${formatTokens(usage.cacheRead)}`);
|
|
117
|
-
if (usage.cacheWrite)
|
|
118
|
-
statsParts.push(`W${formatTokens(usage.cacheWrite)}`);
|
|
119
|
-
if (usage.cost)
|
|
120
|
-
statsParts.push(`$${usage.cost.toFixed(3)}`);
|
|
121
|
-
// Context usage (tokens used / window).
|
|
122
|
-
const contextUsage = ctx.getContextUsage();
|
|
123
|
-
if (contextUsage) {
|
|
124
|
-
const pct = contextUsage.percent !== null ? `${contextUsage.percent.toFixed(1)}%` : "?";
|
|
125
|
-
statsParts.push(`${pct}/${formatTokens(contextUsage.contextWindow)} (auto)`);
|
|
126
|
-
}
|
|
127
|
-
const statsLine = statsParts.join(" ");
|
|
128
|
-
// --- Line 3: extension statuses ---
|
|
129
|
-
const statuses = footerData.getExtensionStatuses();
|
|
130
|
-
const statusParts = [];
|
|
131
|
-
const sorted = [...statuses.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
132
|
-
for (const [, text] of sorted) {
|
|
133
|
-
if (text)
|
|
134
|
-
statusParts.push(text.replace(/[\r\n\t]/g, " ").trim());
|
|
135
|
-
}
|
|
136
|
-
// Assemble lines, truncating each to width.
|
|
137
|
-
const lines = [];
|
|
138
|
-
lines.push(theme.fg("dim", pwd.length > width ? pwd.slice(0, width) : pwd));
|
|
139
|
-
if (statsLine) {
|
|
140
|
-
lines.push(theme.fg("dim", statsLine.length > width ? statsLine.slice(0, width) : statsLine));
|
|
141
|
-
}
|
|
142
|
-
if (statusParts.length > 0) {
|
|
143
|
-
const statusLine = statusParts.join(" ");
|
|
144
|
-
lines.push(theme.fg("dim", statusLine.length > width ? statusLine.slice(0, width) : statusLine));
|
|
145
|
-
}
|
|
146
|
-
return lines;
|
|
252
|
+
const statuses = [...footerData.getExtensionStatuses().entries()]
|
|
253
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
254
|
+
.map(([, text]) => text);
|
|
255
|
+
return renderFooterLines({
|
|
256
|
+
git: gitInfo(),
|
|
257
|
+
model: ctx.model?.id ?? "no-model",
|
|
258
|
+
usage: collectUsage(ctx.sessionManager),
|
|
259
|
+
contextPercent: ctx.getContextUsage()?.percent ?? null,
|
|
260
|
+
statuses,
|
|
261
|
+
}, theme, width);
|
|
262
|
+
},
|
|
263
|
+
invalidate() {
|
|
264
|
+
gitCache = undefined;
|
|
265
|
+
},
|
|
266
|
+
dispose() {
|
|
267
|
+
unsubscribeBranch?.();
|
|
147
268
|
},
|
|
148
|
-
invalidate() { },
|
|
149
269
|
};
|
|
150
270
|
};
|
|
151
271
|
}
|
package/dist/extension/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { registerCostCommand } from "./costHud.js";
|
|
|
14
14
|
import { isDebug } from "./diagnostics.js";
|
|
15
15
|
import { droppedSessionRuns, sessionRunIds } from "./sessionRuns.js";
|
|
16
16
|
import { codeStateHome } from "./stateHome.js";
|
|
17
|
-
import { createYagniFooterFactory } from "./footer.js";
|
|
17
|
+
import { createYagniFooterFactory, formatCwd } from "./footer.js";
|
|
18
18
|
import { RerouteNotifier } from "./rerouteNotice.js";
|
|
19
19
|
import { isFreshWorkspace, registerTeamSetupCommand, runInitPass as defaultRunInitPass } from "./initPass.js";
|
|
20
20
|
import { isInitDone as defaultIsInitDone, markInitDone as defaultMarkInitDone } from "./initDone.js";
|
|
@@ -481,10 +481,15 @@ export async function registerYagni(pi, deps = {}) {
|
|
|
481
481
|
ctx.ui?.setStatus?.("brand", BRAND_NAME);
|
|
482
482
|
// setHeader replaces the built-in header in place (verified against pi
|
|
483
483
|
// 0.80.2 setExtensionHeader); the factory returns a simple Text component.
|
|
484
|
-
|
|
485
|
-
//
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
// Pass the launcher-forwarded CLI version (YAGNI_CODE_VERSION) and the
|
|
485
|
+
// home-collapsed cwd so the masthead shows them like the reference.
|
|
486
|
+
const mastheadVersion = process.env.YAGNI_CODE_VERSION?.trim() || undefined;
|
|
487
|
+
const mastheadCwd = formatCwd(process.cwd(), process.env.HOME);
|
|
488
|
+
ctx.ui?.setHeader?.((_tui, theme) => new Text(buildMastheadString(theme, { version: mastheadVersion, cwd: mastheadCwd })));
|
|
489
|
+
// Replace the built-in footer with the YAGNI status bar: folder +
|
|
490
|
+
// [worktree] + branch on line 1, model + token/cost stats + integer
|
|
491
|
+
// context % on line 2, and extension statuses (brand, todos, mode) on
|
|
492
|
+
// line 3. The factory captures ctx so the footer can read session data
|
|
488
493
|
// (token stats, context usage) that isn't on the footerData provider.
|
|
489
494
|
ctx.ui?.setFooter?.((tui, theme, footerData) => createYagniFooterFactory(ctx)(tui, theme, footerData));
|
|
490
495
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "0.2.1-staging.
|
|
3
|
+
"version": "0.2.1-staging.1033.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"@earendil-works/pi-tui": "0.83.0",
|
|
39
39
|
"typebox": "^1.1.38"
|
|
40
40
|
},
|
|
41
|
-
"yagniSourceSha": "
|
|
41
|
+
"yagniSourceSha": "42e45669472777af680dce047ce01490793d6712"
|
|
42
42
|
}
|