lumira 1.6.1 → 1.7.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 +5 -2
- package/dist/config.js +2 -0
- package/dist/normalize.js +12 -0
- package/dist/render/burn-math.js +10 -0
- package/dist/render/line1.js +12 -0
- package/dist/render/pace.js +8 -10
- package/dist/render/powerline-line1.js +37 -13
- package/dist/render/quota-projection.js +6 -8
- package/dist/types.js +2 -0
- package/package.json +1 -1
- package/skills/lumira/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Interactive wizard — preset, theme, icons — previewed live before write.
|
|
|
26
26
|
|
|
27
27
|
> 3,400+ monthly downloads, zero marketing. Try it for one session — `npx lumira install`.
|
|
28
28
|
|
|
29
|
-
> **What's new in v1.
|
|
29
|
+
> **What's new in v1.7.0:** added-dirs badge (`+N dirs`, orange at ≥5) and worktree origin-branch breadcrumb (`↳ <branch>`) — both togglable via `display.addedDirs` / `display.worktreeBreadcrumb`, on by default in `full`/`balanced`. Combined with the [`lumira stats` CLI](#stats-cli) (v1.5), `API N%` latency widget (v1.4.0), 7-day quota projection warning (v1.3.0), and auto-compact proximity glyph ⚠ (v1.4.1), recent releases add several diagnostic signals to the session.
|
|
30
30
|
|
|
31
31
|
## Table of contents
|
|
32
32
|
|
|
@@ -87,7 +87,7 @@ Inspired by [claude-hud](https://github.com/jarrodwatts/claude-hud); takes a dif
|
|
|
87
87
|
- **Config health widget** — surfaces silent fallbacks (theme/powerline degrading in named-ANSI, missing GSD STATE.md). Opt-in.
|
|
88
88
|
- **Memory usage** — process RSS percentage.
|
|
89
89
|
- **MCP server detection** — count of attached MCP servers per session.
|
|
90
|
-
- **Vim-mode hint, thinking effort, worktree, output style, session name** — all togglable per-field via `display.*`.
|
|
90
|
+
- **Vim-mode hint, thinking effort, worktree, output style, session name, added-dirs badge, worktree origin-branch breadcrumb** — all togglable per-field via `display.*`.
|
|
91
91
|
- **3-tier color system** — named ANSI / 256-color / truecolor, auto-detected.
|
|
92
92
|
- **Config-driven** — every feature toggleable via JSON config + CLI flags.
|
|
93
93
|
|
|
@@ -269,6 +269,7 @@ Create `~/.config/lumira/config.json`:
|
|
|
269
269
|
"burnRate": true,
|
|
270
270
|
"duration": true,
|
|
271
271
|
"tokenSpeed": true,
|
|
272
|
+
"apiLatency": true,
|
|
272
273
|
"rateLimits": true,
|
|
273
274
|
"paceDelta": true,
|
|
274
275
|
"quotaProjection": true,
|
|
@@ -278,6 +279,8 @@ Create `~/.config/lumira/config.json`:
|
|
|
278
279
|
"vim": true,
|
|
279
280
|
"effort": true,
|
|
280
281
|
"worktree": true,
|
|
282
|
+
"addedDirs": true,
|
|
283
|
+
"worktreeBreadcrumb": true,
|
|
281
284
|
"agent": true,
|
|
282
285
|
"sessionName": true,
|
|
283
286
|
"style": true,
|
package/dist/config.js
CHANGED
|
@@ -291,6 +291,8 @@ const PRESET_DEFS = {
|
|
|
291
291
|
// surface (see burnRate/rateLimits/paceDelta etc. above). Default
|
|
292
292
|
// remains true; users on full/balanced see the widget out of the box.
|
|
293
293
|
apiLatency: false,
|
|
294
|
+
addedDirs: false,
|
|
295
|
+
worktreeBreadcrumb: false,
|
|
294
296
|
},
|
|
295
297
|
},
|
|
296
298
|
};
|
package/dist/normalize.js
CHANGED
|
@@ -191,6 +191,18 @@ export function normalize(input) {
|
|
|
191
191
|
? sanitizeTermString(claude.effort.level)
|
|
192
192
|
: undefined,
|
|
193
193
|
worktreeName: input.worktree?.name ? sanitizeTermString(input.worktree.name) : undefined,
|
|
194
|
+
addedDirsCount: (() => {
|
|
195
|
+
const dirs = input.workspace?.added_dirs;
|
|
196
|
+
if (!Array.isArray(dirs) || dirs.length === 0)
|
|
197
|
+
return undefined;
|
|
198
|
+
return dirs.length;
|
|
199
|
+
})(),
|
|
200
|
+
worktreeOriginalBranch: (() => {
|
|
201
|
+
const orig = input.worktree?.original_branch;
|
|
202
|
+
if (!orig || typeof orig !== 'string')
|
|
203
|
+
return undefined;
|
|
204
|
+
return sanitizeTermString(orig);
|
|
205
|
+
})(),
|
|
194
206
|
rateLimits,
|
|
195
207
|
cacheHitRate,
|
|
196
208
|
raw: input,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function computeBurnExtrapolation(usedPct, elapsedSec, remainingSec) {
|
|
2
|
+
const windowSec = elapsedSec + remainingSec;
|
|
3
|
+
const elapsedPct = (elapsedSec / windowSec) * 100;
|
|
4
|
+
const delta = usedPct - elapsedPct;
|
|
5
|
+
const burnRateSec = usedPct / elapsedSec;
|
|
6
|
+
const timeToExhaustSec = (100 - usedPct) / burnRateSec;
|
|
7
|
+
const willExhaustBefore = timeToExhaustSec < remainingSec;
|
|
8
|
+
return { burnRateSec, elapsedSec, remainingSec, delta, timeToExhaustSec, willExhaustBefore };
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=burn-math.js.map
|
package/dist/render/line1.js
CHANGED
|
@@ -40,6 +40,18 @@ export function renderLine1(ctx, c) {
|
|
|
40
40
|
left.push(hyperlink(pathToFileURL(cwd).href, label));
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
// Added dirs badge — only when count > 0; warning color at >= 5
|
|
44
|
+
if (display.addedDirs && input.addedDirsCount != null && input.addedDirsCount > 0) {
|
|
45
|
+
const badge = `+${input.addedDirsCount} dirs`;
|
|
46
|
+
left.push(input.addedDirsCount >= 5 ? c.orange(badge) : c.dim(badge));
|
|
47
|
+
}
|
|
48
|
+
// Worktree origin-branch breadcrumb — only when original_branch is present,
|
|
49
|
+
// there IS a current branch to contrast against (anchor), and they differ.
|
|
50
|
+
const branchForBreadcrumb = input.gitBranch || git.branch;
|
|
51
|
+
if (display.worktreeBreadcrumb && input.worktreeOriginalBranch && branchForBreadcrumb && input.worktreeOriginalBranch !== branchForBreadcrumb) {
|
|
52
|
+
const truncated = truncField(input.worktreeOriginalBranch, 15);
|
|
53
|
+
left.push(c.gray(`↳ ${truncated}`));
|
|
54
|
+
}
|
|
43
55
|
// Duration (Claude only)
|
|
44
56
|
if (display.duration && input.durationMs != null) {
|
|
45
57
|
right.push(c.dim(`${icons.clock} ${formatDuration(input.durationMs)}`));
|
package/dist/render/pace.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { debug } from '../utils/debug.js';
|
|
2
|
+
import { computeBurnExtrapolation } from './burn-math.js';
|
|
2
3
|
const log = debug('pace');
|
|
3
4
|
export function computePaceDelta(usedPercentage, resetsAt, nowSec) {
|
|
4
5
|
const now = nowSec ?? Date.now() / 1000;
|
|
@@ -15,15 +16,12 @@ export function computePaceDelta(usedPercentage, resetsAt, nowSec) {
|
|
|
15
16
|
log({ reason: 'insufficient data (<5min)', elapsedSec, remainingSec });
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// burn rate = usedPercentage / elapsedSec (pct per second)
|
|
23
|
-
// time to exhaust remaining (100 - usedPercentage) pct at that rate
|
|
24
|
-
timeToExhaustion = (100 - usedPercentage) / (usedPercentage / elapsedSec) / 60;
|
|
25
|
-
}
|
|
19
|
+
const burn = computeBurnExtrapolation(usedPercentage, elapsedSec, remainingSec);
|
|
20
|
+
const timeToExhaustion = burn.delta > 0 && usedPercentage > 0
|
|
21
|
+
? burn.timeToExhaustSec / 60
|
|
22
|
+
: null;
|
|
26
23
|
if (log.enabled) {
|
|
24
|
+
const elapsedPct = (elapsedSec / totalWindowSec) * 100;
|
|
27
25
|
log({
|
|
28
26
|
usedPercentage,
|
|
29
27
|
resetsAt,
|
|
@@ -33,11 +31,11 @@ export function computePaceDelta(usedPercentage, resetsAt, nowSec) {
|
|
|
33
31
|
remainingSec: Math.round(remainingSec),
|
|
34
32
|
remainingMin: Math.round(remainingSec / 60),
|
|
35
33
|
elapsedPct: Math.round(elapsedPct * 10) / 10,
|
|
36
|
-
delta: Math.round(delta * 10) / 10,
|
|
34
|
+
delta: Math.round(burn.delta * 10) / 10,
|
|
37
35
|
timeToExhaustionMin: timeToExhaustion != null ? Math.round(timeToExhaustion) : null,
|
|
38
36
|
});
|
|
39
37
|
}
|
|
40
|
-
return { delta, timeToExhaustion };
|
|
38
|
+
return { delta: burn.delta, timeToExhaustion };
|
|
41
39
|
}
|
|
42
40
|
export function formatPaceDelta(pace) {
|
|
43
41
|
const rounded = Math.round(pace.delta);
|
|
@@ -11,19 +11,21 @@ import { derivePowerlinePalette, DEFAULT_POWERLINE_PALETTE, } from '../themes.js
|
|
|
11
11
|
/**
|
|
12
12
|
* Build the line1 segment list for the powerline renderer. Segment priorities
|
|
13
13
|
* control which get dropped first when the terminal is narrow (lower = drops first):
|
|
14
|
-
* model:
|
|
15
|
-
* branch:
|
|
16
|
-
* dir:
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
14
|
+
* model: 100 (always kept)
|
|
15
|
+
* branch: 80
|
|
16
|
+
* dir: 60
|
|
17
|
+
* addedDirs: 59 (renders after dir; data-gated; evicts before dir under pressure)
|
|
18
|
+
* task: 40
|
|
19
|
+
* duration: 35
|
|
20
|
+
* memory: 30
|
|
21
|
+
* tokenSpeed: 25
|
|
22
|
+
* linesChanged: 24
|
|
23
|
+
* worktree: 23
|
|
24
|
+
* worktreeBreadcrumb: 22.5 (between worktree@23 and agent@22; data-gated)
|
|
25
|
+
* agent: 22
|
|
26
|
+
* sessionName: 21
|
|
27
|
+
* version: 20
|
|
28
|
+
* style: 18 (dropped first)
|
|
27
29
|
*/
|
|
28
30
|
function buildSegments(ctx, palette, c) {
|
|
29
31
|
const { input, git, transcript, config: { display }, icons, memory, tokenSpeed } = ctx;
|
|
@@ -77,6 +79,15 @@ function buildSegments(ctx, palette, c) {
|
|
|
77
79
|
priority: 60,
|
|
78
80
|
});
|
|
79
81
|
}
|
|
82
|
+
if (display.addedDirs && input.addedDirsCount != null && input.addedDirsCount > 0) {
|
|
83
|
+
const badge = `+${input.addedDirsCount} dirs`;
|
|
84
|
+
const bg = input.addedDirsCount >= 5 ? palette.taskBg : palette.versionBg;
|
|
85
|
+
// Priority 59 — one BELOW directory@60. The badge annotates the directory,
|
|
86
|
+
// so it must evict before the directory under narrow-terminal pressure
|
|
87
|
+
// (applyPriorityEviction drops lowest-priority first). Insertion order is
|
|
88
|
+
// unchanged, so it still renders right after the directory.
|
|
89
|
+
segments.push({ text: badge, bg, fg: palette.fg, priority: 59 });
|
|
90
|
+
}
|
|
80
91
|
const activeTask = getActiveTodo(transcript);
|
|
81
92
|
if (activeTask) {
|
|
82
93
|
segments.push({
|
|
@@ -136,6 +147,19 @@ function buildSegments(ctx, palette, c) {
|
|
|
136
147
|
priority: 23,
|
|
137
148
|
});
|
|
138
149
|
}
|
|
150
|
+
if (display.worktreeBreadcrumb && input.worktreeOriginalBranch) {
|
|
151
|
+
const currentBranch = input.gitBranch || git.branch;
|
|
152
|
+
// Only render when there is a current branch to contrast against — the
|
|
153
|
+
// breadcrumb is meaningless without an anchor (no branch shown / branch off).
|
|
154
|
+
if (currentBranch && input.worktreeOriginalBranch !== currentBranch) {
|
|
155
|
+
segments.push({
|
|
156
|
+
text: `↳ ${truncField(input.worktreeOriginalBranch, 15)}`,
|
|
157
|
+
bg: palette.versionBg,
|
|
158
|
+
fg: palette.fg,
|
|
159
|
+
priority: 22.5,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
139
163
|
// Mirrors classic line1: prefer the explicit input.agentName (subagent
|
|
140
164
|
// session render), else show the cubes badge when exactly one *named*
|
|
141
165
|
// subagent is running on the parent. Anonymous agents stay collapsed
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { debug } from '../utils/debug.js';
|
|
2
|
+
import { computeBurnExtrapolation } from './burn-math.js';
|
|
2
3
|
const log = debug('quota-projection');
|
|
3
4
|
/**
|
|
4
5
|
* Extrapolates current burn rate to when the quota would hit 100%.
|
|
@@ -27,21 +28,18 @@ export function computeQuotaProjection(usedPct, resetsAt, windowSec, nowSec, min
|
|
|
27
28
|
log({ reason: 'insufficient elapsed', elapsedSec, minElapsedSec });
|
|
28
29
|
return null;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
const burnRate = usedPct / elapsedSec;
|
|
32
|
-
const timeToExhaustSec = (100 - usedPct) / burnRate;
|
|
33
|
-
const willExhaustBefore = timeToExhaustSec < remainingSec;
|
|
31
|
+
const burn = computeBurnExtrapolation(usedPct, elapsedSec, remainingSec);
|
|
34
32
|
if (log.enabled) {
|
|
35
33
|
log({
|
|
36
34
|
usedPct,
|
|
37
35
|
elapsedSec: Math.round(elapsedSec),
|
|
38
36
|
remainingSec: Math.round(remainingSec),
|
|
39
|
-
burnRate,
|
|
40
|
-
timeToExhaustSec: Math.round(timeToExhaustSec),
|
|
41
|
-
willExhaustBefore,
|
|
37
|
+
burnRate: burn.burnRateSec,
|
|
38
|
+
timeToExhaustSec: Math.round(burn.timeToExhaustSec),
|
|
39
|
+
willExhaustBefore: burn.willExhaustBefore,
|
|
42
40
|
});
|
|
43
41
|
}
|
|
44
|
-
return { timeToExhaustSec, willExhaustBefore };
|
|
42
|
+
return { timeToExhaustSec: burn.timeToExhaustSec, willExhaustBefore: burn.willExhaustBefore };
|
|
45
43
|
}
|
|
46
44
|
/**
|
|
47
45
|
* Renders a projection as a short warning string (e.g. "⚠ Mon", "🔥 ~12h").
|
package/dist/types.js
CHANGED
|
@@ -104,6 +104,8 @@ export const DEFAULT_DISPLAY = {
|
|
|
104
104
|
agents: true,
|
|
105
105
|
health: false,
|
|
106
106
|
apiLatency: true,
|
|
107
|
+
addedDirs: true,
|
|
108
|
+
worktreeBreadcrumb: true,
|
|
107
109
|
contextWarningThreshold: DEFAULT_CONTEXT_WARNING_THRESHOLD,
|
|
108
110
|
contextCriticalThreshold: DEFAULT_CONTEXT_CRITICAL_THRESHOLD,
|
|
109
111
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumira",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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",
|
package/skills/lumira/SKILL.md
CHANGED
|
@@ -44,7 +44,7 @@ Your job: read the user's current config, translate their natural-language reque
|
|
|
44
44
|
|
|
45
45
|
Valid keys (anything else must be rejected):
|
|
46
46
|
|
|
47
|
-
`model`, `branch`, `gitChanges`, `directory`, `contextBar`, `contextTokens`, `tokens`, `cost`, `burnRate`, `duration`, `tokenSpeed`, `rateLimits`, `paceDelta`, `quotaProjection`, `tools`, `todos`, `vim`, `effort`, `worktree`, `agent`, `agents`, `sessionName`, `style`, `version`, `linesChanged`, `memory`, `cacheMetrics`, `mcp`, `health`
|
|
47
|
+
`model`, `branch`, `gitChanges`, `directory`, `contextBar`, `contextTokens`, `tokens`, `cost`, `burnRate`, `duration`, `tokenSpeed`, `rateLimits`, `paceDelta`, `quotaProjection`, `tools`, `todos`, `vim`, `effort`, `worktree`, `agent`, `agents`, `sessionName`, `style`, `version`, `linesChanged`, `memory`, `cacheMetrics`, `mcp`, `health`, `apiLatency`, `addedDirs`, `worktreeBreadcrumb`
|
|
48
48
|
|
|
49
49
|
### Display thresholds (`display.*`, numeric)
|
|
50
50
|
|