lumira 1.9.1 → 1.12.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +20 -1
- package/dist/config.js +4 -0
- package/dist/normalize.js +29 -1
- package/dist/parsers/gsd.js +25 -2
- package/dist/render/icons.js +6 -0
- package/dist/render/line2.js +16 -0
- package/dist/render/line4.js +6 -0
- package/dist/render/powerline-line2.js +27 -0
- package/dist/types.js +3 -0
- package/package.json +1 -1
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Real-time statusline HUD for Claude Code and Qwen Code — analytics, quota projection, themes, powerline",
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.12.0"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "lumira",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Real-time statusline HUD for Claude Code and Qwen Code. Session analytics, API latency widget, 7-day quota projection, auto-compact warnings, 7 themes, powerline support. Zero runtime dependencies. Run /lumira:setup after install to activate.",
|
|
15
|
-
"version": "1.
|
|
15
|
+
"version": "1.12.0",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Carlos Cativo"
|
|
18
18
|
},
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
]
|
|
33
33
|
}
|
|
34
34
|
],
|
|
35
|
-
"version": "1.
|
|
35
|
+
"version": "1.12.0"
|
|
36
36
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumira",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Real-time statusline HUD for Claude Code and Qwen Code — session analytics, API latency, 7-day quota projection, auto-compact warnings, 7 themes, powerline. Zero runtime deps.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Carlos Cativo"
|
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ Interactive wizard — preset, theme, icons — previewed live before write.
|
|
|
40
40
|
## Table of contents
|
|
41
41
|
|
|
42
42
|
- [Why lumira?](#why-lumira)
|
|
43
|
+
- [How lumira compares](#how-lumira-compares)
|
|
43
44
|
- [Requirements](#requirements)
|
|
44
45
|
- [Features](#features)
|
|
45
46
|
- [Install](#install)
|
|
@@ -63,7 +64,25 @@ Claude Code's default statusline shows the model name and current directory. Tha
|
|
|
63
64
|
- **Active tools, agents, and todo progress** — parsed from the live transcript, updated every render.
|
|
64
65
|
- **Cross-platform** — same config drives Claude Code and Qwen Code; Qwen sessions auto-collapse to single-line.
|
|
65
66
|
|
|
66
|
-
Inspired by [
|
|
67
|
+
Inspired by [GSD's](https://github.com/open-gsd/gsd-core) statusline; takes its own stance on opt-in powerline rendering, theme contrast guarantees, and Qwen Code compatibility.
|
|
68
|
+
|
|
69
|
+
## How lumira compares
|
|
70
|
+
|
|
71
|
+
The Claude Code statusline space has several good tools. Here's an honest head-to-head on **features** against the other true statuslines (feature claims checked against each tool's current README, 2026-06-14):
|
|
72
|
+
|
|
73
|
+
| Tool | Runtime / deps | Distribution | Platforms | Config UX | Powerline + themes | Session-intel widgets |
|
|
74
|
+
|---|---|---|---|---|---|---|
|
|
75
|
+
| **lumira** | TS / **0 runtime deps** | npm + npx + plugin (+ Qwen skill) | **Claude Code + Qwen Code** | Wizard + JSON + CLI flags | Yes (7 styles) + 7 themes, **WCAG-AA guard** | **Quota projection, pace delta, API-latency, auto-compact glyph + counter, cache, agents, MCP, todos, tools** + `stats` CLI |
|
|
76
|
+
| ccstatusline | TS / bundled | npm + npx | Claude Code | **Ink TUI** (live preview) | Yes + themes | Context, cost, usage %, block timer, compaction count, git; no quota/pace/latency |
|
|
77
|
+
| claude-hud | JS / Node 18+ | Plugin marketplace | Claude Code (v1.0.80+) | Guided `/configure` + JSON | No / no themes | Context, 5h/7d usage, cost, git, tools, agents, todos, cache TTL; no quota ETA/pace/latency |
|
|
78
|
+
| CCometixLine | Rust binary | npm + binary + source | Claude Code | TUI (TOML) | Yes + themes | Model, dir, git, context %, usage, cost, time, output-style |
|
|
79
|
+
| claude-pace | Bash + jq | curl + plugin + npx | Claude Code 2.1.80+ | JSON block | No / no | 5h+7d %, pace delta, reset countdown, git diff; ~10ms (lightest) |
|
|
80
|
+
| cship | Rust binary | binary / script / cargo | Claude Code | TOML (Starship-style) | Yes (Starship) + themes | Cost, context bar, usage limits, model, effort, agent, session, peak-time |
|
|
81
|
+
| starship-claude | Shell / needs Starship | Plugin + manual | Claude Code (no tmux) | Wizard + TOML | Via Starship + palettes | Context bar, model, session |
|
|
82
|
+
|
|
83
|
+
**Where lumira leads:** breadth of session-intelligence widgets — sole owner of an API-latency widget, a 7-day quota *projection ETA*, an MCP-server count, and a bundled `stats` analytics CLI, on top of the full pace / agents / todos / cache set. Plus zero runtime deps, dual-platform (Claude Code **and** Qwen Code), and WCAG-AA contrast enforced in CI on every theme. **Where it doesn't:** no Ink-style interactive widget builder — config is a wizard + JSON, not a live drag-and-drop TUI.
|
|
84
|
+
|
|
85
|
+
See [`docs/competitive-comparison.md`](docs/competitive-comparison.md) for the full per-widget matrix, config-UX detail, and distribution breakdown across every tool.
|
|
67
86
|
|
|
68
87
|
## Requirements
|
|
69
88
|
|
package/dist/config.js
CHANGED
|
@@ -248,6 +248,8 @@ const PRESET_DEFS = {
|
|
|
248
248
|
layout: 'auto',
|
|
249
249
|
display: {
|
|
250
250
|
agents: true,
|
|
251
|
+
pr: true,
|
|
252
|
+
thinking: true,
|
|
251
253
|
burnRate: false,
|
|
252
254
|
duration: false,
|
|
253
255
|
tokenSpeed: false,
|
|
@@ -294,6 +296,8 @@ const PRESET_DEFS = {
|
|
|
294
296
|
addedDirs: false,
|
|
295
297
|
worktreeBreadcrumb: false,
|
|
296
298
|
compactionCount: false,
|
|
299
|
+
pr: false,
|
|
300
|
+
thinking: false,
|
|
297
301
|
},
|
|
298
302
|
},
|
|
299
303
|
};
|
package/dist/normalize.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Single internal format that all renderers can consume.
|
|
4
4
|
// Platform-specific quirks are handled once here.
|
|
5
5
|
// Renderers check field presence, not platform identity.
|
|
6
|
-
import { AUTO_COMPACT_THRESHOLD, AUTO_COMPACT_WARNING_GAP } from './types.js';
|
|
6
|
+
import { AUTO_COMPACT_THRESHOLD, AUTO_COMPACT_WARNING_GAP, PR_REVIEW_STATES } from './types.js';
|
|
7
7
|
export function isQwenInput(input) {
|
|
8
8
|
const raw = input;
|
|
9
9
|
if (!raw.metrics || typeof raw.metrics !== 'object' || !('models' in raw.metrics))
|
|
@@ -28,6 +28,8 @@ export function sanitizeTermString(s) {
|
|
|
28
28
|
}
|
|
29
29
|
/** Allowed values for the reasoning effort level field (CC ≥ 2.1.x). */
|
|
30
30
|
const VALID_EFFORT_LEVELS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
31
|
+
/** Allowed values for PR review state (CC ≥ 2.1.145). */
|
|
32
|
+
const VALID_PR_REVIEW_STATES = new Set(PR_REVIEW_STATES);
|
|
31
33
|
/**
|
|
32
34
|
* Sum input token categories from `context_window.current_usage` to compute
|
|
33
35
|
* a real context usage total (input + cache_read + cache_creation).
|
|
@@ -174,6 +176,30 @@ export function normalize(input) {
|
|
|
174
176
|
const cacheHitRate = (cached != null && cacheTurnDenominator && platform === 'claude-code')
|
|
175
177
|
? Math.min(100, Math.round((cached / cacheTurnDenominator) * 100))
|
|
176
178
|
: undefined;
|
|
179
|
+
// PR widget (Claude only, CC ≥ 2.1.145).
|
|
180
|
+
// number must be a positive integer — drop the whole object if invalid.
|
|
181
|
+
// url: sanitize then accept only https:// scheme (OSC 8 injection guard).
|
|
182
|
+
// reviewState: sanitize then gate against the PR_REVIEW_STATES allowlist.
|
|
183
|
+
let pr;
|
|
184
|
+
if (claude?.pr != null) {
|
|
185
|
+
const rawPr = claude.pr;
|
|
186
|
+
const n = rawPr.number;
|
|
187
|
+
if (typeof n === 'number' && Number.isInteger(n) && n > 0) {
|
|
188
|
+
let prUrl;
|
|
189
|
+
if (typeof rawPr.url === 'string') {
|
|
190
|
+
const sanitized = sanitizeTermString(rawPr.url);
|
|
191
|
+
if (sanitized.startsWith('https://'))
|
|
192
|
+
prUrl = sanitized;
|
|
193
|
+
}
|
|
194
|
+
let prReviewState;
|
|
195
|
+
if (typeof rawPr.review_state === 'string') {
|
|
196
|
+
const sanitized = sanitizeTermString(rawPr.review_state);
|
|
197
|
+
if (VALID_PR_REVIEW_STATES.has(sanitized))
|
|
198
|
+
prReviewState = sanitized;
|
|
199
|
+
}
|
|
200
|
+
pr = { number: n, url: prUrl, reviewState: prReviewState };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
177
203
|
return {
|
|
178
204
|
platform,
|
|
179
205
|
model: sanitizeTermString(modelName),
|
|
@@ -208,6 +234,7 @@ export function normalize(input) {
|
|
|
208
234
|
effortLevel: claude?.effort?.level && VALID_EFFORT_LEVELS.has(claude.effort.level)
|
|
209
235
|
? sanitizeTermString(claude.effort.level)
|
|
210
236
|
: undefined,
|
|
237
|
+
thinkingEnabled: claude?.thinking?.enabled === true ? true : undefined,
|
|
211
238
|
worktreeName: input.worktree?.name ? sanitizeTermString(input.worktree.name) : undefined,
|
|
212
239
|
addedDirsCount: (() => {
|
|
213
240
|
const dirs = input.workspace?.added_dirs;
|
|
@@ -223,6 +250,7 @@ export function normalize(input) {
|
|
|
223
250
|
})(),
|
|
224
251
|
rateLimits,
|
|
225
252
|
cacheHitRate,
|
|
253
|
+
pr,
|
|
226
254
|
raw: input,
|
|
227
255
|
};
|
|
228
256
|
}
|
package/dist/parsers/gsd.js
CHANGED
|
@@ -83,6 +83,22 @@ export function parseStateMd(content) {
|
|
|
83
83
|
state.phaseTotal = phaseMatch[2];
|
|
84
84
|
state.phaseName = phaseMatch[3];
|
|
85
85
|
}
|
|
86
|
+
// Plan progress within the current phase (gsd-core ≥ 1.4.x). Accepts both the
|
|
87
|
+
// compound "X of Y in current phase" and the bare "X of Y" forms. A "Plan: —"
|
|
88
|
+
// (not started) line has no digits and is correctly skipped.
|
|
89
|
+
const planMatch = content.match(/^Plan:\s*(\d+)\s+of\s+(\d+)/m);
|
|
90
|
+
if (planMatch) {
|
|
91
|
+
state.planNum = planMatch[1];
|
|
92
|
+
state.planTotal = planMatch[2];
|
|
93
|
+
}
|
|
94
|
+
// Resume point (gsd-core ≥ 1.4.x). "Resume file: None" means no active resume
|
|
95
|
+
// point and is treated as absent.
|
|
96
|
+
const resumeMatch = content.match(/^Resume file:\s*(.+)/m);
|
|
97
|
+
if (resumeMatch) {
|
|
98
|
+
const path = resumeMatch[1].trim();
|
|
99
|
+
if (path && path.toLowerCase() !== 'none')
|
|
100
|
+
state.resumeFile = path;
|
|
101
|
+
}
|
|
86
102
|
if (!state.status) {
|
|
87
103
|
// Fallback: parse body Status line when frontmatter status is missing
|
|
88
104
|
const bodyStatus = content.match(/^Status:\s*(.+)/m);
|
|
@@ -145,9 +161,13 @@ function formatState(s) {
|
|
|
145
161
|
}
|
|
146
162
|
// Scene selection: activePhase → nextAction → milestone-complete → default
|
|
147
163
|
const phasesStr = s.nextPhases?.length ? s.nextPhases.join('/') : null;
|
|
164
|
+
// Plan progress within the phase (gsd-core ≥ 1.4.x), appended to the phase
|
|
165
|
+
// descriptor in the active/default scenes — e.g. "auth (3/5) p2/9".
|
|
166
|
+
const planSuffix = s.planNum && s.planTotal ? ` p${s.planNum}/${s.planTotal}` : '';
|
|
148
167
|
if (s.activePhase) {
|
|
149
168
|
// Scene 1: activePhase (with optional status)
|
|
150
|
-
|
|
169
|
+
const phase = s.status ? `Phase ${s.activePhase} ${s.status}` : `Phase ${s.activePhase}`;
|
|
170
|
+
parts.push(`${phase}${planSuffix}`);
|
|
151
171
|
}
|
|
152
172
|
else if (s.nextAction && phasesStr) {
|
|
153
173
|
// Scene 2: nextAction + phases when idle
|
|
@@ -163,7 +183,7 @@ function formatState(s) {
|
|
|
163
183
|
parts.push(s.status);
|
|
164
184
|
if (s.phaseNum && s.phaseTotal) {
|
|
165
185
|
const phase = s.phaseName ? `${s.phaseName} (${s.phaseNum}/${s.phaseTotal})` : `ph ${s.phaseNum}/${s.phaseTotal}`;
|
|
166
|
-
parts.push(phase);
|
|
186
|
+
parts.push(`${phase}${planSuffix}`);
|
|
167
187
|
}
|
|
168
188
|
}
|
|
169
189
|
return parts.join(' · ');
|
|
@@ -234,11 +254,13 @@ export function getGsdInfo(cwd, opts = {}) {
|
|
|
234
254
|
const legacyCacheFile = join(claudeDir, 'cache', 'gsd-update-check.json');
|
|
235
255
|
const cacheData = readUpdateCache(openGsdCacheFile, sharedCacheFile, legacyCacheFile);
|
|
236
256
|
let currentTask;
|
|
257
|
+
let hasResume = false;
|
|
237
258
|
const stateFile = findStateMd(cwd || process.cwd());
|
|
238
259
|
if (stateFile) {
|
|
239
260
|
log('STATE.md found:', stateFile);
|
|
240
261
|
try {
|
|
241
262
|
const state = parseStateMd(readFileSync(stateFile, 'utf8'));
|
|
263
|
+
hasResume = state.resumeFile !== undefined;
|
|
242
264
|
const formatted = formatState(state);
|
|
243
265
|
if (formatted) {
|
|
244
266
|
currentTask = sanitizeTermString(formatted);
|
|
@@ -261,6 +283,7 @@ export function getGsdInfo(cwd, opts = {}) {
|
|
|
261
283
|
staleHooks: cacheData.staleHooks || undefined,
|
|
262
284
|
devInstall: cacheData.devInstall || undefined,
|
|
263
285
|
currentTask,
|
|
286
|
+
hasResume: hasResume || undefined,
|
|
264
287
|
};
|
|
265
288
|
}
|
|
266
289
|
//# sourceMappingURL=gsd.js.map
|
package/dist/render/icons.js
CHANGED
|
@@ -60,6 +60,8 @@ export const NERD_ICONS = {
|
|
|
60
60
|
car: '🏎️', // racing car — pace-ahead indicator
|
|
61
61
|
turtle: '🐢', // turtle — pace-behind indicator
|
|
62
62
|
lightning: '⚡', // lightning bolt — cache hit rate
|
|
63
|
+
pr: '', // nf-cod-git_pull_request
|
|
64
|
+
thinking: '', // nf-md-brain
|
|
63
65
|
battery: nerdBattery,
|
|
64
66
|
};
|
|
65
67
|
export const EMOJI_ICONS = {
|
|
@@ -83,6 +85,8 @@ export const EMOJI_ICONS = {
|
|
|
83
85
|
car: '\u{1F3CE}️', // 🏎️ — racing car
|
|
84
86
|
turtle: '\u{1F422}', // 🐢 — turtle
|
|
85
87
|
lightning: '⚡', // ⚡ — cache hit rate
|
|
88
|
+
pr: '\u{1F500}', // 🔀 — twisted rightwards arrows (PR)
|
|
89
|
+
thinking: '\u{1F4AD}', // 💭 — thought bubble
|
|
86
90
|
battery: (pct) => {
|
|
87
91
|
if (!Number.isFinite(pct) || pct < 0)
|
|
88
92
|
return '\u{1F50B}'; // 🔋 — no data / invalid input
|
|
@@ -114,6 +118,8 @@ export const NO_ICONS = {
|
|
|
114
118
|
car: '',
|
|
115
119
|
turtle: '',
|
|
116
120
|
lightning: '',
|
|
121
|
+
pr: 'PR',
|
|
122
|
+
thinking: 'think',
|
|
117
123
|
// No-icon mode keeps the legacy bolt fallback (currently empty) so users who
|
|
118
124
|
// opted out of icons see no shape change from this feature.
|
|
119
125
|
battery: () => '',
|
package/dist/render/line2.js
CHANGED
|
@@ -7,6 +7,7 @@ import { formatTokens, formatCost, formatBurnRate } from '../utils/format.js';
|
|
|
7
7
|
import { getConfigHealth } from '../parsers/config-health.js';
|
|
8
8
|
import { computePaceDelta, formatPaceDelta } from './pace.js';
|
|
9
9
|
import { computeQuotaProjection, formatProjectionWarning } from './quota-projection.js';
|
|
10
|
+
import { hyperlink } from './hyperlink.js';
|
|
10
11
|
const SEVEN_DAY_WINDOW_SEC = 7 * 24 * 3600;
|
|
11
12
|
const SEVEN_DAY_MIN_ELAPSED_SEC = 3600; // 1h floor — see quota-projection.ts
|
|
12
13
|
export function formatCountdown(resetsAt) {
|
|
@@ -115,6 +116,18 @@ export function renderLine2(ctx, c) {
|
|
|
115
116
|
leftParts.push(c.dim(`MCP ${total}`));
|
|
116
117
|
}
|
|
117
118
|
}
|
|
119
|
+
// PR widget (CC ≥ 2.1.145)
|
|
120
|
+
if (display.pr && input.pr) {
|
|
121
|
+
const { number, url, reviewState } = input.pr;
|
|
122
|
+
const stateStr = reviewState ?? '';
|
|
123
|
+
const colorFn = reviewState === 'approved' ? c.green :
|
|
124
|
+
reviewState === 'pending' ? c.yellow :
|
|
125
|
+
reviewState === 'changes_requested' ? c.orange :
|
|
126
|
+
c.dim; // draft or unknown
|
|
127
|
+
const text = `${icons.pr} #${number}${stateStr ? ` ${stateStr}` : ''}`;
|
|
128
|
+
const colored = colorFn(text);
|
|
129
|
+
leftParts.push(url ? hyperlink(url, colored) : colored);
|
|
130
|
+
}
|
|
118
131
|
// Qwen metrics (shared helper)
|
|
119
132
|
leftParts.push(...formatQwenMetrics(input, c, icons));
|
|
120
133
|
// 7d quota projection — computed once, surfaced two ways depending on whether
|
|
@@ -233,6 +246,9 @@ export function renderLine2(ctx, c) {
|
|
|
233
246
|
if (display.effort && effort && effort !== 'medium') {
|
|
234
247
|
rightParts.push(c.dim(`^${effort}`));
|
|
235
248
|
}
|
|
249
|
+
if (display.thinking && input.thinkingEnabled) {
|
|
250
|
+
rightParts.push(c.dim(icons.thinking));
|
|
251
|
+
}
|
|
236
252
|
// Config health hints (opt-in, default off). Sit on the right side as
|
|
237
253
|
// auxiliary signals next to vim/effort, and are dropped silently when the
|
|
238
254
|
// projected line width would overflow `cols` — they are advisory, never
|
package/dist/render/line4.js
CHANGED
|
@@ -13,6 +13,12 @@ export function renderLine4(ctx, c) {
|
|
|
13
13
|
if (gsd.currentTask) {
|
|
14
14
|
parts.push(c.bold(`${icons.hammer} ${truncField(gsd.currentTask, 60)}`));
|
|
15
15
|
}
|
|
16
|
+
// Resume-point indicator (gsd-core ≥ 1.4.x). A ↩ glyph signals STATE.md has
|
|
17
|
+
// an active `.continue-here`/spec resume file — a cue that work can be picked
|
|
18
|
+
// up where it left off. Cyan: informational, distinct from update/stale warns.
|
|
19
|
+
if (gsd.hasResume) {
|
|
20
|
+
parts.push(c.cyan('↩'));
|
|
21
|
+
}
|
|
16
22
|
if (gsd.updateAvailable) {
|
|
17
23
|
parts.push(c.yellow('⬆ /gsd:update'));
|
|
18
24
|
}
|
|
@@ -21,6 +21,20 @@ function getCacheHitBg(rate, palette) {
|
|
|
21
21
|
case 'critical': return palette.branchDirtyBg;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
// Maps PR review state to a powerline bg slot (urgency via background, not text color).
|
|
25
|
+
// approved → dirBg (neutral — PR is ready, low urgency)
|
|
26
|
+
// pending → taskBg (warm — waiting on reviewers)
|
|
27
|
+
// changes_requested → branchDirtyBg (hot — action needed from author)
|
|
28
|
+
// draft → dirBg (neutral — not ready for review)
|
|
29
|
+
function getPrReviewBg(reviewState, palette) {
|
|
30
|
+
switch (reviewState) {
|
|
31
|
+
case 'changes_requested': return palette.branchDirtyBg;
|
|
32
|
+
case 'pending': return palette.taskBg;
|
|
33
|
+
case 'approved':
|
|
34
|
+
case 'draft':
|
|
35
|
+
default: return palette.dirBg;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
24
38
|
// Maps the API latency tier (SSOT in colors.ts) to a powerline bg slot.
|
|
25
39
|
// healthy/notable → dirBg (neutral; api is fast or only slightly slow)
|
|
26
40
|
// warn → taskBg (warm; api is dominating session time)
|
|
@@ -210,6 +224,14 @@ function buildSegments(ctx, palette, c) {
|
|
|
210
224
|
const mcpText = errors > 0 ? `MCP ${total - errors}/${total}` : `MCP ${total}`;
|
|
211
225
|
segments.push({ text: mcpText, bg: palette.taskBg, fg: palette.fg, priority: 50 });
|
|
212
226
|
}
|
|
227
|
+
// PR widget (CC ≥ 2.1.145)
|
|
228
|
+
if (display.pr && input.pr) {
|
|
229
|
+
const { number, reviewState } = input.pr;
|
|
230
|
+
const stateStr = reviewState ?? '';
|
|
231
|
+
const text = `${icons.pr} #${number}${stateStr ? ` ${stateStr}` : ''}`;
|
|
232
|
+
const bg = getPrReviewBg(reviewState, palette);
|
|
233
|
+
segments.push({ text, bg, fg: palette.fg, priority: 55 });
|
|
234
|
+
}
|
|
213
235
|
// Qwen metrics
|
|
214
236
|
const qwenParts = formatQwenMetrics(input, c, icons);
|
|
215
237
|
for (const part of qwenParts) {
|
|
@@ -224,6 +246,11 @@ function buildSegments(ctx, palette, c) {
|
|
|
224
246
|
if (display.effort && effort && effort !== 'medium') {
|
|
225
247
|
segments.push({ text: `^${effort}`, bg: palette.versionBg, fg: palette.fg, priority: 30 });
|
|
226
248
|
}
|
|
249
|
+
// Extended thinking indicator — priority 28 (one below effort/vim at 30) so it
|
|
250
|
+
// evicts before them on narrow terminals; thinking is informational, not urgent.
|
|
251
|
+
if (display.thinking && input.thinkingEnabled) {
|
|
252
|
+
segments.push({ text: icons.thinking, bg: palette.dirBg, fg: palette.fg, priority: 28 });
|
|
253
|
+
}
|
|
227
254
|
// Config health hints (opt-in, lowest priority — evicted first on narrow terminals)
|
|
228
255
|
if (display.health && input.cwd) {
|
|
229
256
|
const colorMode = ctx.config.colors.mode === 'auto' ? detectColorMode() : ctx.config.colors.mode;
|
package/dist/types.js
CHANGED
|
@@ -38,6 +38,7 @@ export const CUSTOM_COMMAND_COLORS = ['dim', 'green', 'yellow', 'orange', 'red',
|
|
|
38
38
|
* `POWERLINE_STYLES` in `src/render/powerline.ts` — that map's keys
|
|
39
39
|
* MUST match this list.
|
|
40
40
|
*/
|
|
41
|
+
export const PR_REVIEW_STATES = ['approved', 'pending', 'changes_requested', 'draft'];
|
|
41
42
|
export const POWERLINE_STYLE_NAMES = [
|
|
42
43
|
'arrow', 'flame', 'slant', 'round', 'diamond', 'compatible', 'plain', 'auto',
|
|
43
44
|
];
|
|
@@ -108,6 +109,8 @@ export const DEFAULT_DISPLAY = {
|
|
|
108
109
|
addedDirs: true,
|
|
109
110
|
worktreeBreadcrumb: true,
|
|
110
111
|
compactionCount: true,
|
|
112
|
+
pr: true,
|
|
113
|
+
thinking: true,
|
|
111
114
|
contextWarningThreshold: DEFAULT_CONTEXT_WARNING_THRESHOLD,
|
|
112
115
|
contextCriticalThreshold: DEFAULT_CONTEXT_CRITICAL_THRESHOLD,
|
|
113
116
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumira",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Real-time statusline HUD for Claude Code and Qwen Code. Includes session analytics CLI, API latency overhead widget, 7d quota projection, auto-compact proximity warnings, themes, and powerline. Zero deps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|