ccstatusline 1.0.0 → 1.0.2

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/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # ccstatusline
2
+
3
+ A customizable status line formatter for Claude Code CLI that displays model info, git branch, token usage, and other metrics in your terminal.
4
+
5
+ ## Features
6
+
7
+ - 📊 **Real-time metrics** - Display model name, git branch, token usage, and more
8
+ - 🎨 **Fully customizable** - Choose what to display and customize colors
9
+ - 🖥️ **Interactive TUI** - Built-in configuration interface using React/Ink
10
+ - 🚀 **Cross-platform** - Works with both Bun and Node.js
11
+ - 📏 **80-character format** - Perfectly sized for Claude Code CLI integration
12
+
13
+ ## Quick Start
14
+
15
+ No installation needed! Use directly with npx:
16
+
17
+ ```bash
18
+ # Run the configuration TUI
19
+ npx ccstatusline
20
+ ```
21
+
22
+ ## Setup
23
+
24
+ ### Configure ccstatusline
25
+
26
+ Run the interactive configuration tool:
27
+
28
+ ```bash
29
+ npx ccstatusline
30
+ ```
31
+
32
+ This launches a TUI where you can:
33
+ - Add/remove status line items
34
+ - Reorder items with arrow keys
35
+ - Customize colors for each element
36
+ - Preview your status line in real-time
37
+
38
+ Your settings are saved to `~/.config/ccstatusline/settings.json`.
39
+
40
+ ## Usage
41
+
42
+ Once configured, ccstatusline automatically formats your Claude Code status line. The status line appears at the bottom of your terminal during Claude Code sessions.
43
+
44
+ ### Available Status Items
45
+
46
+ - **Model Name** - Shows the current Claude model (e.g., "Claude 3.5 Sonnet")
47
+ - **Git Branch** - Displays current git branch name
48
+ - **Token Usage** - Shows input/output/total tokens used
49
+ - **Time** - Current time in HH:MM:SS format
50
+ - **Custom Text** - Add your own static text
51
+ - **Separator** - Visual divider between items
52
+ - **Flex Separator** - Expands to fill available space
53
+
54
+ ## Configuration File
55
+
56
+ The configuration file at `~/.config/ccstatusline/settings.json` looks like:
57
+
58
+ ```json
59
+ {
60
+ "items": [
61
+ {
62
+ "type": "model",
63
+ "color": "cyan"
64
+ },
65
+ {
66
+ "type": "separator",
67
+ "text": " │ ",
68
+ "color": "gray"
69
+ },
70
+ {
71
+ "type": "git_branch",
72
+ "color": "green"
73
+ },
74
+ {
75
+ "type": "separator",
76
+ "text": " │ ",
77
+ "color": "gray"
78
+ },
79
+ {
80
+ "type": "tokens",
81
+ "color": "yellow"
82
+ },
83
+ {
84
+ "type": "flex_separator",
85
+ "text": "─",
86
+ "color": "gray"
87
+ },
88
+ {
89
+ "type": "time",
90
+ "color": "blue"
91
+ }
92
+ ]
93
+ }
94
+ ```
95
+
96
+ ### Color Options
97
+
98
+ Available colors:
99
+ - `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `gray`
100
+ - `brightBlack`, `brightRed`, `brightGreen`, `brightYellow`, `brightBlue`, `brightMagenta`, `brightCyan`, `brightWhite`
101
+
102
+ ## Development
103
+
104
+ ### Prerequisites
105
+
106
+ - [Bun](https://bun.sh)
107
+ - Git
108
+
109
+ ### Setup
110
+
111
+ ```bash
112
+ # Clone the repository
113
+ git clone https://github.com/yourusername/ccstatusline.git
114
+ cd ccstatusline
115
+
116
+ # Install dependencies
117
+ bun install
118
+ ```
119
+
120
+ ### Development Commands
121
+
122
+ ```bash
123
+ # Run in TUI mode (configuration)
124
+ bun run src/ccstatusline.ts
125
+
126
+ # Build for distribution
127
+ bun run build
128
+ ```
129
+
130
+ ### Project Structure
131
+
132
+ ```
133
+ ccstatusline/
134
+ ├── src/
135
+ │ ├── ccstatusline.ts # Main entry point
136
+ │ ├── tui.tsx # React/Ink configuration UI
137
+ │ ├── config.ts # Settings management
138
+ │ └── claude-settings.ts # Claude Code settings integration
139
+ ├── dist/ # Built files (generated)
140
+ ├── package.json
141
+ ├── tsconfig.json
142
+ └── README.md
143
+ ```
144
+ ## Contributing
145
+
146
+ Contributions are welcome! Please feel free to submit a Pull Request.
147
+
148
+ 1. Fork the repository
149
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
150
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
151
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
152
+ 5. Open a Pull Request
153
+
154
+ ## License
155
+
156
+ MIT
157
+
158
+ ## Author
159
+
160
+ Matthew Breedlove
161
+
162
+ ## Acknowledgments
163
+
164
+ Built for use with [Claude Code CLI](https://claude.ai/code) by Anthropic.
@@ -103,13 +103,13 @@ async function saveClaudeSettings(settings) {
103
103
  }
104
104
  async function isInstalled() {
105
105
  const settings = await loadClaudeSettings();
106
- return settings.statusLine?.command === "npx ccstatusline";
106
+ return settings.statusLine?.command === "npx -y ccstatusline";
107
107
  }
108
108
  async function installStatusLine() {
109
109
  const settings = await loadClaudeSettings();
110
110
  settings.statusLine = {
111
111
  type: "command",
112
- command: "npx ccstatusline",
112
+ command: "npx -y ccstatusline",
113
113
  padding: 1
114
114
  };
115
115
  await saveClaudeSettings(settings);
@@ -180,12 +180,13 @@ var StatusLinePreview = ({ items, terminalWidth }) => {
180
180
  const parts = [[]];
181
181
  let currentPart = 0;
182
182
  for (let i = 0;i < items.length; i++) {
183
- if (items[i].type === "flex-separator") {
183
+ const item = items[i];
184
+ if (item && item.type === "flex-separator") {
184
185
  currentPart++;
185
186
  parts[currentPart] = [];
186
187
  } else {
187
188
  const element = elements[i];
188
- if (element !== "FLEX") {
189
+ if (element !== "FLEX" && parts[currentPart]) {
189
190
  parts[currentPart].push(element);
190
191
  }
191
192
  }
@@ -201,7 +202,10 @@ var StatusLinePreview = ({ items, terminalWidth }) => {
201
202
  const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
202
203
  statusLine = "";
203
204
  for (let i = 0;i < parts.length; i++) {
204
- statusLine += parts[i].join("");
205
+ const part = parts[i];
206
+ if (part) {
207
+ statusLine += part.join("");
208
+ }
205
209
  if (i < parts.length - 1) {
206
210
  const spaces = spacePerFlex + (i < extraSpace ? 1 : 0);
207
211
  statusLine += " ".repeat(spaces);
@@ -288,12 +292,20 @@ var ItemsEditor = ({ items, onUpdate, onBack }) => {
288
292
  if (moveMode) {
289
293
  if (key.upArrow && selectedIndex > 0) {
290
294
  const newItems = [...items];
291
- [newItems[selectedIndex], newItems[selectedIndex - 1]] = [newItems[selectedIndex - 1], newItems[selectedIndex]];
295
+ const temp = newItems[selectedIndex];
296
+ const prev = newItems[selectedIndex - 1];
297
+ if (temp && prev) {
298
+ [newItems[selectedIndex], newItems[selectedIndex - 1]] = [prev, temp];
299
+ }
292
300
  onUpdate(newItems);
293
301
  setSelectedIndex(selectedIndex - 1);
294
302
  } else if (key.downArrow && selectedIndex < items.length - 1) {
295
303
  const newItems = [...items];
296
- [newItems[selectedIndex], newItems[selectedIndex + 1]] = [newItems[selectedIndex + 1], newItems[selectedIndex]];
304
+ const temp = newItems[selectedIndex];
305
+ const next = newItems[selectedIndex + 1];
306
+ if (temp && next) {
307
+ [newItems[selectedIndex], newItems[selectedIndex + 1]] = [next, temp];
308
+ }
297
309
  onUpdate(newItems);
298
310
  setSelectedIndex(selectedIndex + 1);
299
311
  } else if (key.escape || key.return) {
@@ -317,12 +329,18 @@ var ItemsEditor = ({ items, onUpdate, onBack }) => {
317
329
  "context-length",
318
330
  "context-percentage"
319
331
  ];
320
- const currentType = items[selectedIndex].type;
321
- const currentIndex = types.indexOf(currentType);
322
- const prevIndex = currentIndex === 0 ? types.length - 1 : currentIndex - 1;
323
- const newItems = [...items];
324
- newItems[selectedIndex] = { ...newItems[selectedIndex], type: types[prevIndex] };
325
- onUpdate(newItems);
332
+ const currentItem = items[selectedIndex];
333
+ if (currentItem) {
334
+ const currentType = currentItem.type;
335
+ const currentIndex = types.indexOf(currentType);
336
+ const prevIndex = currentIndex === 0 ? types.length - 1 : currentIndex - 1;
337
+ const newItems = [...items];
338
+ const prevType = types[prevIndex];
339
+ if (prevType) {
340
+ newItems[selectedIndex] = { ...currentItem, type: prevType };
341
+ onUpdate(newItems);
342
+ }
343
+ }
326
344
  } else if (key.rightArrow && items.length > 0) {
327
345
  const types = [
328
346
  "model",
@@ -336,12 +354,18 @@ var ItemsEditor = ({ items, onUpdate, onBack }) => {
336
354
  "context-length",
337
355
  "context-percentage"
338
356
  ];
339
- const currentType = items[selectedIndex].type;
340
- const currentIndex = types.indexOf(currentType);
341
- const nextIndex = (currentIndex + 1) % types.length;
342
- const newItems = [...items];
343
- newItems[selectedIndex] = { ...newItems[selectedIndex], type: types[nextIndex] };
344
- onUpdate(newItems);
357
+ const currentItem = items[selectedIndex];
358
+ if (currentItem) {
359
+ const currentType = currentItem.type;
360
+ const currentIndex = types.indexOf(currentType);
361
+ const nextIndex = (currentIndex + 1) % types.length;
362
+ const newItems = [...items];
363
+ const nextType = types[nextIndex];
364
+ if (nextType) {
365
+ newItems[selectedIndex] = { ...currentItem, type: nextType };
366
+ onUpdate(newItems);
367
+ }
368
+ }
345
369
  } else if (key.return && items.length > 0) {
346
370
  setMoveMode(true);
347
371
  } else if (input === "a") {
@@ -408,17 +432,17 @@ var ItemsEditor = ({ items, onUpdate, onBack }) => {
408
432
  ]
409
433
  }, undefined, true, undefined, this),
410
434
  moveMode ? /* @__PURE__ */ jsxDEV(Text, {
411
- dim: true,
435
+ dimColor: true,
412
436
  children: "↑↓ to move item, ESC or Enter to exit move mode"
413
437
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
414
- dim: true,
438
+ dimColor: true,
415
439
  children: "↑↓ select, ←→ change type, Enter to move, (a)dd, (i)nsert, (d)elete, ESC back"
416
440
  }, undefined, false, undefined, this),
417
441
  /* @__PURE__ */ jsxDEV(Box, {
418
442
  marginTop: 1,
419
443
  flexDirection: "column",
420
444
  children: items.length === 0 ? /* @__PURE__ */ jsxDEV(Text, {
421
- dim: true,
445
+ dimColor: true,
422
446
  children: "No items. Press 'a' to add one."
423
447
  }, undefined, false, undefined, this) : items.map((item, index) => /* @__PURE__ */ jsxDEV(Box, {
424
448
  children: /* @__PURE__ */ jsxDEV(Text, {
@@ -451,20 +475,29 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
451
475
  bold: true,
452
476
  children: "Configure Colors"
453
477
  }, undefined, false, undefined, this),
454
- /* @__PURE__ */ jsxDEV(Text, {
455
- dim: true,
478
+ /* @__PURE__ */ jsxDEV(Box, {
456
479
  marginTop: 1,
457
- children: "No colorable items in the status line."
480
+ children: /* @__PURE__ */ jsxDEV(Text, {
481
+ dimColor: true,
482
+ children: "No colorable items in the status line."
483
+ }, undefined, false, undefined, this)
458
484
  }, undefined, false, undefined, this),
459
485
  /* @__PURE__ */ jsxDEV(Text, {
460
- dim: true,
486
+ dimColor: true,
461
487
  children: "Add a Model or Git Branch item first."
462
488
  }, undefined, false, undefined, this),
463
- /* @__PURE__ */ jsxDEV(Text, {
489
+ /* @__PURE__ */ jsxDEV(Box, {
464
490
  marginTop: 1,
465
- children: "Press any key to go back..."
491
+ children: /* @__PURE__ */ jsxDEV(Text, {
492
+ children: "Press any key to go back..."
493
+ }, undefined, false, undefined, this)
466
494
  }, undefined, false, undefined, this),
467
- useInput(() => onBack())
495
+ (() => {
496
+ useInput(() => {
497
+ onBack();
498
+ });
499
+ return null;
500
+ })()
468
501
  ]
469
502
  }, undefined, true, undefined, this);
470
503
  }
@@ -553,20 +586,22 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
553
586
  children: "Configure Colors"
554
587
  }, undefined, false, undefined, this),
555
588
  /* @__PURE__ */ jsxDEV(Text, {
556
- dim: true,
589
+ dimColor: true,
557
590
  children: "↑↓ to select item, Enter to cycle color, ESC to go back"
558
591
  }, undefined, false, undefined, this),
559
- selectedItem && /* @__PURE__ */ jsxDEV(Text, {
592
+ selectedItem && /* @__PURE__ */ jsxDEV(Box, {
560
593
  marginTop: 1,
561
- children: [
562
- "Current color (",
563
- colorNumber,
564
- "/",
565
- colors.length,
566
- "): ",
567
- colorDisplay
568
- ]
569
- }, undefined, true, undefined, this),
594
+ children: /* @__PURE__ */ jsxDEV(Text, {
595
+ children: [
596
+ "Current color (",
597
+ colorNumber,
598
+ "/",
599
+ colors.length,
600
+ "): ",
601
+ colorDisplay
602
+ ]
603
+ }, undefined, true, undefined, this)
604
+ }, undefined, false, undefined, this),
570
605
  /* @__PURE__ */ jsxDEV(Box, {
571
606
  marginTop: 1,
572
607
  children: /* @__PURE__ */ jsxDEV(SelectInput, {
@@ -633,12 +668,12 @@ var App = () => {
633
668
  } else {
634
669
  const existing = await getExistingStatusLine();
635
670
  let message;
636
- if (existing && existing !== "npx ccstatusline") {
671
+ if (existing && existing !== "npx -y ccstatusline") {
637
672
  message = `This will modify ~/.claude/settings.json
638
673
 
639
674
  A status line is already configured: "${existing}"
640
- Replace it with ccstatusline?`;
641
- } else if (existing === "npx ccstatusline") {
675
+ Replace it with npx -y ccstatusline?`;
676
+ } else if (existing === "npx -y ccstatusline") {
642
677
  message = `ccstatusline is already installed in ~/.claude/settings.json
643
678
  Reinstall it?`;
644
679
  } else {
@@ -697,7 +732,7 @@ Continue?`;
697
732
  /* @__PURE__ */ jsxDEV(Box, {
698
733
  marginBottom: 1,
699
734
  children: /* @__PURE__ */ jsxDEV(Text, {
700
- dim: true,
735
+ dimColor: true,
701
736
  children: "Preview:"
702
737
  }, undefined, false, undefined, this)
703
738
  }, undefined, false, undefined, this),
@@ -877,7 +912,8 @@ async function renderStatusLine(data) {
877
912
  }
878
913
  break;
879
914
  case "separator":
880
- if (elements.length > 0 && elements[elements.length - 1].type !== "separator") {
915
+ const lastElement = elements[elements.length - 1];
916
+ if (elements.length > 0 && lastElement && lastElement.type !== "separator") {
881
917
  const sepColor = chalk2[settings.colors.separator] || chalk2.dim;
882
918
  elements.push({ content: sepColor(" | "), type: "separator" });
883
919
  }
@@ -913,7 +949,10 @@ async function renderStatusLine(data) {
913
949
  const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
914
950
  statusLine = "";
915
951
  for (let i = 0;i < parts.length; i++) {
916
- statusLine += parts[i].join("");
952
+ const part = parts[i];
953
+ if (part) {
954
+ statusLine += part.join("");
955
+ }
917
956
  if (i < parts.length - 1) {
918
957
  const spaces = spacePerFlex + (i < extraSpace ? 1 : 0);
919
958
  statusLine += " ".repeat(spaces);
@@ -952,7 +991,7 @@ async function main() {
952
991
  process.exit(1);
953
992
  }
954
993
  } else {
955
- await runTUI();
994
+ runTUI();
956
995
  }
957
996
  }
958
997
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",