lumira 1.14.0 → 1.15.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 +2 -1
- package/dist/config.js +2 -0
- package/dist/render/line1.js +30 -15
- package/dist/render/powerline-line1.js +14 -13
- package/dist/render/text.js +18 -6
- package/dist/types.js +1 -0
- package/dist/utils/terminal.js +11 -6
- 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.15.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.15.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.15.0"
|
|
36
36
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumira",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.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
|
@@ -321,6 +321,7 @@ Create `~/.config/lumira/config.json`:
|
|
|
321
321
|
"theme": "tokyo-night",
|
|
322
322
|
"icons": "nerd",
|
|
323
323
|
"style": "classic",
|
|
324
|
+
"line1Align": "justified",
|
|
324
325
|
"powerline": { "style": "auto" },
|
|
325
326
|
"gsd": false,
|
|
326
327
|
"colors": { "mode": "auto" },
|
|
@@ -364,7 +365,7 @@ Create `~/.config/lumira/config.json`:
|
|
|
364
365
|
}
|
|
365
366
|
```
|
|
366
367
|
|
|
367
|
-
All fields are optional — defaults are shown above. `display.health` defaults to `false` (opt-in widget).
|
|
368
|
+
All fields are optional — defaults are shown above. `display.health` defaults to `false` (opt-in widget). `line1Align` controls line 1's classic layout: `"justified"` (default) pins the left cluster to the start and the right cluster to the end; `"packed"` packs every segment tightly to the left with no forced middle gap, except the app version string, which stays pinned to the true right edge (the same idiom line 2 uses for its small right-anchored indicators). On a terminal too narrow to fit the version alongside the packed content, the version disappears entirely rather than truncating — an intentional all-or-nothing tradeoff. Powerline mode always packs.
|
|
368
369
|
|
|
369
370
|
**Context bar thresholds** — `contextWarningThreshold` (default 70) and `contextCriticalThreshold` (default 85) control when the bar transitions through yellow/orange/red. Both are clamped to `[0, 100]` and `warning < critical` is required (invalid pairs fall back to defaults with a one-shot stderr warning). Lower them for earlier warnings, raise them if your workflow tolerates fuller buffers.
|
|
370
371
|
|
package/dist/config.js
CHANGED
|
@@ -196,6 +196,7 @@ function mergeConfig(rawIn) {
|
|
|
196
196
|
raw = { ...raw, preset: 'minimal' };
|
|
197
197
|
}
|
|
198
198
|
const layout = ['multiline', 'singleline', 'auto'].includes(raw.layout) ? raw.layout : DEFAULT_CONFIG.layout;
|
|
199
|
+
const line1Align = ['justified', 'packed'].includes(raw.line1Align) ? raw.line1Align : DEFAULT_CONFIG.line1Align;
|
|
199
200
|
const colors = { ...DEFAULT_CONFIG.colors };
|
|
200
201
|
if (raw.colors && typeof raw.colors === 'object') {
|
|
201
202
|
const m = raw.colors.mode;
|
|
@@ -204,6 +205,7 @@ function mergeConfig(rawIn) {
|
|
|
204
205
|
}
|
|
205
206
|
const result = {
|
|
206
207
|
layout,
|
|
208
|
+
line1Align,
|
|
207
209
|
gsd: typeof raw.gsd === 'boolean' ? raw.gsd : DEFAULT_CONFIG.gsd,
|
|
208
210
|
display: { ...DEFAULT_DISPLAY },
|
|
209
211
|
colors,
|
package/dist/render/line1.js
CHANGED
|
@@ -6,7 +6,7 @@ import { hyperlink } from './hyperlink.js';
|
|
|
6
6
|
import { formatDuration } from '../utils/format.js';
|
|
7
7
|
import { isNamedAgentType } from '../parsers/subagents.js';
|
|
8
8
|
export function renderLine1(ctx, c) {
|
|
9
|
-
const { input, git, transcript, config: { display }, cols, icons, memory, tokenSpeed } = ctx;
|
|
9
|
+
const { input, git, transcript, config: { display, line1Align }, cols, icons, memory, tokenSpeed } = ctx;
|
|
10
10
|
const left = [];
|
|
11
11
|
const right = [];
|
|
12
12
|
// Model
|
|
@@ -27,7 +27,19 @@ export function renderLine1(ctx, c) {
|
|
|
27
27
|
}
|
|
28
28
|
left.push(branchStr);
|
|
29
29
|
}
|
|
30
|
-
//
|
|
30
|
+
// Repo segment — owner/name from workspace.repo, clickable to open the repo
|
|
31
|
+
// on its host. Sits right after branch: both are git-identity (where you are
|
|
32
|
+
// in the DAG + which remote it tracks), so they stay grouped. Distinct from
|
|
33
|
+
// the directory breadcrumb below (a local path): this is the canonical remote
|
|
34
|
+
// identity, and surfaces the owner that the bare cwd basename can't.
|
|
35
|
+
if (display.repo && input.repo) {
|
|
36
|
+
const { owner, name, url } = input.repo;
|
|
37
|
+
const repoLen = cols < 80 ? 14 : cols < 120 ? 24 : 36;
|
|
38
|
+
const label = c.brightBlue(`${icons.repo} ${truncField(`${owner}/${name}`, repoLen)}`);
|
|
39
|
+
left.push(hyperlink(url, label));
|
|
40
|
+
}
|
|
41
|
+
// Directory — local filesystem context (where on disk), orthogonal to the
|
|
42
|
+
// git identity above. Rendered after repo so the two git segments group.
|
|
31
43
|
if (display.directory) {
|
|
32
44
|
const cwd = input.cwd;
|
|
33
45
|
if (cwd) {
|
|
@@ -40,16 +52,6 @@ export function renderLine1(ctx, c) {
|
|
|
40
52
|
left.push(hyperlink(pathToFileURL(cwd).href, label));
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
|
-
// Repo segment — owner/name from workspace.repo, clickable to open the repo
|
|
44
|
-
// on its host. Distinct from the directory breadcrumb above (a local path):
|
|
45
|
-
// this is the canonical remote identity, and surfaces the owner that the
|
|
46
|
-
// bare cwd basename can't.
|
|
47
|
-
if (display.repo && input.repo) {
|
|
48
|
-
const { owner, name, url } = input.repo;
|
|
49
|
-
const repoLen = cols < 80 ? 14 : cols < 120 ? 24 : 36;
|
|
50
|
-
const label = c.brightBlue(`${icons.repo} ${truncField(`${owner}/${name}`, repoLen)}`);
|
|
51
|
-
left.push(hyperlink(url, label));
|
|
52
|
-
}
|
|
53
55
|
// Added dirs badge — only when count > 0; warning color at >= 5
|
|
54
56
|
if (display.addedDirs && input.addedDirsCount != null && input.addedDirsCount > 0) {
|
|
55
57
|
const badge = `+${input.addedDirsCount} dirs`;
|
|
@@ -118,8 +120,11 @@ export function renderLine1(ctx, c) {
|
|
|
118
120
|
right.push(c.gray(input.outputStyle));
|
|
119
121
|
}
|
|
120
122
|
// Version — link to the Claude Code npm page for quick changelog lookup.
|
|
123
|
+
// Kept as its own segment (not pushed straight into `right`) because packed
|
|
124
|
+
// mode pins it alone to the true right edge while everything else packs left.
|
|
125
|
+
let versionSeg = null;
|
|
121
126
|
if (display.version && input.version) {
|
|
122
|
-
|
|
127
|
+
versionSeg = hyperlink(`https://www.npmjs.com/package/@anthropic-ai/claude-code/v/${encodeURIComponent(input.version)}`, c.dim(`v${input.version}`));
|
|
123
128
|
}
|
|
124
129
|
// Custom commands (issue #143 phase 3) — appended last on the left so they
|
|
125
130
|
// sit after core widgets and evict first under fitSegments' narrow-cols
|
|
@@ -129,8 +134,18 @@ export function renderLine1(ctx, c) {
|
|
|
129
134
|
if (seg)
|
|
130
135
|
left.push(seg);
|
|
131
136
|
}
|
|
132
|
-
if (left.length === 0 && right.length === 0)
|
|
137
|
+
if (left.length === 0 && right.length === 0 && !versionSeg)
|
|
133
138
|
return '';
|
|
134
|
-
|
|
139
|
+
// packed: pack every non-version segment tightly to the left with a single
|
|
140
|
+
// separator (no forced middle gap), leaving ONLY the version string pinned to
|
|
141
|
+
// the true right edge — the same idiom line2 uses for its small right-anchored
|
|
142
|
+
// cluster. fitSegments already leaves a gap before a short right array, and
|
|
143
|
+
// drops it whole when it can't fit (version vanishes rather than truncating on
|
|
144
|
+
// a narrow terminal — an accepted all-or-nothing tradeoff, see README).
|
|
145
|
+
if (line1Align === 'packed') {
|
|
146
|
+
return fitSegments(left.concat(right), versionSeg ? [versionSeg] : [], SEP, cols);
|
|
147
|
+
}
|
|
148
|
+
// justified: version rejoins the tail of the right cluster, pinned to the edge.
|
|
149
|
+
return fitSegments(left, versionSeg ? [...right, versionSeg] : right, SEP, cols);
|
|
135
150
|
}
|
|
136
151
|
//# sourceMappingURL=line1.js.map
|
|
@@ -69,27 +69,28 @@ function buildSegments(ctx, palette, c) {
|
|
|
69
69
|
priority: 80,
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
|
-
if (display.
|
|
73
|
-
const
|
|
72
|
+
if (display.repo && input.repo) {
|
|
73
|
+
const { owner, name, url } = input.repo;
|
|
74
|
+
// Priority 61 — above directory@60. Repo is git identity (owner/name of the
|
|
75
|
+
// remote), grouped with branch, so it survives narrow-terminal pressure a
|
|
76
|
+
// step longer than the local directory. Rendered before directory to keep
|
|
77
|
+
// the git-identity segments adjacent. Same dir-family background.
|
|
74
78
|
segments.push({
|
|
75
|
-
text: hyperlink(
|
|
76
|
-
icon: icons.
|
|
79
|
+
text: hyperlink(url, truncField(`${owner}/${name}`, 36)),
|
|
80
|
+
icon: icons.repo,
|
|
77
81
|
bg: palette.dirBg,
|
|
78
82
|
fg: palette.fg,
|
|
79
|
-
priority:
|
|
83
|
+
priority: 61,
|
|
80
84
|
});
|
|
81
85
|
}
|
|
82
|
-
if (display.
|
|
83
|
-
const
|
|
84
|
-
// Priority 58 — below directory@60 and the addedDirs badge@59. Repo is the
|
|
85
|
-
// canonical remote identity (annotates the local dir), so it cedes first
|
|
86
|
-
// under narrow-terminal pressure. Same dir-family background as directory.
|
|
86
|
+
if (display.directory && input.cwd) {
|
|
87
|
+
const dirName = basename(input.cwd) || input.cwd;
|
|
87
88
|
segments.push({
|
|
88
|
-
text: hyperlink(
|
|
89
|
-
icon: icons.
|
|
89
|
+
text: hyperlink(pathToFileURL(input.cwd).href, truncField(dirName, 30)),
|
|
90
|
+
icon: icons.folder,
|
|
90
91
|
bg: palette.dirBg,
|
|
91
92
|
fg: palette.fg,
|
|
92
|
-
priority:
|
|
93
|
+
priority: 60,
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
if (display.addedDirs && input.addedDirsCount != null && input.addedDirsCount > 0) {
|
package/dist/render/text.js
CHANGED
|
@@ -15,7 +15,11 @@ export function displayWidth(str) {
|
|
|
15
15
|
w += 1;
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
// 0x2300–0x24FF covers wide Misc-Technical / Enclosed-Alphanumeric symbols
|
|
19
|
+
// but STOPS before the Box Drawing block (U+2500–U+257F), whose glyphs —
|
|
20
|
+
// including │ (U+2502), lumira's default SEP separator — are single-cell.
|
|
21
|
+
// Counting them as 2 over-reserved width on every separated line.
|
|
22
|
+
if (cp >= 0x1F000 || (cp >= 0x2300 && cp <= 0x24FF) || (cp >= 0x25A0 && cp <= 0x25FF) ||
|
|
19
23
|
(cp >= 0x2600 && cp <= 0x27BF) || (cp >= 0x2B00 && cp <= 0x2BFF) ||
|
|
20
24
|
(cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3000 && cp <= 0x303F) || (cp >= 0xFF00 && cp <= 0xFFEF)) {
|
|
21
25
|
w += 2;
|
|
@@ -53,13 +57,21 @@ export function truncatePath(str, maxLen = 20) {
|
|
|
53
57
|
return filename.slice(0, maxLen - 3) + '...';
|
|
54
58
|
return '.../' + filename;
|
|
55
59
|
}
|
|
60
|
+
// The usable width for a status line: total cols minus a 4-cell right margin.
|
|
61
|
+
// The margin absorbs terminals that reserve a trailing cell and prevents the
|
|
62
|
+
// off-by-one wraps that motivated it. Kept as the single source of this
|
|
63
|
+
// constant so callers computing leftover/fill width can't drift out of sync
|
|
64
|
+
// with fitSegments' own bound.
|
|
65
|
+
export function safeCols(cols) {
|
|
66
|
+
return Math.max(1, cols - 4);
|
|
67
|
+
}
|
|
56
68
|
export function fitSegments(left, right, sep, cols) {
|
|
57
|
-
const
|
|
69
|
+
const safe = safeCols(cols);
|
|
58
70
|
for (let l = left.length; l >= 1; l--) {
|
|
59
71
|
const lSlice = left.slice(0, l);
|
|
60
72
|
const leftStr = lSlice.join(sep);
|
|
61
73
|
const leftW = displayWidth(leftStr);
|
|
62
|
-
if (leftW >
|
|
74
|
+
if (leftW > safe)
|
|
63
75
|
continue;
|
|
64
76
|
for (let r = right.length; r >= 0; r--) {
|
|
65
77
|
const rSlice = right.slice(0, r);
|
|
@@ -67,8 +79,8 @@ export function fitSegments(left, right, sep, cols) {
|
|
|
67
79
|
return leftStr;
|
|
68
80
|
const rightStr = rSlice.join(sep);
|
|
69
81
|
const rightW = displayWidth(rightStr);
|
|
70
|
-
if (leftW + 1 + rightW <=
|
|
71
|
-
const gap = Math.max(1,
|
|
82
|
+
if (leftW + 1 + rightW <= safe) {
|
|
83
|
+
const gap = Math.max(1, safe - leftW - rightW);
|
|
72
84
|
return leftStr + ' '.repeat(gap) + rightStr;
|
|
73
85
|
}
|
|
74
86
|
}
|
|
@@ -77,7 +89,7 @@ export function fitSegments(left, right, sep, cols) {
|
|
|
77
89
|
// Safe because left[0] is the model name (~20 chars) — callers must ensure
|
|
78
90
|
// the first segment is short enough to truncate gracefully.
|
|
79
91
|
// Strip ANSI before hard-truncating to avoid cutting mid-escape-sequence.
|
|
80
|
-
return truncField(stripAnsi(left[0] ?? ''),
|
|
92
|
+
return truncField(stripAnsi(left[0] ?? ''), safe);
|
|
81
93
|
}
|
|
82
94
|
export function padLine(left, right, cols) {
|
|
83
95
|
const leftW = displayWidth(left);
|
package/dist/types.js
CHANGED
|
@@ -123,6 +123,7 @@ export const DEFAULT_DISPLAY = {
|
|
|
123
123
|
};
|
|
124
124
|
export const DEFAULT_CONFIG = {
|
|
125
125
|
layout: 'auto',
|
|
126
|
+
line1Align: 'justified',
|
|
126
127
|
// GSD on by default, mirroring GSD's own always-on statusline. Self-gates to
|
|
127
128
|
// nothing when there's no .planning/STATE.md and no update-check cache, so
|
|
128
129
|
// non-GSD users see no extra line and pay only a few cheap existsSync checks.
|
package/dist/utils/terminal.js
CHANGED
|
@@ -46,12 +46,17 @@ export function getTermCols() {
|
|
|
46
46
|
return 120;
|
|
47
47
|
}
|
|
48
48
|
// When stdout isn't a TTY (the statusline case — Claude Code pipes our output)
|
|
49
|
-
// we
|
|
50
|
-
//
|
|
51
|
-
// the
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
49
|
+
// we trust the resolved rawCols since proc-tree / COLUMNS / tput give the real
|
|
50
|
+
// terminal width, and use the full width (factor 1.0). Earlier versions reserved
|
|
51
|
+
// headroom (0.7, then 0.9) on the theory that CC appends chrome (separators,
|
|
52
|
+
// gutters) to the statusline's own row. Inspecting a real render showed that is
|
|
53
|
+
// not the case: CC's own hints ("bypass permissions …", "PR #…", "← for agents")
|
|
54
|
+
// render on a SEPARATE line below the statusline, never appended to its row — so
|
|
55
|
+
// the reservation was speculative and only ever showed up as empty space at the
|
|
56
|
+
// far-right edge. Every renderer that treats `cols` as a width budget already
|
|
57
|
+
// protects itself: line1/line2 via fitSegments and the powerline lines via
|
|
58
|
+
// renderPowerline both subtract a proven 4-column margin (see text.ts / powerline.ts).
|
|
59
|
+
export function getLayoutCols(rawCols, isTTY, factor = 1.0) {
|
|
55
60
|
if (isTTY)
|
|
56
61
|
return rawCols;
|
|
57
62
|
const clamped = Math.min(Math.max(factor, 0.3), 1.0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumira",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.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",
|