@say8425/cc-statusline 6.2.4 → 6.3.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/README.md +25 -21
- package/dist/index.js +52 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,9 +54,9 @@ Add the following to `~/.claude/settings.json`:
|
|
|
54
54
|
- **Session Time**: Current session elapsed time
|
|
55
55
|
- **Cost**: Session cost in USD — hidden by default, set `CC_STATUSLINE_SHOW_COST=1` to show (see [Configuration](#configuration))
|
|
56
56
|
- **Context**: Token usage with percentage (color-coded)
|
|
57
|
-
- **Model**: Current model name and reasoning effort (e.g., `Fable 5 high
|
|
57
|
+
- **Model**: Current model name and reasoning effort (e.g., `Fable 5 high`), with an `⚡ultra` badge in ultracode sessions
|
|
58
58
|
- **Git Diff**: File count, insertions, deletions
|
|
59
|
-
- **Clickable Diff Viewer**: Click `✏️` to open a local diff viewer in your browser
|
|
59
|
+
- **Clickable Diff Viewer**: Click `✏️` to open a local diff viewer in your browser (see [Diff Viewer](#diff-viewer))
|
|
60
60
|
- **PR URL**: Clickable OSC 8 hyperlink
|
|
61
61
|
- **Worktree Support**: Shows real project name when running in a `cc --worktree` session
|
|
62
62
|
- **TrueColor**: Dynamic colors based on thresholds
|
|
@@ -64,7 +64,7 @@ Add the following to `~/.claude/settings.json`:
|
|
|
64
64
|
- **Block Usage**: 5-hour utilization percentage
|
|
65
65
|
- **Weekly Reset Timer**: Weekly limit reset time (MM/DD(Fri) HH:MM — the weekday name follows your locale)
|
|
66
66
|
- **Weekly Usage**: 7-day utilization percentage
|
|
67
|
-
- **Session
|
|
67
|
+
- **Session Name**: This session's mention address, shown as `@"name"` — paste it into another Claude session to message this one
|
|
68
68
|
|
|
69
69
|
## Emoji Guide
|
|
70
70
|
|
|
@@ -77,7 +77,7 @@ Add the following to `~/.claude/settings.json`:
|
|
|
77
77
|
| 💰 | Session cost in USD (hidden by default — see [Configuration](#configuration)) |
|
|
78
78
|
| 🧠 | Context window usage |
|
|
79
79
|
| 🤖 | Current model and effort |
|
|
80
|
-
|
|
|
80
|
+
| `@` | Session name — the mention address |
|
|
81
81
|
| ⏳ | Limit reset time |
|
|
82
82
|
| 📊 | 5-hour utilization % |
|
|
83
83
|
| ⏰ | Weekly limit reset time |
|
|
@@ -139,34 +139,38 @@ Click `✏️` in the statusline to open a local diff viewer in your browser. Th
|
|
|
139
139
|
- **diff-grab**: select code in the diff (drag the text for a character-precise selection, or use the gutter `+` for whole lines), type a prompt, and press Enter — the file path, line range, code snippet, and your prompt are copied to the clipboard, ready to paste into an agent like Claude Code
|
|
140
140
|
- **Include untracked** files toggle
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
## How It Works
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
The two `CC_STATUSLINE_DIFF_*` variables that control it are listed in [Configuration](#configuration).
|
|
147
|
-
|
|
148
|
-
> [!TIP]
|
|
149
|
-
> Open the viewer through the `✏️` link instead of a bookmark — the link always carries a fresh token and makes sure the server is running.
|
|
144
|
+
Most of what the statusline shows comes from the JSON Claude Code passes on stdin — see the [official statusline docs](https://code.claude.com/docs/en/statusline) for the full schema. The few values stdin doesn't carry are read locally, as described below.
|
|
150
145
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Shows usage information from Claude Code's stdin JSON input.
|
|
154
|
-
|
|
155
|
-
### How It Works
|
|
146
|
+
### Usage Metrics
|
|
156
147
|
|
|
157
|
-
Claude Code passes `rate_limits` in the stdin JSON
|
|
148
|
+
Claude Code passes `rate_limits` in the stdin JSON (CLI 2.1.80+). The usage line appears automatically whenever it is present — no flags or configuration needed:
|
|
158
149
|
|
|
159
150
|
1. **5-hour utilization** - Usage percentage for the current billing block (`rate_limits.five_hour.used_percentage`)
|
|
160
151
|
2. **7-day utilization** - Weekly usage percentage (`rate_limits.seven_day.used_percentage`)
|
|
161
152
|
3. **Reset timer** - Exact reset time (`rate_limits.five_hour.resets_at`), shown as `HH:MM`
|
|
162
153
|
4. **Weekly reset timer** - Weekly limit reset time (`rate_limits.seven_day.resets_at`), shown as `MM/DD(weekday) HH:MM`. The weekday name is localized from `LC_ALL` / `LC_TIME` / `LANG` (e.g., `02/15(Thu) 17:00` under `en_US.UTF-8`, `02/15(목) 17:00` under `ko_KR.UTF-8`). If none of the three holds a usable value — unset, empty, or `C`/`POSIX`, which mean "do not localize" — the weekday falls back to the runtime default locale (`en-US` with current Bun). macOS Terminal leaves `LANG` empty unless "Set locale environment variables on startup" is enabled
|
|
163
154
|
|
|
164
|
-
|
|
155
|
+
> [!NOTE]
|
|
156
|
+
> `rate_limits` is only available for Claude.ai subscribers (Pro/Max) after the first API response.
|
|
165
157
|
|
|
166
|
-
|
|
158
|
+
### Model and Ultracode
|
|
167
159
|
|
|
168
|
-
|
|
169
|
-
|
|
160
|
+
The model name and effort come from `model.display_name` and `effort.level` (effort is sent only for models that support it). Ultracode isn't exposed on stdin, so the statusline reads the `ultracode` key from your Claude Code settings files (managed → project local → project → user) and shows `⚡ultra` only when the session also reports `xhigh` effort.
|
|
161
|
+
|
|
162
|
+
### Session Name
|
|
163
|
+
|
|
164
|
+
The session name is the address other Claude sessions use to message this one: the name set with `/rename` or `claude -n`, otherwise the default display name such as `my-app-3f`. stdin's `session_name` can't stand in for it — for an unnamed session it holds an AI-generated title, which isn't an address, and it never holds the default display name. The statusline therefore reads Claude Code's local session registry (`<CLAUDE_CONFIG_DIR or ~/.claude>/sessions`) and picks the entry matching `session_id`. The name is always quoted, so names with spaces or non-ASCII characters paste straight into a mention, and the segment is hidden when the registry has no entry for the session. It doesn't depend on `rate_limits`.
|
|
165
|
+
|
|
166
|
+
### Diff Viewer
|
|
167
|
+
|
|
168
|
+
The statusline spawns diffdeck as a background daemon on demand at `127.0.0.1:49573` whenever the repo has something to show. Requests are token-protected and bound to localhost.
|
|
169
|
+
|
|
170
|
+
The two `CC_STATUSLINE_DIFF_*` variables that control it are listed in [Configuration](#configuration).
|
|
171
|
+
|
|
172
|
+
> [!TIP]
|
|
173
|
+
> Open the viewer through the `✏️` link instead of a bookmark — the link always carries a fresh token and makes sure the server is running.
|
|
170
174
|
|
|
171
175
|
## Dependencies
|
|
172
176
|
|
package/dist/index.js
CHANGED
|
@@ -560,9 +560,9 @@ var renderStatusLine = (ctx) => {
|
|
|
560
560
|
modelText += " \u26A1ultra";
|
|
561
561
|
line2 += ` | ${C.WHITE}\uD83E\uDD16 ${modelText}${C.RESET}`;
|
|
562
562
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
line2 += ` | ${C.WHITE}${
|
|
563
|
+
if (ctx.sessionName) {
|
|
564
|
+
const quoted = ctx.sessionName.replaceAll('"', "\\\"");
|
|
565
|
+
line2 += ` | ${C.WHITE}@"${quoted}"${C.RESET}`;
|
|
566
566
|
}
|
|
567
567
|
lines.push(line2);
|
|
568
568
|
const usageParts = [];
|
|
@@ -619,6 +619,42 @@ var renderStatusLine = (ctx) => {
|
|
|
619
619
|
return lines;
|
|
620
620
|
};
|
|
621
621
|
|
|
622
|
+
// src/sessionName.ts
|
|
623
|
+
import { readdir, readFile } from "fs/promises";
|
|
624
|
+
import { homedir as homedir2 } from "os";
|
|
625
|
+
import { join as join4 } from "path";
|
|
626
|
+
var sessionsDir = (env = process.env, homeDir = homedir2()) => join4(env.CLAUDE_CONFIG_DIR || join4(homeDir, ".claude"), "sessions");
|
|
627
|
+
var parseEntry = (content, sessionId) => {
|
|
628
|
+
try {
|
|
629
|
+
const parsed = JSON.parse(content);
|
|
630
|
+
if (typeof parsed !== "object" || parsed === null || !("sessionId" in parsed) || parsed.sessionId !== sessionId || !("name" in parsed) || typeof parsed.name !== "string") {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
const name = parsed.name.trim();
|
|
634
|
+
if (!name)
|
|
635
|
+
return null;
|
|
636
|
+
const updatedAt = "updatedAt" in parsed && typeof parsed.updatedAt === "number" ? parsed.updatedAt : 0;
|
|
637
|
+
return { name, updatedAt };
|
|
638
|
+
} catch {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
var readSessionName = async (sessionId, dir = sessionsDir()) => {
|
|
643
|
+
if (!sessionId)
|
|
644
|
+
return null;
|
|
645
|
+
let files;
|
|
646
|
+
try {
|
|
647
|
+
files = (await readdir(dir)).filter((file) => file.endsWith(".json"));
|
|
648
|
+
} catch {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const contents = await Promise.all(files.map((file) => readFile(join4(dir, file), "utf8").catch(() => null)));
|
|
652
|
+
const matches = contents.map((content) => content === null ? null : parseEntry(content, sessionId)).filter((entry) => entry !== null);
|
|
653
|
+
if (matches.length === 0)
|
|
654
|
+
return null;
|
|
655
|
+
return matches.reduce((a, b) => b.updatedAt > a.updatedAt ? b : a).name;
|
|
656
|
+
};
|
|
657
|
+
|
|
622
658
|
// src/stdin.ts
|
|
623
659
|
var readStdin = async () => {
|
|
624
660
|
const chunks = [];
|
|
@@ -629,20 +665,20 @@ var readStdin = async () => {
|
|
|
629
665
|
};
|
|
630
666
|
|
|
631
667
|
// src/ultracode.ts
|
|
632
|
-
import { readFile } from "fs/promises";
|
|
633
|
-
import { homedir as
|
|
634
|
-
import { join as
|
|
635
|
-
var MANAGED_SETTINGS_PATH = process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : process.platform === "win32" ?
|
|
668
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
669
|
+
import { homedir as homedir3 } from "os";
|
|
670
|
+
import { join as join5 } from "path";
|
|
671
|
+
var MANAGED_SETTINGS_PATH = process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : process.platform === "win32" ? join5(process.env.ProgramData || "C:\\ProgramData", "ClaudeCode", "managed-settings.json") : "/etc/claude-code/managed-settings.json";
|
|
636
672
|
var ultracodeSettingsPaths = (projectDir, homeDir) => {
|
|
637
673
|
const paths = [MANAGED_SETTINGS_PATH];
|
|
638
674
|
if (projectDir) {
|
|
639
|
-
paths.push(
|
|
675
|
+
paths.push(join5(projectDir, ".claude", "settings.local.json"), join5(projectDir, ".claude", "settings.json"));
|
|
640
676
|
}
|
|
641
|
-
paths.push(
|
|
677
|
+
paths.push(join5(homeDir, ".claude", "settings.json"));
|
|
642
678
|
return paths;
|
|
643
679
|
};
|
|
644
680
|
var resolveUltracodeFromFiles = async (paths) => {
|
|
645
|
-
const contents = await Promise.all(paths.map((path) =>
|
|
681
|
+
const contents = await Promise.all(paths.map((path) => readFile2(path, "utf8").catch(() => null)));
|
|
646
682
|
for (const content of contents) {
|
|
647
683
|
if (content === null)
|
|
648
684
|
continue;
|
|
@@ -655,7 +691,7 @@ var resolveUltracodeFromFiles = async (paths) => {
|
|
|
655
691
|
}
|
|
656
692
|
return false;
|
|
657
693
|
};
|
|
658
|
-
var getUltracodeCached = async (projectDir, homeDir =
|
|
694
|
+
var getUltracodeCached = async (projectDir, homeDir = homedir3()) => {
|
|
659
695
|
if (Date.now() - cache.ultracode.timestamp < CACHE_TTL.ultracode) {
|
|
660
696
|
return cache.ultracode.value;
|
|
661
697
|
}
|
|
@@ -668,12 +704,13 @@ var getUltracodeCached = async (projectDir, homeDir = homedir2()) => {
|
|
|
668
704
|
var main = async () => {
|
|
669
705
|
const claudeJson = JSON.parse(await readStdin() || "{}");
|
|
670
706
|
const repo = claudeJson.workspace?.project_dir || claudeJson.workspace?.current_dir || "";
|
|
671
|
-
const [branch, gitChanges, prInfo, mainProject, ultracode] = await Promise.all([
|
|
707
|
+
const [branch, gitChanges, prInfo, mainProject, ultracode, sessionName] = await Promise.all([
|
|
672
708
|
getBranchCached(),
|
|
673
709
|
getGitChangesCached(),
|
|
674
710
|
getPrInfoCached(),
|
|
675
711
|
getMainProjectCached(),
|
|
676
|
-
getUltracodeCached(repo)
|
|
712
|
+
getUltracodeCached(repo),
|
|
713
|
+
readSessionName(claudeJson.session_id)
|
|
677
714
|
]);
|
|
678
715
|
const mainProjectName = mainProject?.name ?? null;
|
|
679
716
|
const projectDirUrl = repo ? toFileUrl(repo) : null;
|
|
@@ -719,7 +756,8 @@ var main = async () => {
|
|
|
719
756
|
baseChanges,
|
|
720
757
|
baseDiffViewerUrl,
|
|
721
758
|
projectDirUrl,
|
|
722
|
-
mainProjectUrl
|
|
759
|
+
mainProjectUrl,
|
|
760
|
+
sessionName
|
|
723
761
|
});
|
|
724
762
|
for (const line of lines) {
|
|
725
763
|
console.log(line);
|