lumira 1.8.2 → 1.9.1
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 +36 -0
- package/.claude-plugin/plugin.json +25 -0
- package/README.md +26 -6
- package/dist/index.js +47 -3
- package/dist/normalize.js +26 -8
- package/dist/parsers/gsd.js +10 -6
- package/dist/parsers/transcript-stats.js +27 -5
- package/package.json +3 -1
- package/skills/setup/SKILL.md +64 -0
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "lumira",
|
|
13
|
+
"source": "./",
|
|
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.9.0",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Carlos Cativo"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/cativo23/lumira#readme",
|
|
20
|
+
"repository": "https://github.com/cativo23/lumira",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"statusline",
|
|
24
|
+
"hud",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"qwen-code",
|
|
27
|
+
"analytics",
|
|
28
|
+
"metrics",
|
|
29
|
+
"terminal",
|
|
30
|
+
"powerline",
|
|
31
|
+
"themes"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"version": "1.9.0"
|
|
36
|
+
}
|
|
@@ -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
|
@@ -8,6 +8,17 @@ Real-time statusline plugin for [Claude Code](https://code.claude.com) and Qwen
|
|
|
8
8
|
|
|
9
9
|
## Quick start
|
|
10
10
|
|
|
11
|
+
**Via Claude Code plugin (recommended):**
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
/plugin marketplace add cativo23/lumira
|
|
15
|
+
/lumira:setup
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
No npm required. The `/lumira:setup` skill writes `statusLine.command` automatically. Restart Claude Code when done.
|
|
19
|
+
|
|
20
|
+
**Via npm:**
|
|
21
|
+
|
|
11
22
|
```bash
|
|
12
23
|
npx lumira install
|
|
13
24
|
```
|
|
@@ -24,9 +35,7 @@ Interactive wizard — preset, theme, icons — previewed live before write.
|
|
|
24
35
|

|
|
25
36
|

|
|
26
37
|
|
|
27
|
-
> 3,
|
|
28
|
-
|
|
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).
|
|
38
|
+
> **What's new in v1.9.0:** lumira is now a **Claude Code plugin** — install with `/plugin marketplace add cativo23/lumira`, no npm required. Run `/lumira:setup` to activate. v1.8.2 made the installer write a fast per-render command (~10× faster). v1.8.1 brought the GSD widget to parity with GSD 1.42.3. Earlier: 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).
|
|
30
39
|
|
|
31
40
|
## Table of contents
|
|
32
41
|
|
|
@@ -95,15 +104,26 @@ Inspired by [claude-hud](https://github.com/jarrodwatts/claude-hud); takes a dif
|
|
|
95
104
|
|
|
96
105
|
## Install
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
### Option 1 — Claude Code plugin (recommended)
|
|
108
|
+
|
|
109
|
+
No npm required. Works with the Claude Code plugin marketplace:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
/plugin marketplace add cativo23/lumira
|
|
113
|
+
/lumira:setup
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`/lumira:setup` finds the cached binary, writes `statusLine.command` to `~/.claude/settings.json`, and creates a default config. Restart Claude Code when done. To customize afterward: `/lumira:lumira`.
|
|
117
|
+
|
|
118
|
+
### Option 2 — npm
|
|
99
119
|
|
|
100
120
|
```bash
|
|
101
121
|
npx lumira install
|
|
102
122
|
```
|
|
103
123
|
|
|
104
|
-
The installer walks you through
|
|
124
|
+
The installer walks you through **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
125
|
|
|
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` (
|
|
126
|
+
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` (~10× slower). It also migrates older `npx lumira@latest` setups to the faster form automatically.
|
|
107
127
|
|
|
108
128
|
Or install globally:
|
|
109
129
|
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { realpathSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
6
|
import { readStdin as defaultReadStdin, StdinParseError } from './stdin.js';
|
|
7
7
|
import { parseGitStatus } from './parsers/git.js';
|
|
8
8
|
import { parseTranscript } from './parsers/transcript.js';
|
|
@@ -98,9 +98,49 @@ function isDirectRun() {
|
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
function getVersion() {
|
|
102
|
+
try {
|
|
103
|
+
// Assumes this file lives at dist/index.js — one level below package.json.
|
|
104
|
+
const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../package.json');
|
|
105
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
106
|
+
return pkg.version ?? 'unknown';
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return 'unknown';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function printHelp() {
|
|
113
|
+
const version = getVersion();
|
|
114
|
+
const usage = `lumira v${version}
|
|
115
|
+
|
|
116
|
+
Usage: lumira [command]
|
|
117
|
+
|
|
118
|
+
Commands:
|
|
119
|
+
install Interactive setup wizard
|
|
120
|
+
uninstall Remove statusline configuration
|
|
121
|
+
stats [path] Show session statistics
|
|
122
|
+
themes Browse and preview themes
|
|
123
|
+
custom Manage custom commands
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
--help, -h Show this help
|
|
127
|
+
--version, -v Print version
|
|
128
|
+
|
|
129
|
+
Run lumira with no arguments to render the statusline (requires Claude Code stdin).
|
|
130
|
+
`;
|
|
131
|
+
process.stdout.write(usage);
|
|
132
|
+
}
|
|
101
133
|
if (isDirectRun()) {
|
|
102
134
|
const cmd = process.argv[2];
|
|
103
|
-
if (cmd === '
|
|
135
|
+
if (cmd === '--help' || cmd === '-h') {
|
|
136
|
+
printHelp();
|
|
137
|
+
process.exit(0);
|
|
138
|
+
}
|
|
139
|
+
else if (cmd === '--version' || cmd === '-v') {
|
|
140
|
+
process.stdout.write(`${getVersion()}\n`);
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
else if (cmd === 'install') {
|
|
104
144
|
const configPath = join(homedir(), '.config', 'lumira', 'config.json');
|
|
105
145
|
install({ configPath }).then(o => process.stdout.write(o)).catch(e => process.stderr.write(`Install error: ${e.message}\n`));
|
|
106
146
|
}
|
|
@@ -147,6 +187,10 @@ if (isDirectRun()) {
|
|
|
147
187
|
// event loop refed. Reads the spec JSON from its own stdin.
|
|
148
188
|
runCustomRefreshFromStdin().then(() => process.exit(0)).catch(() => process.exit(0));
|
|
149
189
|
}
|
|
190
|
+
else if (cmd !== undefined) {
|
|
191
|
+
process.stderr.write(`Unknown command: ${cmd}. Run with --help for usage.\n`);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
150
194
|
else {
|
|
151
195
|
main().then(o => process.stdout.write(o)).catch(e => { if (!(e instanceof StdinParseError))
|
|
152
196
|
process.stderr.write(`Statusline error: ${e.message}\n`); });
|
package/dist/normalize.js
CHANGED
|
@@ -29,8 +29,12 @@ export function sanitizeTermString(s) {
|
|
|
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
31
|
/**
|
|
32
|
-
* Sum
|
|
33
|
-
* a real context usage total (input +
|
|
32
|
+
* Sum input token categories from `context_window.current_usage` to compute
|
|
33
|
+
* a real context usage total (input + cache_read + cache_creation).
|
|
34
|
+
* Excludes output_tokens: they are per-turn and reset each call, which would
|
|
35
|
+
* cause the context bar to jitter (jump down at the start of every new turn).
|
|
36
|
+
* Context window fill is determined by what was READ from the context, not
|
|
37
|
+
* by how many tokens were output.
|
|
34
38
|
* Returns undefined when `cu` is absent or not an object shape.
|
|
35
39
|
*/
|
|
36
40
|
function getRealUsageTotal(cu) {
|
|
@@ -38,7 +42,6 @@ function getRealUsageTotal(cu) {
|
|
|
38
42
|
return undefined;
|
|
39
43
|
const obj = cu;
|
|
40
44
|
const total = (obj.input_tokens ?? 0)
|
|
41
|
-
+ (obj.output_tokens ?? 0)
|
|
42
45
|
+ (obj.cache_read_input_tokens ?? 0)
|
|
43
46
|
+ (obj.cache_creation_input_tokens ?? 0);
|
|
44
47
|
return total;
|
|
@@ -100,9 +103,9 @@ export function normalize(input) {
|
|
|
100
103
|
if (claude) {
|
|
101
104
|
({ denominator: cacheTurnDenominator } = getCacheFields(claude.context_window?.current_usage));
|
|
102
105
|
}
|
|
103
|
-
// Real context usage percentage (Claude only):
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
+
// Real context usage percentage (Claude only): sums input + cache_read + cache_creation
|
|
107
|
+
// (output_tokens excluded — per-turn, resets each call, causes bar jitter). More stable
|
|
108
|
+
// than the hook-provided `used_percentage` near auto-compact thresholds.
|
|
106
109
|
let realUsedPercentage;
|
|
107
110
|
if (claude) {
|
|
108
111
|
const total = getRealUsageTotal(claude.context_window?.current_usage);
|
|
@@ -113,10 +116,25 @@ export function normalize(input) {
|
|
|
113
116
|
}
|
|
114
117
|
// Auto-compact proximity warning: fires when context fill is in the
|
|
115
118
|
// [threshold-gap, threshold) window. Uses realUsedPercentage when available
|
|
116
|
-
// (more accurate;
|
|
119
|
+
// (more accurate; excludes output tokens), falls back to usedPercentage for
|
|
117
120
|
// legacy payloads. Gated by platform (different thresholds Claude vs Qwen).
|
|
121
|
+
// For claude-code, honors CLAUDE_CODE_AUTO_COMPACT_WINDOW env var — a fill-%
|
|
122
|
+
// threshold (1-100) that mirrors Claude Code's own auto-compact trigger point.
|
|
123
|
+
// Users who changed this setting in Claude Code should set the same value here.
|
|
124
|
+
// Falls back to the hardcoded 80% default when absent or invalid.
|
|
118
125
|
const effectivePct = realUsedPercentage ?? contextWindow.used_percentage ?? 0;
|
|
119
|
-
|
|
126
|
+
let platformAutoCompactThreshold = AUTO_COMPACT_THRESHOLD[platform];
|
|
127
|
+
if (platform === 'claude-code') {
|
|
128
|
+
const envVal = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
|
|
129
|
+
if (envVal !== undefined) {
|
|
130
|
+
// Use Number() + Number.isInteger() so floats ("75.5") and trailing-junk
|
|
131
|
+
// strings ("80abc") are rejected rather than silently truncated by parseInt.
|
|
132
|
+
const parsed = Number(envVal);
|
|
133
|
+
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 100) {
|
|
134
|
+
platformAutoCompactThreshold = parsed;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
120
138
|
const nearAutoCompact = effectivePct >= (platformAutoCompactThreshold - AUTO_COMPACT_WARNING_GAP)
|
|
121
139
|
&& effectivePct < platformAutoCompactThreshold;
|
|
122
140
|
// Performance (Qwen only)
|
package/dist/parsers/gsd.js
CHANGED
|
@@ -185,14 +185,16 @@ function semverCompare(a, b) {
|
|
|
185
185
|
return 0;
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
|
-
* Read GSD update-check cache. Checks
|
|
189
|
-
*
|
|
190
|
-
*
|
|
188
|
+
* Read GSD update-check cache. Checks candidates in order — first match wins:
|
|
189
|
+
* 1. open-gsd per-package cache (`gsd-update-check-opengsd-gsd-core.json`, gsd-core ≥ 1.4.x)
|
|
190
|
+
* 2. Legacy shared tool-agnostic cache (`gsd-update-check.json`, get-shit-done-cc ≥ 1.x)
|
|
191
|
+
* 3. Legacy per-runtime location (`~/.claude/cache/`) for older installs
|
|
191
192
|
* Returns update status, stale hooks flag, and dev install flag.
|
|
192
193
|
*/
|
|
193
|
-
function readUpdateCache(sharedCacheFile, legacyCacheFile) {
|
|
194
|
+
function readUpdateCache(openGsdCacheFile, sharedCacheFile, legacyCacheFile) {
|
|
194
195
|
const result = { updateAvailable: false, staleHooks: false, devInstall: false };
|
|
195
196
|
const candidates = [
|
|
197
|
+
['open-gsd', openGsdCacheFile],
|
|
196
198
|
['shared', sharedCacheFile],
|
|
197
199
|
['legacy', legacyCacheFile],
|
|
198
200
|
];
|
|
@@ -226,9 +228,11 @@ function readUpdateCache(sharedCacheFile, legacyCacheFile) {
|
|
|
226
228
|
}
|
|
227
229
|
export function getGsdInfo(cwd, opts = {}) {
|
|
228
230
|
const claudeDir = opts.claudeDir ?? process.env['CLAUDE_CONFIG_DIR'] ?? join(homedir(), '.claude');
|
|
229
|
-
const
|
|
231
|
+
const gsdCacheDir = join(homedir(), '.cache', 'gsd');
|
|
232
|
+
const openGsdCacheFile = opts.openGsdCacheFile ?? join(gsdCacheDir, 'gsd-update-check-opengsd-gsd-core.json');
|
|
233
|
+
const sharedCacheFile = opts.sharedCacheFile ?? join(gsdCacheDir, 'gsd-update-check.json');
|
|
230
234
|
const legacyCacheFile = join(claudeDir, 'cache', 'gsd-update-check.json');
|
|
231
|
-
const cacheData = readUpdateCache(sharedCacheFile, legacyCacheFile);
|
|
235
|
+
const cacheData = readUpdateCache(openGsdCacheFile, sharedCacheFile, legacyCacheFile);
|
|
232
236
|
let currentTask;
|
|
233
237
|
const stateFile = findStateMd(cwd || process.cwd());
|
|
234
238
|
if (stateFile) {
|
|
@@ -53,6 +53,14 @@ export async function aggregateStats(transcriptPath) {
|
|
|
53
53
|
throw new Error(`Transcript file not found: ${transcriptPath}`);
|
|
54
54
|
}
|
|
55
55
|
const stats = emptyStats();
|
|
56
|
+
// Dedup guard: Claude Code streams one JSONL entry per content block for
|
|
57
|
+
// the same logical message (thinking → text → tool_use all share one
|
|
58
|
+
// message.id). Each entry carries the full usage block for that turn, so
|
|
59
|
+
// naively accumulating every entry inflates tokens/cost by the number of
|
|
60
|
+
// content blocks. Track seen message IDs and skip usage+cost on repeats.
|
|
61
|
+
// Content extraction (tool_use, tool_result) is NOT gated — each block
|
|
62
|
+
// appears exactly once in its own entry and must still be counted.
|
|
63
|
+
const seenMessageIds = new Set();
|
|
56
64
|
let fileStream = null;
|
|
57
65
|
try {
|
|
58
66
|
fileStream = createReadStream(resolved);
|
|
@@ -81,10 +89,20 @@ export async function aggregateStats(transcriptPath) {
|
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
const message = (entry.message ?? null);
|
|
92
|
+
// Determine whether this is the first time we're seeing this message.id.
|
|
93
|
+
// Entries without a message.id (e.g. user turns, summary lines) are
|
|
94
|
+
// always treated as first-occurrence so they're never suppressed.
|
|
95
|
+
const messageId = message !== null && typeof message.id === 'string' ? message.id : null;
|
|
96
|
+
const isFirstOccurrence = messageId === null || !seenMessageIds.has(messageId);
|
|
97
|
+
if (messageId !== null)
|
|
98
|
+
seenMessageIds.add(messageId);
|
|
84
99
|
// Usage block (assistant turns only). The mere presence of a usage
|
|
85
100
|
// payload flips hasCostData=true even if all counts are zero — see
|
|
86
101
|
// the "zero-cost-with-usage" test for why this matters.
|
|
87
|
-
|
|
102
|
+
//
|
|
103
|
+
// Gated on isFirstOccurrence: duplicate entries for the same message.id
|
|
104
|
+
// carry identical usage blocks; accumulating them inflates counts 2-3×.
|
|
105
|
+
if (isFirstOccurrence && entry.type === 'assistant' && message && typeof message === 'object') {
|
|
88
106
|
const usage = message.usage;
|
|
89
107
|
if (usage && typeof usage === 'object') {
|
|
90
108
|
stats.hasCostData = true;
|
|
@@ -104,10 +122,14 @@ export async function aggregateStats(transcriptPath) {
|
|
|
104
122
|
// undefined`), not truthiness — Anthropic emits `total_cost_usd: 0` for
|
|
105
123
|
// fully-cached turns, and a `topCost || msgCost` short-circuit would
|
|
106
124
|
// incorrectly fall through to the message field in that case.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
//
|
|
126
|
+
// Also gated on isFirstOccurrence for the same dedup reason as usage above.
|
|
127
|
+
if (isFirstOccurrence) {
|
|
128
|
+
const topCost = safeNumber(entry.total_cost_usd);
|
|
129
|
+
const msgCost = message ? safeNumber(message.total_cost_usd) : 0;
|
|
130
|
+
const costContribution = entry.total_cost_usd !== undefined ? topCost : msgCost;
|
|
131
|
+
stats.costUsd += costContribution;
|
|
132
|
+
}
|
|
111
133
|
// Tool / agent / error extraction from the message.content array.
|
|
112
134
|
const content = message?.content;
|
|
113
135
|
if (!Array.isArray(content))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lumira",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
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,64 @@
|
|
|
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** — Only create `~/.config/lumira/config.json` if it does not already exist. Run the following Bash — the `test -f` guard is mandatory, do not skip it:
|
|
20
|
+
```bash
|
|
21
|
+
if [ ! -f ~/.config/lumira/config.json ]; then
|
|
22
|
+
mkdir -p ~/.config/lumira
|
|
23
|
+
printf '{"preset": "balanced"}\n' > ~/.config/lumira/config.json
|
|
24
|
+
echo "Created default config at ~/.config/lumira/config.json"
|
|
25
|
+
else
|
|
26
|
+
echo "Config already exists at ~/.config/lumira/config.json — not modified"
|
|
27
|
+
fi
|
|
28
|
+
```
|
|
29
|
+
Never overwrite an existing config under any circumstances.
|
|
30
|
+
7. **Confirm and instruct** — Tell the user what was written and that they must restart Claude Code.
|
|
31
|
+
|
|
32
|
+
## Finding the install path
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cat ~/.claude/plugins/installed_plugins.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Parse the JSON and find the entry whose key starts with `lumira@`. The `installPath` field is the absolute path to the cached plugin directory.
|
|
39
|
+
|
|
40
|
+
If multiple entries match (unlikely), use the one with the most recent `lastUpdated`.
|
|
41
|
+
|
|
42
|
+
## settings.json merge rules
|
|
43
|
+
|
|
44
|
+
- Always read the full file first, then patch only `statusLine.command`.
|
|
45
|
+
- Write back the complete merged object — never truncate other fields.
|
|
46
|
+
- If the file is missing, create it with just `{"statusLine": {"command": "<node command>"}}`.
|
|
47
|
+
- If the file is not valid JSON, report the parse error and stop. Do not overwrite.
|
|
48
|
+
|
|
49
|
+
## Success output
|
|
50
|
+
|
|
51
|
+
After writing, tell the user:
|
|
52
|
+
- The exact command written
|
|
53
|
+
- That they need to **restart Claude Code** for the statusline to appear
|
|
54
|
+
- "Run /lumira:lumira to customize preset, theme, or display widgets"
|
|
55
|
+
|
|
56
|
+
## Error cases
|
|
57
|
+
|
|
58
|
+
- 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."
|
|
59
|
+
- `dist/index.js` missing at install path → "Plugin installed but dist/ is missing. Reinstall: uninstall lumira then `/plugin marketplace add cativo23/lumira`."
|
|
60
|
+
- settings.json parse error → show the exact error, stop, do not write.
|
|
61
|
+
|
|
62
|
+
## Language
|
|
63
|
+
|
|
64
|
+
Respond in the user's language. Spanish input → Rioplatense Spanish (voseo: "vos tenés", "reiniciá", "fijate").
|