lumira 1.5.0 → 1.6.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.
Files changed (49) hide show
  1. package/README.md +65 -1
  2. package/dist/commands/custom-refresh.d.ts +38 -0
  3. package/dist/commands/custom-refresh.js +124 -0
  4. package/dist/commands/custom-refresh.js.map +1 -0
  5. package/dist/commands/custom.d.ts +15 -0
  6. package/dist/commands/custom.js +280 -0
  7. package/dist/commands/custom.js.map +1 -0
  8. package/dist/config.js +150 -6
  9. package/dist/config.js.map +1 -1
  10. package/dist/index.js +52 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/installer.js +1 -0
  13. package/dist/installer.js.map +1 -1
  14. package/dist/parsers/custom-commands.d.ts +79 -0
  15. package/dist/parsers/custom-commands.js +232 -0
  16. package/dist/parsers/custom-commands.js.map +1 -0
  17. package/dist/render/index.js +6 -1
  18. package/dist/render/index.js.map +1 -1
  19. package/dist/render/line1.js +9 -1
  20. package/dist/render/line1.js.map +1 -1
  21. package/dist/render/line2.js +9 -1
  22. package/dist/render/line2.js.map +1 -1
  23. package/dist/render/line3.js +9 -1
  24. package/dist/render/line3.js.map +1 -1
  25. package/dist/render/line4.js +20 -9
  26. package/dist/render/line4.js.map +1 -1
  27. package/dist/render/minimal.js +8 -2
  28. package/dist/render/minimal.js.map +1 -1
  29. package/dist/render/powerline-line1.d.ts +1 -1
  30. package/dist/render/powerline-line1.js +19 -3
  31. package/dist/render/powerline-line1.js.map +1 -1
  32. package/dist/render/powerline-line2.js +10 -1
  33. package/dist/render/powerline-line2.js.map +1 -1
  34. package/dist/render/powerline-line3.d.ts +1 -1
  35. package/dist/render/powerline-line3.js +15 -3
  36. package/dist/render/powerline-line3.js.map +1 -1
  37. package/dist/render/shared.d.ts +32 -0
  38. package/dist/render/shared.js +62 -1
  39. package/dist/render/shared.js.map +1 -1
  40. package/dist/types.d.ts +89 -0
  41. package/dist/types.js +19 -0
  42. package/dist/types.js.map +1 -1
  43. package/dist/utils/custom-cache.d.ts +21 -0
  44. package/dist/utils/custom-cache.js +77 -0
  45. package/dist/utils/custom-cache.js.map +1 -0
  46. package/dist/utils/exec-bg.d.ts +50 -0
  47. package/dist/utils/exec-bg.js +183 -0
  48. package/dist/utils/exec-bg.js.map +1 -0
  49. package/package.json +2 -2
package/README.md CHANGED
@@ -24,7 +24,9 @@ Interactive wizard — preset, theme, icons — previewed live before write.
24
24
  ![Claude Code](https://img.shields.io/badge/Claude_Code-compatible-2d3748?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCI+PHBhdGggZD0iTTY0IDEyOEMzNS44IDEyOCAxMyAxMDUuMiAxMyA3N0MxMyA0OC44IDM1LjggMjYgNjQgMjZjMjguMiAwIDUxIDIyLjggNTEgNTFzLTIyLjggNTEtNTEgNTF6IiBmaWxsPSIjMjQyNTJGIi8+PC9zdmc+)
25
25
  ![Qwen Code](https://img.shields.io/badge/Qwen_Code-compatible-6156FF)
26
26
 
27
- > 1,700+ downloads in the first month[share your setup](https://github.com/cativo23/lumira/discussions) in Discussions.
27
+ > 3,400+ monthly downloads, zero marketing. Try it for one session `npx lumira install`.
28
+
29
+ > **What's new in v1.5:** the [`lumira stats` CLI](#stats-cli) prints post-session analytics (cost, tokens, cache hit, tool frequency, burn rate). Combined with the `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.
28
30
 
29
31
  ## Table of contents
30
32
 
@@ -310,6 +312,68 @@ lumira --icons=nerd|emoji|none # Override icon set
310
312
  lumira --preset=full|balanced|minimal
311
313
  ```
312
314
 
315
+ ## Custom Commands
316
+
317
+ User-defined shell commands rendered as statusline segments on any of the 4 lines. Disabled by default.
318
+
319
+ ### Enable
320
+
321
+ ```bash
322
+ lumira custom enable
323
+ ```
324
+
325
+ ### Configure
326
+
327
+ Add a `customCommands` block to `~/.config/lumira/config.json`:
328
+
329
+ ```json
330
+ {
331
+ "customCommands": {
332
+ "enabled": true,
333
+ "commands": [
334
+ {
335
+ "id": "git-status",
336
+ "command": ["git", "status", "--short"],
337
+ "label": "",
338
+ "line": 1,
339
+ "refreshMs": 5000,
340
+ "onError": "hide"
341
+ }
342
+ ]
343
+ }
344
+ }
345
+ ```
346
+
347
+ **Key fields:**
348
+
349
+ | Field | Description |
350
+ |---|---|
351
+ | `id` | Unique identifier for the command |
352
+ | `command` | Argv array — no shell expansion, pipes, or redirects |
353
+ | `line` | Statusline line to render on (`1`–`4`) |
354
+ | `refreshMs` | Refresh interval in milliseconds (default: `5000`) |
355
+ | `label` | Optional prefix shown before the command output |
356
+ | `color` | Optional color override for the segment |
357
+ | `onError` | What to show on non-zero exit: `hide` (default), `placeholder`, `output`, or `stale` |
358
+ | `onTimeout` | What to show on timeout: same options as `onError`, defaults to `hide` |
359
+ | `timeoutMs` | Max execution time in ms (clamped to 2000) |
360
+ | `maxBytes` | Max stdout bytes captured (clamped to 4096) |
361
+ | `ansi` | Set `true` to pass through ANSI escape sequences from the command |
362
+
363
+ `command` must be an argv array (`["git", "status", "--short"]`). Shell strings with pipes or redirects are not supported — wrap them in a script if needed.
364
+
365
+ Output is cached with a TTL and refreshed in the background, so the hot render path never blocks on subprocess execution.
366
+
367
+ ### CLI subcommands
368
+
369
+ ```bash
370
+ lumira custom list # list configured commands and their status
371
+ lumira custom enable # enable the custom commands feature
372
+ lumira custom disable # disable the custom commands feature
373
+ lumira custom test <id> # run a command immediately and print its output
374
+ lumira custom logs # show recent execution logs
375
+ ```
376
+
313
377
  ## Architecture
314
378
 
315
379
  ```text
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Internal helper for the Custom Command widget (#143). Runs ONE custom
3
+ * command and writes the result into the cache file. Intended to be
4
+ * launched by the renderer as a detached child process so the renderer
5
+ * itself can exit immediately without waiting on the spawned command
6
+ * (B1: the original "fire and forget" via void async-IIFE still kept
7
+ * the Node event loop refed because data listeners hold stdin/stdout
8
+ * streams open until child exit + cache write).
9
+ *
10
+ * Wire protocol: a single JSON object on stdin describing the spawn
11
+ * spec, cache path, and (optional) stdin envelope to forward to the
12
+ * user command:
13
+ *
14
+ * {
15
+ * "id": "k8s-context",
16
+ * "command": ["kubectl", "config", "current-context"],
17
+ * "timeoutMs": 1500,
18
+ * "maxBytes": 256,
19
+ * "env": { ... }, // optional
20
+ * "cwd": "/abs/path", // optional
21
+ * "onError": "hide", // 'hide' | 'placeholder' | 'output' | 'stale'
22
+ * "cachePath": "/abs/path/to/custom-commands.json",
23
+ * "stdin": "{}" // optional
24
+ * }
25
+ *
26
+ * Errors are swallowed — this process must NEVER print to stdout or
27
+ * crash visibly. The renderer doesn't read this process's exit code.
28
+ */
29
+ /**
30
+ * Read the JSON spec from stdin, run the command via execBg, persist the
31
+ * result. Never throws — every failure path returns silently.
32
+ */
33
+ export declare function runCustomRefresh(stdinPayload: string): Promise<void>;
34
+ /**
35
+ * CLI entrypoint used by the renderer. Reads stdin to EOF, then runs.
36
+ * Wrapped in a Promise so the caller can await before exiting.
37
+ */
38
+ export declare function runCustomRefreshFromStdin(): Promise<void>;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Internal helper for the Custom Command widget (#143). Runs ONE custom
3
+ * command and writes the result into the cache file. Intended to be
4
+ * launched by the renderer as a detached child process so the renderer
5
+ * itself can exit immediately without waiting on the spawned command
6
+ * (B1: the original "fire and forget" via void async-IIFE still kept
7
+ * the Node event loop refed because data listeners hold stdin/stdout
8
+ * streams open until child exit + cache write).
9
+ *
10
+ * Wire protocol: a single JSON object on stdin describing the spawn
11
+ * spec, cache path, and (optional) stdin envelope to forward to the
12
+ * user command:
13
+ *
14
+ * {
15
+ * "id": "k8s-context",
16
+ * "command": ["kubectl", "config", "current-context"],
17
+ * "timeoutMs": 1500,
18
+ * "maxBytes": 256,
19
+ * "env": { ... }, // optional
20
+ * "cwd": "/abs/path", // optional
21
+ * "onError": "hide", // 'hide' | 'placeholder' | 'output' | 'stale'
22
+ * "cachePath": "/abs/path/to/custom-commands.json",
23
+ * "stdin": "{}" // optional
24
+ * }
25
+ *
26
+ * Errors are swallowed — this process must NEVER print to stdout or
27
+ * crash visibly. The renderer doesn't read this process's exit code.
28
+ */
29
+ import { execBg } from '../utils/exec-bg.js';
30
+ import { isAbsolute } from 'node:path';
31
+ import { readCacheFile, writeCacheFile } from '../utils/custom-cache.js';
32
+ function isValidSpec(raw) {
33
+ if (!raw || typeof raw !== 'object')
34
+ return false;
35
+ const s = raw;
36
+ if (typeof s.id !== 'string' || s.id.length === 0)
37
+ return false;
38
+ if (!Array.isArray(s.command) || s.command.length === 0)
39
+ return false;
40
+ if (!s.command.every((x) => typeof x === 'string' && x.length > 0))
41
+ return false;
42
+ if (typeof s.timeoutMs !== 'number' || !Number.isFinite(s.timeoutMs) || s.timeoutMs > 2000)
43
+ return false;
44
+ if (typeof s.maxBytes !== 'number' || !Number.isFinite(s.maxBytes) || s.maxBytes > 4096)
45
+ return false;
46
+ if (typeof s.onError !== 'string')
47
+ return false;
48
+ if (typeof s.cachePath !== 'string' || s.cachePath.length === 0 || !isAbsolute(s.cachePath))
49
+ return false;
50
+ return true;
51
+ }
52
+ /**
53
+ * Read the JSON spec from stdin, run the command via execBg, persist the
54
+ * result. Never throws — every failure path returns silently.
55
+ */
56
+ export async function runCustomRefresh(stdinPayload) {
57
+ let spec;
58
+ try {
59
+ const parsed = JSON.parse(stdinPayload);
60
+ if (!isValidSpec(parsed))
61
+ return;
62
+ spec = parsed;
63
+ }
64
+ catch {
65
+ return;
66
+ }
67
+ try {
68
+ const result = await execBg({
69
+ command: spec.command,
70
+ timeoutMs: spec.timeoutMs,
71
+ maxBytes: spec.maxBytes,
72
+ env: spec.env,
73
+ cwd: spec.cwd,
74
+ stdin: spec.stdin,
75
+ });
76
+ const now = Date.now();
77
+ let entry;
78
+ switch (result.kind) {
79
+ case 'ok':
80
+ entry = { text: result.stdout, capturedAt: now, state: 'ok' };
81
+ break;
82
+ case 'timeout':
83
+ entry = { text: result.stdout, capturedAt: now, state: 'timeout' };
84
+ break;
85
+ case 'nonzero': {
86
+ // For onError:'output' the renderer shows entry.text directly, so
87
+ // store stdout (partial output before the command failed). For all
88
+ // other strategies the text is not displayed — store stdout anyway
89
+ // so the entry is useful if the strategy is later changed.
90
+ entry = { text: result.stdout, capturedAt: now, state: 'nonzero' };
91
+ break;
92
+ }
93
+ case 'spawn-error':
94
+ entry = { text: '', capturedAt: now, state: 'nonzero' };
95
+ break;
96
+ }
97
+ const current = readCacheFile(spec.cachePath);
98
+ current[spec.id] = entry;
99
+ writeCacheFile(spec.cachePath, current);
100
+ }
101
+ catch {
102
+ /* swallow — helper must never crash visibly */
103
+ }
104
+ }
105
+ /**
106
+ * CLI entrypoint used by the renderer. Reads stdin to EOF, then runs.
107
+ * Wrapped in a Promise so the caller can await before exiting.
108
+ */
109
+ export async function runCustomRefreshFromStdin() {
110
+ const chunks = [];
111
+ try {
112
+ await new Promise((resolve) => {
113
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
114
+ process.stdin.on('end', () => resolve());
115
+ process.stdin.on('error', () => resolve());
116
+ });
117
+ }
118
+ catch {
119
+ return;
120
+ }
121
+ const payload = Buffer.concat(chunks).toString('utf8');
122
+ await runCustomRefresh(payload);
123
+ }
124
+ //# sourceMappingURL=custom-refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-refresh.js","sourceRoot":"","sources":["../../src/commands/custom-refresh.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ type Result = {
2
+ output: string;
3
+ exitCode: number;
4
+ };
5
+ /**
6
+ * Execute `lumira custom [subcommand] [...args]`.
7
+ *
8
+ * argv is the full process.argv; 'custom' starts at argv[2], the subcommand
9
+ * at argv[3], additional arguments from argv[4] onward.
10
+ *
11
+ * Returns `{ output, exitCode }` — the dispatcher writes output to stdout and
12
+ * sets process.exitCode from the returned value.
13
+ */
14
+ export declare function runCustomCommand(argv: string[]): Promise<Result>;
15
+ export {};
@@ -0,0 +1,280 @@
1
+ /**
2
+ * `lumira custom` subcommand (issue #143 phase 4).
3
+ *
4
+ * Provides a CLI interface for managing the custom commands feature:
5
+ *
6
+ * lumira custom list List configured commands from config file
7
+ * lumira custom enable Set enabled:true in config file
8
+ * lumira custom disable Set enabled:false in config file
9
+ * lumira custom test <id> Run a command once, print output + timing
10
+ * lumira custom logs Show cached outputs from the cache file
11
+ *
12
+ * Design constraints:
13
+ * - No runtime deps beyond Node built-ins.
14
+ * - All FS reads in try/catch — graceful errors, exit 1 on failure.
15
+ * - Color: process.stdout.isTTY ? 'named' : 'none'.
16
+ * - Return type: Promise<{ output: string; exitCode: number }> so the
17
+ * dispatcher can set process.exitCode.
18
+ */
19
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
20
+ import { homedir } from 'node:os';
21
+ import { join, dirname } from 'node:path';
22
+ import { execBg } from '../utils/exec-bg.js';
23
+ import { createColors } from '../render/colors.js';
24
+ import { loadConfig } from '../config.js';
25
+ import { readCacheFile } from '../utils/custom-cache.js';
26
+ // ── constants ──────────────────────────────────────────────────────────────
27
+ const CONFIG_FILE = 'config.json';
28
+ const CONFIG_DIR = join('.config', 'lumira');
29
+ const CACHE_FILE = 'custom-commands.json';
30
+ const CACHE_DIR = join('.cache', 'lumira');
31
+ // ── helpers ────────────────────────────────────────────────────────────────
32
+ function configPath() {
33
+ return join(homedir(), CONFIG_DIR, CONFIG_FILE);
34
+ }
35
+ function cachePath() {
36
+ return join(homedir(), CACHE_DIR, CACHE_FILE);
37
+ }
38
+ function ok(output) {
39
+ return { output, exitCode: 0 };
40
+ }
41
+ function fail(output) {
42
+ return { output, exitCode: 1 };
43
+ }
44
+ /**
45
+ * Read the raw config JSON from disk. Returns an empty object `{}` if the
46
+ * file doesn't exist, throws on malformed JSON so the caller can surface a
47
+ * useful error.
48
+ */
49
+ function readConfigRaw() {
50
+ const p = configPath();
51
+ if (!existsSync(p))
52
+ return {};
53
+ const raw = readFileSync(p, 'utf8');
54
+ const parsed = JSON.parse(raw);
55
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
56
+ return {};
57
+ return parsed;
58
+ }
59
+ /**
60
+ * Write (or create) the config file. Creates the parent directory if needed.
61
+ * The value is always pretty-printed with 2-space indents.
62
+ */
63
+ function writeConfigRaw(value) {
64
+ const p = configPath();
65
+ mkdirSync(dirname(p), { recursive: true });
66
+ writeFileSync(p, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });
67
+ }
68
+ /**
69
+ * Extract the `customCommands` block from the raw config. Returns a minimal
70
+ * default when the block is missing or malformed.
71
+ */
72
+ function readCustomCommandsBlock(raw) {
73
+ const cc = raw.customCommands;
74
+ if (!cc || typeof cc !== 'object' || Array.isArray(cc)) {
75
+ return { enabled: false, commands: [] };
76
+ }
77
+ const obj = cc;
78
+ const enabled = typeof obj.enabled === 'boolean' ? obj.enabled : false;
79
+ const commands = Array.isArray(obj.commands) ? obj.commands : [];
80
+ return { enabled, commands };
81
+ }
82
+ // ── color ──────────────────────────────────────────────────────────────────
83
+ /**
84
+ * Only use color when stdout is a real TTY and NO_COLOR is not set.
85
+ * In pipe/test contexts or when NO_COLOR is in env, produces no escape
86
+ * sequences, keeping output clean for programmatic use.
87
+ */
88
+ function makeColors() {
89
+ const noColor = 'NO_COLOR' in process.env || process.env.TERM === 'dumb';
90
+ if (noColor || !process.stdout.isTTY)
91
+ return null;
92
+ return createColors('named');
93
+ }
94
+ // ── subcommands ────────────────────────────────────────────────────────────
95
+ async function cmdEnable() {
96
+ try {
97
+ const raw = readConfigRaw();
98
+ const cc = readCustomCommandsBlock(raw);
99
+ const updated = {
100
+ ...raw,
101
+ customCommands: {
102
+ ...cc,
103
+ enabled: true,
104
+ },
105
+ };
106
+ writeConfigRaw(updated);
107
+ return ok(`Custom commands enabled.\nConfig written to: ${configPath()}\n`);
108
+ }
109
+ catch (e) {
110
+ const msg = e instanceof Error ? e.message : String(e);
111
+ return fail(`lumira custom enable: ${msg}\n`);
112
+ }
113
+ }
114
+ async function cmdDisable() {
115
+ try {
116
+ const raw = readConfigRaw();
117
+ const cc = readCustomCommandsBlock(raw);
118
+ const updated = {
119
+ ...raw,
120
+ customCommands: {
121
+ ...cc,
122
+ enabled: false,
123
+ },
124
+ };
125
+ writeConfigRaw(updated);
126
+ return ok(`Custom commands disabled.\nConfig written to: ${configPath()}\n`);
127
+ }
128
+ catch (e) {
129
+ const msg = e instanceof Error ? e.message : String(e);
130
+ return fail(`lumira custom disable: ${msg}\n`);
131
+ }
132
+ }
133
+ async function cmdList() {
134
+ const c = makeColors();
135
+ let enabled = false;
136
+ let commands = [];
137
+ try {
138
+ const cfg = loadConfig();
139
+ enabled = cfg.customCommands.enabled;
140
+ commands = cfg.customCommands.commands;
141
+ }
142
+ catch {
143
+ // config unreadable — fall through with empty defaults
144
+ }
145
+ const statusLine = enabled
146
+ ? `Custom commands: ${c ? c.green('enabled') : 'enabled'}\n`
147
+ : `Custom commands: ${c ? c.yellow('disabled') : 'disabled'}\n`;
148
+ if (commands.length === 0) {
149
+ return ok(statusLine
150
+ + '\nNo custom commands configured.\n'
151
+ + `Add commands to ${configPath()} under customCommands.commands.\n`);
152
+ }
153
+ // Table: id | line | refresh | cmd
154
+ const header = `${'id'.padEnd(20)} ${'line'.padEnd(6)} ${'refresh'.padEnd(10)} cmd`;
155
+ const sep = '-'.repeat(header.length);
156
+ const rows = commands.map(cmd => {
157
+ const id = cmd.id.padEnd(20);
158
+ const line = String(cmd.line).padEnd(6);
159
+ const refresh = `${cmd.refreshMs}ms`.padEnd(10);
160
+ const cmdStr = cmd.command.join(' ');
161
+ return `${id} ${line} ${refresh} ${cmdStr}`;
162
+ });
163
+ const table = [header, sep, ...rows].join('\n');
164
+ return ok(`${statusLine}\n${table}\n`);
165
+ }
166
+ async function cmdTest(id) {
167
+ if (!id) {
168
+ return fail('lumira custom test: missing command id.\n\n'
169
+ + 'Usage: lumira custom test <id>\n'
170
+ + "Use 'lumira custom list' to see configured command ids.\n");
171
+ }
172
+ let commands;
173
+ try {
174
+ commands = loadConfig().customCommands.commands;
175
+ }
176
+ catch (e) {
177
+ const msg = e instanceof Error ? e.message : String(e);
178
+ return fail(`lumira custom test: could not read config: ${msg}\n`);
179
+ }
180
+ const cmd = commands.find(c => c.id === id);
181
+ if (!cmd) {
182
+ const knownIds = commands.map(c => c.id).join(', ');
183
+ return fail(`lumira custom test: command id "${id}" not found.\n`
184
+ + (knownIds ? `Known ids: ${knownIds}\n` : 'No commands configured.\n'));
185
+ }
186
+ const result = await execBg({
187
+ command: cmd.command,
188
+ timeoutMs: cmd.timeoutMs,
189
+ maxBytes: cmd.maxBytes,
190
+ });
191
+ const lines = [
192
+ `Command: ${cmd.command.join(' ')}`,
193
+ `Duration: ${result.durationMs}ms`,
194
+ ];
195
+ if (result.kind === 'ok') {
196
+ lines.push(`Exit: 0 (ok)`);
197
+ lines.push(`Output:\n${result.stdout || '(empty)'}`);
198
+ }
199
+ else if (result.kind === 'nonzero') {
200
+ lines.push(`Exit: ${result.exitCode} (nonzero)`);
201
+ if (result.stdout)
202
+ lines.push(`Stdout:\n${result.stdout}`);
203
+ if (result.stderr)
204
+ lines.push(`Stderr:\n${result.stderr}`);
205
+ }
206
+ else if (result.kind === 'timeout') {
207
+ lines.push(`Exit: timeout (killed after ${cmd.timeoutMs}ms)`);
208
+ if (result.stdout)
209
+ lines.push(`Stdout (partial):\n${result.stdout}`);
210
+ }
211
+ else {
212
+ // spawn-error
213
+ lines.push(`Exit: spawn-error — ${result.message}`);
214
+ }
215
+ return ok(lines.join('\n') + '\n');
216
+ }
217
+ async function cmdLogs() {
218
+ const p = cachePath();
219
+ const cacheData = readCacheFile(p);
220
+ const entries = Object.entries(cacheData);
221
+ if (entries.length === 0) {
222
+ return ok(`No cache file found at ${p}.\n`
223
+ + "Run lumira once with custom commands enabled to populate the cache.\n");
224
+ }
225
+ const lines = [`Cache: ${p}`, ''];
226
+ for (const [id, entry] of entries) {
227
+ const { text, capturedAt, state } = entry;
228
+ const dateStr = capturedAt > 0
229
+ ? new Date(capturedAt).toLocaleString()
230
+ : 'unknown';
231
+ const truncated = text.length > 100 ? text.slice(0, 100) + '…' : text;
232
+ lines.push(`id: ${id}`);
233
+ lines.push(` state: ${state}`);
234
+ lines.push(` capturedAt: ${dateStr}`);
235
+ lines.push(` text: ${truncated || '(empty)'}`);
236
+ lines.push('');
237
+ }
238
+ return ok(lines.join('\n'));
239
+ }
240
+ function helpText() {
241
+ return [
242
+ 'Usage: lumira custom <subcommand>',
243
+ '',
244
+ 'Subcommands:',
245
+ ' list List configured custom commands',
246
+ ' enable Enable custom commands in config',
247
+ ' disable Disable custom commands in config',
248
+ ' test <id> Run a command once and print output + timing',
249
+ ' logs Show cached command outputs',
250
+ '',
251
+ ].join('\n');
252
+ }
253
+ // ── entry point ────────────────────────────────────────────────────────────
254
+ /**
255
+ * Execute `lumira custom [subcommand] [...args]`.
256
+ *
257
+ * argv is the full process.argv; 'custom' starts at argv[2], the subcommand
258
+ * at argv[3], additional arguments from argv[4] onward.
259
+ *
260
+ * Returns `{ output, exitCode }` — the dispatcher writes output to stdout and
261
+ * sets process.exitCode from the returned value.
262
+ */
263
+ export async function runCustomCommand(argv) {
264
+ const sub = argv[3];
265
+ switch (sub) {
266
+ case 'enable':
267
+ return cmdEnable();
268
+ case 'disable':
269
+ return cmdDisable();
270
+ case 'list':
271
+ return cmdList();
272
+ case 'test':
273
+ return cmdTest(argv[4]);
274
+ case 'logs':
275
+ return cmdLogs();
276
+ default:
277
+ return fail(helpText());
278
+ }
279
+ }
280
+ //# sourceMappingURL=custom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/commands/custom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD,8EAA8E;AAE9E,MAAM,WAAW,GAAG,aAAa,CAAC;AAClC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAE7C,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE3C,8EAA8E;AAE9E,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAChD,CAAC;AAID,SAAS,EAAE,CAAC,MAAc;IACxB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AACjC,CAAC;AAED,SAAS,IAAI,CAAC,MAAc;IAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa;IACpB,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;IACvB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9E,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,KAA8B;IACpD,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;IACvB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,SAAS,uBAAuB,CAC9B,GAA4B;IAE5B,MAAM,EAAE,GAAG,GAAG,CAAC,cAAc,CAAC;IAC9B,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,GAAG,GAAG,EAA6B,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACvE,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED,8EAA8E;AAE9E;;;;GAIG;AACH,SAAS,UAAU;IACjB,MAAM,OAAO,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;IACzE,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,8EAA8E;AAE9E,KAAK,UAAU,SAAS;IACtB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,OAAO,GAA4B;YACvC,GAAG,GAAG;YACN,cAAc,EAAE;gBACd,GAAG,EAAE;gBACL,OAAO,EAAE,IAAI;aACd;SACF,CAAC;QACF,cAAc,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,EAAE,CAAC,gDAAgD,UAAU,EAAE,IAAI,CAAC,CAAC;IAC9E,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,OAAO,GAA4B;YACvC,GAAG,GAAG;YACN,cAAc,EAAE;gBACd,GAAG,EAAE;gBACL,OAAO,EAAE,KAAK;aACf;SACF,CAAC;QACF,cAAc,CAAC,OAAO,CAAC,CAAC;QACxB,OAAO,EAAE,CAAC,iDAAiD,UAAU,EAAE,IAAI,CAAC,CAAC;IAC/E,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,QAAQ,GAAoB,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;QACzB,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;QACrC,QAAQ,GAAG,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,uDAAuD;IACzD,CAAC;IAED,MAAM,UAAU,GAAG,OAAO;QACxB,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI;QAC5D,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC;IAElE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CACP,UAAU;cACR,oCAAoC;cACpC,mBAAmB,UAAU,EAAE,mCAAmC,CACrE,CAAC;IACJ,CAAC;IAED,mCAAmC;IACnC,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC;IACpF,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC9B,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,OAAO,GAAG,EAAE,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,EAAE,CAAC,GAAG,UAAU,KAAK,KAAK,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,EAAsB;IAC3C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,OAAO,IAAI,CACT,6CAA6C;cAC3C,kCAAkC;cAClC,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IAED,IAAI,QAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,QAAQ,GAAG,UAAU,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC;IAClD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,8CAA8C,GAAG,IAAI,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAE5C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,OAAO,IAAI,CACT,mCAAmC,EAAE,gBAAgB;cACnD,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,QAAQ,IAAI,CAAC,CAAC,CAAC,2BAA2B,CAAC,CACxE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC;QAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC,CAAC;IAEH,MAAM,KAAK,GAAa;QACtB,YAAY,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;QACnC,aAAa,MAAM,CAAC,UAAU,IAAI;KACnC,CAAC;IAEF,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC;SAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,QAAQ,YAAY,CAAC,CAAC;QACjD,IAAI,MAAM,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;SAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,+BAA+B,GAAG,CAAC,SAAS,KAAK,CAAC,CAAC;QAC9D,IAAI,MAAM,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,cAAc;QACd,KAAK,CAAC,IAAI,CAAC,uBAAwB,MAAuE,CAAC,OAAO,EAAE,CAAC,CAAC;IACxH,CAAC;IAED,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;IAEtB,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAE1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CACP,0BAA0B,CAAC,KAAK;cAC9B,uEAAuE,CAC1E,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAa,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAE5C,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QAClC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;QAC1C,MAAM,OAAO,GAAG,UAAU,GAAG,CAAC;YAC5B,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,cAAc,EAAE;YACvC,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QAEtE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;QACvC,KAAK,CAAC,IAAI,CAAC,iBAAiB,SAAS,IAAI,SAAS,EAAE,CAAC,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,QAAQ;IACf,OAAO;QACL,mCAAmC;QACnC,EAAE;QACF,cAAc;QACd,sDAAsD;QACtD,uDAAuD;QACvD,wDAAwD;QACxD,mEAAmE;QACnE,kDAAkD;QAClD,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAc;IACnD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAEpB,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,QAAQ;YACX,OAAO,SAAS,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,UAAU,EAAE,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,OAAO,EAAE,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,KAAK,MAAM;YACT,OAAO,OAAO,EAAE,CAAC;QACnB;YACE,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC"}