@narumitw/pi-statusline 0.7.0 → 0.9.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 +4 -2
- package/package.json +1 -1
- package/src/statusline.ts +35 -17
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Use it to monitor model selection, thinking level, git branch, working directory
|
|
|
14
14
|
- Owns extension status icons through optional JSON config, including per-extension icon suppression with `""`.
|
|
15
15
|
- Warns when the same extension package is installed from multiple sources.
|
|
16
16
|
- Uses emoji-labeled segments for readability in both classic and Tokyo Night presets.
|
|
17
|
-
- Adapts to terminal width and
|
|
17
|
+
- Adapts to terminal width and wraps long extension status lines safely.
|
|
18
18
|
- Requires no configuration, with optional preset selection through `PI_STATUSLINE_PRESET`.
|
|
19
19
|
|
|
20
20
|
## 📦 Install
|
|
@@ -59,6 +59,7 @@ Extension statuses use built-in icons by status key. Override or suppress them i
|
|
|
59
59
|
{
|
|
60
60
|
"extensionStatusIcons": {
|
|
61
61
|
"caffeinate": "☕",
|
|
62
|
+
"github-pr": "🔎",
|
|
62
63
|
"goal": "🎯",
|
|
63
64
|
"pisync": "☁️",
|
|
64
65
|
"unknown-error-retry": "",
|
|
@@ -90,13 +91,14 @@ The default `tokyo-night` statusline uses a Starship-inspired `░▒▓` / `
|
|
|
90
91
|
- 💸 estimated cost.
|
|
91
92
|
- 🕒 clock.
|
|
92
93
|
|
|
93
|
-
Statuses from other extensions appear
|
|
94
|
+
Statuses from other extensions appear below the main statusline, use each preset's separator, and wrap onto additional footer lines when they exceed the terminal width.
|
|
94
95
|
|
|
95
96
|
`pi-statusline` is extension-agnostic: it consumes Pi's generic extension status API and does not import or depend on status-producing extensions.
|
|
96
97
|
|
|
97
98
|
Examples:
|
|
98
99
|
|
|
99
100
|
- `🎯 active` for `goal: active` using the built-in `goal` icon.
|
|
101
|
+
- `🔎 PR #123 checks passing` for `github-pr: PR #123 checks passing` using the built-in `github-pr` icon.
|
|
100
102
|
- `☕ display` when JSON config sets `"caffeinate": "☕"`.
|
|
101
103
|
- `receiving` when JSON config sets `"unknown-error-retry": ""`.
|
|
102
104
|
- `🔌 running` for an unknown extension status key with no configured icon.
|
package/package.json
CHANGED
package/src/statusline.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
Theme,
|
|
10
10
|
ThemeColor,
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
12
|
+
import { truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
13
13
|
import { classicExtensionSeparator, renderClassicStatusline } from "../presets/classic.js";
|
|
14
14
|
import { renderTokyoNightStatusline, tokyoNightExtensionSeparator } from "../presets/tokyo-night.js";
|
|
15
15
|
import type {
|
|
@@ -41,6 +41,7 @@ interface TokenTotals {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const STATUSLINE_KEY = "statusline";
|
|
44
|
+
const GITHUB_PR_KEY = "github-pr";
|
|
44
45
|
const SETTINGS_FILE = "pi-statusline-settings.json";
|
|
45
46
|
const DEFAULT_PRESET: StatuslinePresetName = "tokyo-night";
|
|
46
47
|
|
|
@@ -49,6 +50,7 @@ const DEFAULT_EXTENSION_STATUS_ICONS: Record<string, string> = {
|
|
|
49
50
|
"codex-usage": "📊",
|
|
50
51
|
caffeinate: "💊",
|
|
51
52
|
firecrawl: "🔥",
|
|
53
|
+
"github-pr": "🔎",
|
|
52
54
|
goal: "🎯",
|
|
53
55
|
lsp: "🧰",
|
|
54
56
|
"plan-mode": "📝",
|
|
@@ -112,14 +114,7 @@ export default function statusline(pi: ExtensionAPI) {
|
|
|
112
114
|
invalidate() {},
|
|
113
115
|
render(width: number): string[] {
|
|
114
116
|
const lines = [renderStatusline(width, ctx, footerData, theme, config, runtime)];
|
|
115
|
-
|
|
116
|
-
width,
|
|
117
|
-
footerData,
|
|
118
|
-
theme,
|
|
119
|
-
config,
|
|
120
|
-
runtime,
|
|
121
|
-
);
|
|
122
|
-
if (extensionStatusLine) lines.push(extensionStatusLine);
|
|
117
|
+
lines.push(...renderExtensionStatusline(width, footerData, theme, config, runtime));
|
|
123
118
|
return lines;
|
|
124
119
|
},
|
|
125
120
|
};
|
|
@@ -255,11 +250,9 @@ function renderExtensionStatusline(
|
|
|
255
250
|
theme: Theme,
|
|
256
251
|
config: StatuslineConfig,
|
|
257
252
|
runtime: RuntimeState,
|
|
258
|
-
): string
|
|
253
|
+
): string[] {
|
|
259
254
|
const status = formatExtensionStatuses(footerData.getExtensionStatuses(), theme, config, runtime);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return truncateToWidth(status, width, "");
|
|
255
|
+
return wrapExtensionStatusline(status, width);
|
|
263
256
|
}
|
|
264
257
|
|
|
265
258
|
function buildSegment(
|
|
@@ -284,8 +277,11 @@ function buildSegment(
|
|
|
284
277
|
thinkingColor(runtime.thinkingLevel),
|
|
285
278
|
"header",
|
|
286
279
|
);
|
|
287
|
-
case "branch":
|
|
288
|
-
|
|
280
|
+
case "branch": {
|
|
281
|
+
const branch = footerData.getGitBranch();
|
|
282
|
+
const pr = branch ? prLinkFromStatuses(footerData.getExtensionStatuses()) : undefined;
|
|
283
|
+
return segment(name, pr ? `🌿 ${branch} (${pr})` : `🌿 ${branch ?? "no-git"}`, color, "git");
|
|
284
|
+
}
|
|
289
285
|
case "cwd":
|
|
290
286
|
return segment(name, `📁 ${basename(ctx.cwd) || ctx.cwd}`, color, "directory");
|
|
291
287
|
case "tools":
|
|
@@ -381,6 +377,19 @@ export function formatToolActivity(runtime: RuntimeState): string {
|
|
|
381
377
|
return "💤 idle";
|
|
382
378
|
}
|
|
383
379
|
|
|
380
|
+
export function prLinkFromStatuses(statuses: ReadonlyMap<string, string>): string | undefined {
|
|
381
|
+
const value = statuses.get(GITHUB_PR_KEY);
|
|
382
|
+
if (!value) return undefined;
|
|
383
|
+
// Extract the OSC 8 hyperlink span (the clickable "#123"); skip non-PR states
|
|
384
|
+
// like "PR gh missing" that carry no link. github-pr emits exactly one link, so the
|
|
385
|
+
// first OSC 8 span is the PR number.
|
|
386
|
+
const open = value.indexOf("\x1b]8;;");
|
|
387
|
+
if (open === -1) return undefined;
|
|
388
|
+
const closeMarker = "\x1b]8;;\x07";
|
|
389
|
+
const close = value.indexOf(closeMarker, open + 1);
|
|
390
|
+
return close === -1 ? undefined : value.slice(open, close + closeMarker.length);
|
|
391
|
+
}
|
|
392
|
+
|
|
384
393
|
function formatExtensionStatuses(
|
|
385
394
|
statuses: ReadonlyMap<string, string>,
|
|
386
395
|
theme: Theme,
|
|
@@ -391,7 +400,11 @@ function formatExtensionStatuses(
|
|
|
391
400
|
const visibleStatuses = [
|
|
392
401
|
...formatDuplicateExtensionStatus(runtime, theme),
|
|
393
402
|
...[...statuses.entries()]
|
|
394
|
-
|
|
403
|
+
// github-pr is rendered inline in the branch segment, so skip it here to avoid duplication.
|
|
404
|
+
.filter(
|
|
405
|
+
([key, value]) =>
|
|
406
|
+
key !== STATUSLINE_KEY && key !== GITHUB_PR_KEY && value.trim().length > 0,
|
|
407
|
+
)
|
|
395
408
|
.map(([key, value]) => formatExtensionStatus(key, value, theme, config)),
|
|
396
409
|
].slice(0, 5);
|
|
397
410
|
|
|
@@ -405,7 +418,7 @@ export function formatExtensionStatus(
|
|
|
405
418
|
config: Pick<StatuslineConfig, "extensionStatusIcons">,
|
|
406
419
|
): string {
|
|
407
420
|
const status = splitExtensionStatusIcon(stripExtensionStatusPrefix(key, value));
|
|
408
|
-
const text =
|
|
421
|
+
const text = simplifyExtensionStatusText(status.text);
|
|
409
422
|
const color = extensionColor(key, value);
|
|
410
423
|
const textColor = color === "warning" ? "warning" : "muted";
|
|
411
424
|
const icon = extensionStatusIcon(key, status.icon, config.extensionStatusIcons);
|
|
@@ -422,6 +435,11 @@ function extensionStatusIcon(
|
|
|
422
435
|
return leadingIcon ?? DEFAULT_EXTENSION_STATUS_ICONS[key] ?? "🔌";
|
|
423
436
|
}
|
|
424
437
|
|
|
438
|
+
export function wrapExtensionStatusline(status: string, width: number): string[] {
|
|
439
|
+
if (!status || width <= 0) return [];
|
|
440
|
+
return wrapTextWithAnsi(status, width);
|
|
441
|
+
}
|
|
442
|
+
|
|
425
443
|
function formatDuplicateExtensionStatus(runtime: RuntimeState, theme: Theme): string[] {
|
|
426
444
|
if (runtime.duplicateExtensions.length === 0) return [];
|
|
427
445
|
const names = runtime.duplicateExtensions.slice(0, 2).join(", ");
|