lumira 1.12.2 → 1.13.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.
@@ -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.12.2"
8
+ "version": "1.13.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.12.2",
15
+ "version": "1.13.0",
16
16
  "author": {
17
17
  "name": "Carlos Cativo"
18
18
  },
@@ -32,5 +32,5 @@
32
32
  ]
33
33
  }
34
34
  ],
35
- "version": "1.12.2"
35
+ "version": "1.13.0"
36
36
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumira",
3
- "version": "1.12.2",
3
+ "version": "1.13.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
@@ -87,7 +87,7 @@ See [`docs/competitive-comparison.md`](docs/competitive-comparison.md) for the f
87
87
  ## Requirements
88
88
 
89
89
  - **Node ≥18**
90
- - **Nerd Font** (recommended) — for branch, folder, model, and spinner icons. Falls back to plain glyphs via `icons: emoji` or `icons: none`.
90
+ - **Nerd Font** (recommended) — for branch, folder, model, and spinner icons. **Use a recent build (Nerd Fonts v3.0+);** some indicators use newer Material Design Icons codepoints (e.g. the extended-thinking glyph `U+F09D1`) that older font releases render as a blank/tofu cell. Falls back to plain glyphs via `icons: emoji` or `icons: none`.
91
91
  - **Truecolor terminal** (for themes / powerline) — auto-detected via `COLORTERM=truecolor`. 256-color terminals get a nearest-index projection; named-ANSI terminals fall back to default colors silently.
92
92
 
93
93
  ## Features
@@ -95,7 +95,7 @@ See [`docs/competitive-comparison.md`](docs/competitive-comparison.md) for the f
95
95
  - **Context bar with thresholds** — green → yellow → orange → blinking red, plus an actionable `/compact?` hint when fill is high.
96
96
  - **Session intelligence** — pace delta (🐢/🏎️) shows whether you're burning quota faster than the time window allows, with ETA when ahead of pace. Live agent count and cache hit rate round it out.
97
97
  - **Powerline mode** + 7 separator presets (`arrow`, `flame`, `slant`, `round`, `diamond`, `compatible`, `plain`) across 3 lines.
98
- - **OSC 8 hyperlinks** — clickable directory and version tag on iTerm2, WezTerm, Kitty, VS Code, Alacritty.
98
+ - **OSC 8 hyperlinks** — clickable directory, repo, PR, and version tag on iTerm2, WezTerm, Kitty, VS Code, Alacritty.
99
99
  - **7 hand-curated themes** — `dracula`, `nord`, `tokyo-night`, `catppuccin`, `monokai`, `gruvbox`, `solarized`. WCAG AA contrast guaranteed in CI.
100
100
  - **Token + cost metrics** — input/output counts, speed (tok/s), $ total + burn rate ($/h), cache hit rate.
101
101
  - **Auto-fits at <70 cols** — switches from 3-line custom mode to single-line minimal automatically.
@@ -106,6 +106,7 @@ See [`docs/competitive-comparison.md`](docs/competitive-comparison.md) for the f
106
106
  <summary>Everything else lumira shows</summary>
107
107
 
108
108
  - **Git status** — branch + staged/modified/untracked counts, 5s TTL cache. Branch turns red on dirty repos in powerline mode.
109
+ - **Repo identity** — when Claude Code exposes `workspace.repo`, line1 shows `owner/name` as a segment linked (OSC 8) to the repo on its host. Toggle via `display.repo`.
109
110
  - **Rate limits** — 5h/7d usage as a battery glyph (Nerd Font level fill, or 🔋/🪫 in emoji mode) with color tier and reset countdown. Threshold-gated at 50% to stay invisible while you have margin.
110
111
  - **Pace delta** — `usedPct − elapsedPct` of the 5h window. Turtle when behind pace (healthy), car with time-to-exhaustion when ahead. Color escalates green → yellow → orange → blinkRed. Toggle independently via `display.paceDelta`.
111
112
  - **7d quota projection** — when the current burn rate would exhaust the 7d quota before the window resets, the 7d segment grows a warning: `⚠ ~24h`, `⚠ ~2d`, `⚠ Tue`, or `🔥 ~8h` (critical icon under 12h). Default on. Toggle via `display.quotaProjection`; off in the `minimal` preset. Different from pace delta — pace looks backwards at the 5h window's actual vs proportional burn, projection looks forwards at the 7d window's exhaustion ETA.
package/dist/config.js CHANGED
@@ -249,6 +249,7 @@ const PRESET_DEFS = {
249
249
  display: {
250
250
  agents: true,
251
251
  pr: true,
252
+ repo: true,
252
253
  thinking: true,
253
254
  burnRate: false,
254
255
  duration: false,
@@ -297,6 +298,7 @@ const PRESET_DEFS = {
297
298
  worktreeBreadcrumb: false,
298
299
  compactionCount: false,
299
300
  pr: false,
301
+ repo: false,
300
302
  thinking: false,
301
303
  },
302
304
  },
package/dist/normalize.js CHANGED
@@ -123,7 +123,7 @@ export function normalize(input) {
123
123
  // For claude-code, honors CLAUDE_CODE_AUTO_COMPACT_WINDOW env var — a fill-%
124
124
  // threshold (1-100) that mirrors Claude Code's own auto-compact trigger point.
125
125
  // Users who changed this setting in Claude Code should set the same value here.
126
- // Falls back to the hardcoded 80% default when absent or invalid.
126
+ // Falls back to the hardcoded 84% default when absent or invalid.
127
127
  const effectivePct = realUsedPercentage ?? contextWindow.used_percentage ?? 0;
128
128
  let platformAutoCompactThreshold = AUTO_COMPACT_THRESHOLD[platform];
129
129
  if (platform === 'claude-code') {
@@ -200,9 +200,24 @@ export function normalize(input) {
200
200
  pr = { number: n, url: prUrl, reviewState: prReviewState };
201
201
  }
202
202
  }
203
+ // Repository identity from workspace.repo (CC parses host/owner/name from the
204
+ // origin remote). All three parts must be present and well-formed: the url is
205
+ // rendered as an OSC 8 hyperlink, so each part is validated against a strict
206
+ // pattern to keep a malformed payload from injecting into the link target.
207
+ let repo;
208
+ const rawRepo = input.workspace?.repo;
209
+ if (rawRepo != null) {
210
+ const host = typeof rawRepo.host === 'string' ? sanitizeTermString(rawRepo.host) : '';
211
+ const owner = typeof rawRepo.owner === 'string' ? sanitizeTermString(rawRepo.owner) : '';
212
+ const name = typeof rawRepo.name === 'string' ? sanitizeTermString(rawRepo.name) : '';
213
+ if (/^[a-zA-Z0-9.-]+$/.test(host) && /^[\w.-]+$/.test(owner) && /^[\w.-]+$/.test(name)) {
214
+ repo = { host, owner, name, url: `https://${host}/${owner}/${name}` };
215
+ }
216
+ }
203
217
  return {
204
218
  platform,
205
219
  model: sanitizeTermString(modelName),
220
+ repo,
206
221
  sessionId: sanitizeTermString(input.session_id),
207
222
  version: input.version ? sanitizeTermString(input.version) : undefined,
208
223
  cwd: sanitizeTermString(cwd),
@@ -235,7 +250,13 @@ export function normalize(input) {
235
250
  ? sanitizeTermString(claude.effort.level)
236
251
  : undefined,
237
252
  thinkingEnabled: claude?.thinking?.enabled === true ? true : undefined,
238
- worktreeName: input.worktree?.name ? sanitizeTermString(input.worktree.name) : undefined,
253
+ // Prefer the top-level worktree.name; fall back to workspace.git_worktree,
254
+ // which CC populates for ANY git worktree (verified on v2.1.193) — even
255
+ // sessions not started with --worktree, where worktree.name is absent.
256
+ worktreeName: (() => {
257
+ const n = input.worktree?.name ?? input.workspace?.git_worktree;
258
+ return n ? sanitizeTermString(n) : undefined;
259
+ })(),
239
260
  addedDirsCount: (() => {
240
261
  const dirs = input.workspace?.added_dirs;
241
262
  if (!Array.isArray(dirs) || dirs.length === 0)
@@ -61,6 +61,7 @@ export const NERD_ICONS = {
61
61
  turtle: '🐢', // turtle — pace-behind indicator
62
62
  lightning: '󱐋', // U+F140B — nerd lightning-bolt (was ⚡ emoji); cache hit rate
63
63
  pr: '', // nf-cod-git_pull_request
64
+ repo: '', // U+EA62 nf-cod-repo
64
65
  thinking: '󰧑', // U+F09D1 — clearer "thinking" glyph; nf-md-brain (U+F1824) renders faint in many fonts
65
66
  battery: nerdBattery,
66
67
  };
@@ -86,6 +87,7 @@ export const EMOJI_ICONS = {
86
87
  turtle: '\u{1F422}', // 🐢 — turtle
87
88
  lightning: '⚡', // ⚡ — cache hit rate
88
89
  pr: '\u{1F500}', // 🔀 — twisted rightwards arrows (PR)
90
+ repo: '\u{1F4E6}', // 📦 — package (repository)
89
91
  thinking: '\u{1F4AD}', // 💭 — thought bubble
90
92
  battery: (pct) => {
91
93
  if (!Number.isFinite(pct) || pct < 0)
@@ -119,6 +121,7 @@ export const NO_ICONS = {
119
121
  turtle: '',
120
122
  lightning: '',
121
123
  pr: 'PR',
124
+ repo: '',
122
125
  thinking: 'think',
123
126
  // No-icon mode keeps the legacy bolt fallback (currently empty) so users who
124
127
  // opted out of icons see no shape change from this feature.
@@ -40,6 +40,16 @@ export function renderLine1(ctx, c) {
40
40
  left.push(hyperlink(pathToFileURL(cwd).href, label));
41
41
  }
42
42
  }
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
+ }
43
53
  // Added dirs badge — only when count > 0; warning color at >= 5
44
54
  if (display.addedDirs && input.addedDirsCount != null && input.addedDirsCount > 0) {
45
55
  const badge = `+${input.addedDirsCount} dirs`;
@@ -79,6 +79,19 @@ function buildSegments(ctx, palette, c) {
79
79
  priority: 60,
80
80
  });
81
81
  }
82
+ if (display.repo && input.repo) {
83
+ const { owner, name, url } = input.repo;
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.
87
+ segments.push({
88
+ text: hyperlink(url, truncField(`${owner}/${name}`, 36)),
89
+ icon: icons.repo,
90
+ bg: palette.dirBg,
91
+ fg: palette.fg,
92
+ priority: 58,
93
+ });
94
+ }
82
95
  if (display.addedDirs && input.addedDirsCount != null && input.addedDirsCount > 0) {
83
96
  const badge = `+${input.addedDirsCount} dirs`;
84
97
  const bg = input.addedDirsCount >= 5 ? palette.taskBg : palette.versionBg;
@@ -115,13 +115,13 @@ function buildMockContext(opts) {
115
115
  * the context-bar auto-compact behavior they'll see in the live statusline.
116
116
  *
117
117
  * The platform thresholds referenced here are external constraints, not
118
- * lumira's: Claude Code auto-compacts at ~80% (hardcoded, reserves headroom
118
+ * lumira's: Claude Code auto-compacts at ~84% (hardcoded, reserves headroom
119
119
  * for output generation); Qwen Code defaults to 70% via the configurable
120
120
  * `model.chatCompression.contextPercentageThreshold`. The ⚠ glyph fires
121
121
  * 5pp before whichever applies — independent of user-tunable colour
122
122
  * thresholds, which track preference, not the platform's hard ceiling.
123
123
  */
124
- const CONTEXT_BAR_NOTE = '\n Context bar: ⚠ appears in the 5pp window before auto-compact fires (~80% on Claude Code,\n' +
124
+ const CONTEXT_BAR_NOTE = '\n Context bar: ⚠ appears in the 5pp window before auto-compact fires (~84% on Claude Code,\n' +
125
125
  ' 70% default on Qwen Code). New defaults (warning 65 / critical 78) turn the bar red before\n' +
126
126
  ' auto-compact triggers. Qwen users with a custom model.chatCompression.contextPercentageThreshold\n' +
127
127
  ' should mirror that value in lumira\'s contextCriticalThreshold for accurate gating.\n';
package/dist/types.js CHANGED
@@ -56,7 +56,7 @@ export const QUOTA_CRITICAL = 85;
56
56
  * Auto-compact threshold per platform — the % of context window at which
57
57
  * the platform automatically compacts the conversation history.
58
58
  *
59
- * - Claude Code: ~80% (hardcoded internally; reserves ~40K tokens for output generation).
59
+ * - Claude Code: ~84% (hardcoded internally; reserves ~30K tokens for output generation).
60
60
  * Not user-configurable in the main conversation. See anthropics/claude-code#34126.
61
61
  * - Qwen Code: 70% by default; user-configurable via
62
62
  * `model.chatCompression.contextPercentageThreshold` in qwen settings.json.
@@ -64,13 +64,19 @@ export const QUOTA_CRITICAL = 85;
64
64
  * `contextCriticalThreshold` — the constant below is the platform default only.
65
65
  */
66
66
  export const AUTO_COMPACT_THRESHOLD = {
67
- 'claude-code': 80,
67
+ // Measured on CC v2.1.193 by capturing the live statusline payload right up to
68
+ // an auto-compaction: the last render before context dropped was 166177/200000
69
+ // = 83.09% (CC's own "% until auto-compact" is NOT linear, so adding it to the
70
+ // fill over-estimated — only the pre-drop peak is reliable). Set to 84 so the
71
+ // nearAutoCompact warning window [79, 84) keeps the glyph lit through the real
72
+ // ~83.1% trigger (83 would extinguish it a hair before compaction).
73
+ 'claude-code': 84,
68
74
  'qwen-code': 70,
69
75
  };
70
76
  /**
71
77
  * Gap (in percentage points) before the auto-compact threshold at which the
72
78
  * `nearAutoCompact` warning glyph fires. The glyph appears in the window
73
- * `[AUTO_COMPACT_THRESHOLD - GAP, AUTO_COMPACT_THRESHOLD)` — i.e., 75-80%
79
+ * `[AUTO_COMPACT_THRESHOLD - GAP, AUTO_COMPACT_THRESHOLD)` — i.e., 79-84%
74
80
  * on Claude, 65-70% on Qwen. This is an immutable system constant (not the
75
81
  * same as the user-configurable `contextWarningThreshold`).
76
82
  */
@@ -110,6 +116,7 @@ export const DEFAULT_DISPLAY = {
110
116
  worktreeBreadcrumb: true,
111
117
  compactionCount: true,
112
118
  pr: true,
119
+ repo: true,
113
120
  thinking: true,
114
121
  contextWarningThreshold: DEFAULT_CONTEXT_WARNING_THRESHOLD,
115
122
  contextCriticalThreshold: DEFAULT_CONTEXT_CRITICAL_THRESHOLD,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumira",
3
- "version": "1.12.2",
3
+ "version": "1.13.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",