@badliveware/pi-footer-framework 0.1.1 → 0.2.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/README.md +264 -30
- package/assets/footer-framework-showcase.png +0 -0
- package/examples/footer-framework.config.ts +115 -0
- package/index.ts +1560 -266
- package/package.json +4 -1
- package/skills/footer-framework-config/SKILL.md +45 -15
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# pi-footer-framework
|
|
2
2
|
|
|
3
|
-
Configurable footer replacement for Pi. It
|
|
3
|
+
Configurable footer replacement for Pi. It owns footer layout, formatting, color, truncation, and placement while other extensions publish structured status/data for it to render.
|
|
4
4
|
|
|
5
|
-
Use it when
|
|
5
|
+
Use it when you want to customize Pi's footer through a configurable framework that agents can inspect and reconfigure from natural-language prompts.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -18,31 +18,228 @@ No external services or credentials are required.
|
|
|
18
18
|
/footerfx on
|
|
19
19
|
/footerfx item context line 1
|
|
20
20
|
/footerfx item context after model
|
|
21
|
-
/footerfx
|
|
21
|
+
/footerfx item pr line 3
|
|
22
|
+
/footerfx anchor all right
|
|
22
23
|
/footerfx save user
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
The
|
|
26
|
+
The default layout uses two footer lines. If you place an item on another positive line number, the footer grows to include that line. Disable the replacement footer with:
|
|
26
27
|
|
|
27
28
|
```text
|
|
28
29
|
/footerfx off
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
##
|
|
32
|
+
## Showcase
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
This is a real Pi terminal using TS render closures, theme-aware styles, right-anchored layout, responsive column placement, PR state, and an adapted extension status item:
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
- `model` with thinking level
|
|
37
|
-
- `branch`
|
|
38
|
-
- `stats`
|
|
39
|
-
- `context` usage, such as `ctx 52.2% 142K/272K`
|
|
40
|
-
- `pr` state from compatible PR extensions
|
|
41
|
-
- `ext` status text from other extensions
|
|
36
|
+

|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
The screenshot demonstrates:
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
- combined render output: cwd plus branch/PR label in one item
|
|
41
|
+
- token-level styling: labels, model id, thinking level, token stats, context, and cost use different Pi theme colors/attributes
|
|
42
|
+
- built-in Pi data: `cwd`, `branch`, `model`, `stats`, `context`, and `pr`
|
|
43
|
+
- extension status adaptation: `compaction-continue` status becomes `watchdog:on`
|
|
44
|
+
- flexible placement: line 1 and line 2 are right-anchored, while line 3 uses responsive `column` placement for middle and center-right items
|
|
45
|
+
|
|
46
|
+
<details>
|
|
47
|
+
<summary>Config used for the screenshot</summary>
|
|
48
|
+
|
|
49
|
+
Save this as `~/.pi/agent/footer-framework.config.ts`. The same file ships as `examples/footer-framework.config.ts` in the npm package.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import type { FooterFrameworkConfig } from "@badliveware/pi-footer-framework";
|
|
53
|
+
|
|
54
|
+
function shortPath(value: string, maxWidth = 48, tailSegments = 2): string {
|
|
55
|
+
const normalized = value.replace(/^\/home\/[^/]+/, "~");
|
|
56
|
+
const prefix = normalized.startsWith("~/") ? "~/" : normalized.startsWith("/") ? "/" : "";
|
|
57
|
+
const parts = normalized.slice(prefix.length).split("/").filter(Boolean);
|
|
58
|
+
const compact = parts.length > tailSegments ? `${prefix}…/${parts.slice(-tailSegments).join("/")}` : normalized;
|
|
59
|
+
return compact.length > maxWidth ? `…${compact.slice(-(maxWidth - 1))}` : compact;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const config = {
|
|
63
|
+
enabled: true,
|
|
64
|
+
lineAnchors: { 1: "right", 2: "right", 3: "left" },
|
|
65
|
+
minGap: 2,
|
|
66
|
+
maxGap: 24,
|
|
67
|
+
items: {
|
|
68
|
+
branch: { visible: false },
|
|
69
|
+
ext: { visible: false },
|
|
70
|
+
cwd: {
|
|
71
|
+
visible: true,
|
|
72
|
+
line: 1,
|
|
73
|
+
zone: "left",
|
|
74
|
+
order: 10,
|
|
75
|
+
render: ({ pi, span, fn }) => [
|
|
76
|
+
span("cwd", "muted"),
|
|
77
|
+
" ",
|
|
78
|
+
span(shortPath(pi.cwd.trim(), 48, 2), "dim"),
|
|
79
|
+
span(" · ", "muted"),
|
|
80
|
+
span(fn.truncate(pi.branch?.label ?? "", 22), "accent"),
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
model: {
|
|
84
|
+
visible: true,
|
|
85
|
+
line: 1,
|
|
86
|
+
zone: "right",
|
|
87
|
+
order: 10,
|
|
88
|
+
render: ({ pi, span }) => [
|
|
89
|
+
span("model:", "muted"),
|
|
90
|
+
span(pi.model.id ?? "no-model", "accent"),
|
|
91
|
+
span("/", "muted"),
|
|
92
|
+
span(pi.model.thinking ?? "", "thinkingXhigh,bold"),
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
stats: {
|
|
96
|
+
visible: true,
|
|
97
|
+
line: 2,
|
|
98
|
+
zone: "left",
|
|
99
|
+
order: 10,
|
|
100
|
+
render: ({ pi, span }) => [
|
|
101
|
+
span("↑", "dim"), span(pi.stats.inputText ?? "0", "dim"), " ",
|
|
102
|
+
span("↓", "dim"), span(pi.stats.outputText ?? "0", "dim"), " ",
|
|
103
|
+
span("$", "accent"), span(pi.stats.costText ?? "0.000", "success"),
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
context: {
|
|
107
|
+
visible: true,
|
|
108
|
+
line: 3,
|
|
109
|
+
zone: "left",
|
|
110
|
+
order: 10,
|
|
111
|
+
column: "50%",
|
|
112
|
+
render: ({ pi, span }) => {
|
|
113
|
+
if (!pi.context) return undefined;
|
|
114
|
+
const tone = pi.context.tone ?? "muted";
|
|
115
|
+
return [span("ctx", tone), " ", span(pi.context.percentText ?? "?%", tone), " ", span(pi.context.tokenText ?? "?/?", tone)];
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
pr: {
|
|
119
|
+
visible: true,
|
|
120
|
+
line: 3,
|
|
121
|
+
zone: "left",
|
|
122
|
+
order: 20,
|
|
123
|
+
column: "66%",
|
|
124
|
+
render: ({ pi, span }) => {
|
|
125
|
+
if (!pi.pr) return undefined;
|
|
126
|
+
return [span("PR ", "muted"), span(pi.pr.checkGlyph ?? "•", pi.pr.checkTone ?? "muted"), span(pi.pr.commentsText ?? "", "muted")];
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
adapters: {
|
|
131
|
+
watchdog: {
|
|
132
|
+
source: "extensionStatus",
|
|
133
|
+
key: "compaction-continue",
|
|
134
|
+
itemId: "watchdog",
|
|
135
|
+
match: "(on|off)",
|
|
136
|
+
group: 1,
|
|
137
|
+
urlPath: "url",
|
|
138
|
+
placement: { visible: true, line: 2, zone: "right", order: 20 },
|
|
139
|
+
render: ({ value, span }) => [span("watchdog:", "muted"), span(value ?? "", "accent,bold")],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
} satisfies FooterFrameworkConfig;
|
|
143
|
+
|
|
144
|
+
export default config;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
String `column` positions such as `"50%"`, `"66%"`, and `"center"` are resolved from the current terminal width, so they keep their relative position after resize. Numeric columns remain fixed absolute terminal columns.
|
|
148
|
+
|
|
149
|
+
</details>
|
|
150
|
+
|
|
151
|
+
## How it works
|
|
152
|
+
|
|
153
|
+
Footer-framework renders normalized footer items from adapter mappings, direct TS/JS item renderers, and extension-published items. Adapter mappings can read three source types:
|
|
154
|
+
|
|
155
|
+
| Source | Use it for |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `pi` | Built-in Pi/session/footer data such as `cwd`, `model`, `stats`, `context`, `branch`, `pr`, and `extensionStatuses`. |
|
|
158
|
+
| `extensionStatus` | Existing `ctx.ui.setStatus()` footer/status entries from other extensions. |
|
|
159
|
+
| `sessionEntry` | The latest custom session entry written by an extension with `pi.appendEntry()`. |
|
|
160
|
+
|
|
161
|
+
The built-in footer items (`cwd`, `model`, `branch`, `stats`, `context`, `pr`) are regular default adapters unless you replace them with `items.<id>.render` in TS/JS config. User/project config overrides built-in defaults and producer hints.
|
|
162
|
+
|
|
163
|
+
Agents can inspect concise footer-relevant data with `footer_framework_sources`, then add adapter rules with `footer_framework_adapter_config` or adjust layout with `footer_framework_config`. Runtime metadata such as tools, commands, skills, descriptions, and `sourceInfo` is opt-in via `includeTools`, `includeCommands`, `includeSkills`, and `includeDetails`.
|
|
164
|
+
|
|
165
|
+
## Templates and styles
|
|
166
|
+
|
|
167
|
+
Adapters can render with a restricted Liquid-style interpolation subset:
|
|
168
|
+
|
|
169
|
+
```liquid
|
|
170
|
+
{{ pi.stats.costText }}
|
|
171
|
+
{{ "EUR" }}
|
|
172
|
+
{{ "EUR" | style: "accent" }}{{ pi.stats.costText | style: "success,bold" }}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Quoted strings are literals. Unquoted terms are variables, so missing variables are reported as template diagnostics instead of being guessed as text. Diagnostics appear in `/footerfx-debug`, `footer_framework_state`, and `footer_framework_sources`.
|
|
176
|
+
|
|
177
|
+
Useful template context:
|
|
178
|
+
|
|
179
|
+
| Path | Meaning |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `value`, `label`, `status`, `data`, `url` | The current adapter source. |
|
|
182
|
+
| `pi.cwd` | Current working directory. |
|
|
183
|
+
| `pi.model.id`, `pi.model.provider`, `pi.model.thinking` | Current model information. |
|
|
184
|
+
| `pi.stats.inputText`, `pi.stats.outputText`, `pi.stats.costText` | Formatted session token/cost stats. Raw numbers are `input`, `output`, and `cost`. |
|
|
185
|
+
| `pi.context.percentText`, `pi.context.tokenText`, `pi.context.tone` | Context usage and recommended tone. |
|
|
186
|
+
| `pi.branch.name`, `pi.branch.label` | Git branch values. Use `truncate` in templates when you want a shorter display. |
|
|
187
|
+
| `pi.pr.number`, `pi.pr.url`, `pi.pr.checkGlyph`, `pi.pr.checkTone`, `pi.pr.commentsText` | Pull request state when available. |
|
|
188
|
+
|
|
189
|
+
Supported filters:
|
|
190
|
+
|
|
191
|
+
| Filter | Example |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| `style` / `color` | `{{ value | style: "accent,bold" }}` |
|
|
194
|
+
| `bg` / `background` | `{{ value | bg: "toolSuccessBg" }}` |
|
|
195
|
+
| `bold`, `italic`, `underline`, `inverse`, `strikethrough` | `{{ value | underline }}` |
|
|
196
|
+
| `link` | `{{ pi.pr.number | link: pi.pr.url }}` |
|
|
197
|
+
| `truncate` | `{{ pi.branch.label | truncate: 22 }}` limits any value to 22 cells with an ellipsis. |
|
|
198
|
+
| `compactPath` | `{{ pi.cwd | compactPath: 48, 2 }}` keeps the last 2 path segments when the path is wider than 48 cells. |
|
|
199
|
+
| `default` | `{{ data.state | default: "unknown" }}` |
|
|
200
|
+
|
|
201
|
+
Style strings use Pi theme tokens and text attributes. Foreground examples: `accent`, `muted`, `dim`, `success`, `warning`, `error`, `text`, `mdLink`, `toolDiffAdded`, and the other Pi theme foreground tokens. Backgrounds use `bg:<token>`, such as `bg:toolSuccessBg`. Attributes are `bold`, `italic`, `underline`, `inverse`, and `strikethrough`.
|
|
202
|
+
|
|
203
|
+
## TypeScript render config
|
|
204
|
+
|
|
205
|
+
For personal formatting logic, use a normal TS/JS config file with render closures. Footer-framework still owns data collection, layout, clipping, diagnostics, and final rendering; the closure only returns text/spans for one item.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import type { FooterFrameworkConfig } from "@badliveware/pi-footer-framework";
|
|
209
|
+
|
|
210
|
+
function shortPath(value: string, maxWidth = 48, tailSegments = 2) {
|
|
211
|
+
const normalized = value.replace(/^\/home\/[^/]+/, "~");
|
|
212
|
+
const prefix = normalized.startsWith("~/") ? "~/" : normalized.startsWith("/") ? "/" : "";
|
|
213
|
+
const parts = normalized.slice(prefix.length).split("/").filter(Boolean);
|
|
214
|
+
const compact = parts.length > tailSegments ? `${prefix}…/${parts.slice(-tailSegments).join("/")}` : normalized;
|
|
215
|
+
return compact.length > maxWidth ? `…${compact.slice(-(maxWidth - 1))}` : compact;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export default {
|
|
219
|
+
items: {
|
|
220
|
+
branch: { visible: false },
|
|
221
|
+
ext: { visible: false },
|
|
222
|
+
cwd: {
|
|
223
|
+
line: 1,
|
|
224
|
+
zone: "left",
|
|
225
|
+
order: 10,
|
|
226
|
+
render: ({ pi, span, fn }) => [
|
|
227
|
+
span("cwd", "muted"),
|
|
228
|
+
" ",
|
|
229
|
+
span(shortPath(pi.cwd.trim(), 48, 2), "dim"),
|
|
230
|
+
span(" · ", "muted"),
|
|
231
|
+
span(fn.truncate(pi.branch?.label ?? "", 22), "accent"),
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
} satisfies FooterFrameworkConfig;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Render functions are synchronous and may return strings, spans, arrays, `null`, or `undefined`. Use `span(text, style, { url })` for token-level style/link metadata and `fn.text`, `fn.width`, `fn.truncate`, or `fn.compactPath` for footer-safe helpers. Adapter render functions also receive `value`, `label`, `status`, `data`, `url`, and `source`.
|
|
239
|
+
|
|
240
|
+
`/footerfx-debug`, `footer_framework_state`, and `footer_framework_sources` show which TS/JS renderers loaded plus each rendered item's final tokens, width, placement, and diagnostics. If a render closure throws or returns a promise, the footer item is skipped and a diagnostic is recorded.
|
|
241
|
+
|
|
242
|
+
## Configuration files
|
|
46
243
|
|
|
47
244
|
User settings persist to:
|
|
48
245
|
|
|
@@ -50,13 +247,21 @@ User settings persist to:
|
|
|
50
247
|
~/.pi/agent/footer-framework.json
|
|
51
248
|
```
|
|
52
249
|
|
|
250
|
+
Optional user TS/JS config files live next to it:
|
|
251
|
+
|
|
252
|
+
```text
|
|
253
|
+
~/.pi/agent/footer-framework.config.ts
|
|
254
|
+
~/.pi/agent/footer-framework.config.js
|
|
255
|
+
```
|
|
256
|
+
|
|
53
257
|
Project settings can override them:
|
|
54
258
|
|
|
55
259
|
```text
|
|
56
260
|
<project>/.pi/footer-framework.json
|
|
261
|
+
<project>/.pi/footer-framework.config.ts
|
|
57
262
|
```
|
|
58
263
|
|
|
59
|
-
Use `/footerfx save project` only when you intentionally want a project-specific footer layout.
|
|
264
|
+
Load order is defaults, user TS/JS, user JSON, project TS/JS, then project JSON. `/footerfx` commands write JSON overrides; they do not rewrite TS/JS source. Use `/footerfx save project` only when you intentionally want a project-specific footer layout.
|
|
60
265
|
|
|
61
266
|
## Commands
|
|
62
267
|
|
|
@@ -64,42 +269,71 @@ Use `/footerfx save project` only when you intentionally want a project-specific
|
|
|
64
269
|
| --- | --- |
|
|
65
270
|
| `/footerfx` | Show current config and source. |
|
|
66
271
|
| `/footerfx on` / `/footerfx off` | Enable or disable the replacement footer. |
|
|
272
|
+
| `/footerfx config` | Show loaded config source and config paths. |
|
|
67
273
|
| `/footerfx load` | Reload user/project config files. |
|
|
68
274
|
| `/footerfx save user` | Save current settings as the user default. |
|
|
69
275
|
| `/footerfx save project` | Save current settings for the current project. |
|
|
70
276
|
| `/footerfx reset` | Restore defaults and persist them to user config. |
|
|
277
|
+
| `/footerfx section <cwd|stats|context|model|branch|pr|ext> <on|off>` | Convenience alias for item visibility. |
|
|
71
278
|
| `/footerfx item <id> <show|hide|reset>` | Control item visibility. |
|
|
72
|
-
| `/footerfx item <id> line <
|
|
279
|
+
| `/footerfx item <id> line <n>` / `row <n>` | Move an item to any positive footer line. |
|
|
73
280
|
| `/footerfx item <id> zone <left|right>` | Move an item between left/right zones. |
|
|
74
|
-
| `/footerfx item <id> before <other-id>` | Place an item
|
|
75
|
-
| `/footerfx item <id>
|
|
76
|
-
| `/footerfx
|
|
77
|
-
| `/footerfx
|
|
281
|
+
| `/footerfx item <id> before <other-id>` / `after <other-id>` | Place an item relative to another item. |
|
|
282
|
+
| `/footerfx item <id> column <n|center|middle|percent|off>` | Pin, center, percentage-place, or unpin an item. Percent examples: `50%`, `66%`. |
|
|
283
|
+
| `/footerfx anchor <line|all> <gap|left|center|right|spread>` | Control line alignment. `line3` and `3` both work. |
|
|
284
|
+
| `/footerfx adapter` | List configured adapters. |
|
|
285
|
+
| `/footerfx adapter <id> pi <source-key> [label]` | Adapt a built-in Pi source. |
|
|
286
|
+
| `/footerfx adapter <id> status <status-key> [label]` | Adapt an existing extension status key. |
|
|
287
|
+
| `/footerfx adapter <id> custom <custom-type> <path> [label]` | Adapt the latest matching custom session entry. |
|
|
288
|
+
| `/footerfx adapter <id> template <template>` | Set the adapter's render template. |
|
|
289
|
+
| `/footerfx adapter <id> empty-template <template>` | Set the template used for an empty adapter value. |
|
|
290
|
+
| `/footerfx adapter <id> style <style>` | Apply a default style to the rendered adapter text. |
|
|
291
|
+
| `/footerfx adapter <id> remove` | Remove an adapter. For built-ins, hide the item with `/footerfx item <id> hide`. |
|
|
78
292
|
| `/footerfx gap <min> <max>` | Set spacing bounds. |
|
|
79
|
-
| `/footerfx
|
|
80
|
-
| `/footerfx-debug` | Show render snapshot, settings, and layout telemetry. |
|
|
293
|
+
| `/footerfx-debug` | Show render snapshot, settings, template/render diagnostics, loaded TS/JS renderers, and layout telemetry. |
|
|
81
294
|
|
|
82
295
|
## Agent tools
|
|
83
296
|
|
|
84
|
-
The extension exposes
|
|
297
|
+
The extension exposes tools so agents can inspect and adjust the footer without asking you to run commands:
|
|
85
298
|
|
|
86
299
|
- `footer_framework_state`
|
|
300
|
+
- `footer_framework_sources`
|
|
87
301
|
- `footer_framework_config`
|
|
302
|
+
- `footer_framework_adapter_config`
|
|
88
303
|
|
|
89
|
-
|
|
304
|
+
`footer_framework_sources` is concise by default. Pass `includeTools`, `includeCommands`, `includeSkills`, and `includeDetails` only when runtime metadata is directly useful.
|
|
90
305
|
|
|
91
|
-
|
|
306
|
+
## Extension data API
|
|
307
|
+
|
|
308
|
+
Compatible extensions should publish data, not pre-rendered layout. The framework decides final text, color, position, and truncation. Producers may include hints, but user config wins.
|
|
92
309
|
|
|
93
310
|
```ts
|
|
94
311
|
pi.events.emit("footer-framework:item", {
|
|
95
|
-
id: "
|
|
96
|
-
|
|
97
|
-
|
|
312
|
+
id: "cache:status",
|
|
313
|
+
label: "cache",
|
|
314
|
+
value: "warm",
|
|
315
|
+
tone: "success",
|
|
316
|
+
data: { entries: 42 },
|
|
317
|
+
hint: {
|
|
318
|
+
icon: "◇",
|
|
319
|
+
format: "label-value",
|
|
320
|
+
placement: { line: 2, zone: "right", order: 50 }
|
|
321
|
+
}
|
|
98
322
|
});
|
|
99
323
|
```
|
|
100
324
|
|
|
325
|
+
Legacy `text` and top-level `placement` fields still work for existing extensions, but new integrations should prefer `label`, `value`, `status`, `data`, and `hint`.
|
|
326
|
+
|
|
101
327
|
Remove an item with:
|
|
102
328
|
|
|
103
329
|
```ts
|
|
104
|
-
pi.events.emit("footer-framework:item", { id: "
|
|
330
|
+
pi.events.emit("footer-framework:item", { id: "cache:status", remove: true });
|
|
105
331
|
```
|
|
332
|
+
|
|
333
|
+
## Troubleshooting
|
|
334
|
+
|
|
335
|
+
### Blank space below the footer
|
|
336
|
+
|
|
337
|
+
If blank rows sometimes appear below the footer and disappear after you send a prompt, check `/footerfx-debug` or `footer_framework_state`. When `lastFooterSnapshot.lines` contains only the expected footer lines, the framework is not rendering extra rows. This is usually Pi's TUI viewport/differential rendering leaving unused terminal space below the last rendered component.
|
|
338
|
+
|
|
339
|
+
A Pi-side workaround is `terminal.clearOnShrink: true` in `~/.pi/agent/settings.json`, but that can add redraw flicker. Footer-framework does not change this setting.
|
|
Binary file
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { FooterFrameworkConfig } from "@badliveware/pi-footer-framework";
|
|
2
|
+
|
|
3
|
+
function shortPath(value: string, maxWidth = 48, tailSegments = 2): string {
|
|
4
|
+
const normalized = value.replace(/^\/home\/[^/]+/, "~");
|
|
5
|
+
const prefix = normalized.startsWith("~/") ? "~/" : normalized.startsWith("/") ? "/" : "";
|
|
6
|
+
const parts = normalized.slice(prefix.length).split("/").filter(Boolean);
|
|
7
|
+
const compact = parts.length > tailSegments ? `${prefix}…/${parts.slice(-tailSegments).join("/")}` : normalized;
|
|
8
|
+
return compact.length > maxWidth ? `…${compact.slice(-(maxWidth - 1))}` : compact;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const config = {
|
|
12
|
+
enabled: true,
|
|
13
|
+
lineAnchors: {
|
|
14
|
+
1: "right",
|
|
15
|
+
2: "right",
|
|
16
|
+
3: "left",
|
|
17
|
+
},
|
|
18
|
+
minGap: 2,
|
|
19
|
+
maxGap: 24,
|
|
20
|
+
items: {
|
|
21
|
+
branch: { visible: false },
|
|
22
|
+
ext: { visible: false },
|
|
23
|
+
cwd: {
|
|
24
|
+
visible: true,
|
|
25
|
+
line: 1,
|
|
26
|
+
zone: "left",
|
|
27
|
+
order: 10,
|
|
28
|
+
render: ({ pi, span, fn }) => [
|
|
29
|
+
span("cwd", "muted"),
|
|
30
|
+
" ",
|
|
31
|
+
span(shortPath(pi.cwd.trim(), 48, 2), "dim"),
|
|
32
|
+
span(" · ", "muted"),
|
|
33
|
+
span(fn.truncate(pi.branch?.label ?? "", 22), "accent"),
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
model: {
|
|
37
|
+
visible: true,
|
|
38
|
+
line: 1,
|
|
39
|
+
zone: "right",
|
|
40
|
+
order: 10,
|
|
41
|
+
render: ({ pi, span }) => [
|
|
42
|
+
span("model:", "muted"),
|
|
43
|
+
span(pi.model.id ?? "no-model", "accent"),
|
|
44
|
+
span("/", "muted"),
|
|
45
|
+
span(pi.model.thinking ?? "", "thinkingXhigh,bold"),
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
stats: {
|
|
49
|
+
visible: true,
|
|
50
|
+
line: 2,
|
|
51
|
+
zone: "left",
|
|
52
|
+
order: 10,
|
|
53
|
+
render: ({ pi, span }) => [
|
|
54
|
+
span("↑", "dim"),
|
|
55
|
+
span(pi.stats.inputText ?? "0", "dim"),
|
|
56
|
+
" ",
|
|
57
|
+
span("↓", "dim"),
|
|
58
|
+
span(pi.stats.outputText ?? "0", "dim"),
|
|
59
|
+
" ",
|
|
60
|
+
span("$", "accent"),
|
|
61
|
+
span(pi.stats.costText ?? "0.000", "success"),
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
context: {
|
|
65
|
+
visible: true,
|
|
66
|
+
line: 3,
|
|
67
|
+
zone: "left",
|
|
68
|
+
order: 10,
|
|
69
|
+
column: "50%",
|
|
70
|
+
render: ({ pi, span }) => {
|
|
71
|
+
if (!pi.context) return undefined;
|
|
72
|
+
const tone = pi.context.tone ?? "muted";
|
|
73
|
+
return [
|
|
74
|
+
span("ctx", tone),
|
|
75
|
+
" ",
|
|
76
|
+
span(pi.context.percentText ?? "?%", tone),
|
|
77
|
+
" ",
|
|
78
|
+
span(pi.context.tokenText ?? "?/?", tone),
|
|
79
|
+
];
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
pr: {
|
|
83
|
+
visible: true,
|
|
84
|
+
line: 3,
|
|
85
|
+
zone: "left",
|
|
86
|
+
order: 20,
|
|
87
|
+
column: "66%",
|
|
88
|
+
render: ({ pi, span }) => {
|
|
89
|
+
if (!pi.pr) return undefined;
|
|
90
|
+
return [
|
|
91
|
+
span("PR ", "muted"),
|
|
92
|
+
span(pi.pr.checkGlyph ?? "•", pi.pr.checkTone ?? "muted"),
|
|
93
|
+
span(pi.pr.commentsText ?? "", "muted"),
|
|
94
|
+
];
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
adapters: {
|
|
99
|
+
watchdog: {
|
|
100
|
+
source: "extensionStatus",
|
|
101
|
+
key: "compaction-continue",
|
|
102
|
+
itemId: "watchdog",
|
|
103
|
+
match: "(on|off)",
|
|
104
|
+
group: 1,
|
|
105
|
+
urlPath: "url",
|
|
106
|
+
placement: { visible: true, line: 2, zone: "right", order: 20 },
|
|
107
|
+
render: ({ value, span }) => [
|
|
108
|
+
span("watchdog:", "muted"),
|
|
109
|
+
span(value ?? "", "accent,bold"),
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
} satisfies FooterFrameworkConfig;
|
|
114
|
+
|
|
115
|
+
export default config;
|