@narumitw/pi-statusline 0.21.0 → 0.25.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 +139 -91
- package/package.json +3 -4
- package/src/commands.ts +118 -0
- package/src/extension-status.ts +29 -32
- package/src/git-status.ts +11 -3
- package/src/render.ts +103 -65
- package/src/settings.ts +492 -109
- package/src/statusline.ts +51 -19
- package/src/tokyo-night.ts +180 -0
- package/src/types.ts +67 -0
- package/presets/classic.ts +0 -55
- package/presets/tokyo-night.ts +0 -84
- package/presets/types.ts +0 -39
- /package/{presets → src}/ansi.ts +0 -0
package/README.md
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
# ✨ pi-statusline —
|
|
1
|
+
# ✨ pi-statusline — Configurable Tokyo Night Footer for Pi
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@narumitw/pi-statusline) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
`@narumitw/pi-statusline` is a native [Pi coding agent](https://pi.dev) extension that replaces Pi's footer with a
|
|
6
|
-
|
|
7
|
-
Use it to monitor provider, model selection, thinking level, git branch, working directory, active tools, context usage, token totals, estimated cost, time, and statuses from other Pi extensions.
|
|
5
|
+
`@narumitw/pi-statusline` is a native [Pi coding agent](https://pi.dev) extension that replaces Pi's footer with a configurable Tokyo Night powerline statusline.
|
|
8
6
|
|
|
9
7
|
## ✨ Features
|
|
10
8
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- Requires no configuration, with optional preset selection through `PI_STATUSLINE_PRESET`.
|
|
9
|
+
- Shows provider, model, thinking, directory, Git/PR state, tools, context, tokens, cost, time, and extension statuses.
|
|
10
|
+
- Uses one Starship-inspired `░▒▓` / `` Tokyo Night layout.
|
|
11
|
+
- Configures segment order, visibility, multiline breaks, surrounding text, palette, density, separators, and extension icons through JSON.
|
|
12
|
+
- Creates a complete editable default configuration on first session start.
|
|
13
|
+
- Applies validated `/statusline settings` edits immediately after an atomic save.
|
|
14
|
+
- Caches Git state outside footer rendering and guards stale session results.
|
|
15
|
+
- Wraps extension statuses safely at narrow terminal widths.
|
|
19
16
|
|
|
20
17
|
## 📦 Install
|
|
21
18
|
|
|
@@ -23,137 +20,188 @@ Use it to monitor provider, model selection, thinking level, git branch, working
|
|
|
23
20
|
pi install npm:@narumitw/pi-statusline
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
Try
|
|
23
|
+
Try it once or from a checkout:
|
|
27
24
|
|
|
28
25
|
```bash
|
|
29
26
|
pi -e npm:@narumitw/pi-statusline
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Try this package locally from the repository root:
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
27
|
pi -e ./extensions/pi-statusline
|
|
36
28
|
```
|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
Do not enable this together with `@narumitw/pi-starship`: both extensions own Pi's footer. Use `pi-starship` instead when you need full Starship-style format/style grammar.
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
## ⚙️ Configuration
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
PI_STATUSLINE_PRESET=tokyo-night pi
|
|
44
|
-
PI_STATUSLINE_PRESET=classic pi
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Supported presets:
|
|
34
|
+
The only configuration source is:
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
-
|
|
36
|
+
```text
|
|
37
|
+
<getAgentDir()>/pi-statusline.json
|
|
38
|
+
```
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
On first session start, the extension atomically creates the complete default document. It never overwrites an existing malformed or unreadable file. A valid legacy `pi-statusline-settings.json` is migrated by preserving its original bytes; the canonical filename wins when both exist.
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
There are no project overrides or environment-variable overrides.
|
|
55
43
|
|
|
56
|
-
|
|
44
|
+
### Default JSON
|
|
57
45
|
|
|
58
46
|
```json
|
|
59
47
|
{
|
|
48
|
+
"palette": "tokyo-night",
|
|
49
|
+
"density": "compact",
|
|
50
|
+
"separator": "none",
|
|
51
|
+
"segments": [
|
|
52
|
+
"brand",
|
|
53
|
+
"provider",
|
|
54
|
+
"model",
|
|
55
|
+
"thinking",
|
|
56
|
+
"cwd",
|
|
57
|
+
"branch",
|
|
58
|
+
"tools",
|
|
59
|
+
"context",
|
|
60
|
+
"tokens",
|
|
61
|
+
"cost",
|
|
62
|
+
"time"
|
|
63
|
+
],
|
|
64
|
+
"segmentText": {
|
|
65
|
+
"brand": { "prefix": "", "suffix": "" },
|
|
66
|
+
"provider": { "prefix": "🔌 ", "suffix": "" },
|
|
67
|
+
"model": { "prefix": "🤖 ", "suffix": "" },
|
|
68
|
+
"thinking": { "prefix": "🧠 ", "suffix": "" },
|
|
69
|
+
"cwd": { "prefix": "📁 ", "suffix": "" },
|
|
70
|
+
"branch": { "prefix": "🌿 ", "suffix": "" },
|
|
71
|
+
"tools": { "prefix": "", "suffix": "" },
|
|
72
|
+
"context": { "prefix": "🪟 ctx ", "suffix": "" },
|
|
73
|
+
"tokens": { "prefix": "🔢 ", "suffix": "" },
|
|
74
|
+
"cost": { "prefix": "💸 $", "suffix": "" },
|
|
75
|
+
"time": { "prefix": "🕒 ", "suffix": "" },
|
|
76
|
+
"turn": { "prefix": "🔁 #", "suffix": "" }
|
|
77
|
+
},
|
|
60
78
|
"extensionStatusIcons": {
|
|
61
|
-
"
|
|
79
|
+
"chrome-devtools": "🌐",
|
|
80
|
+
"codex-usage": "📊",
|
|
81
|
+
"usage": "📊",
|
|
82
|
+
"caffeinate": "💊",
|
|
83
|
+
"firecrawl": "🔥",
|
|
62
84
|
"github-pr": "🔎",
|
|
63
85
|
"goal": "🎯",
|
|
64
|
-
"
|
|
65
|
-
"unknown-error-retry": "",
|
|
86
|
+
"lsp": "🧰",
|
|
66
87
|
"plan-mode": "📝",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
88
|
+
"pisync": "🔄",
|
|
89
|
+
"subagents": "🧑🤝🧑",
|
|
90
|
+
"unknown-error-retry": "🔁"
|
|
69
91
|
}
|
|
70
92
|
}
|
|
71
93
|
```
|
|
72
94
|
|
|
73
|
-
|
|
95
|
+
All fields are optional in an existing document. Missing fields use defaults.
|
|
96
|
+
|
|
97
|
+
### Appearance
|
|
98
|
+
|
|
99
|
+
- `palette`: `tokyo-night`, `ocean`, `sunset`, `forest`, `candy`, `neon`, or `mono`.
|
|
100
|
+
- `density`: `compact` or `cozy`.
|
|
101
|
+
- `separator`: `none`, `dot`, `bar`, `powerline`, or `round`.
|
|
102
|
+
|
|
103
|
+
The separator applies only between adjacent segments in the same color block. Color-block transitions always use ``. Extension statuses remain on separate wrapped lines with their own palette-colored separator.
|
|
104
|
+
|
|
105
|
+
### Segments
|
|
106
|
+
|
|
107
|
+
`segments` is an ordered list containing:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
brand provider model thinking cwd branch tools context tokens cost time turn line_break
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The array controls visibility and actual rendering order. Data segments must remain unique. The special `line_break` segment starts another footer row and may repeat when another segment separates each occurrence; consecutive `line_break` entries are invalid. Each row receives its own powerline start and end. `line_break` has no `segmentText` entry.
|
|
114
|
+
|
|
115
|
+
An empty array hides the main powerline while still allowing extension statuses to render. `turn` is available but omitted by default.
|
|
116
|
+
|
|
117
|
+
Example multiline layout:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"segments": ["model", "line_break", "cwd", "line_break", "branch"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This is valid because the `line_break` entries are separated. `["model", "line_break", "line_break", "cwd"]` is invalid.
|
|
126
|
+
|
|
127
|
+
### Segment text
|
|
74
128
|
|
|
75
|
-
|
|
76
|
-
- Installed extension id: for installed packages, use the package name/source such as `"@vendor/pi-foo"`, `"npm:@vendor/pi-foo@1.2.3"`, `"pi-foo"`, or the derived key `"foo"`.
|
|
77
|
-
- Namespaced status keys: package `@vendor/pi-foo` can match `foo`, `foo:server`, and `foo/server`, but not fuzzy matches like `foobar`.
|
|
78
|
-
- Missing key: use the built-in icon, or `🔌` for an unknown status key.
|
|
79
|
-
- String value: use that string as the icon.
|
|
80
|
-
- Empty string: show the status text without an icon.
|
|
81
|
-
- If multiple installed packages derive the same key, use the exact status key to disambiguate.
|
|
82
|
-
- `PI_STATUSLINE_PRESET` remains the only preset setting; this JSON file only controls extension status icons.
|
|
129
|
+
Each visible segment renders as:
|
|
83
130
|
|
|
84
|
-
|
|
131
|
+
```text
|
|
132
|
+
prefix + Pi-owned dynamic value + suffix
|
|
133
|
+
```
|
|
85
134
|
|
|
86
|
-
|
|
135
|
+
Override either string independently:
|
|
87
136
|
|
|
88
|
-
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"segmentText": {
|
|
140
|
+
"provider": { "prefix": "Provider: " },
|
|
141
|
+
"context": { "prefix": "[", "suffix": "]" },
|
|
142
|
+
"cost": { "prefix": "Cost $", "suffix": " USD" }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
89
146
|
|
|
90
|
-
-
|
|
91
|
-
- 🔌 current provider.
|
|
92
|
-
- 🤖 current model.
|
|
93
|
-
- 🧠 thinking level.
|
|
94
|
-
- 📁 current project directory.
|
|
95
|
-
- 🌿 git branch, with compact git status tokens when dirty or ahead/behind.
|
|
96
|
-
- ⚙ active or last tool.
|
|
97
|
-
- 🪟 context usage percentage.
|
|
98
|
-
- 🔢 token totals.
|
|
99
|
-
- 💸 estimated cost.
|
|
100
|
-
- 🕒 clock.
|
|
147
|
+
Prefix and suffix values must be single-line text without terminal control characters; use the `line_break` segment for additional rows.
|
|
101
148
|
|
|
102
|
-
|
|
149
|
+
This structured model intentionally does not provide variables or a format language. Dynamic Git, PR, activity, usage, token, and cost formatting remains owned by the extension.
|
|
103
150
|
|
|
104
|
-
|
|
151
|
+
### Extension status icons
|
|
105
152
|
|
|
106
|
-
`
|
|
153
|
+
`extensionStatusIcons` preserves these rules:
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
- An exact status key wins, such as `goal` or `foo:server`.
|
|
156
|
+
- Installed package aliases such as `@vendor/pi-foo`, `npm:@vendor/pi-foo@1.2.3`, `pi-foo`, or `foo` can configure namespaced statuses.
|
|
157
|
+
- An empty string hides the icon but keeps the status text.
|
|
158
|
+
- A missing key uses the built-in icon or `🔌` for an unknown key.
|
|
159
|
+
- Ambiguous package aliases require an exact status key.
|
|
109
160
|
|
|
110
|
-
|
|
111
|
-
- `🔎 PR #123 checks passing` for `github-pr: PR #123 checks passing` using the built-in `github-pr` icon.
|
|
112
|
-
- `☕ display` when JSON config sets `"caffeinate": "☕"`.
|
|
113
|
-
- `🧪 running` for third-party status `foo:server` when an installed package is named `@vendor/pi-foo` and JSON config sets `"@vendor/pi-foo": "🧪"`.
|
|
114
|
-
- `receiving` when JSON config sets `"unknown-error-retry": ""`.
|
|
115
|
-
- `🔌 running` for an unknown extension status key with no configured icon.
|
|
116
|
-
- `⚠️ dup biome-lsp` when local and npm installs register the same extension.
|
|
161
|
+
Statuses from other extensions appear below the main powerline. The linked GitHub PR status is hidden from that line when the branch segment already renders it.
|
|
117
162
|
|
|
118
|
-
##
|
|
163
|
+
## 💬 Commands
|
|
119
164
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
165
|
+
| Command | Purpose |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| `/statusline settings` | Edit raw JSON in TUI, validate, atomically save, and apply immediately |
|
|
168
|
+
| `/statusline status` | Show settings path/source, effective appearance, segments, and diagnostics |
|
|
169
|
+
| `/statusline help` | Show command and schema guidance |
|
|
170
|
+
|
|
171
|
+
Invalid or cancelled edits leave both the previous file and effective runtime configuration unchanged. The editor is TUI-only; status and help are safe in TUI, print, JSON, and RPC modes.
|
|
172
|
+
|
|
173
|
+
## 🌿 Git and activity details
|
|
174
|
+
|
|
175
|
+
Git status tokens are hidden for clean repositories. When present, they mean `⇡` ahead, `⇣` behind, `+` staged, `~` modified/deleted, `?` untracked, and `!` conflicts.
|
|
176
|
+
|
|
177
|
+
The tools segment distinguishes active tools, streaming/thinking, the last completed tool, and idle state. Parallel calls are summarized without running subprocesses during footer rendering.
|
|
125
178
|
|
|
126
179
|
## 🗂️ Package layout
|
|
127
180
|
|
|
128
|
-
```
|
|
181
|
+
```text
|
|
129
182
|
extensions/pi-statusline/
|
|
130
183
|
├── src/
|
|
131
|
-
│ ├── statusline.ts # Pi entrypoint and watcher lifecycle
|
|
132
|
-
│ └── *.ts # Package-local git, extension status, settings, and render modules
|
|
133
|
-
├── presets/
|
|
134
184
|
│ ├── ansi.ts
|
|
135
|
-
│ ├──
|
|
185
|
+
│ ├── commands.ts
|
|
186
|
+
│ ├── extension-status.ts
|
|
187
|
+
│ ├── git-status.ts
|
|
188
|
+
│ ├── render.ts
|
|
189
|
+
│ ├── settings.ts
|
|
190
|
+
│ ├── statusline.ts
|
|
136
191
|
│ ├── tokyo-night.ts
|
|
137
192
|
│ └── types.ts
|
|
193
|
+
├── test/
|
|
138
194
|
├── README.md
|
|
139
195
|
├── LICENSE
|
|
140
196
|
├── tsconfig.json
|
|
141
197
|
└── package.json
|
|
142
198
|
```
|
|
143
199
|
|
|
144
|
-
Only `statusline.ts` is a Pi entrypoint
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"pi": {
|
|
149
|
-
"extensions": ["./src/statusline.ts"]
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
200
|
+
Only `src/statusline.ts` is a Pi entrypoint. Other modules are package-internal.
|
|
153
201
|
|
|
154
202
|
## 🔎 Keywords
|
|
155
203
|
|
|
156
|
-
Pi extension, Pi coding agent, statusline,
|
|
204
|
+
Pi extension, Pi coding agent, configurable statusline, Tokyo Night, terminal footer, token usage, context window, model status, TypeScript Pi package.
|
|
157
205
|
|
|
158
206
|
## 📄 License
|
|
159
207
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-statusline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Pi extension that replaces the footer with an information-rich statusline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
"src",
|
|
17
|
-
"presets",
|
|
18
17
|
"README.md",
|
|
19
18
|
"LICENSE"
|
|
20
19
|
],
|
|
@@ -24,8 +23,8 @@
|
|
|
24
23
|
]
|
|
25
24
|
},
|
|
26
25
|
"scripts": {
|
|
27
|
-
"check": "biome check . && npm run typecheck",
|
|
28
|
-
"format": "biome check --write .",
|
|
26
|
+
"check": "biome check --vcs-use-ignore-file=false src test package.json tsconfig.json README.md && npm run typecheck",
|
|
27
|
+
"format": "biome check --write --vcs-use-ignore-file=false src test package.json tsconfig.json README.md",
|
|
29
28
|
"typecheck": "tsc --noEmit"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AutocompleteItem } from "@earendil-works/pi-tui";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_STATUSLINE_DOCUMENT,
|
|
5
|
+
type LoadedStatuslineSettings,
|
|
6
|
+
saveStatuslineSettingsDocument,
|
|
7
|
+
} from "./settings.js";
|
|
8
|
+
|
|
9
|
+
const SUBCOMMANDS: AutocompleteItem[] = [
|
|
10
|
+
{ value: "settings", label: "settings", description: "Edit pi-statusline.json" },
|
|
11
|
+
{ value: "status", label: "status", description: "Show effective statusline settings" },
|
|
12
|
+
{ value: "help", label: "help", description: "Show configuration help" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
export interface StatuslineCommandOptions {
|
|
16
|
+
settingsPath: string;
|
|
17
|
+
getLoaded(): LoadedStatuslineSettings;
|
|
18
|
+
apply(loaded: LoadedStatuslineSettings, ctx: ExtensionCommandContext): void;
|
|
19
|
+
save?: (settingsPath: string, rawDocument: string) => LoadedStatuslineSettings;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerStatuslineCommand(pi: ExtensionAPI, options: StatuslineCommandOptions) {
|
|
23
|
+
pi.registerCommand("statusline", {
|
|
24
|
+
description: "Edit or inspect the Tokyo Night footer settings",
|
|
25
|
+
getArgumentCompletions(prefix: string): AutocompleteItem[] | null {
|
|
26
|
+
const normalized = prefix.trim().toLowerCase();
|
|
27
|
+
const matches = SUBCOMMANDS.filter((item) => item.value.startsWith(normalized));
|
|
28
|
+
return matches.length > 0 ? matches : null;
|
|
29
|
+
},
|
|
30
|
+
handler: async (args, ctx) => {
|
|
31
|
+
const subcommand = args.trim().split(/\s+/u)[0]?.toLowerCase() || "help";
|
|
32
|
+
switch (subcommand) {
|
|
33
|
+
case "settings":
|
|
34
|
+
await editSettings(ctx, options);
|
|
35
|
+
return;
|
|
36
|
+
case "status":
|
|
37
|
+
showStatus(ctx, options);
|
|
38
|
+
return;
|
|
39
|
+
case "help":
|
|
40
|
+
showHelp(ctx, options.settingsPath);
|
|
41
|
+
return;
|
|
42
|
+
default:
|
|
43
|
+
if (canNotify(ctx)) {
|
|
44
|
+
ctx.ui.notify(`Unknown /statusline subcommand: ${subcommand}`, "warning");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function editSettings(ctx: ExtensionCommandContext, options: StatuslineCommandOptions) {
|
|
52
|
+
if (ctx.mode !== "tui") {
|
|
53
|
+
if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${options.settingsPath}`, "info");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const current = options.getLoaded();
|
|
57
|
+
const edited = await ctx.ui.editor(
|
|
58
|
+
"pi-statusline.json — save and close to apply",
|
|
59
|
+
current.rawDocument ?? DEFAULT_STATUSLINE_DOCUMENT,
|
|
60
|
+
);
|
|
61
|
+
if (edited === undefined) return;
|
|
62
|
+
try {
|
|
63
|
+
const loaded = (options.save ?? saveStatuslineSettingsDocument)(options.settingsPath, edited);
|
|
64
|
+
options.apply(loaded, ctx);
|
|
65
|
+
const suffix =
|
|
66
|
+
loaded.diagnostics.length > 0
|
|
67
|
+
? ` (${loaded.diagnostics.length} warning${loaded.diagnostics.length === 1 ? "" : "s"})`
|
|
68
|
+
: "";
|
|
69
|
+
ctx.ui.notify(`pi-statusline settings saved and applied${suffix}.`, "info");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
ctx.ui.notify(`pi-statusline settings were not saved: ${formatError(error)}`, "error");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function showStatus(ctx: ExtensionCommandContext, options: StatuslineCommandOptions) {
|
|
76
|
+
if (!canNotify(ctx)) return;
|
|
77
|
+
const loaded = options.getLoaded();
|
|
78
|
+
const diagnostics = loaded.diagnostics
|
|
79
|
+
.slice(0, 5)
|
|
80
|
+
.map((item) => `${item.path || "root"}: ${item.message}`)
|
|
81
|
+
.join("; ");
|
|
82
|
+
ctx.ui.notify(
|
|
83
|
+
[
|
|
84
|
+
`pi-statusline source: ${loaded.source}`,
|
|
85
|
+
`path: ${options.settingsPath}`,
|
|
86
|
+
`palette: ${loaded.config.palette}`,
|
|
87
|
+
`density: ${loaded.config.density}`,
|
|
88
|
+
`separator: ${loaded.config.separator}`,
|
|
89
|
+
`segments: ${loaded.config.segments.join(", ") || "none"}`,
|
|
90
|
+
diagnostics ? `warnings: ${diagnostics}` : "warnings: none",
|
|
91
|
+
].join("\n"),
|
|
92
|
+
loaded.diagnostics.length > 0 ? "warning" : "info",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function showHelp(ctx: ExtensionCommandContext, settingsPath: string) {
|
|
97
|
+
if (!canNotify(ctx)) return;
|
|
98
|
+
ctx.ui.notify(
|
|
99
|
+
[
|
|
100
|
+
"/statusline settings — edit and apply JSON",
|
|
101
|
+
"/statusline status — show source, path, and warnings",
|
|
102
|
+
"/statusline help — show this help",
|
|
103
|
+
`Settings: ${settingsPath}`,
|
|
104
|
+
"Fields: palette, density, separator, segments, segmentText, extensionStatusIcons",
|
|
105
|
+
"Use line_break between segments for another footer row; repeats must not be consecutive.",
|
|
106
|
+
"The segmentText entries support prefix and suffix strings around Pi-owned dynamic values.",
|
|
107
|
+
].join("\n"),
|
|
108
|
+
"info",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function canNotify(ctx: ExtensionCommandContext): boolean {
|
|
113
|
+
return ctx.mode === "tui" || ctx.hasUI;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function formatError(error: unknown): string {
|
|
117
|
+
return error instanceof Error ? error.message : String(error);
|
|
118
|
+
}
|
package/src/extension-status.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
6
|
-
import {
|
|
7
|
-
import { tokyoNightExtensionSeparator } from "
|
|
8
|
-
import type { StatuslineConfig
|
|
6
|
+
import { DEFAULT_EXTENSION_STATUS_ICONS } from "./settings.js";
|
|
7
|
+
import { tokyoNightExtensionSeparator } from "./tokyo-night.js";
|
|
8
|
+
import type { StatuslineConfig } from "./types.js";
|
|
9
9
|
|
|
10
10
|
export type ExtensionStatusIconAliasMap = ReadonlyMap<string, readonly string[]>;
|
|
11
11
|
export interface ExtensionStatusRuntime {
|
|
@@ -14,26 +14,9 @@ export interface ExtensionStatusRuntime {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const STATUSLINE_KEY = "statusline";
|
|
17
|
-
const GITHUB_PR_KEY = "github-pr";
|
|
18
17
|
const EMPTY_EXTENSION_STATUS_ICON_ALIASES: ExtensionStatusIconAliasMap = new Map();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"codex-usage": "📊",
|
|
22
|
-
caffeinate: "💊",
|
|
23
|
-
firecrawl: "🔥",
|
|
24
|
-
"github-pr": "🔎",
|
|
25
|
-
goal: "🎯",
|
|
26
|
-
lsp: "🧰",
|
|
27
|
-
"plan-mode": "📝",
|
|
28
|
-
pisync: "🔄",
|
|
29
|
-
subagents: "🧑🤝🧑",
|
|
30
|
-
"unknown-error-retry": "🔁",
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function extensionStatusSeparator(presetName: StatuslinePresetName, theme: Theme): string {
|
|
34
|
-
return presetName === "classic"
|
|
35
|
-
? classicExtensionSeparator(theme)
|
|
36
|
-
: tokyoNightExtensionSeparator(theme);
|
|
18
|
+
function extensionStatusSeparator(config: StatuslineConfig, theme: Theme): string {
|
|
19
|
+
return tokyoNightExtensionSeparator(theme, config.palette);
|
|
37
20
|
}
|
|
38
21
|
|
|
39
22
|
export function formatExtensionStatuses(
|
|
@@ -41,15 +24,14 @@ export function formatExtensionStatuses(
|
|
|
41
24
|
theme: Theme,
|
|
42
25
|
config: StatuslineConfig,
|
|
43
26
|
runtime: ExtensionStatusRuntime,
|
|
27
|
+
hiddenKeys: ReadonlySet<string> = new Set(),
|
|
44
28
|
): string {
|
|
45
|
-
const separator = extensionStatusSeparator(config
|
|
29
|
+
const separator = extensionStatusSeparator(config, theme);
|
|
46
30
|
const visibleStatuses = [
|
|
47
31
|
...formatDuplicateExtensionStatus(runtime, theme),
|
|
48
32
|
...[...statuses.entries()]
|
|
49
|
-
// github-pr is rendered inline in the branch segment, so skip it here to avoid duplication.
|
|
50
33
|
.filter(
|
|
51
|
-
([key, value]) =>
|
|
52
|
-
key !== STATUSLINE_KEY && key !== GITHUB_PR_KEY && value.trim().length > 0,
|
|
34
|
+
([key, value]) => key !== STATUSLINE_KEY && !hiddenKeys.has(key) && value.trim().length > 0,
|
|
53
35
|
)
|
|
54
36
|
.map(([key, value]) =>
|
|
55
37
|
formatExtensionStatus(key, value, theme, config, runtime.extensionStatusIconAliases),
|
|
@@ -70,7 +52,12 @@ export function formatExtensionStatus(
|
|
|
70
52
|
const text = simplifyExtensionStatusText(status.text);
|
|
71
53
|
const color = extensionColor(key, value);
|
|
72
54
|
const textColor = color === "warning" ? "warning" : "muted";
|
|
73
|
-
const icon = extensionStatusIcon(
|
|
55
|
+
const icon = extensionStatusIcon(
|
|
56
|
+
key,
|
|
57
|
+
status.icon,
|
|
58
|
+
config.extensionStatusIcons,
|
|
59
|
+
extensionStatusIconAliases,
|
|
60
|
+
);
|
|
74
61
|
const renderedText = theme.fg(textColor, text);
|
|
75
62
|
return icon ? `${theme.fg(color, icon)} ${renderedText}` : renderedText;
|
|
76
63
|
}
|
|
@@ -187,7 +174,9 @@ function extensionSettingsFiles(cwd: string): string[] {
|
|
|
187
174
|
].filter((file) => existsSync(file));
|
|
188
175
|
}
|
|
189
176
|
|
|
190
|
-
export function findDuplicateExtensions(
|
|
177
|
+
export function findDuplicateExtensions(
|
|
178
|
+
installedPackages: readonly InstalledExtensionPackage[],
|
|
179
|
+
): string[] {
|
|
191
180
|
const sourcesByPackage = new Map<string, Set<string>>();
|
|
192
181
|
|
|
193
182
|
for (const extensionPackage of installedPackages) {
|
|
@@ -207,17 +196,25 @@ export function buildExtensionStatusIconAliases(
|
|
|
207
196
|
const packageAliasesByStatusBase = new Map<string, Map<string, string[]>>();
|
|
208
197
|
|
|
209
198
|
for (const extensionPackage of installedPackages) {
|
|
210
|
-
const candidate = extensionStatusIconAliasCandidate(
|
|
199
|
+
const candidate = extensionStatusIconAliasCandidate(
|
|
200
|
+
extensionPackage.packageName,
|
|
201
|
+
extensionPackage.source,
|
|
202
|
+
);
|
|
211
203
|
if (!candidate) continue;
|
|
212
|
-
const aliasesByPackage =
|
|
204
|
+
const aliasesByPackage =
|
|
205
|
+
packageAliasesByStatusBase.get(candidate.statusBase) ?? new Map<string, string[]>();
|
|
213
206
|
const existingAliases = aliasesByPackage.get(extensionPackage.packageName) ?? [];
|
|
214
|
-
aliasesByPackage.set(
|
|
207
|
+
aliasesByPackage.set(
|
|
208
|
+
extensionPackage.packageName,
|
|
209
|
+
uniqueStrings([...existingAliases, ...candidate.aliases]),
|
|
210
|
+
);
|
|
215
211
|
packageAliasesByStatusBase.set(candidate.statusBase, aliasesByPackage);
|
|
216
212
|
}
|
|
217
213
|
|
|
218
214
|
const aliases = new Map<string, string[]>();
|
|
219
215
|
for (const [statusBase, aliasesByPackage] of packageAliasesByStatusBase) {
|
|
220
|
-
if (aliasesByPackage.size === 1)
|
|
216
|
+
if (aliasesByPackage.size === 1)
|
|
217
|
+
aliases.set(statusBase, [...aliasesByPackage.values()][0] ?? []);
|
|
221
218
|
}
|
|
222
219
|
return aliases;
|
|
223
220
|
}
|
package/src/git-status.ts
CHANGED
|
@@ -87,14 +87,22 @@ export function formatGitStatusSummary(summary: GitStatusSummary | undefined): s
|
|
|
87
87
|
.join(" ");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export function
|
|
90
|
+
export function formatGitBranchValue(
|
|
91
91
|
branch: string | null,
|
|
92
92
|
status: GitStatusSummary | undefined,
|
|
93
93
|
pr?: string,
|
|
94
94
|
): string {
|
|
95
|
-
if (!branch) return "
|
|
95
|
+
if (!branch) return "no-git";
|
|
96
96
|
const suffixes = [formatGitStatusSummary(status), pr ? `(${pr})` : ""].filter(Boolean);
|
|
97
|
-
return suffixes.length > 0 ?
|
|
97
|
+
return suffixes.length > 0 ? `${branch} ${suffixes.join(" ")}` : branch;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function formatGitBranchText(
|
|
101
|
+
branch: string | null,
|
|
102
|
+
status: GitStatusSummary | undefined,
|
|
103
|
+
pr?: string,
|
|
104
|
+
): string {
|
|
105
|
+
return `🌿 ${formatGitBranchValue(branch, status, pr)}`;
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
export function gitStatusSummaryEqual(
|