lumira 1.8.1 → 1.9.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.
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "lumira",
3
+ "owner": {
4
+ "name": "Carlos Cativo"
5
+ },
6
+ "metadata": {
7
+ "description": "Real-time statusline HUD for Claude Code and Qwen Code — analytics, quota projection, themes, powerline",
8
+ "version": "1.9.0",
9
+ "homepage": "https://github.com/cativo23/lumira"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "lumira",
14
+ "source": "./",
15
+ "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.",
16
+ "version": "1.9.0",
17
+ "author": {
18
+ "name": "Carlos Cativo"
19
+ },
20
+ "homepage": "https://github.com/cativo23/lumira#readme",
21
+ "repository": "https://github.com/cativo23/lumira",
22
+ "license": "MIT",
23
+ "keywords": [
24
+ "statusline",
25
+ "hud",
26
+ "claude-code",
27
+ "qwen-code",
28
+ "analytics",
29
+ "metrics",
30
+ "terminal",
31
+ "powerline",
32
+ "themes"
33
+ ]
34
+ }
35
+ ],
36
+ "version": "1.9.0"
37
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "lumira",
3
+ "version": "1.9.0",
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
+ "author": {
6
+ "name": "Carlos Cativo"
7
+ },
8
+ "repository": "https://github.com/cativo23/lumira",
9
+ "homepage": "https://github.com/cativo23/lumira#readme",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "claude-code",
13
+ "qwen-code",
14
+ "statusline",
15
+ "hud",
16
+ "terminal",
17
+ "analytics",
18
+ "session",
19
+ "metrics",
20
+ "nerd-font",
21
+ "powerline",
22
+ "themes"
23
+ ],
24
+ "skills": "./skills/"
25
+ }
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.8.1:** the GSD widget now mirrors get-shit-done (GSD)'s own statusline — phase/milestone lifecycle, a milestone progress bar, and `⬆ /gsd:update` / `⚠ stale hooks` indicators that show in any project. GSD support is on by default and self-gates (no GSD project → nothing renders). Earlier releases added the compaction counter `⊙ N` (v1.8.0), added-dirs badge + worktree breadcrumb (v1.7.0), [`lumira stats` CLI](#stats-cli) (v1.5), `API N%` latency widget (v1.4.0), 7-day quota projection (v1.3.0), and the auto-compact proximity glyph ⚠ (v1.4.1).
29
+ > **What's new in v1.8.2:** the installer now writes a fast per-render command — it runs the compiled `lumira` binary directly (~10× faster than `npx lumira@latest`, which hit the npm registry on every render) and migrates existing setups automatically. v1.8.1 brought the GSD widget to parity with get-shit-done (GSD)'s own statusline — phase/milestone lifecycle, a milestone progress bar, and `⬆ /gsd:update` / `⚠ stale hooks` indicators that show in any project (on by default, self-gating). Earlier releases added the compaction counter `⊙ N` (v1.8.0), added-dirs badge + worktree breadcrumb (v1.7.0), [`lumira stats` CLI](#stats-cli) (v1.5), `API N%` latency widget (v1.4.0), 7-day quota projection (v1.3.0), and the auto-compact proximity glyph ⚠ (v1.4.1).
30
30
 
31
31
  ## Table of contents
32
32
 
@@ -103,6 +103,8 @@ npx lumira install
103
103
 
104
104
  The installer walks you through three choices — **preset** (`full` / `balanced` / `minimal`), **theme**, and **icons** — showing a live preview at each step. Press `Esc` to abort without writing anything. In non-interactive shells (piped stdin, CI), the installer skips the wizard and writes sensible defaults (`preset: balanced`, `icons: nerd`). If Qwen Code is detected (`~/.qwen/` exists), the `/lumira` skill is installed for both CLIs.
105
105
 
106
+ For the fastest statusline (the command runs on **every** render), the installer offers to install lumira globally so it can invoke the compiled binary directly (`lumira`, ~60ms) instead of `npx` (which is ~10× slower). It also migrates older `npx lumira@latest` setups to the faster form automatically.
107
+
106
108
  Or install globally:
107
109
 
108
110
  ```bash
@@ -120,19 +122,19 @@ Your preferences are saved to `~/.config/lumira/config.json` — hand-edited key
120
122
 
121
123
  ### Manual setup
122
124
 
123
- Add to `~/.claude/settings.json`:
125
+ The `statusLine.command` runs on every render, so prefer the **direct binary**. Install globally (`npm install -g lumira`), then add to `~/.claude/settings.json`:
124
126
 
125
127
  ```json
126
128
  {
127
129
  "statusLine": {
128
130
  "type": "command",
129
- "command": "npx lumira@latest",
131
+ "command": "lumira",
130
132
  "padding": 0
131
133
  }
132
134
  }
133
135
  ```
134
136
 
135
- If installed from source:
137
+ If installed from source, point at the compiled entry:
136
138
 
137
139
  ```json
138
140
  {
@@ -144,6 +146,8 @@ If installed from source:
144
146
  }
145
147
  ```
146
148
 
149
+ > Without a global install you can use `"command": "npx lumira"` — it works, but resolves through npx on every render (~10× slower). Avoid `npx lumira@latest`: the `@latest` hits the npm registry on every render.
150
+
147
151
  ## Display
148
152
 
149
153
  ### Custom Mode (default, >=70 columns)
package/dist/index.js CHANGED
File without changes
package/dist/installer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, existsSync, copyFileSync, unlinkSync, mkdirSync, rmdirSync, renameSync, openSync, writeSync, fsyncSync, closeSync } from 'node:fs';
2
2
  import { join, dirname, resolve } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
+ import { execFileSync } from 'node:child_process';
4
5
  import { sanitizeTermString } from './normalize.js';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import { createInterface } from 'node:readline';
@@ -17,11 +18,67 @@ const ok = (msg) => `${GREEN}✓${RST} ${msg}`;
17
18
  const warn = (msg) => `${YELLOW}⚠${RST} ${msg}`;
18
19
  const header = () => `\n${CYAN} lumira installer${RST}\n`;
19
20
  // ── StatusLine value ────────────────────────────────────────────────
20
- const LUMIRA_STATUSLINE = {
21
- type: 'command',
22
- command: 'npx lumira@latest',
23
- padding: 0,
24
- };
21
+ // The per-render command. `lumira` (a real global bin) runs the compiled
22
+ // binary directly (~60ms). `npx lumira` resolves from cache (~150-300ms).
23
+ // `npx lumira@latest` hits the npm registry EVERY render (~600ms) — never
24
+ // write that form; it's the perf bug this installer migrates away from.
25
+ function makeStatusLine(command) {
26
+ return { type: 'command', command, padding: 0 };
27
+ }
28
+ // Rank a statusLine command by per-render speed (higher = faster).
29
+ // 2 = direct binary (`lumira`, `node …/dist/index.js`, ${CLAUDE_PLUGIN_ROOT})
30
+ // 1 = npx, cached (`npx lumira`)
31
+ // 0 = npx, registry (`npx lumira@latest` / any pinned `@version`)
32
+ // Used to decide migration: only ever rewrite TOWARD a faster form.
33
+ export function commandSpeed(command) {
34
+ const c = command.trim();
35
+ // `npx` as a bare word or a path basename (e.g. /usr/local/bin/npx, …\npx).
36
+ if (/(^|[\s/\\])npx(\s|$)/.test(c)) {
37
+ return /@(latest|\d)/.test(c) ? 0 : 1;
38
+ }
39
+ return 2;
40
+ }
41
+ // Is `lumira` resolvable as a global bin on PATH?
42
+ function defaultHasGlobalBin() {
43
+ const probe = process.platform === 'win32' ? 'where' : 'which';
44
+ try {
45
+ execFileSync(probe, ['lumira'], { stdio: 'ignore', timeout: 3000 });
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ // Install lumira globally so the per-render command can invoke it directly.
53
+ function defaultInstallGlobal() {
54
+ try {
55
+ execFileSync('npm', ['install', '-g', 'lumira'], { stdio: 'inherit', timeout: 120000 });
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ // Resolve the fastest statusLine command available in this environment.
63
+ // In a TTY with no global bin, offer to `npm i -g lumira` (confirmed) so the
64
+ // command can be the direct `lumira`; otherwise fall back to cached `npx lumira`.
65
+ async function resolveStatusLineCommand(ctx) {
66
+ if (ctx.hasGlobalBin())
67
+ return 'lumira';
68
+ if (ctx.isTTY) {
69
+ const accepted = await ctx.confirm('Install lumira globally for ~10× faster rendering (npm i -g lumira)?');
70
+ if (accepted) {
71
+ if (ctx.installGlobal()) {
72
+ ctx.lines.push(ok('Installed lumira globally — statusline runs the compiled binary directly'));
73
+ return 'lumira';
74
+ }
75
+ ctx.lines.push(warn('Global install failed — using npx for now (run npm i -g lumira later for full speed)'));
76
+ return 'npx lumira';
77
+ }
78
+ ctx.lines.push(` ${DIM}Tip: npm i -g lumira for ~10× faster rendering${RST}`);
79
+ }
80
+ return 'npx lumira';
81
+ }
25
82
  function defaultSettingsPath() {
26
83
  return join(homedir(), '.claude', 'settings.json');
27
84
  }
@@ -95,6 +152,8 @@ export async function install(opts = {}) {
95
152
  const confirm = opts.confirm ?? promptYN;
96
153
  const stdin = opts.stdin ?? process.stdin;
97
154
  const stdout = opts.stdout ?? process.stdout;
155
+ const hasGlobalBin = opts.hasGlobalBin ?? defaultHasGlobalBin;
156
+ const installGlobal = opts.installGlobal ?? defaultInstallGlobal;
98
157
  const lines = [];
99
158
  // Build banner prelude (shown on each wizard frame so it survives screen clears)
100
159
  let prelude = '';
@@ -159,20 +218,41 @@ export async function install(opts = {}) {
159
218
  wizard = { preset: 'balanced', icons: 'nerd' };
160
219
  lines.push(ok('Non-interactive mode — using defaults (preset: balanced, icons: nerd)'));
161
220
  }
162
- // ── settings.json replace/backup ───────────────────────────────
163
- if (settings.statusLine) {
164
- if (isLumira(settings.statusLine)) {
165
- lines.push(ok('lumira is already configured as your statusline'));
166
- saveConfig(wizard, configPath);
167
- lines.push(ok(`Saved config → ${DIM}${configPath}${RST}`));
168
- emitFooter(lines, opts.homeOverride);
169
- return lines.join('\n') + '\n';
221
+ // Save config + emit footer + render output. Shared by every exit below.
222
+ const finalize = () => {
223
+ saveConfig(wizard, configPath);
224
+ lines.push(ok(`Saved config ${DIM}${configPath}${RST}`));
225
+ emitFooter(lines, opts.homeOverride);
226
+ return lines.join('\n') + '\n';
227
+ };
228
+ // ── settings.json replace / backup / migrate ───────────────────
229
+ const existingIsLumira = !!settings.statusLine && isLumira(settings.statusLine);
230
+ const existingCmd = existingIsLumira
231
+ ? String(settings.statusLine.command ?? '')
232
+ : '';
233
+ // Already on the fastest form (direct binary) — nothing to rewrite.
234
+ if (existingIsLumira && commandSpeed(existingCmd) >= 2) {
235
+ lines.push(ok('lumira is already configured (optimal command)'));
236
+ return finalize();
237
+ }
238
+ // Resolve the fastest per-render command this environment can offer.
239
+ const resolvedCmd = await resolveStatusLineCommand({
240
+ isTTY: !!stdin?.isTTY, confirm, hasGlobalBin, installGlobal, lines,
241
+ });
242
+ if (existingIsLumira) {
243
+ // Existing lumira command (npx form) — only rewrite if strictly faster,
244
+ // so we never downgrade a user's direct binary to npx.
245
+ if (commandSpeed(resolvedCmd) <= commandSpeed(existingCmd)) {
246
+ lines.push(ok('lumira is already configured'));
247
+ return finalize();
170
248
  }
249
+ }
250
+ else if (settings.statusLine) {
171
251
  // Foreign statusLine already confirmed above — back it up and replace.
172
252
  copyFileSync(settingsPath, backupPath);
173
253
  lines.push(ok(`Backed up existing settings → ${DIM}settings.json.lumira.bak${RST}`));
174
254
  }
175
- settings.statusLine = { ...LUMIRA_STATUSLINE };
255
+ settings.statusLine = makeStatusLine(resolvedCmd);
176
256
  mkdirSync(dirname(settingsPath), { recursive: true });
177
257
  const tmp = `${settingsPath}.${process.pid}.${Date.now()}.lumira.tmp`;
178
258
  try {
@@ -189,11 +269,10 @@ export async function install(opts = {}) {
189
269
  catch { }
190
270
  throw e;
191
271
  }
192
- lines.push(ok('Configured lumira as statusline'));
193
- saveConfig(wizard, configPath);
194
- lines.push(ok(`Saved config ${DIM}${configPath}${RST}`));
195
- emitFooter(lines, opts.homeOverride);
196
- return lines.join('\n') + '\n';
272
+ lines.push(ok(existingIsLumira
273
+ ? `Upgraded statusline command → ${DIM}${resolvedCmd}${RST} (faster)`
274
+ : 'Configured lumira as statusline'));
275
+ return finalize();
197
276
  }
198
277
  // ── Uninstall ───────────────────────────────────────────────────────
199
278
  export function uninstall(opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumira",
3
- "version": "1.8.1",
3
+ "version": "1.9.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",
@@ -15,6 +15,7 @@
15
15
  "test:coverage": "vitest run --coverage",
16
16
  "lint": "tsc --noEmit",
17
17
  "themes:validate": "npm run build && node scripts/validate-themes.mjs",
18
+ "version": "node scripts/bump-plugin-version.mjs",
18
19
  "prepublishOnly": "npm run build && npm run lint && node scripts/validate-themes.mjs && npm run test"
19
20
  },
20
21
  "devDependencies": {
@@ -50,6 +51,7 @@
50
51
  "files": [
51
52
  "dist",
52
53
  "skills",
54
+ ".claude-plugin",
53
55
  "!dist/**/*.map",
54
56
  "!dist/**/*.d.ts"
55
57
  ]
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: setup
3
+ description: Use after installing the lumira plugin to activate the statusline. Writes statusLine.command to ~/.claude/settings.json pointing to the plugin-cached binary. Run this once after /plugin marketplace add cativo23/lumira.
4
+ allowed-tools: Bash, Read, Write
5
+ license: MIT
6
+ ---
7
+
8
+ # /lumira:setup — Activate lumira statusline
9
+
10
+ You activate the **lumira** statusline for Claude Code after plugin installation.
11
+
12
+ ## Workflow
13
+
14
+ 1. **Find install path** — Read `~/.claude/plugins/installed_plugins.json`. Look for any key matching `lumira@*` (the exact marketplace key depends on how the user installed it). Extract `installPath` from the first match.
15
+ 2. **Verify binary** — Check that `<installPath>/dist/index.js` exists.
16
+ 3. **Read settings** — Read `~/.claude/settings.json`. If the file is **missing**, start from `{}`. If it is **present but invalid JSON**, stop and report the parse error — do not overwrite.
17
+ 4. **Check existing** — If `statusLine.command` is already set to any non-empty value, tell the user what's currently set and ask if they want to replace it. Do not proceed until they confirm. If it is empty or absent, skip this step.
18
+ 5. **Write command** — Set `statusLine.command` to `node "<installPath>/dist/index.js"`.
19
+ 6. **Init config** — If `~/.config/lumira/config.json` does not exist, create it with `{"preset": "balanced"}`. Never overwrite an existing config.
20
+ 7. **Confirm and instruct** — Tell the user what was written and that they must restart Claude Code.
21
+
22
+ ## Finding the install path
23
+
24
+ ```bash
25
+ cat ~/.claude/plugins/installed_plugins.json
26
+ ```
27
+
28
+ Parse the JSON and find the entry whose key starts with `lumira@`. The `installPath` field is the absolute path to the cached plugin directory.
29
+
30
+ If multiple entries match (unlikely), use the one with the most recent `lastUpdated`.
31
+
32
+ ## settings.json merge rules
33
+
34
+ - Always read the full file first, then patch only `statusLine.command`.
35
+ - Write back the complete merged object — never truncate other fields.
36
+ - If the file is missing, create it with just `{"statusLine": {"command": "<node command>"}}`.
37
+ - If the file is not valid JSON, report the parse error and stop. Do not overwrite.
38
+
39
+ ## Success output
40
+
41
+ After writing, tell the user:
42
+ - The exact command written
43
+ - That they need to **restart Claude Code** for the statusline to appear
44
+ - "Run /lumira:lumira to customize preset, theme, or display widgets"
45
+
46
+ ## Error cases
47
+
48
+ - No `lumira@*` key in installed_plugins.json → "Lumira doesn't appear to be installed as a plugin. Run: `/plugin marketplace add cativo23/lumira` then try again."
49
+ - `dist/index.js` missing at install path → "Plugin installed but dist/ is missing. Reinstall: uninstall lumira then `/plugin marketplace add cativo23/lumira`."
50
+ - settings.json parse error → show the exact error, stop, do not write.
51
+
52
+ ## Language
53
+
54
+ Respond in the user's language. Spanish input → Rioplatense Spanish (voseo: "vos tenés", "reiniciá", "fijate").