@vanillagreen/pi-qol 1.0.0 → 1.0.2
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 +17 -0
- package/extensions/qol.ts +36 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,23 @@
|
|
|
7
7
|
|
|
8
8
|
Quality-of-life extension for Pi.
|
|
9
9
|
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Via [npm](https://www.npmjs.com/package/@vanillagreen/pi-qol):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pi install npm:@vanillagreen/pi-qol
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Via [vstack](https://github.com/vanillagreencom/vstack):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cargo install --git https://github.com/vanillagreencom/vstack.git vstack
|
|
22
|
+
vstack add vanillagreencom/vstack --pi-extension pi-qol --harness pi -y
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Restart Pi after installation.
|
|
26
|
+
|
|
10
27
|
## What it provides
|
|
11
28
|
|
|
12
29
|
- Compact statusline and `π` prompt editor: shows repo/project, branch/dirty state, model, thinking level, context window size, and remaining context percent while replacing Pi's default footer/editor chrome.
|
package/extensions/qol.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
10
10
|
|
|
11
11
|
const INSTALL_SYMBOL = Symbol.for("vstack.pi-qol.installed");
|
|
12
|
+
const CONFIG_ID = "@vanillagreen/pi-qol";
|
|
12
13
|
const STATUS_KEY = "qol-attachments";
|
|
13
14
|
const SESSION_SEARCH_STATUS_KEY = "qol-session-search";
|
|
14
15
|
const SESSION_SEARCH_CONTEXT_TYPE = "qol-session-context";
|
|
@@ -44,6 +45,7 @@ const DEFAULT_SESSION_SEARCH_SHORTCUT = "f3";
|
|
|
44
45
|
const DEFAULT_SESSION_SEARCH_SUMMARY_INPUT_CHARS = 180_000;
|
|
45
46
|
const DEFAULT_SESSION_SEARCH_SUMMARY_MAX_TOKENS = 4096;
|
|
46
47
|
const DEFAULT_SESSION_SEARCH_CACHE_TTL_SECONDS = 0;
|
|
48
|
+
const SESSION_SEARCH_OVERLAY_HEIGHT_RATIO = 0.9;
|
|
47
49
|
const DEFAULT_AUTO_RENAME_MODEL = "openai-codex/gpt-5.4-mini";
|
|
48
50
|
const DEFAULT_AUTO_RENAME_FALLBACK_MODEL = "current";
|
|
49
51
|
const DEFAULT_AUTO_RENAME_INPUT_CHARS = 2000;
|
|
@@ -131,7 +133,7 @@ function readVstackConfig(cwd?: string): VstackConfig {
|
|
|
131
133
|
if (!existsSync(path)) continue;
|
|
132
134
|
try {
|
|
133
135
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
134
|
-
const config = parsed?.vstack?.extensionManager?.config?.[
|
|
136
|
+
const config = parsed?.vstack?.extensionManager?.config?.[CONFIG_ID];
|
|
135
137
|
if (config && typeof config === "object" && !Array.isArray(config)) Object.assign(merged, config);
|
|
136
138
|
} catch {
|
|
137
139
|
// Ignore malformed optional manager config.
|
|
@@ -3071,7 +3073,7 @@ class QolSessionSearchComponent {
|
|
|
3071
3073
|
|
|
3072
3074
|
constructor(
|
|
3073
3075
|
private readonly done: (action: QolSessionPaletteAction) => void,
|
|
3074
|
-
private readonly tui: { requestRender(): void },
|
|
3076
|
+
private readonly tui: { requestRender(): void; terminal?: { rows?: number } },
|
|
3075
3077
|
private readonly theme: Theme,
|
|
3076
3078
|
private readonly sessions: QolSessionSearchSession[],
|
|
3077
3079
|
private readonly cwd: string,
|
|
@@ -3089,6 +3091,30 @@ class QolSessionSearchComponent {
|
|
|
3089
3091
|
|
|
3090
3092
|
invalidate(): void {}
|
|
3091
3093
|
|
|
3094
|
+
private maxOverlayRows(): number {
|
|
3095
|
+
const terminalRows = Number(this.tui.terminal?.rows ?? process.stdout.rows ?? 30);
|
|
3096
|
+
const safeRows = Number.isFinite(terminalRows) && terminalRows > 0 ? terminalRows : 30;
|
|
3097
|
+
return Math.max(8, Math.floor(safeRows * SESSION_SEARCH_OVERLAY_HEIGHT_RATIO));
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
private searchMaxVisibleRows(): number {
|
|
3101
|
+
const configured = Math.max(1, Math.floor(settingNumber("sessionSearch.maxVisible", 8, this.cwd)));
|
|
3102
|
+
// Search results render as 3 content rows plus a divider between rows.
|
|
3103
|
+
// The remaining chrome is title, search box, help text, scroll status,
|
|
3104
|
+
// footer, and frame. Keep the rendered line count within overlay maxHeight
|
|
3105
|
+
// so Pi does not clip the bottom of the popup on short terminals.
|
|
3106
|
+
const responsive = Math.max(1, Math.floor((this.maxOverlayRows() - 12) / 4));
|
|
3107
|
+
return Math.max(1, Math.min(configured, responsive));
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
private messageMaxVisibleRows(): number {
|
|
3111
|
+
const configured = Math.max(1, Math.floor(settingNumber("sessionSearch.messageMaxVisible", 12, this.cwd)));
|
|
3112
|
+
// Message rows are one line each; reserve space for session metadata,
|
|
3113
|
+
// optional scroll status, footer, and frame.
|
|
3114
|
+
const responsive = Math.max(1, this.maxOverlayRows() - 14);
|
|
3115
|
+
return Math.max(1, Math.min(configured, responsive));
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3092
3118
|
render(width: number): string[] {
|
|
3093
3119
|
const configured = Math.max(70, Math.floor(settingNumber("sessionSearch.overlayWidth", 104, this.cwd)));
|
|
3094
3120
|
const renderWidth = Math.min(Math.max(48, width), configured);
|
|
@@ -3165,11 +3191,11 @@ class QolSessionSearchComponent {
|
|
|
3165
3191
|
return;
|
|
3166
3192
|
}
|
|
3167
3193
|
if (matchesKey(data, "-") || matchesKey(data, "pageup")) {
|
|
3168
|
-
state.selected = Math.max(0, state.selected -
|
|
3194
|
+
state.selected = Math.max(0, state.selected - this.searchMaxVisibleRows());
|
|
3169
3195
|
return;
|
|
3170
3196
|
}
|
|
3171
3197
|
if (matchesKey(data, "=") || matchesKey(data, "pagedown")) {
|
|
3172
|
-
state.selected = Math.min(state.results.length - 1, state.selected +
|
|
3198
|
+
state.selected = Math.min(state.results.length - 1, state.selected + this.searchMaxVisibleRows());
|
|
3173
3199
|
return;
|
|
3174
3200
|
}
|
|
3175
3201
|
if (matchesKey(data, "left")) {
|
|
@@ -3239,11 +3265,11 @@ class QolSessionSearchComponent {
|
|
|
3239
3265
|
return;
|
|
3240
3266
|
}
|
|
3241
3267
|
if (matchesKey(data, "-") || matchesKey(data, "pageup")) {
|
|
3242
|
-
state.selected = Math.max(0, state.selected -
|
|
3268
|
+
state.selected = Math.max(0, state.selected - this.messageMaxVisibleRows());
|
|
3243
3269
|
return;
|
|
3244
3270
|
}
|
|
3245
3271
|
if (matchesKey(data, "=") || matchesKey(data, "pagedown")) {
|
|
3246
|
-
state.selected = Math.min(state.messages.length - 1, state.selected +
|
|
3272
|
+
state.selected = Math.min(state.messages.length - 1, state.selected + this.messageMaxVisibleRows());
|
|
3247
3273
|
return;
|
|
3248
3274
|
}
|
|
3249
3275
|
if (matchesKey(data, "home") || matchesKey(data, "ctrl+a")) {
|
|
@@ -3365,7 +3391,7 @@ class QolSessionSearchComponent {
|
|
|
3365
3391
|
if (state.results.length === 0) {
|
|
3366
3392
|
lines.push(row(muted(state.query.trim() ? "No sessions match your search" : "No sessions found")), empty());
|
|
3367
3393
|
} else {
|
|
3368
|
-
const maxVisible =
|
|
3394
|
+
const maxVisible = this.searchMaxVisibleRows();
|
|
3369
3395
|
const start = Math.max(0, Math.min(state.selected - Math.floor(maxVisible / 2), state.results.length - maxVisible));
|
|
3370
3396
|
const end = Math.min(start + maxVisible, state.results.length);
|
|
3371
3397
|
const rowBudget = Math.max(10, inner);
|
|
@@ -3394,7 +3420,7 @@ class QolSessionSearchComponent {
|
|
|
3394
3420
|
if (state.results.length > maxVisible) lines.push(empty(), row(dim(`${state.selected + 1}/${state.results.length} ${state.query.trim() ? "matches" : "recent sessions"}`)));
|
|
3395
3421
|
}
|
|
3396
3422
|
lines.push(divider(), empty());
|
|
3397
|
-
lines.push(row(`${ansiYellow("
|
|
3423
|
+
lines.push(row(`${ansiYellow("-/=")} ${dim("page")} ${ansiYellow("enter")} ${dim("prompts")} ${ansiYellow("ctrl+u")} ${dim("clear")} ${ansiYellow("esc")} ${dim("close")}`));
|
|
3398
3424
|
lines.push(bottom());
|
|
3399
3425
|
return lines;
|
|
3400
3426
|
}
|
|
@@ -3419,7 +3445,7 @@ class QolSessionSearchComponent {
|
|
|
3419
3445
|
lines.push(row(dim(`${state.messages.length} user prompt${state.messages.length === 1 ? "" : "s"} · enter opens prompt actions · r resumes latest session state`)));
|
|
3420
3446
|
lines.push(empty(), divider(), empty());
|
|
3421
3447
|
|
|
3422
|
-
const maxVisible =
|
|
3448
|
+
const maxVisible = this.messageMaxVisibleRows();
|
|
3423
3449
|
const start = Math.max(0, Math.min(state.selected - Math.floor(maxVisible / 2), state.messages.length - maxVisible));
|
|
3424
3450
|
const end = Math.min(start + maxVisible, state.messages.length);
|
|
3425
3451
|
for (let i = start; i < end; i++) {
|
|
@@ -3434,7 +3460,7 @@ class QolSessionSearchComponent {
|
|
|
3434
3460
|
}
|
|
3435
3461
|
if (state.messages.length > maxVisible) lines.push(empty(), row(dim(`${state.selected + 1}/${state.messages.length} user prompts`)));
|
|
3436
3462
|
lines.push(divider(), empty());
|
|
3437
|
-
lines.push(row(`${ansiYellow("
|
|
3463
|
+
lines.push(row(`${ansiYellow("-/=")} ${dim("page")} ${ansiYellow("enter")} ${dim("prompt actions")} ${ansiYellow("r")} ${dim("resume session")} ${ansiYellow("esc")} ${dim("sessions")}`));
|
|
3438
3464
|
lines.push(bottom());
|
|
3439
3465
|
return lines;
|
|
3440
3466
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-qol",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|