@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.
Files changed (3) hide show
  1. package/README.md +25 -21
  2. package/dist/index.js +52 -14
  3. 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`; effort shown only for models that support it), with an `⚡ultra` badge when ultracode is enabled in your Claude Code settings and the session reports `xhigh` effort
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, powered by [diffdeck](https://github.com/say8425/diffdeck) (installed automatically as a dependency) — file tree, working-tree / vs-base modes, watch mode (auto-refresh), file folding, and in-app search (`Cmd/Ctrl+F`) across the full diff, including deleted lines
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 ID**: Full session UUID at the end of the session-time line (to the right of the model segment), with no emoji label — ready to copy into `claude --resume <id>` or a log lookup
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
- | _(none)_ | Session ID — the full UUID, shown after 🤖 (or at the end of the session-time line) without an emoji label |
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
- ### How It Works (Diff Viewer)
142
+ ## How It Works
143
143
 
144
- 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.
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
- ## Usage Metrics
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 input (CLI 2.1.80+):
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
- Usage metrics are **automatically displayed** when `rate_limits` is present in the stdin JSON. No additional flags or configuration needed.
155
+ > [!NOTE]
156
+ > `rate_limits` is only available for Claude.ai subscribers (Pro/Max) after the first API response.
165
157
 
166
- The session ID (`session_id`) lives on the session-time line instead — see [Features](#features) — since it comes from a separate field and doesn't depend on `rate_limits`.
158
+ ### Model and Ultracode
167
159
 
168
- > [!NOTE]
169
- > `rate_limits` is only available for Claude.ai subscribers (Pro/Max) after the first API response. See the [official statusline docs](https://code.claude.com/docs/en/statusline) for the full JSON schema.
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
- const sessionId = ctx.claudeJson.session_id;
564
- if (sessionId) {
565
- line2 += ` | ${C.WHITE}${sessionId}${C.RESET}`;
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 homedir2 } from "os";
634
- import { join as join4 } from "path";
635
- var MANAGED_SETTINGS_PATH = process.platform === "darwin" ? "/Library/Application Support/ClaudeCode/managed-settings.json" : process.platform === "win32" ? join4(process.env.ProgramData || "C:\\ProgramData", "ClaudeCode", "managed-settings.json") : "/etc/claude-code/managed-settings.json";
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(join4(projectDir, ".claude", "settings.local.json"), join4(projectDir, ".claude", "settings.json"));
675
+ paths.push(join5(projectDir, ".claude", "settings.local.json"), join5(projectDir, ".claude", "settings.json"));
640
676
  }
641
- paths.push(join4(homeDir, ".claude", "settings.json"));
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) => readFile(path, "utf8").catch(() => null)));
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 = homedir2()) => {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@say8425/cc-statusline",
3
- "version": "6.2.4",
3
+ "version": "6.3.0",
4
4
  "description": "Custom statusline for Claude Code",
5
5
  "repository": {
6
6
  "type": "git",