@spences10/pi-footer 0.0.1 → 0.0.3
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 +16 -0
- package/dist/commands/footer-command.d.ts +3 -0
- package/dist/commands/footer-command.js +202 -0
- package/dist/commands/footer-command.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/extension/install.d.ts +4 -0
- package/dist/extension/install.js +21 -0
- package/dist/extension/install.js.map +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +12 -264
- package/dist/index.js.map +1 -1
- package/dist/model/footer-model.d.ts +21 -0
- package/dist/model/footer-model.js +94 -0
- package/dist/model/footer-model.js.map +1 -0
- package/dist/model/git.d.ts +11 -0
- package/dist/model/git.js +83 -0
- package/dist/model/git.js.map +1 -0
- package/dist/model/thinking.d.ts +4 -0
- package/dist/model/thinking.js +34 -0
- package/dist/model/thinking.js.map +1 -0
- package/dist/presets/types.d.ts +23 -0
- package/dist/presets/types.js +51 -0
- package/dist/presets/types.js.map +1 -0
- package/dist/reference/research.d.ts +6 -0
- package/dist/reference/research.js +50 -0
- package/dist/reference/research.js.map +1 -0
- package/dist/render/footer-lines.d.ts +4 -0
- package/dist/render/footer-lines.js +117 -0
- package/dist/render/footer-lines.js.map +1 -0
- package/dist/render/preview.d.ts +6 -0
- package/dist/render/preview.js +34 -0
- package/dist/render/preview.js.map +1 -0
- package/dist/render/status-line.d.ts +3 -0
- package/dist/render/status-line.js +32 -0
- package/dist/render/status-line.js.map +1 -0
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +46 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/theme/tokens.d.ts +16 -0
- package/dist/theme/tokens.js +26 -0
- package/dist/theme/tokens.js.map +1 -0
- package/dist/utils/text.d.ts +3 -0
- package/dist/utils/text.js +23 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @spences10/pi-footer
|
|
2
2
|
|
|
3
|
+
[](https://viteplus.dev)
|
|
4
|
+
[](https://vitest.dev)
|
|
5
|
+
[](https://www.npmjs.com/package/@spences10/pi-footer)
|
|
6
|
+
[](https://www.npmjs.com/package/@spences10/pi-footer)
|
|
7
|
+
|
|
3
8
|
Configurable Pi footer/statusline extension.
|
|
4
9
|
|
|
5
10
|
It owns `ctx.ui.setFooter(...)` and renders core Pi session data plus
|
|
@@ -16,3 +21,14 @@ extension statuses published by other extensions with
|
|
|
16
21
|
- `minimal` — compact cwd/model/context footer.
|
|
17
22
|
- `power` — fuller status-forward layout.
|
|
18
23
|
- `git-heavy` — emphasizes cwd/git/status widgets.
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
|
|
27
|
+
Package scripts build transitive workspace dependencies first, then
|
|
28
|
+
run local tools through Vite+ with `vp exec`.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm --filter @spences10/pi-footer run check
|
|
32
|
+
pnpm --filter @spences10/pi-footer run test
|
|
33
|
+
pnpm --filter @spences10/pi-footer run build
|
|
34
|
+
```
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { SelectList } from '@earendil-works/pi-tui';
|
|
2
|
+
import { show_modal, show_settings_modal, } from '@spences10/pi-tui-modal';
|
|
3
|
+
import { save_footer_state } from '../config.js';
|
|
4
|
+
import { get_current_footer_data, install_footer, } from '../extension/install.js';
|
|
5
|
+
import { FOOTER_DENSITIES, FOOTER_PRESETS, FOOTER_TONES, FOOTER_WIDGETS, GIT_ICON_MODES, STATUS_LABEL_MODES, } from '../presets/types.js';
|
|
6
|
+
import { FOOTER_RESEARCH_REFERENCES } from '../reference/research.js';
|
|
7
|
+
import { render_footer_lines } from '../render/footer-lines.js';
|
|
8
|
+
export function register_footer_command(pi, state) {
|
|
9
|
+
pi.registerCommand('footer', {
|
|
10
|
+
description: 'Configure the Pi footer',
|
|
11
|
+
handler: async (_args, ctx) => configure_footer(ctx, state),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async function configure_footer(ctx, state) {
|
|
15
|
+
let open_widgets = false;
|
|
16
|
+
await show_settings_modal(ctx, {
|
|
17
|
+
title: 'Footer settings',
|
|
18
|
+
subtitle: 'Changes apply live and persist to ~/.pi/agent/extensions/pi-footer.json.',
|
|
19
|
+
footer: 'space/enter cycles values • esc close',
|
|
20
|
+
items: get_footer_settings(state),
|
|
21
|
+
enable_search: true,
|
|
22
|
+
detail: (item) => get_setting_detail(item.id),
|
|
23
|
+
metadata: (item) => get_setting_metadata(item?.id, state),
|
|
24
|
+
on_change: (id, new_value) => {
|
|
25
|
+
if (id === 'widgets') {
|
|
26
|
+
open_widgets = true;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
apply_footer_setting(state, id, new_value);
|
|
30
|
+
save_footer_state(state);
|
|
31
|
+
install_footer(ctx, state);
|
|
32
|
+
return false;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (open_widgets)
|
|
36
|
+
await configure_footer_widgets(ctx, state);
|
|
37
|
+
}
|
|
38
|
+
function get_footer_settings(state) {
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
id: 'preset',
|
|
42
|
+
label: 'Preset',
|
|
43
|
+
description: 'Overall footer layout',
|
|
44
|
+
currentValue: state.preset,
|
|
45
|
+
values: [...FOOTER_PRESETS],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'density',
|
|
49
|
+
label: 'Density',
|
|
50
|
+
description: 'How many footer rows to use',
|
|
51
|
+
currentValue: state.density,
|
|
52
|
+
values: [...FOOTER_DENSITIES],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'tone',
|
|
56
|
+
label: 'Tone',
|
|
57
|
+
description: 'Footer color treatment from the active Pi theme',
|
|
58
|
+
currentValue: state.tone,
|
|
59
|
+
values: [...FOOTER_TONES],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'status-labels',
|
|
63
|
+
label: 'Status labels',
|
|
64
|
+
description: 'How extension statuses are labelled',
|
|
65
|
+
currentValue: state.status_label_mode,
|
|
66
|
+
values: [...STATUS_LABEL_MODES],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'git-icons',
|
|
70
|
+
label: 'Git icons',
|
|
71
|
+
description: 'Use Nerd Font git glyphs or plain symbols',
|
|
72
|
+
currentValue: state.git_icon_mode,
|
|
73
|
+
values: [...GIT_ICON_MODES],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'widgets',
|
|
77
|
+
label: 'Widgets',
|
|
78
|
+
description: 'Open widget visibility settings',
|
|
79
|
+
currentValue: `${get_enabled_widget_count(state)}/${FOOTER_WIDGETS.length} on`,
|
|
80
|
+
values: ['open'],
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
async function configure_footer_widgets(ctx, state) {
|
|
85
|
+
await show_modal(ctx, {
|
|
86
|
+
title: 'Footer widgets',
|
|
87
|
+
subtitle: () => get_widget_modal_subtitle(ctx, state),
|
|
88
|
+
footer: 'space toggles • esc back',
|
|
89
|
+
}, ({ done }, theme, layout) => {
|
|
90
|
+
const items = FOOTER_WIDGETS.map((widget) => ({
|
|
91
|
+
value: widget,
|
|
92
|
+
label: widget,
|
|
93
|
+
description: format_widget_state(state, widget),
|
|
94
|
+
}));
|
|
95
|
+
const list = new SelectList(items, Math.min(items.length, layout.get_max_body_lines()), {
|
|
96
|
+
selectedPrefix: (text) => theme.fg('accent', text),
|
|
97
|
+
selectedText: (text) => theme.fg('accent', text),
|
|
98
|
+
description: (text) => theme.fg('muted', text),
|
|
99
|
+
scrollInfo: (text) => theme.fg('dim', text),
|
|
100
|
+
noMatch: (text) => theme.fg('dim', text),
|
|
101
|
+
});
|
|
102
|
+
list.onCancel = () => done();
|
|
103
|
+
return {
|
|
104
|
+
render: (width) => list.render(width),
|
|
105
|
+
invalidate: () => list.invalidate(),
|
|
106
|
+
handleInput: (data) => {
|
|
107
|
+
if (data === ' ') {
|
|
108
|
+
const selected = list.getSelectedItem();
|
|
109
|
+
if (!selected)
|
|
110
|
+
return;
|
|
111
|
+
const widget = selected.value;
|
|
112
|
+
state.widgets[widget] = !state.widgets[widget];
|
|
113
|
+
selected.description = format_widget_state(state, widget);
|
|
114
|
+
save_footer_state(state);
|
|
115
|
+
install_footer(ctx, state);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
list.handleInput(data);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function get_enabled_widget_count(state) {
|
|
124
|
+
return FOOTER_WIDGETS.filter((widget) => state.widgets[widget])
|
|
125
|
+
.length;
|
|
126
|
+
}
|
|
127
|
+
function get_widget_modal_subtitle(ctx, state) {
|
|
128
|
+
const footer_data = get_current_footer_data();
|
|
129
|
+
if (!footer_data)
|
|
130
|
+
return ['Choose footer building blocks to show or hide.'];
|
|
131
|
+
const lines = render_footer_lines(ctx, ctx.ui.theme, footer_data, state, 72);
|
|
132
|
+
return [
|
|
133
|
+
'Choose footer building blocks to show or hide.',
|
|
134
|
+
'',
|
|
135
|
+
'Preview:',
|
|
136
|
+
...(lines.length > 0
|
|
137
|
+
? lines.map((line) => ` ${line}`)
|
|
138
|
+
: [' —']),
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
function format_widget_state(state, widget) {
|
|
142
|
+
return state.widgets[widget] ? '● enabled' : '○ disabled';
|
|
143
|
+
}
|
|
144
|
+
function apply_footer_setting(state, id, new_value) {
|
|
145
|
+
if (id === 'preset' &&
|
|
146
|
+
FOOTER_PRESETS.includes(new_value)) {
|
|
147
|
+
state.preset = new_value;
|
|
148
|
+
}
|
|
149
|
+
if (id === 'density' &&
|
|
150
|
+
FOOTER_DENSITIES.includes(new_value)) {
|
|
151
|
+
state.density = new_value;
|
|
152
|
+
}
|
|
153
|
+
if (id === 'tone' &&
|
|
154
|
+
FOOTER_TONES.includes(new_value)) {
|
|
155
|
+
state.tone = new_value;
|
|
156
|
+
}
|
|
157
|
+
if (id === 'status-labels' &&
|
|
158
|
+
STATUS_LABEL_MODES.includes(new_value)) {
|
|
159
|
+
state.status_label_mode = new_value;
|
|
160
|
+
}
|
|
161
|
+
if (id === 'git-icons' &&
|
|
162
|
+
GIT_ICON_MODES.includes(new_value)) {
|
|
163
|
+
state.git_icon_mode = new_value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function get_setting_detail(id) {
|
|
167
|
+
if (id === 'preset')
|
|
168
|
+
return 'Presets are starter arrangements; widget toggles decide exact content.';
|
|
169
|
+
if (id === 'density')
|
|
170
|
+
return 'Compact is one row, comfortable is normal 2–3 rows, expanded adds diagnostic/footer mode detail.';
|
|
171
|
+
if (id === 'tone')
|
|
172
|
+
return 'Muted uses dim theme color, balanced uses plain terminal foreground, bright uses theme accent.';
|
|
173
|
+
if (id === 'status-labels')
|
|
174
|
+
return 'Smart avoids doubled labels such as mcp:MCP 6/6 connected.';
|
|
175
|
+
if (id === 'git-icons')
|
|
176
|
+
return 'Nerd uses terminal font glyphs; plain uses ASCII-safe symbols.';
|
|
177
|
+
if (id === 'widgets')
|
|
178
|
+
return 'Open a dedicated picker for footer building blocks.';
|
|
179
|
+
}
|
|
180
|
+
function get_setting_metadata(id, state) {
|
|
181
|
+
const lines = [
|
|
182
|
+
`Current preset: ${state.preset}`,
|
|
183
|
+
`Density: ${state.density}`,
|
|
184
|
+
`Tone: ${state.tone}`,
|
|
185
|
+
`Status labels: ${state.status_label_mode}`,
|
|
186
|
+
`Git icons: ${state.git_icon_mode}`,
|
|
187
|
+
];
|
|
188
|
+
if (id === 'widgets') {
|
|
189
|
+
lines.push('', 'Enabled widgets:');
|
|
190
|
+
for (const widget of FOOTER_WIDGETS.filter((widget) => state.widgets[widget])) {
|
|
191
|
+
lines.push(`• ${widget}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (id === 'preset') {
|
|
195
|
+
lines.push('', 'Research references to fold in:');
|
|
196
|
+
for (const reference of FOOTER_RESEARCH_REFERENCES.slice(0, 4)) {
|
|
197
|
+
lines.push(`• ${reference.name}: ${reference.takeaways[0]}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return lines;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=footer-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"footer-command.js","sourceRoot":"","sources":["../../src/commands/footer-command.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type FooterState } from './presets/types.js';
|
|
2
|
+
export declare function load_footer_state(): FooterState;
|
|
3
|
+
export declare function save_footer_state(state: FooterState): void;
|
|
4
|
+
export declare function normalize_footer_state(state: Partial<FooterState>): FooterState;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { DEFAULT_FOOTER_STATE, DEFAULT_FOOTER_WIDGETS, FOOTER_DENSITIES, FOOTER_PRESETS, FOOTER_TONES, FOOTER_WIDGETS, GIT_ICON_MODES, STATUS_LABEL_MODES, } from './presets/types.js';
|
|
4
|
+
const CONFIG_PATH = join(process.env.PI_HOME ??
|
|
5
|
+
join(process.env.HOME ?? '.', '.pi', 'agent'), 'extensions', 'pi-footer.json');
|
|
6
|
+
export function load_footer_state() {
|
|
7
|
+
if (!existsSync(CONFIG_PATH))
|
|
8
|
+
return clone_default_state();
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
|
|
11
|
+
return normalize_footer_state(parsed);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return clone_default_state();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function save_footer_state(state) {
|
|
18
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
19
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify(state, null, '\t')}\n`);
|
|
20
|
+
}
|
|
21
|
+
export function normalize_footer_state(state) {
|
|
22
|
+
return {
|
|
23
|
+
preset: FOOTER_PRESETS.includes(state.preset)
|
|
24
|
+
? state.preset
|
|
25
|
+
: DEFAULT_FOOTER_STATE.preset,
|
|
26
|
+
density: FOOTER_DENSITIES.includes(state.density)
|
|
27
|
+
? state.density
|
|
28
|
+
: DEFAULT_FOOTER_STATE.density,
|
|
29
|
+
status_label_mode: STATUS_LABEL_MODES.includes(state.status_label_mode)
|
|
30
|
+
? state.status_label_mode
|
|
31
|
+
: DEFAULT_FOOTER_STATE.status_label_mode,
|
|
32
|
+
tone: FOOTER_TONES.includes(state.tone)
|
|
33
|
+
? state.tone
|
|
34
|
+
: DEFAULT_FOOTER_STATE.tone,
|
|
35
|
+
git_icon_mode: GIT_ICON_MODES.includes(state.git_icon_mode)
|
|
36
|
+
? state.git_icon_mode
|
|
37
|
+
: DEFAULT_FOOTER_STATE.git_icon_mode,
|
|
38
|
+
widgets: {
|
|
39
|
+
...DEFAULT_FOOTER_WIDGETS,
|
|
40
|
+
...Object.fromEntries(Object.entries(state.widgets ?? {}).filter(([key]) => FOOTER_WIDGETS.includes(key))),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function clone_default_state() {
|
|
45
|
+
return normalize_footer_state(DEFAULT_FOOTER_STATE);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,SAAS,EACT,YAAY,EACZ,aAAa,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACN,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,cAAc,EACd,kBAAkB,GAElB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,GAAG,IAAI,CACvB,OAAO,CAAC,GAAG,CAAC,OAAO;IAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,EAC9C,YAAY,EACZ,gBAAgB,CAChB,CAAC;AAEF,MAAM,UAAU,iBAAiB;IAChC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,mBAAmB,EAAE,CAAC;IAC3D,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACxB,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CACT,CAAC;QAC1B,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,mBAAmB,EAAE,CAAC;IAC9B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAkB;IACnD,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,aAAa,CACZ,WAAW,EACX,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CACxC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CACrC,KAA2B;IAE3B,OAAO;QACN,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAe,CAAC;YACrD,CAAC,CAAC,KAAK,CAAC,MAAO;YACf,CAAC,CAAC,oBAAoB,CAAC,MAAM;QAC9B,OAAO,EAAE,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAgB,CAAC;YACzD,CAAC,CAAC,KAAK,CAAC,OAAQ;YAChB,CAAC,CAAC,oBAAoB,CAAC,OAAO;QAC/B,iBAAiB,EAAE,kBAAkB,CAAC,QAAQ,CAC7C,KAAK,CAAC,iBAA0B,CAChC;YACA,CAAC,CAAC,KAAK,CAAC,iBAAkB;YAC1B,CAAC,CAAC,oBAAoB,CAAC,iBAAiB;QACzC,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAa,CAAC;YAC/C,CAAC,CAAC,KAAK,CAAC,IAAK;YACb,CAAC,CAAC,oBAAoB,CAAC,IAAI;QAC5B,aAAa,EAAE,cAAc,CAAC,QAAQ,CACrC,KAAK,CAAC,aAAsB,CAC5B;YACA,CAAC,CAAC,KAAK,CAAC,aAAc;YACtB,CAAC,CAAC,oBAAoB,CAAC,aAAa;QACrC,OAAO,EAAE;YACR,GAAG,sBAAsB;YACzB,GAAG,MAAM,CAAC,WAAW,CACpB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CACpD,cAAc,CAAC,QAAQ,CAAC,GAAY,CAAC,CACrC,CACD;SACD;KACD,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB;IAC3B,OAAO,sBAAsB,CAAC,oBAAoB,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { FooterState } from '../presets/types.js';
|
|
3
|
+
export declare function get_current_footer_data(): ReadonlyFooterDataProvider | undefined;
|
|
4
|
+
export declare function install_footer(ctx: ExtensionContext, state: FooterState): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { render_footer_lines } from '../render/footer-lines.js';
|
|
2
|
+
let current_footer_data;
|
|
3
|
+
export function get_current_footer_data() {
|
|
4
|
+
return current_footer_data;
|
|
5
|
+
}
|
|
6
|
+
export function install_footer(ctx, state) {
|
|
7
|
+
if (!ctx.hasUI)
|
|
8
|
+
return;
|
|
9
|
+
ctx.ui.setFooter((tui, theme, footer_data) => {
|
|
10
|
+
current_footer_data = footer_data;
|
|
11
|
+
const unsubscribe = footer_data.onBranchChange(() => tui.requestRender());
|
|
12
|
+
return {
|
|
13
|
+
dispose: unsubscribe,
|
|
14
|
+
invalidate() { },
|
|
15
|
+
render(width) {
|
|
16
|
+
return render_footer_lines(ctx, theme, footer_data, state, width);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/extension/install.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE,IAAI,mBAA2D,CAAC;AAEhE,MAAM,UAAU,uBAAuB;IAGtC,OAAO,mBAAmB,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,cAAc,CAC7B,GAAqB,EACrB,KAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO;IACvB,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;QAC5C,mBAAmB,GAAG,WAAW,CAAC;QAClC,MAAM,WAAW,GAAG,WAAW,CAAC,cAAc,CAAC,GAAG,EAAE,CACnD,GAAG,CAAC,aAAa,EAAE,CACnB,CAAC;QACF,OAAO;YACN,OAAO,EAAE,WAAW;YACpB,UAAU,KAAI,CAAC;YACf,MAAM,CAAC,KAAa;gBACnB,OAAO,mBAAmB,CACzB,GAAG,EACH,KAAK,EACL,WAAW,EACX,KAAK,EACL,KAAK,CACL,CAAC;YACH,CAAC;SACD,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
export { get_current_thinking_level, get_default_footer_thinking_level, } from './model/thinking.js';
|
|
3
|
+
export { FOOTER_DENSITIES, FOOTER_PRESETS, FOOTER_TONES, FOOTER_WIDGETS, type FooterDensity, type FooterPreset, type FooterTone, type FooterWidget, } from './presets/types.js';
|
|
4
|
+
export { FOOTER_RESEARCH_REFERENCES } from './reference/research.js';
|
|
5
|
+
export { render_footer_lines } from './render/footer-lines.js';
|
|
6
|
+
export { render_footer_status_line } from './render/status-line.js';
|
|
7
|
+
export { FOOTER_COLORS, type FooterTheme } from './theme/tokens.js';
|
|
6
8
|
export default function footer_extension(pi: ExtensionAPI): void;
|
package/dist/index.js
CHANGED
|
@@ -1,269 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.replace(/ +/g, ' ')
|
|
12
|
-
.trim();
|
|
13
|
-
}
|
|
14
|
-
function format_token_count(count) {
|
|
15
|
-
if (count < 1000)
|
|
16
|
-
return count.toString();
|
|
17
|
-
if (count < 10000)
|
|
18
|
-
return `${(count / 1000).toFixed(1)}k`;
|
|
19
|
-
if (count < 1000000)
|
|
20
|
-
return `${Math.round(count / 1000)}k`;
|
|
21
|
-
if (count < 10000000)
|
|
22
|
-
return `${(count / 1000000).toFixed(1)}M`;
|
|
23
|
-
return `${Math.round(count / 1000000)}M`;
|
|
24
|
-
}
|
|
25
|
-
export function render_footer_status_line(theme, width, left_items, right_item) {
|
|
26
|
-
const left = sanitize_status_text(left_items.join(' '));
|
|
27
|
-
const right = right_item ? sanitize_status_text(right_item) : '';
|
|
28
|
-
if (!left && !right)
|
|
29
|
-
return undefined;
|
|
30
|
-
if (!right) {
|
|
31
|
-
return truncateToWidth(theme.fg('dim', left), width, theme.fg('dim', '...'));
|
|
32
|
-
}
|
|
33
|
-
if (!left) {
|
|
34
|
-
const themed_right = theme.fg('dim', right);
|
|
35
|
-
const right_width = visibleWidth(themed_right);
|
|
36
|
-
return right_width >= width
|
|
37
|
-
? truncateToWidth(themed_right, width, theme.fg('dim', '...'))
|
|
38
|
-
: `${' '.repeat(width - right_width)}${themed_right}`;
|
|
39
|
-
}
|
|
40
|
-
const right_width = visibleWidth(right);
|
|
41
|
-
if (right_width >= width) {
|
|
42
|
-
return truncateToWidth(theme.fg('dim', right), width, theme.fg('dim', '...'));
|
|
43
|
-
}
|
|
44
|
-
const min_gap = 1;
|
|
45
|
-
const available_left = Math.max(0, width - right_width - min_gap);
|
|
46
|
-
const truncated_left = truncateToWidth(left, available_left, '...');
|
|
47
|
-
const left_width = visibleWidth(truncated_left);
|
|
48
|
-
const gap = Math.max(min_gap, width - left_width - right_width);
|
|
49
|
-
return (theme.fg('dim', truncated_left) +
|
|
50
|
-
' '.repeat(gap) +
|
|
51
|
-
theme.fg('dim', right));
|
|
52
|
-
}
|
|
53
|
-
const VALID_THINKING_LEVELS = new Set([
|
|
54
|
-
'off',
|
|
55
|
-
'minimal',
|
|
56
|
-
'low',
|
|
57
|
-
'medium',
|
|
58
|
-
'high',
|
|
59
|
-
'xhigh',
|
|
60
|
-
]);
|
|
61
|
-
function is_model_thinking_level(level) {
|
|
62
|
-
return VALID_THINKING_LEVELS.has(level);
|
|
63
|
-
}
|
|
64
|
-
export function get_default_footer_thinking_level(model) {
|
|
65
|
-
if (!model?.reasoning)
|
|
66
|
-
return 'off';
|
|
67
|
-
return clampThinkingLevel(model, 'medium');
|
|
68
|
-
}
|
|
69
|
-
export function get_current_thinking_level(ctx) {
|
|
70
|
-
const entries = ctx.sessionManager.getEntries();
|
|
71
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
72
|
-
const entry = entries[i];
|
|
73
|
-
if (entry.type === 'thinking_level_change' &&
|
|
74
|
-
typeof entry.thinkingLevel === 'string' &&
|
|
75
|
-
is_model_thinking_level(entry.thinkingLevel)) {
|
|
76
|
-
if (!ctx.model?.reasoning)
|
|
77
|
-
return 'off';
|
|
78
|
-
return getSupportedThinkingLevels(ctx.model).includes(entry.thinkingLevel)
|
|
79
|
-
? entry.thinkingLevel
|
|
80
|
-
: clampThinkingLevel(ctx.model, entry.thinkingLevel);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return get_default_footer_thinking_level(ctx.model);
|
|
84
|
-
}
|
|
85
|
-
function build_footer_model(ctx, footer_data, theme) {
|
|
86
|
-
let total_input = 0;
|
|
87
|
-
let total_output = 0;
|
|
88
|
-
let total_cache_read = 0;
|
|
89
|
-
let total_cache_write = 0;
|
|
90
|
-
let total_cost = 0;
|
|
91
|
-
for (const entry of ctx.sessionManager.getEntries()) {
|
|
92
|
-
if (entry.type === 'message' &&
|
|
93
|
-
entry.message.role === 'assistant') {
|
|
94
|
-
total_input += entry.message.usage.input;
|
|
95
|
-
total_output += entry.message.usage.output;
|
|
96
|
-
total_cache_read += entry.message.usage.cacheRead;
|
|
97
|
-
total_cache_write += entry.message.usage.cacheWrite;
|
|
98
|
-
total_cost += entry.message.usage.cost.total;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const context_usage = ctx.getContextUsage();
|
|
102
|
-
const context_window = context_usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
103
|
-
const context_percent_value = context_usage?.percent ?? 0;
|
|
104
|
-
const context_percent = context_usage?.percent !== null
|
|
105
|
-
? context_percent_value.toFixed(1)
|
|
106
|
-
: '?';
|
|
107
|
-
let pwd = ctx.cwd;
|
|
108
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
109
|
-
if (home && pwd.startsWith(home)) {
|
|
110
|
-
pwd = `~${pwd.slice(home.length)}`;
|
|
111
|
-
}
|
|
112
|
-
const branch = footer_data.getGitBranch();
|
|
113
|
-
if (branch) {
|
|
114
|
-
pwd = `${pwd} (${branch})`;
|
|
115
|
-
}
|
|
116
|
-
const session_name = ctx.sessionManager.getSessionName();
|
|
117
|
-
if (session_name) {
|
|
118
|
-
pwd = `${pwd} • ${session_name}`;
|
|
119
|
-
}
|
|
120
|
-
const stats_parts = [];
|
|
121
|
-
if (total_input)
|
|
122
|
-
stats_parts.push(`↑${format_token_count(total_input)}`);
|
|
123
|
-
if (total_output)
|
|
124
|
-
stats_parts.push(`↓${format_token_count(total_output)}`);
|
|
125
|
-
if (total_cache_read)
|
|
126
|
-
stats_parts.push(`R${format_token_count(total_cache_read)}`);
|
|
127
|
-
if (total_cache_write)
|
|
128
|
-
stats_parts.push(`W${format_token_count(total_cache_write)}`);
|
|
129
|
-
const using_subscription = ctx.model
|
|
130
|
-
? ctx.modelRegistry.isUsingOAuth(ctx.model)
|
|
131
|
-
: false;
|
|
132
|
-
if (total_cost || using_subscription) {
|
|
133
|
-
stats_parts.push(`$${total_cost.toFixed(3)}${using_subscription ? ' (sub)' : ''}`);
|
|
134
|
-
}
|
|
135
|
-
const context_percent_display = context_percent === '?'
|
|
136
|
-
? `?/${format_token_count(context_window)}`
|
|
137
|
-
: `${context_percent}%/${format_token_count(context_window)}`;
|
|
138
|
-
let context_percent_str = context_percent_display;
|
|
139
|
-
if (context_percent_value > 90) {
|
|
140
|
-
context_percent_str = theme.fg('error', context_percent_display);
|
|
141
|
-
}
|
|
142
|
-
else if (context_percent_value > 70) {
|
|
143
|
-
context_percent_str = theme.fg('warning', context_percent_display);
|
|
144
|
-
}
|
|
145
|
-
stats_parts.push(context_percent_str);
|
|
146
|
-
const model_name = ctx.model?.id || 'no-model';
|
|
147
|
-
const thinking_level = get_current_thinking_level(ctx);
|
|
148
|
-
let model_text = model_name;
|
|
149
|
-
if (ctx.model?.reasoning) {
|
|
150
|
-
model_text =
|
|
151
|
-
thinking_level === 'off'
|
|
152
|
-
? `${model_name} • thinking off`
|
|
153
|
-
: `${model_name} • ${thinking_level}`;
|
|
154
|
-
}
|
|
155
|
-
if (footer_data.getAvailableProviderCount() > 1 && ctx.model) {
|
|
156
|
-
model_text = `(${ctx.model.provider}) ${model_text}`;
|
|
157
|
-
}
|
|
158
|
-
const statuses = new Map(footer_data.getExtensionStatuses());
|
|
159
|
-
const preset_status = statuses.get('preset');
|
|
160
|
-
statuses.delete('preset');
|
|
161
|
-
return {
|
|
162
|
-
pwd,
|
|
163
|
-
stats_parts,
|
|
164
|
-
model_text,
|
|
165
|
-
statuses,
|
|
166
|
-
preset_status,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
function render_stats_line(model, theme, width) {
|
|
170
|
-
let stats_left = model.stats_parts.join(' ');
|
|
171
|
-
let stats_left_width = visibleWidth(stats_left);
|
|
172
|
-
if (stats_left_width > width) {
|
|
173
|
-
stats_left = truncateToWidth(stats_left, width, '...');
|
|
174
|
-
stats_left_width = visibleWidth(stats_left);
|
|
175
|
-
}
|
|
176
|
-
let right_side = model.model_text;
|
|
177
|
-
if (stats_left_width + 2 + visibleWidth(right_side) > width) {
|
|
178
|
-
right_side = right_side.replace(/^\([^)]*\) /, '');
|
|
179
|
-
}
|
|
180
|
-
const right_side_width = visibleWidth(right_side);
|
|
181
|
-
const total_needed = stats_left_width + 2 + right_side_width;
|
|
182
|
-
let stats_line;
|
|
183
|
-
if (total_needed <= width) {
|
|
184
|
-
const padding = ' '.repeat(width - stats_left_width - right_side_width);
|
|
185
|
-
stats_line = stats_left + padding + right_side;
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
const available_for_right = width - stats_left_width - 2;
|
|
189
|
-
if (available_for_right > 0) {
|
|
190
|
-
const truncated_right = truncateToWidth(right_side, available_for_right, '');
|
|
191
|
-
const truncated_right_width = visibleWidth(truncated_right);
|
|
192
|
-
const padding = ' '.repeat(Math.max(0, width - stats_left_width - truncated_right_width));
|
|
193
|
-
stats_line = stats_left + padding + truncated_right;
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
stats_line = stats_left;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const dim_stats_left = theme.fg('dim', stats_left);
|
|
200
|
-
const remainder = stats_line.slice(stats_left.length);
|
|
201
|
-
return dim_stats_left + theme.fg('dim', remainder);
|
|
202
|
-
}
|
|
203
|
-
function render_statuses(model, theme, width) {
|
|
204
|
-
const other_statuses = Array.from(model.statuses.entries())
|
|
205
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
206
|
-
.map(([key, text]) => `${key}:${sanitize_status_text(text)}`);
|
|
207
|
-
return render_footer_status_line(theme, width, other_statuses, model.preset_status);
|
|
208
|
-
}
|
|
209
|
-
function render_footer_lines(ctx, theme, footer_data, width) {
|
|
210
|
-
const model = build_footer_model(ctx, footer_data, theme);
|
|
211
|
-
const lines = [];
|
|
212
|
-
if (state.preset === 'minimal') {
|
|
213
|
-
lines.push(render_footer_status_line(theme, width, [model.pwd], model.model_text) ?? '');
|
|
214
|
-
return lines.filter(Boolean);
|
|
215
|
-
}
|
|
216
|
-
lines.push(truncateToWidth(theme.fg('dim', model.pwd), width, theme.fg('dim', '...')));
|
|
217
|
-
lines.push(render_stats_line(model, theme, width));
|
|
218
|
-
const status_line = render_statuses(model, theme, width);
|
|
219
|
-
if (status_line)
|
|
220
|
-
lines.push(status_line);
|
|
221
|
-
if (state.preset === 'power') {
|
|
222
|
-
const footer_mode = theme.fg('dim', `footer:${state.preset}`);
|
|
223
|
-
lines.push(truncateToWidth(footer_mode, width, theme.fg('dim', '...')));
|
|
224
|
-
}
|
|
225
|
-
return lines;
|
|
226
|
-
}
|
|
227
|
-
function install_footer(ctx) {
|
|
228
|
-
if (!ctx.hasUI)
|
|
229
|
-
return;
|
|
230
|
-
ctx.ui.setFooter((tui, theme, footer_data) => {
|
|
231
|
-
const unsubscribe = footer_data.onBranchChange(() => tui.requestRender());
|
|
232
|
-
return {
|
|
233
|
-
dispose: unsubscribe,
|
|
234
|
-
invalidate() { },
|
|
235
|
-
render(width) {
|
|
236
|
-
return render_footer_lines(ctx, theme, footer_data, width);
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
});
|
|
240
|
-
}
|
|
1
|
+
import { register_footer_command } from './commands/footer-command.js';
|
|
2
|
+
import { load_footer_state } from './config.js';
|
|
3
|
+
import { install_footer } from './extension/install.js';
|
|
4
|
+
export { get_current_thinking_level, get_default_footer_thinking_level, } from './model/thinking.js';
|
|
5
|
+
export { FOOTER_DENSITIES, FOOTER_PRESETS, FOOTER_TONES, FOOTER_WIDGETS, } from './presets/types.js';
|
|
6
|
+
export { FOOTER_RESEARCH_REFERENCES } from './reference/research.js';
|
|
7
|
+
export { render_footer_lines } from './render/footer-lines.js';
|
|
8
|
+
export { render_footer_status_line } from './render/status-line.js';
|
|
9
|
+
export { FOOTER_COLORS } from './theme/tokens.js';
|
|
10
|
+
const state = load_footer_state();
|
|
241
11
|
export default function footer_extension(pi) {
|
|
242
|
-
pi
|
|
243
|
-
description: 'Configure the Pi footer',
|
|
244
|
-
handler: async (_args, ctx) => {
|
|
245
|
-
const selected = await show_picker_modal(ctx, {
|
|
246
|
-
title: 'Footer preset',
|
|
247
|
-
items: PRESETS.map((preset) => ({
|
|
248
|
-
value: preset,
|
|
249
|
-
label: preset,
|
|
250
|
-
description: preset === state.preset
|
|
251
|
-
? 'Current footer preset'
|
|
252
|
-
: undefined,
|
|
253
|
-
})),
|
|
254
|
-
initial_index: PRESETS.indexOf(state.preset),
|
|
255
|
-
});
|
|
256
|
-
if (!selected)
|
|
257
|
-
return;
|
|
258
|
-
if (PRESETS.includes(selected)) {
|
|
259
|
-
state.preset = selected;
|
|
260
|
-
install_footer(ctx);
|
|
261
|
-
ctx.ui.notify(`Footer preset: ${state.preset}`, 'info');
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
});
|
|
12
|
+
register_footer_command(pi, state);
|
|
265
13
|
pi.on('session_start', async (_event, ctx) => {
|
|
266
|
-
install_footer(ctx);
|
|
14
|
+
install_footer(ctx, state);
|
|
267
15
|
});
|
|
268
16
|
pi.on('session_shutdown', async (_event, ctx) => {
|
|
269
17
|
ctx.ui.setFooter(undefined);
|