@d3ara1n/pi-editor-shell 0.4.0 → 0.6.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 +24 -9
- package/package.json +2 -2
- package/src/config.ts +44 -0
- package/src/index.ts +32 -15
package/README.md
CHANGED
|
@@ -1,30 +1,43 @@
|
|
|
1
1
|
# pi-editor-shell
|
|
2
2
|
|
|
3
|
-
Replaces pi's default editor and status bar with a unified rounded-corner shell drawn with box-drawing glyphs (`╭╮││╰╯`), with status info embedded in the border.
|
|
3
|
+
Replaces pi's default editor and status bar with a unified rounded-corner shell drawn with box-drawing glyphs (`╭╮││╰╯`), with status info embedded in the border. The frame and spinner use only standard Unicode; the six border icons are Nerd Font glyphs (overridable — see [Configuration](#configuration)).
|
|
4
4
|
|
|
5
5
|
## What shows up where
|
|
6
6
|
|
|
7
|
-
- **Top border** — ` model · thinking-level ` (left) + pinned extension statuses (right, via `pinnedStatus` config)
|
|
8
|
-
- **Bottom border** — ` ctx NN%/
|
|
7
|
+
- **Top border** — ` provider/model · thinking-level ` (left) + pinned extension statuses (right, via `pinnedStatus` config)
|
|
8
|
+
- **Bottom border** — ` ctx NN%/NNk|N.NM · ⚡ cacheRead (total) hitRate% ` (left) + ` ~/Projects (main +2 ~1) ` (right, shows git branch + dirty state when in a repo)
|
|
9
9
|
- **Below shell** — Auto-wrapping extension status line (all `setStatus` entries not pinned to the top)
|
|
10
10
|
- **Border color** follows pi's thinking-level / bash-mode indicator automatically.
|
|
11
11
|
|
|
12
|
-
All segments are re-read from live session state on every paint, so switching thinking level or burning context updates the frame on the next render with no extra wiring.
|
|
12
|
+
All segments are re-read from live session state on every paint, so switching thinking level or burning context updates the frame on the next render with no extra wiring. When the agent is active, the current phase spinner (thinking/outputting/toolcall/exec) replaces the model text in the top-left slot.
|
|
13
13
|
|
|
14
14
|
## Configuration
|
|
15
15
|
|
|
16
16
|
In `~/.pi/agent/settings.json` under the `editorShell` key:
|
|
17
17
|
|
|
18
|
-
```
|
|
18
|
+
```json
|
|
19
19
|
{
|
|
20
20
|
"editorShell": {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
"pinnedStatus": ["subagent", "access-denied"],
|
|
22
|
+
"icons": {
|
|
23
|
+
"model": "robot",
|
|
24
|
+
"cache": "\\uf0e7"
|
|
25
|
+
}
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
```
|
|
27
29
|
|
|
30
|
+
### Default icons
|
|
31
|
+
|
|
32
|
+
| Slot | Glyph | Nerd Font name |
|
|
33
|
+
|------|-------|----------------|
|
|
34
|
+
| `model` | `` | oct-cpu |
|
|
35
|
+
| `thinking` | `` | oct-light_bulb |
|
|
36
|
+
| `context` | `` | oct-cache |
|
|
37
|
+
| `cache` | `⚡` | oct-zap |
|
|
38
|
+
| `hitRate` | `` | fa-bullseye |
|
|
39
|
+
| `folder` | `` | fa-folder_open |
|
|
40
|
+
|
|
28
41
|
## Commands
|
|
29
42
|
|
|
30
43
|
| Command | Description |
|
|
@@ -35,6 +48,8 @@ In `~/.pi/agent/settings.json` under the `editorShell` key:
|
|
|
35
48
|
|
|
36
49
|
The default pi editor only draws a horizontal line above and below the input area (no side borders), and a separate footer renders the status bar. This extension replaces both — it wraps the built-in `CustomEditor`, renders it at `width - 2`, wraps every line with left/right glyphs, and embeds the status bar information (extension statuses) below the shell. The total width is unchanged. Border color follows pi's `borderColor` (which encodes thinking level / bash mode), so the shell stays semantically consistent and reacts to theme changes automatically.
|
|
37
50
|
|
|
51
|
+
When the autocomplete popup is open, the divider between editor content and popup items becomes a T-junction (`├─┤`) carrying the context/cwd info, closing everything into one connected card with two panes. Below `MIN_WIDTH` (20 columns), it falls back to the default editor.
|
|
52
|
+
|
|
38
53
|
## Installation
|
|
39
54
|
|
|
40
55
|
```bash
|
|
@@ -43,7 +58,7 @@ pi install npm:@d3ara1n/pi-editor-shell
|
|
|
43
58
|
|
|
44
59
|
Or add to `~/.pi/agent/settings.json`:
|
|
45
60
|
|
|
46
|
-
```
|
|
61
|
+
```json
|
|
47
62
|
{
|
|
48
63
|
"extensions": [
|
|
49
64
|
"/absolute/path/to/pi-extensions/packages/pi-editor-shell"
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d3ara1n/pi-editor-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Replaces pi's default editor and status bar with a unified rounded-corner shell
|
|
5
|
+
"description": "Replaces pi's default editor and status bar with a unified rounded-corner shell embedding status info in the border",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi",
|
package/src/config.ts
CHANGED
|
@@ -10,16 +10,55 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
|
|
13
|
+
/** Border icon slots that users can override. Each holds a single glyph
|
|
14
|
+
* (Nerd Font codepoint, Unicode symbol, or emoji) — whatever the user's
|
|
15
|
+
* terminal can render. Defaults live next to the renderer in index.ts. */
|
|
16
|
+
export interface EditorShellIcons {
|
|
17
|
+
model: string;
|
|
18
|
+
thinking: string;
|
|
19
|
+
context: string;
|
|
20
|
+
cache: string;
|
|
21
|
+
hitRate: string;
|
|
22
|
+
folder: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
export interface EditorShellConfig {
|
|
14
26
|
/**
|
|
15
27
|
* Status keys to pin to the shell's top-right corner.
|
|
16
28
|
* Only keys set via ctx.ui.setStatus() are eligible.
|
|
17
29
|
*/
|
|
18
30
|
pinnedStatus: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Per-slot border-icon overrides. Any subset; missing keys fall back to
|
|
33
|
+
* the built-in Nerd Font set. Values are raw characters — JSON `"\uf0e7"`
|
|
34
|
+
* for a Nerd Font glyph, or `"🤖"` for an emoji, etc.
|
|
35
|
+
*/
|
|
36
|
+
icons: Partial<EditorShellIcons>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ICON_KEYS: ReadonlyArray<keyof EditorShellIcons> = [
|
|
40
|
+
"model",
|
|
41
|
+
"thinking",
|
|
42
|
+
"context",
|
|
43
|
+
"cache",
|
|
44
|
+
"hitRate",
|
|
45
|
+
"folder",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** Keep only known icon slots with string values — silently drops typos and
|
|
49
|
+
* wrong-typed entries so a bad config never crashes the renderer. */
|
|
50
|
+
function filterIcons(obj: Record<string, unknown>): Partial<EditorShellIcons> {
|
|
51
|
+
const out: Partial<EditorShellIcons> = {};
|
|
52
|
+
for (const key of ICON_KEYS) {
|
|
53
|
+
const v = obj[key];
|
|
54
|
+
if (typeof v === "string") out[key] = v;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
19
57
|
}
|
|
20
58
|
|
|
21
59
|
export const DEFAULT_CONFIG: EditorShellConfig = {
|
|
22
60
|
pinnedStatus: [],
|
|
61
|
+
icons: {},
|
|
23
62
|
};
|
|
24
63
|
|
|
25
64
|
function getAgentDir(): string {
|
|
@@ -51,9 +90,14 @@ export function loadEditorShellConfig(cwd?: string): EditorShellConfig {
|
|
|
51
90
|
if (!raw) return { ...DEFAULT_CONFIG };
|
|
52
91
|
|
|
53
92
|
const pinned = raw.pinnedStatus;
|
|
93
|
+
const iconsRaw = raw.icons;
|
|
54
94
|
return {
|
|
55
95
|
pinnedStatus: Array.isArray(pinned)
|
|
56
96
|
? pinned.filter((k): k is string => typeof k === "string")
|
|
57
97
|
: DEFAULT_CONFIG.pinnedStatus,
|
|
98
|
+
icons:
|
|
99
|
+
iconsRaw && typeof iconsRaw === "object"
|
|
100
|
+
? filterIcons(iconsRaw as Record<string, unknown>)
|
|
101
|
+
: {},
|
|
58
102
|
};
|
|
59
103
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { CardEditor, type FrameProvider, type SpinnerPhase } from "./card-editor";
|
|
7
|
-
import { loadEditorShellConfig, type EditorShellConfig } from "./config";
|
|
7
|
+
import { loadEditorShellConfig, type EditorShellConfig, type EditorShellIcons } from "./config";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* pi-editor-shell — Replaces pi's default editor and status bar with a
|
|
@@ -47,15 +47,28 @@ function contextToken(pct: number | null | undefined): ThemeColor {
|
|
|
47
47
|
return "success";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
const
|
|
50
|
+
function trimFixed1(n: number): string {
|
|
51
|
+
const text = n.toFixed(1);
|
|
52
|
+
return text.endsWith(".0") ? text.slice(0, -2) : text;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatContextWindow(tokens: number): string {
|
|
56
|
+
if (tokens >= 1_000_000) return `${trimFixed1(tokens / 1_000_000)}M`;
|
|
57
|
+
return `${(tokens / 1_000).toFixed(0)}k`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Built-in icon set (Nerd Font). Users can override any subset via the
|
|
61
|
+
// `editorShell.icons` config — see config.ts. `cache` uses U+26A1, which
|
|
62
|
+
// Nerd Fonts maps `oct-zap` to directly (no dedicated glyph), so it is
|
|
63
|
+
// the same glyph in and out of a Nerd Font terminal.
|
|
64
|
+
const DEFAULT_ICONS: EditorShellIcons = {
|
|
52
65
|
model: "\uf4bc", // oct-cpu
|
|
53
|
-
thinking: "\uf400", // oct-
|
|
66
|
+
thinking: "\uf400", // oct-light_bulb
|
|
54
67
|
context: "\uf49b", // oct-cache
|
|
55
|
-
cache: "\u26a1", // ⚡ oct-zap
|
|
68
|
+
cache: "\u26a1", // ⚡ oct-zap (NF maps this codepoint to U+26A1)
|
|
56
69
|
hitRate: "\uf140", // fa-bullseye(靶心,缓存命中率)
|
|
57
|
-
folder: "\uf07c", // fa-
|
|
58
|
-
}
|
|
70
|
+
folder: "\uf07c", // fa-folder_open
|
|
71
|
+
};
|
|
59
72
|
|
|
60
73
|
/** Minimal inline types to read cache-read totals without importing the
|
|
61
74
|
* full pi-ai message union tree. */
|
|
@@ -192,7 +205,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
192
205
|
// The factory may run again when pi rebuilds the editor (model switch,
|
|
193
206
|
// reload, …), so always drive whichever instance is current.
|
|
194
207
|
let editor: CardEditor | undefined;
|
|
195
|
-
let config: EditorShellConfig = { pinnedStatus: [] };
|
|
208
|
+
let config: EditorShellConfig = { pinnedStatus: [], icons: {} };
|
|
209
|
+
// Resolved icons for the current session: built-in defaults merged with
|
|
210
|
+
// the user's overrides. Re-computed at session_start.
|
|
211
|
+
let icons: EditorShellIcons = { ...DEFAULT_ICONS };
|
|
196
212
|
// Shared footer-data ref — the provider (running inside CardEditor.render)
|
|
197
213
|
// reads it to resolve pinned status keys to their current text.
|
|
198
214
|
let footerSnap: FooterSnap | undefined;
|
|
@@ -241,6 +257,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
241
257
|
|
|
242
258
|
_cwd = ctx.cwd;
|
|
243
259
|
config = loadEditorShellConfig(ctx.cwd);
|
|
260
|
+
icons = { ...DEFAULT_ICONS, ...config.icons };
|
|
244
261
|
_cacheTotal = sumCacheRead(ctx);
|
|
245
262
|
_latestUsage = latestAssistantUsage(ctx);
|
|
246
263
|
refreshGitDirty(ctx.cwd, () => editor?.requestRender());
|
|
@@ -273,8 +290,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
273
290
|
const ctxWindow = usage?.contextWindow ?? ctx.model?.contextWindow;
|
|
274
291
|
const ctxText =
|
|
275
292
|
pct != null && ctxWindow
|
|
276
|
-
? `${pct.toFixed(1)}%/${(ctxWindow
|
|
277
|
-
: "?/??
|
|
293
|
+
? `${pct.toFixed(1)}%/${formatContextWindow(ctxWindow)}`
|
|
294
|
+
: "?/??";
|
|
278
295
|
|
|
279
296
|
// Cache-read tokens — per-turn figure first, session total in parens,
|
|
280
297
|
// then hit rate (pi's "CHxx%" formula). All refreshed at agent_end and
|
|
@@ -283,7 +300,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
283
300
|
const hitRate = cacheHitRate(_latestUsage);
|
|
284
301
|
const cachePart =
|
|
285
302
|
_cacheTotal > 0
|
|
286
|
-
? `${theme.fg("dim", " · ")}${theme.fg("warning", `${
|
|
303
|
+
? `${theme.fg("dim", " · ")}${theme.fg("warning", `${icons.cache} ${formatTokens(cacheReadNow)} (${formatTokens(_cacheTotal)})${hitRate != null ? ` ${icons.hitRate} ${hitRate.toFixed(1)}%` : ""}`)}`
|
|
287
304
|
: "";
|
|
288
305
|
|
|
289
306
|
// Git branch + dirty state — pi's format: ~/Projects (main).
|
|
@@ -292,16 +309,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
292
309
|
const dirty = branch ? gitDirtyDisplay() : "";
|
|
293
310
|
const cwdDisplay =
|
|
294
311
|
branch && branch !== "detached"
|
|
295
|
-
? `${
|
|
296
|
-
: `${
|
|
312
|
+
? `${icons.folder} ${cwdText} (${branch}${dirty})`
|
|
313
|
+
: `${icons.folder} ${cwdText}`;
|
|
297
314
|
|
|
298
315
|
// Model in accent; thinking label in its level token — same hue the
|
|
299
316
|
// border takes on, so switching levels visibly retints both together.
|
|
300
317
|
return {
|
|
301
|
-
topLeft: ` ${theme.fg("accent", `${
|
|
318
|
+
topLeft: ` ${theme.fg("accent", `${icons.model} ${model}`)}${theme.fg("dim", " · ")}${theme.fg(thinkingColor, `${icons.thinking} ${thinking}`)} `,
|
|
302
319
|
topRight: buildPinned(),
|
|
303
320
|
// Context in severity color; cwd stays muted so it never competes.
|
|
304
|
-
bottomLeft: ` ${theme.fg(contextToken(pct), `${
|
|
321
|
+
bottomLeft: ` ${theme.fg(contextToken(pct), `${icons.context} ${ctxText}`)}${cachePart} `,
|
|
305
322
|
bottomRight: theme.fg("muted", ` ${cwdDisplay} `),
|
|
306
323
|
};
|
|
307
324
|
};
|