claudeline 1.0.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/dist/index.js ADDED
@@ -0,0 +1,1193 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { program } from "commander";
5
+
6
+ // src/colors.ts
7
+ var COLORS = {
8
+ // Foreground colors
9
+ black: "30",
10
+ red: "31",
11
+ green: "32",
12
+ yellow: "33",
13
+ blue: "34",
14
+ magenta: "35",
15
+ cyan: "36",
16
+ white: "37",
17
+ gray: "90",
18
+ grey: "90",
19
+ // Bright foreground
20
+ "bright-red": "91",
21
+ "bright-green": "92",
22
+ "bright-yellow": "93",
23
+ "bright-blue": "94",
24
+ "bright-magenta": "95",
25
+ "bright-cyan": "96",
26
+ "bright-white": "97",
27
+ // Background colors
28
+ "bg-black": "40",
29
+ "bg-red": "41",
30
+ "bg-green": "42",
31
+ "bg-yellow": "43",
32
+ "bg-blue": "44",
33
+ "bg-magenta": "45",
34
+ "bg-cyan": "46",
35
+ "bg-white": "47",
36
+ // Styles
37
+ bold: "1",
38
+ dim: "2",
39
+ italic: "3",
40
+ underline: "4",
41
+ blink: "5",
42
+ inverse: "7",
43
+ hidden: "8",
44
+ strikethrough: "9"
45
+ };
46
+ var RESET = "0";
47
+
48
+ // src/parser.ts
49
+ var STYLE_PREFIXES = new Set(Object.keys(COLORS));
50
+ function parseFormat(format) {
51
+ const normalized = format.trim();
52
+ const components = [];
53
+ let i = 0;
54
+ while (i < normalized.length) {
55
+ while (i < normalized.length && /\s/.test(normalized[i])) i++;
56
+ if (i >= normalized.length) break;
57
+ if (normalized[i] === "[" || normalized[i] === "(" || normalized[i] === "{" || normalized[i] === "<") {
58
+ const openBracket = normalized[i];
59
+ const closeBracket = openBracket === "[" ? "]" : openBracket === "(" ? ")" : openBracket === "{" ? "}" : ">";
60
+ const bracketType = openBracket === "[" ? "square" : openBracket === "(" ? "paren" : openBracket === "{" ? "curly" : "angle";
61
+ let depth = 1;
62
+ let j = i + 1;
63
+ while (j < normalized.length && depth > 0) {
64
+ if (normalized[j] === openBracket) depth++;
65
+ else if (normalized[j] === closeBracket) depth--;
66
+ j++;
67
+ }
68
+ const inner = normalized.slice(i + 1, j - 1);
69
+ components.push({
70
+ type: "group",
71
+ key: bracketType,
72
+ styles: [],
73
+ children: parseFormat(inner)
74
+ });
75
+ i = j;
76
+ continue;
77
+ }
78
+ if (normalized.slice(i, i + 3) === "if:") {
79
+ const condStart = i + 3;
80
+ let condEnd = condStart;
81
+ while (condEnd < normalized.length && normalized[condEnd] !== "(") condEnd++;
82
+ const condition = normalized.slice(condStart, condEnd);
83
+ if (normalized[condEnd] === "(") {
84
+ let depth = 1;
85
+ let j = condEnd + 1;
86
+ while (j < normalized.length && depth > 0) {
87
+ if (normalized[j] === "(") depth++;
88
+ else if (normalized[j] === ")") depth--;
89
+ j++;
90
+ }
91
+ const inner = normalized.slice(condEnd + 1, j - 1);
92
+ components.push({
93
+ type: "conditional",
94
+ key: condition,
95
+ condition,
96
+ styles: [],
97
+ children: parseFormat(inner)
98
+ });
99
+ i = j;
100
+ } else {
101
+ i = condEnd;
102
+ }
103
+ continue;
104
+ }
105
+ let tokenEnd = i;
106
+ while (tokenEnd < normalized.length && !/[\s,\[\](){}<>]/.test(normalized[tokenEnd])) {
107
+ tokenEnd++;
108
+ }
109
+ const token = normalized.slice(i, tokenEnd);
110
+ if (token) {
111
+ if (token === ",") {
112
+ i = tokenEnd;
113
+ continue;
114
+ }
115
+ const component = parseComponent(token);
116
+ if (component) {
117
+ components.push(component);
118
+ }
119
+ }
120
+ i = tokenEnd;
121
+ while (i < normalized.length && (normalized[i] === "," || /\s/.test(normalized[i]))) i++;
122
+ }
123
+ return components;
124
+ }
125
+ function parseComponent(token) {
126
+ if (!token || token === ",") return null;
127
+ const styles = [];
128
+ let remaining = token;
129
+ while (true) {
130
+ const colonIdx = remaining.indexOf(":");
131
+ if (colonIdx === -1) break;
132
+ const prefix = remaining.slice(0, colonIdx);
133
+ if (STYLE_PREFIXES.has(prefix)) {
134
+ styles.push(prefix);
135
+ remaining = remaining.slice(colonIdx + 1);
136
+ } else {
137
+ break;
138
+ }
139
+ }
140
+ const parts = remaining.split(":");
141
+ if (parts.length === 0 || !parts[0]) {
142
+ return null;
143
+ }
144
+ if (parts[0] === "text") {
145
+ const text = parts.slice(1).join(":").replace(/^["']|["']$/g, "");
146
+ return { type: "text", key: text, styles };
147
+ }
148
+ if (parts[0] === "emoji") {
149
+ return { type: "emoji", key: parts[1] || "", styles };
150
+ }
151
+ if (parts[0] === "sep") {
152
+ return { type: "sep", key: parts[1] || "pipe", styles };
153
+ }
154
+ return {
155
+ type: parts[0],
156
+ key: parts[1] || "",
157
+ args: parts[2],
158
+ styles
159
+ };
160
+ }
161
+ function listComponents() {
162
+ const categories = [
163
+ {
164
+ name: "Claude/Session",
165
+ prefix: "claude",
166
+ items: ["model", "model-id", "version", "session", "session-full", "style"]
167
+ },
168
+ {
169
+ name: "Context Window",
170
+ prefix: "ctx",
171
+ items: ["percent", "remaining", "tokens", "in", "out", "size", "bar", "bar:N", "emoji", "used-tokens"]
172
+ },
173
+ {
174
+ name: "Cost/Usage",
175
+ prefix: "cost",
176
+ items: ["total", "total-cents", "duration", "api", "lines", "added", "removed", "lines-both"]
177
+ },
178
+ {
179
+ name: "File System",
180
+ prefix: "fs",
181
+ items: ["path", "dir", "project", "project-path", "home", "cwd", "relative"]
182
+ },
183
+ {
184
+ name: "Git",
185
+ prefix: "git",
186
+ items: [
187
+ "branch",
188
+ "status",
189
+ "status-emoji",
190
+ "status-word",
191
+ "ahead",
192
+ "behind",
193
+ "ahead-behind",
194
+ "stash",
195
+ "staged",
196
+ "modified",
197
+ "untracked",
198
+ "commit",
199
+ "commit-long",
200
+ "tag",
201
+ "remote",
202
+ "repo",
203
+ "user",
204
+ "email",
205
+ "remote-url"
206
+ ]
207
+ },
208
+ {
209
+ name: "Environment",
210
+ prefix: "env",
211
+ items: [
212
+ "node",
213
+ "node-short",
214
+ "bun",
215
+ "npm",
216
+ "pnpm",
217
+ "yarn",
218
+ "python",
219
+ "deno",
220
+ "rust",
221
+ "go",
222
+ "ruby",
223
+ "java",
224
+ "user",
225
+ "hostname",
226
+ "hostname-short",
227
+ "shell",
228
+ "term",
229
+ "os",
230
+ "arch",
231
+ "os-release",
232
+ "cpus",
233
+ "memory"
234
+ ]
235
+ },
236
+ {
237
+ name: "Time",
238
+ prefix: "time",
239
+ items: [
240
+ "now",
241
+ "seconds",
242
+ "12h",
243
+ "date",
244
+ "full",
245
+ "iso",
246
+ "unix",
247
+ "weekday",
248
+ "weekday-long",
249
+ "month",
250
+ "month-long",
251
+ "year",
252
+ "day",
253
+ "hour",
254
+ "minute",
255
+ "elapsed"
256
+ ]
257
+ },
258
+ {
259
+ name: "Separators",
260
+ prefix: "sep",
261
+ items: [
262
+ "pipe",
263
+ "arrow",
264
+ "arrow-left",
265
+ "chevron",
266
+ "chevron-left",
267
+ "dot",
268
+ "dash",
269
+ "slash",
270
+ "space",
271
+ "none",
272
+ "colon",
273
+ "double-colon",
274
+ "tilde",
275
+ "double-pipe",
276
+ "bullet",
277
+ "diamond",
278
+ "star",
279
+ "powerline",
280
+ "powerline-left"
281
+ ]
282
+ },
283
+ {
284
+ name: "Emojis",
285
+ prefix: "emoji",
286
+ items: [
287
+ "folder",
288
+ "file",
289
+ "home",
290
+ "branch",
291
+ "git",
292
+ "commit",
293
+ "merge",
294
+ "tag",
295
+ "stash",
296
+ "check",
297
+ "x",
298
+ "warn",
299
+ "error",
300
+ "success",
301
+ "info",
302
+ "star",
303
+ "fire",
304
+ "rocket",
305
+ "sparkle",
306
+ "lightning",
307
+ "heart",
308
+ "diamond",
309
+ "circle",
310
+ "square",
311
+ "triangle",
312
+ "node",
313
+ "python",
314
+ "rust",
315
+ "go",
316
+ "ruby",
317
+ "java",
318
+ "docker",
319
+ "green",
320
+ "yellow",
321
+ "orange",
322
+ "red",
323
+ "up",
324
+ "down",
325
+ "clock",
326
+ "calendar",
327
+ "money"
328
+ ]
329
+ },
330
+ {
331
+ name: "Text",
332
+ prefix: "text",
333
+ items: ["<custom text>"]
334
+ },
335
+ {
336
+ name: "Colors/Styles",
337
+ prefix: "",
338
+ items: [
339
+ "red:",
340
+ "green:",
341
+ "blue:",
342
+ "yellow:",
343
+ "magenta:",
344
+ "cyan:",
345
+ "white:",
346
+ "gray:",
347
+ "bold:",
348
+ "dim:",
349
+ "italic:",
350
+ "underline:",
351
+ "bg-red:",
352
+ "bg-green:",
353
+ "bg-blue:",
354
+ "bg-yellow:"
355
+ ]
356
+ },
357
+ {
358
+ name: "Conditionals",
359
+ prefix: "if",
360
+ items: ["git(...)", "dirty(...)", "node(...)", "python(...)"]
361
+ },
362
+ {
363
+ name: "Grouping",
364
+ prefix: "",
365
+ items: ["[...]", "(...)", "{...}", "<...>"]
366
+ }
367
+ ];
368
+ console.log("\n\x1B[1mAvailable Components:\x1B[0m\n");
369
+ for (const cat of categories) {
370
+ console.log(`\x1B[36m${cat.name}\x1B[0m`);
371
+ const prefix = cat.prefix ? `${cat.prefix}:` : "";
372
+ console.log(` ${cat.items.map((i) => `${prefix}${i}`).join(", ")}`);
373
+ console.log();
374
+ }
375
+ }
376
+
377
+ // src/themes.ts
378
+ var THEMES = {
379
+ minimal: "claude:model fs:dir",
380
+ default: "[claude:model] emoji:folder fs:dir if:git(sep:pipe emoji:branch git:branch git:status)",
381
+ powerline: "bold:cyan:claude:model sep:powerline fs:dir if:git(sep:powerline green:git:branch git:status)",
382
+ full: "[bold:cyan:claude:model] fs:home sep:arrow green:git:branch git:status sep:pipe ctx:bar ctx:percent sep:pipe cost:total",
383
+ git: "[claude:model] emoji:folder fs:dir sep:pipe emoji:branch git:branch git:status git:ahead-behind if:dirty(sep:pipe git:staged git:modified git:untracked)",
384
+ tokens: "claude:model sep:pipe ctx:emoji ctx:tokens sep:pipe cost:lines",
385
+ dev: "[fs:dir] git:branch sep:pipe env:node sep:pipe time:now",
386
+ dashboard: "[bold:claude:model] fs:dir sep:pipe git:branch git:status sep:pipe ctx:percent sep:pipe cost:total sep:pipe time:now",
387
+ "context-focus": "claude:model sep:pipe ctx:bar sep:pipe ctx:tokens sep:pipe ctx:emoji",
388
+ cost: "claude:model sep:pipe cost:total sep:pipe cost:duration sep:pipe cost:lines-both",
389
+ simple: "claude:model fs:dir git:branch",
390
+ verbose: "[claude:model] [claude:version] sep:pipe fs:home sep:pipe git:branch git:status git:ahead-behind sep:pipe ctx:bar ctx:percent sep:pipe cost:total cost:duration sep:pipe time:now",
391
+ nerd: "emoji:node env:node-short sep:dot emoji:folder fs:dir sep:dot emoji:branch git:branch git:status",
392
+ compact: "claude:model sep:slash fs:dir sep:slash git:branch",
393
+ colorful: "bold:magenta:claude:model sep:arrow cyan:fs:dir sep:arrow green:git:branch yellow:git:status sep:arrow blue:ctx:percent",
394
+ luca: "claude:model-letter git:repo git:branch git:dirty cost:total"
395
+ };
396
+ function getTheme(name) {
397
+ return THEMES[name] || null;
398
+ }
399
+ function listThemes() {
400
+ console.log("\n\x1B[1mAvailable Themes:\x1B[0m\n");
401
+ for (const [name, format] of Object.entries(THEMES)) {
402
+ console.log(`\x1B[36m${name}\x1B[0m`);
403
+ console.log(` ${format}`);
404
+ console.log();
405
+ }
406
+ }
407
+
408
+ // src/installer.ts
409
+ import * as fs from "fs";
410
+ import * as path from "path";
411
+ import * as os from "os";
412
+ function getClaudeDir(project) {
413
+ if (project) {
414
+ return path.join(process.cwd(), ".claude");
415
+ }
416
+ return path.join(os.homedir(), ".claude");
417
+ }
418
+ function escapeForShell(str) {
419
+ return str.replace(/'/g, "'\\''");
420
+ }
421
+ function detectPackageRunner() {
422
+ if (process.versions.bun || process.env.BUN_INSTALL) {
423
+ return "bunx";
424
+ }
425
+ return "npx";
426
+ }
427
+ function buildCommand(format, options) {
428
+ const parts = [];
429
+ if (options.globalInstall) {
430
+ parts.push("claudeline");
431
+ } else if (options.useBunx) {
432
+ parts.push("bunx claudeline");
433
+ } else if (options.useNpx) {
434
+ parts.push("npx claudeline");
435
+ } else {
436
+ parts.push(`${detectPackageRunner()} claudeline`);
437
+ }
438
+ parts.push("run");
439
+ parts.push(`'${escapeForShell(format)}'`);
440
+ if (options.noEmoji) {
441
+ parts.push("--disable-emoji");
442
+ }
443
+ if (options.noColor) {
444
+ parts.push("--disable-color");
445
+ }
446
+ return parts.join(" ");
447
+ }
448
+ function install(format, project = false, options = {}) {
449
+ const claudeDir = getClaudeDir(project);
450
+ const settingsPath = path.join(claudeDir, "settings.json");
451
+ try {
452
+ if (!fs.existsSync(claudeDir)) {
453
+ fs.mkdirSync(claudeDir, { recursive: true });
454
+ }
455
+ const command = buildCommand(format, options);
456
+ let settings = {};
457
+ if (fs.existsSync(settingsPath)) {
458
+ try {
459
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
460
+ } catch {
461
+ }
462
+ }
463
+ settings.statusLine = {
464
+ type: "command",
465
+ command,
466
+ padding: 0
467
+ };
468
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
469
+ return {
470
+ success: true,
471
+ message: `Updated settings at ${settingsPath}
472
+ Command: ${command}`
473
+ };
474
+ } catch (error) {
475
+ return {
476
+ success: false,
477
+ message: `Failed to install: ${error instanceof Error ? error.message : String(error)}`
478
+ };
479
+ }
480
+ }
481
+ function uninstall(project = false) {
482
+ const claudeDir = getClaudeDir(project);
483
+ const settingsPath = path.join(claudeDir, "settings.json");
484
+ try {
485
+ if (fs.existsSync(settingsPath)) {
486
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
487
+ delete settings.statusLine;
488
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
489
+ }
490
+ return {
491
+ success: true,
492
+ message: "Statusline configuration removed"
493
+ };
494
+ } catch (error) {
495
+ return {
496
+ success: false,
497
+ message: `Failed to uninstall: ${error instanceof Error ? error.message : String(error)}`
498
+ };
499
+ }
500
+ }
501
+
502
+ // src/preview.ts
503
+ var SAMPLE_DATA = {
504
+ hook_event_name: "Status",
505
+ session_id: "abc123def456789",
506
+ transcript_path: "/tmp/transcript.json",
507
+ cwd: "/Users/demo/projects/myapp",
508
+ model: {
509
+ id: "claude-opus-4-1",
510
+ display_name: "Opus"
511
+ },
512
+ workspace: {
513
+ current_dir: "/Users/demo/projects/myapp",
514
+ project_dir: "/Users/demo/projects/myapp"
515
+ },
516
+ version: "1.0.80",
517
+ output_style: {
518
+ name: "default"
519
+ },
520
+ cost: {
521
+ total_cost_usd: 0.0234,
522
+ total_duration_ms: 125e3,
523
+ total_api_duration_ms: 4500,
524
+ total_lines_added: 156,
525
+ total_lines_removed: 23
526
+ },
527
+ context_window: {
528
+ total_input_tokens: 15234,
529
+ total_output_tokens: 4521,
530
+ context_window_size: 2e5,
531
+ used_percentage: 42.5,
532
+ remaining_percentage: 57.5,
533
+ current_usage: {
534
+ input_tokens: 8500,
535
+ output_tokens: 1200,
536
+ cache_creation_input_tokens: 5e3,
537
+ cache_read_input_tokens: 2e3
538
+ }
539
+ }
540
+ };
541
+ function getSampleDataJson() {
542
+ return JSON.stringify(SAMPLE_DATA, null, 2);
543
+ }
544
+
545
+ // src/runtime.ts
546
+ import * as path2 from "path";
547
+ import * as os2 from "os";
548
+ import * as fs2 from "fs";
549
+ import { execSync } from "child_process";
550
+
551
+ // src/separators.ts
552
+ var SEPARATORS = {
553
+ pipe: " | ",
554
+ arrow: " \u2192 ",
555
+ "arrow-left": " \u2190 ",
556
+ "arrow-right": " \u2192 ",
557
+ chevron: " \u203A ",
558
+ "chevron-left": " \u2039 ",
559
+ dot: " \u2022 ",
560
+ dash: " - ",
561
+ slash: " / ",
562
+ backslash: " \\ ",
563
+ space: " ",
564
+ none: "",
565
+ colon: ": ",
566
+ "double-colon": " :: ",
567
+ tilde: " ~ ",
568
+ "double-pipe": " \u2016 ",
569
+ bullet: " \u25E6 ",
570
+ diamond: " \u25C7 ",
571
+ star: " \u2605 ",
572
+ // Powerline-style
573
+ powerline: "",
574
+ "powerline-left": "",
575
+ "powerline-thin": "",
576
+ "powerline-thin-left": ""
577
+ };
578
+ function getSeparator(name) {
579
+ return SEPARATORS[name] ?? " | ";
580
+ }
581
+
582
+ // src/emojis.ts
583
+ var EMOJIS = {
584
+ // Files & Folders
585
+ folder: "\u{1F4C1}",
586
+ file: "\u{1F4C4}",
587
+ home: "\u{1F3E0}",
588
+ // Git
589
+ branch: "\u{1F33F}",
590
+ git: "",
591
+ commit: "\u{1F4DD}",
592
+ merge: "\u{1F500}",
593
+ tag: "\u{1F3F7}\uFE0F",
594
+ stash: "\u{1F4E6}",
595
+ // Status
596
+ check: "\u2713",
597
+ x: "\u2717",
598
+ warn: "\u26A0",
599
+ error: "\u274C",
600
+ success: "\u2705",
601
+ info: "\u2139",
602
+ // Decorative
603
+ star: "\u2605",
604
+ fire: "\u{1F525}",
605
+ rocket: "\u{1F680}",
606
+ sparkle: "\u2728",
607
+ lightning: "\u26A1",
608
+ heart: "\u2764",
609
+ diamond: "\u25C6",
610
+ circle: "\u25CF",
611
+ square: "\u25A0",
612
+ triangle: "\u25B2",
613
+ // Tech
614
+ node: "\u2B22",
615
+ python: "\u{1F40D}",
616
+ rust: "\u{1F980}",
617
+ go: "\u{1F439}",
618
+ ruby: "\u{1F48E}",
619
+ java: "\u2615",
620
+ docker: "\u{1F433}",
621
+ // Context indicators
622
+ green: "\u{1F7E2}",
623
+ yellow: "\u{1F7E1}",
624
+ orange: "\u{1F7E0}",
625
+ red: "\u{1F534}",
626
+ // Arrows
627
+ up: "\u2191",
628
+ down: "\u2193",
629
+ left: "\u2190",
630
+ right: "\u2192",
631
+ "up-down": "\u2195",
632
+ // Time
633
+ clock: "\u{1F550}",
634
+ calendar: "\u{1F4C5}",
635
+ // Money
636
+ money: "\u{1F4B0}",
637
+ dollar: "\u{1F4B5}"
638
+ };
639
+ function getEmoji(name) {
640
+ return EMOJIS[name] || "";
641
+ }
642
+
643
+ // src/runtime.ts
644
+ function formatTokens(n) {
645
+ if (n >= 1e6) return Math.round(n / 1e6) + "M";
646
+ if (n >= 1e3) return Math.round(n / 1e3) + "k";
647
+ return n.toString();
648
+ }
649
+ function formatDuration(ms) {
650
+ if (ms < 1e3) return ms + "ms";
651
+ if (ms < 6e4) return (ms / 1e3).toFixed(1) + "s";
652
+ if (ms < 36e5) return Math.round(ms / 6e4) + "m";
653
+ return (ms / 36e5).toFixed(1) + "h";
654
+ }
655
+ function execCommand(cmd) {
656
+ try {
657
+ return execSync(cmd, { encoding: "utf8" }).trim();
658
+ } catch {
659
+ return "";
660
+ }
661
+ }
662
+ function applyStyles(text, styles, noColor) {
663
+ if (noColor || styles.length === 0 || !text) return text;
664
+ const codes = styles.map((s) => COLORS[s]).filter(Boolean);
665
+ if (codes.length === 0) return text;
666
+ return `\x1B[${codes.join(";")}m${text}\x1B[${RESET}m`;
667
+ }
668
+ function evaluateClaudeComponent(key, data) {
669
+ switch (key) {
670
+ case "model":
671
+ return data.model?.display_name || "Claude";
672
+ case "model-id":
673
+ return data.model?.id || "";
674
+ case "model-letter":
675
+ return (data.model?.display_name || "C")[0].toUpperCase();
676
+ case "version":
677
+ return data.version || "";
678
+ case "session":
679
+ return (data.session_id || "").slice(0, 8);
680
+ case "session-full":
681
+ return data.session_id || "";
682
+ case "style":
683
+ return data.output_style?.name || "default";
684
+ default:
685
+ return "";
686
+ }
687
+ }
688
+ function evaluateFsComponent(key, data) {
689
+ switch (key) {
690
+ case "path":
691
+ return data.workspace?.current_dir || data.cwd || "";
692
+ case "dir":
693
+ return path2.basename(data.workspace?.current_dir || data.cwd || "");
694
+ case "project":
695
+ return path2.basename(data.workspace?.project_dir || "");
696
+ case "project-path":
697
+ return data.workspace?.project_dir || "";
698
+ case "home":
699
+ return (data.workspace?.current_dir || data.cwd || "").replace(os2.homedir(), "~");
700
+ case "cwd":
701
+ return data.cwd || "";
702
+ case "relative": {
703
+ const curr = data.workspace?.current_dir || "";
704
+ const proj = data.workspace?.project_dir || "";
705
+ if (proj && curr.startsWith(proj)) {
706
+ return curr.slice(proj.length + 1) || ".";
707
+ }
708
+ return path2.basename(curr);
709
+ }
710
+ default:
711
+ return "";
712
+ }
713
+ }
714
+ function evaluateGitComponent(key) {
715
+ switch (key) {
716
+ case "branch":
717
+ return execCommand("git branch --show-current 2>/dev/null");
718
+ case "status":
719
+ try {
720
+ execSync("git diff --quiet 2>/dev/null");
721
+ return "\u2713";
722
+ } catch {
723
+ return "*";
724
+ }
725
+ case "status-emoji":
726
+ try {
727
+ execSync("git diff --quiet 2>/dev/null");
728
+ return "\u2728";
729
+ } catch {
730
+ return "\u{1F4DD}";
731
+ }
732
+ case "status-word":
733
+ try {
734
+ execSync("git diff --quiet 2>/dev/null");
735
+ return "clean";
736
+ } catch {
737
+ return "dirty";
738
+ }
739
+ case "ahead": {
740
+ const n = execCommand("git rev-list --count @{u}..HEAD 2>/dev/null");
741
+ return n && n !== "0" ? "\u2191" + n : "";
742
+ }
743
+ case "behind": {
744
+ const n = execCommand("git rev-list --count HEAD..@{u} 2>/dev/null");
745
+ return n && n !== "0" ? "\u2193" + n : "";
746
+ }
747
+ case "ahead-behind": {
748
+ const ahead = execCommand("git rev-list --count @{u}..HEAD 2>/dev/null");
749
+ const behind = execCommand("git rev-list --count HEAD..@{u} 2>/dev/null");
750
+ let result = "";
751
+ if (ahead && ahead !== "0") result += "\u2191" + ahead;
752
+ if (behind && behind !== "0") result += "\u2193" + behind;
753
+ return result;
754
+ }
755
+ case "stash": {
756
+ const n = execCommand("git stash list 2>/dev/null | wc -l").trim();
757
+ return n && n !== "0" ? "\u2691" + n : "";
758
+ }
759
+ case "staged": {
760
+ const n = execCommand("git diff --cached --numstat 2>/dev/null | wc -l").trim();
761
+ return n && n !== "0" ? "\u25CF" + n : "";
762
+ }
763
+ case "modified": {
764
+ const n = execCommand("git diff --numstat 2>/dev/null | wc -l").trim();
765
+ return n && n !== "0" ? "+" + n : "";
766
+ }
767
+ case "untracked": {
768
+ const n = execCommand("git ls-files --others --exclude-standard 2>/dev/null | wc -l").trim();
769
+ return n && n !== "0" ? "?" + n : "";
770
+ }
771
+ case "dirty": {
772
+ const staged = parseInt(execCommand("git diff --cached --numstat 2>/dev/null | wc -l")) || 0;
773
+ const modified = parseInt(execCommand("git diff --numstat 2>/dev/null | wc -l")) || 0;
774
+ const untracked = parseInt(execCommand("git ls-files --others --exclude-standard 2>/dev/null | wc -l")) || 0;
775
+ if (staged === 0 && modified === 0 && untracked === 0) return "";
776
+ const parts = [];
777
+ if (staged > 0) parts.push("+" + staged);
778
+ if (untracked > 0) parts.push("-" + untracked);
779
+ if (modified > 0) parts.push("~" + modified);
780
+ return parts.join(" ");
781
+ }
782
+ case "commit":
783
+ return execCommand("git rev-parse --short HEAD 2>/dev/null");
784
+ case "commit-long":
785
+ return execCommand("git rev-parse HEAD 2>/dev/null");
786
+ case "tag":
787
+ return execCommand("git describe --tags --abbrev=0 2>/dev/null");
788
+ case "remote":
789
+ return execCommand("git remote 2>/dev/null").split("\n")[0] || "";
790
+ case "repo":
791
+ return path2.basename(execCommand("git rev-parse --show-toplevel 2>/dev/null"));
792
+ case "user":
793
+ return execCommand("git config user.name 2>/dev/null");
794
+ case "email":
795
+ return execCommand("git config user.email 2>/dev/null");
796
+ case "remote-url":
797
+ return execCommand("git remote get-url origin 2>/dev/null");
798
+ default:
799
+ return "";
800
+ }
801
+ }
802
+ function evaluateContextComponent(key, data, args) {
803
+ const ctx = data.context_window;
804
+ switch (key) {
805
+ case "percent":
806
+ return Math.round(ctx?.used_percentage || 0) + "%";
807
+ case "remaining":
808
+ return Math.round(ctx?.remaining_percentage || 0) + "%";
809
+ case "tokens": {
810
+ const used = (ctx?.total_input_tokens || 0) + (ctx?.total_output_tokens || 0);
811
+ const total = ctx?.context_window_size || 2e5;
812
+ return formatTokens(used) + "/" + formatTokens(total);
813
+ }
814
+ case "in":
815
+ return formatTokens(ctx?.total_input_tokens || 0);
816
+ case "out":
817
+ return formatTokens(ctx?.total_output_tokens || 0);
818
+ case "size":
819
+ return formatTokens(ctx?.context_window_size || 2e5);
820
+ case "bar": {
821
+ const pct = ctx?.used_percentage || 0;
822
+ const width = args ? parseInt(args, 10) || 10 : 10;
823
+ const filled = Math.round(pct / 100 * width);
824
+ return "[" + "\u2588".repeat(filled) + "\u2591".repeat(width - filled) + "]";
825
+ }
826
+ case "emoji": {
827
+ const pct = ctx?.used_percentage || 0;
828
+ if (pct < 50) return "\u{1F7E2}";
829
+ if (pct < 75) return "\u{1F7E1}";
830
+ if (pct < 90) return "\u{1F7E0}";
831
+ return "\u{1F534}";
832
+ }
833
+ case "used-tokens": {
834
+ const used = (ctx?.total_input_tokens || 0) + (ctx?.total_output_tokens || 0);
835
+ return formatTokens(used);
836
+ }
837
+ default:
838
+ return "";
839
+ }
840
+ }
841
+ function evaluateCostComponent(key, data) {
842
+ const cost = data.cost;
843
+ switch (key) {
844
+ case "total":
845
+ return "$" + (cost?.total_cost_usd || 0).toFixed(2);
846
+ case "total-cents":
847
+ return Math.round((cost?.total_cost_usd || 0) * 100) + "\xA2";
848
+ case "duration":
849
+ return formatDuration(cost?.total_duration_ms || 0);
850
+ case "api":
851
+ case "api-duration":
852
+ return formatDuration(cost?.total_api_duration_ms || 0);
853
+ case "lines": {
854
+ const added = cost?.total_lines_added || 0;
855
+ const removed = cost?.total_lines_removed || 0;
856
+ const net = added - removed;
857
+ return (net >= 0 ? "+" : "") + net;
858
+ }
859
+ case "added":
860
+ case "lines-added":
861
+ return "+" + (cost?.total_lines_added || 0);
862
+ case "removed":
863
+ case "lines-removed":
864
+ return "-" + (cost?.total_lines_removed || 0);
865
+ case "lines-both": {
866
+ const added = cost?.total_lines_added || 0;
867
+ const removed = cost?.total_lines_removed || 0;
868
+ return "+" + added + " -" + removed;
869
+ }
870
+ default:
871
+ return "";
872
+ }
873
+ }
874
+ function evaluateEnvComponent(key) {
875
+ switch (key) {
876
+ case "node":
877
+ return execCommand("node --version 2>/dev/null");
878
+ case "node-short":
879
+ return execCommand("node --version 2>/dev/null").replace("v", "");
880
+ case "bun":
881
+ return execCommand("bun --version 2>/dev/null");
882
+ case "npm":
883
+ return execCommand("npm --version 2>/dev/null");
884
+ case "pnpm":
885
+ return execCommand("pnpm --version 2>/dev/null");
886
+ case "yarn":
887
+ return execCommand("yarn --version 2>/dev/null");
888
+ case "python":
889
+ return execCommand("python3 --version 2>/dev/null || python --version 2>/dev/null").replace("Python ", "");
890
+ case "deno":
891
+ return execCommand("deno --version 2>/dev/null").split("\n")[0].replace("deno ", "");
892
+ case "rust":
893
+ return execCommand("rustc --version 2>/dev/null").split(" ")[1] || "";
894
+ case "go":
895
+ return execCommand("go version 2>/dev/null").split(" ")[2]?.replace("go", "") || "";
896
+ case "ruby":
897
+ return execCommand("ruby --version 2>/dev/null").split(" ")[1] || "";
898
+ case "java": {
899
+ const output = execCommand("java -version 2>&1");
900
+ const match = output.match(/"([^"]+)"/);
901
+ return match?.[1] || "";
902
+ }
903
+ case "user":
904
+ return os2.userInfo().username;
905
+ case "hostname":
906
+ return os2.hostname();
907
+ case "hostname-short":
908
+ return os2.hostname().split(".")[0];
909
+ case "shell":
910
+ return path2.basename(process.env.SHELL || "");
911
+ case "term":
912
+ return process.env.TERM || "";
913
+ case "os":
914
+ return process.platform;
915
+ case "arch":
916
+ return process.arch;
917
+ case "os-release":
918
+ return os2.release();
919
+ case "cpus":
920
+ return os2.cpus().length.toString();
921
+ case "memory":
922
+ return Math.round(os2.totalmem() / 1024 / 1024 / 1024) + "GB";
923
+ default:
924
+ return "";
925
+ }
926
+ }
927
+ function evaluateTimeComponent(key, data) {
928
+ const now = /* @__PURE__ */ new Date();
929
+ switch (key) {
930
+ case "now":
931
+ return now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false });
932
+ case "seconds":
933
+ return now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false });
934
+ case "12h":
935
+ return now.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true });
936
+ case "date":
937
+ return now.toLocaleDateString("en-US", { month: "short", day: "numeric" });
938
+ case "full":
939
+ return now.toISOString().split("T")[0];
940
+ case "iso":
941
+ return now.toISOString();
942
+ case "unix":
943
+ return Math.floor(Date.now() / 1e3).toString();
944
+ case "weekday":
945
+ return now.toLocaleDateString("en-US", { weekday: "short" });
946
+ case "weekday-long":
947
+ return now.toLocaleDateString("en-US", { weekday: "long" });
948
+ case "month":
949
+ return now.toLocaleDateString("en-US", { month: "short" });
950
+ case "month-long":
951
+ return now.toLocaleDateString("en-US", { month: "long" });
952
+ case "year":
953
+ return now.getFullYear().toString();
954
+ case "day":
955
+ return now.getDate().toString();
956
+ case "hour":
957
+ return now.getHours().toString().padStart(2, "0");
958
+ case "minute":
959
+ return now.getMinutes().toString().padStart(2, "0");
960
+ case "elapsed": {
961
+ const ms = data.cost?.total_duration_ms || 0;
962
+ if (ms < 6e4) return Math.round(ms / 1e3) + "s";
963
+ if (ms < 36e5) return Math.round(ms / 6e4) + "m";
964
+ return Math.round(ms / 36e5) + "h";
965
+ }
966
+ default:
967
+ return "";
968
+ }
969
+ }
970
+ function evaluateCondition(condition) {
971
+ switch (condition) {
972
+ case "git":
973
+ try {
974
+ execSync("git rev-parse --git-dir 2>/dev/null");
975
+ return true;
976
+ } catch {
977
+ return false;
978
+ }
979
+ case "dirty":
980
+ try {
981
+ execSync("git diff --quiet 2>/dev/null");
982
+ return false;
983
+ } catch {
984
+ return true;
985
+ }
986
+ case "clean":
987
+ try {
988
+ execSync("git diff --quiet 2>/dev/null");
989
+ return true;
990
+ } catch {
991
+ return false;
992
+ }
993
+ case "node":
994
+ return fs2.existsSync("package.json");
995
+ case "python":
996
+ return fs2.existsSync("pyproject.toml") || fs2.existsSync("setup.py") || fs2.existsSync("requirements.txt");
997
+ case "rust":
998
+ return fs2.existsSync("Cargo.toml");
999
+ case "go":
1000
+ return fs2.existsSync("go.mod");
1001
+ default:
1002
+ return true;
1003
+ }
1004
+ }
1005
+ function evaluateComponent(comp, data, options) {
1006
+ let result = "";
1007
+ switch (comp.type) {
1008
+ case "claude":
1009
+ result = evaluateClaudeComponent(comp.key, data);
1010
+ break;
1011
+ case "fs":
1012
+ result = evaluateFsComponent(comp.key, data);
1013
+ break;
1014
+ case "git":
1015
+ result = evaluateGitComponent(comp.key);
1016
+ break;
1017
+ case "ctx":
1018
+ result = evaluateContextComponent(comp.key, data, comp.args);
1019
+ break;
1020
+ case "cost":
1021
+ result = evaluateCostComponent(comp.key, data);
1022
+ break;
1023
+ case "env":
1024
+ result = evaluateEnvComponent(comp.key);
1025
+ break;
1026
+ case "time":
1027
+ result = evaluateTimeComponent(comp.key, data);
1028
+ break;
1029
+ case "sep":
1030
+ result = getSeparator(comp.key);
1031
+ break;
1032
+ case "emoji":
1033
+ result = options.noEmoji ? "" : getEmoji(comp.key);
1034
+ break;
1035
+ case "text":
1036
+ result = comp.key;
1037
+ break;
1038
+ case "group": {
1039
+ if (comp.children) {
1040
+ const inner = evaluateComponents(comp.children, data, options);
1041
+ const brackets = {
1042
+ square: ["[", "]"],
1043
+ paren: ["(", ")"],
1044
+ curly: ["{", "}"],
1045
+ angle: ["<", ">"]
1046
+ };
1047
+ const [open, close] = brackets[comp.key] || ["[", "]"];
1048
+ result = open + inner + close;
1049
+ }
1050
+ break;
1051
+ }
1052
+ case "conditional": {
1053
+ if (comp.children && evaluateCondition(comp.key)) {
1054
+ result = evaluateComponents(comp.children, data, options);
1055
+ }
1056
+ break;
1057
+ }
1058
+ }
1059
+ return applyStyles(result, comp.styles, options.noColor);
1060
+ }
1061
+ function evaluateComponents(components, data, options) {
1062
+ const parts = [];
1063
+ for (let i = 0; i < components.length; i++) {
1064
+ const comp = components[i];
1065
+ const prev = components[i - 1];
1066
+ const value = evaluateComponent(comp, data, options);
1067
+ if (i > 0 && comp.type !== "sep" && prev?.type !== "sep" && value) {
1068
+ parts.push(" ");
1069
+ }
1070
+ if (value) {
1071
+ parts.push(value);
1072
+ }
1073
+ }
1074
+ return parts.join("");
1075
+ }
1076
+ function evaluateFormat(format, data, options = {}) {
1077
+ const opts = {
1078
+ noEmoji: options.noEmoji ?? false,
1079
+ noColor: options.noColor ?? false
1080
+ };
1081
+ const components = parseFormat(format);
1082
+ return evaluateComponents(components, data, opts);
1083
+ }
1084
+
1085
+ // src/index.ts
1086
+ var VERSION = "1.0.0";
1087
+ async function readStdin() {
1088
+ return new Promise((resolve, reject) => {
1089
+ let input = "";
1090
+ process.stdin.setEncoding("utf8");
1091
+ process.stdin.on("data", (chunk) => {
1092
+ input += chunk;
1093
+ });
1094
+ process.stdin.on("end", () => {
1095
+ resolve(input);
1096
+ });
1097
+ process.stdin.on("error", reject);
1098
+ });
1099
+ }
1100
+ program.command("run <format>").description("Evaluate a format string and output the status line").option("--disable-emoji", "Disable emoji output").option("--disable-color", "Disable color output").action(async (format, options) => {
1101
+ try {
1102
+ const input = await readStdin();
1103
+ const data = JSON.parse(input);
1104
+ const output = evaluateFormat(format, data, {
1105
+ noEmoji: options.disableEmoji ?? false,
1106
+ noColor: options.disableColor ?? false
1107
+ });
1108
+ console.log(output);
1109
+ } catch (e) {
1110
+ console.log("[claudeline]");
1111
+ }
1112
+ });
1113
+ program.name("claudeline").description("Customizable status line generator for Claude Code").version(VERSION).argument("[format]", "Status line format string").option("-i, --install", "Install directly to ~/.claude/").option("-u, --uninstall", "Remove statusline configuration").option("--project", "Install/uninstall to project .claude/ instead of global").option("-t, --theme <name>", "Use a preset theme").option("-l, --list", "List all available components").option("--themes", "List all available themes").option("-p, --preview", "Show sample JSON data for testing").option("--no-emoji", "Disable emoji output").option("--no-color", "Disable color output").option("--use-bunx", "Use bunx in installed command").option("--use-npx", "Use npx in installed command").option("--global-install", "Assume claudeline is globally installed (use claudeline directly)").addHelpText("after", `
1114
+ Examples:
1115
+ $ npx claudeline --theme minimal --install
1116
+ $ npx claudeline --theme powerline --install --project
1117
+ $ npx claudeline --list
1118
+ $ npx claudeline --themes
1119
+
1120
+ # Test the run command directly:
1121
+ $ echo '{"model":{"display_name":"Sonnet"}}' | npx claudeline run "claude:model fs:dir"
1122
+
1123
+ Format Syntax:
1124
+ component Single component (e.g., fs:dir, git:branch)
1125
+ prefix:comp Styled component (e.g., green:git:branch, bold:red:text:ERROR)
1126
+ sep:name Separator (e.g., sep:pipe, sep:arrow)
1127
+ emoji:name Emoji (e.g., emoji:folder, emoji:branch)
1128
+ text:value Custom text (e.g., text:Hello)
1129
+ [...] Group with square brackets
1130
+ if:cond(...) Conditional (e.g., if:git(git:branch))
1131
+
1132
+ Components can be separated by spaces or commas.
1133
+ Multiple style prefixes can be chained: bold:green:git:branch
1134
+ `).action((format, options) => {
1135
+ if (options.list) {
1136
+ listComponents();
1137
+ return;
1138
+ }
1139
+ if (options.themes) {
1140
+ listThemes();
1141
+ return;
1142
+ }
1143
+ if (options.uninstall) {
1144
+ const result = uninstall(options.project);
1145
+ console.log(result.message);
1146
+ process.exit(result.success ? 0 : 1);
1147
+ }
1148
+ if (options.preview) {
1149
+ console.log("Sample JSON input for testing:\n");
1150
+ console.log(getSampleDataJson());
1151
+ console.log("\nTest with the run command:");
1152
+ console.log(` echo '<json>' | npx claudeline run "claude:model fs:dir"`);
1153
+ return;
1154
+ }
1155
+ let formatStr = format;
1156
+ if (options.theme) {
1157
+ const theme = getTheme(options.theme);
1158
+ if (!theme) {
1159
+ console.error(`Unknown theme: ${options.theme}`);
1160
+ console.error(`Available themes: ${Object.keys(THEMES).join(", ")}`);
1161
+ process.exit(1);
1162
+ }
1163
+ formatStr = theme;
1164
+ }
1165
+ if (!formatStr) {
1166
+ formatStr = THEMES.default;
1167
+ }
1168
+ const components = parseFormat(formatStr);
1169
+ if (components.length === 0) {
1170
+ console.error("No valid components found in format string");
1171
+ process.exit(1);
1172
+ }
1173
+ if (options.install) {
1174
+ const result = install(formatStr, options.project, {
1175
+ useBunx: options.useBunx,
1176
+ useNpx: options.useNpx,
1177
+ globalInstall: options.globalInstall,
1178
+ noEmoji: !options.emoji,
1179
+ noColor: !options.color
1180
+ });
1181
+ if (result.success) {
1182
+ console.log("\u2713 " + result.message);
1183
+ console.log("\nRestart Claude Code to see your new status line!");
1184
+ } else {
1185
+ console.error("\u2717 " + result.message);
1186
+ process.exit(1);
1187
+ }
1188
+ return;
1189
+ }
1190
+ console.log("Format string validated. Use --install to install.");
1191
+ console.log(`Command: npx claudeline run '${formatStr}'`);
1192
+ });
1193
+ program.parse();