ccstatusline 2.2.5 → 2.2.6

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 CHANGED
@@ -46,7 +46,7 @@
46
46
 
47
47
  ## 🆕 Recent Updates
48
48
 
49
- ### v2.2.0 - v2.2.5 - Token Speed + Skills widget updates
49
+ ### v2.2.0 - v2.2.6 - Speed, widgets, links, and reliability updates
50
50
 
51
51
  - **🚀 New Token Speed widgets** - Added three widgets: **Input Speed**, **Output Speed**, and **Total Speed**.
52
52
  - Each speed widget supports a configurable window of `0-120` seconds in the widget editor (`w` key).
@@ -57,6 +57,7 @@
57
57
  - **🧠 New Thinking Effort widget (v2.2.4)** - Added a widget that shows the current Claude Code thinking effort level.
58
58
  - **🍎 Better macOS usage lookup reliability (v2.2.5)** - Improved reliability when loading usage API tokens on macOS.
59
59
  - **⌨️ New Vim Mode widget (v2.2.5)** - Added a widget that shows the current vim mode, with ASCII and optional Nerd Font icon display.
60
+ - **🔗 Git widget link modes (v2.2.6)** - `Git Branch` can render clickable GitHub branch links, and `Git Root Dir` can render clickable IDE links for VS Code and Cursor.
60
61
  - **🤝 Better subagent-aware speed reporting** - Token speed calculations continue to include referenced subagent activity so displayed speeds better reflect actual concurrent work.
61
62
 
62
63
  ### v2.1.0 - v2.1.10 - Usage widgets, links, new git insertions / deletions widgets, and reliability fixes
@@ -467,6 +468,7 @@ bun run example
467
468
  - **Weekly Reset Timer** - Shows time remaining until weekly usage reset
468
469
  - **Context Bar** - Shows context usage as a progress bar with short/full display modes
469
470
  - **Skills** - Shows skill activity as last used, total count, or unique list (with optional list limit and hide-when-empty toggle)
471
+ - **Vim Mode** - Displays current vim editor mode
470
472
  - **Separator** - Visual divider between widgets (available when Powerline mode is off and no default separator is configured)
471
473
  - **Flex Separator** - Expands to fill available space (available when Powerline mode is off)
472
474
 
@@ -54864,6 +54864,75 @@ function getGitChangeCounts(context) {
54864
54864
  }
54865
54865
  var init_git = () => {};
54866
54866
 
54867
+ // src/utils/hyperlink.ts
54868
+ function renderOsc8Link(url2, text) {
54869
+ return `\x1B]8;;${url2}\x1B\\${text}\x1B]8;;\x1B\\`;
54870
+ }
54871
+ function parseGitHubRepositoryPath(pathname) {
54872
+ const trimmedPath = pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
54873
+ const segments = trimmedPath.split("/").filter(Boolean);
54874
+ if (segments.length !== 2) {
54875
+ return null;
54876
+ }
54877
+ return `${segments[0]}/${segments[1]}`;
54878
+ }
54879
+ function parseGitHubBaseUrl(remoteUrl) {
54880
+ const trimmed = remoteUrl.trim();
54881
+ if (trimmed.length === 0) {
54882
+ return null;
54883
+ }
54884
+ const sshMatch = /^(?:[^@]+@)?github\.com:([^/]+\/[^/]+?)(?:\.git)?\/?$/.exec(trimmed);
54885
+ if (sshMatch?.[1]) {
54886
+ return `https://github.com/${sshMatch[1]}`;
54887
+ }
54888
+ try {
54889
+ const parsedUrl = new URL(trimmed);
54890
+ const supportedProtocols = new Set([
54891
+ "http:",
54892
+ "https:",
54893
+ "ssh:",
54894
+ "git:"
54895
+ ]);
54896
+ if (parsedUrl.hostname.toLowerCase() !== "github.com" || !supportedProtocols.has(parsedUrl.protocol)) {
54897
+ return null;
54898
+ }
54899
+ const repoPath = parseGitHubRepositoryPath(parsedUrl.pathname);
54900
+ if (!repoPath) {
54901
+ return null;
54902
+ }
54903
+ return `https://github.com/${repoPath}`;
54904
+ } catch {
54905
+ return null;
54906
+ }
54907
+ }
54908
+ function encodeGitRefForUrlPath(ref) {
54909
+ return ref.split("/").map((segment) => encodeURIComponent(segment)).join("/");
54910
+ }
54911
+ function encodeFilePathForUri(path) {
54912
+ return path.replace(/\\/g, "/").split("/").map((segment) => encodeURIComponent(segment)).join("/");
54913
+ }
54914
+ function buildIdeFileUrl(filePath, ideLinkMode) {
54915
+ const normalizedPath = filePath.replace(/\\/g, "/");
54916
+ const uncMatch = /^\/\/([^/]+)(\/.*)?$/.exec(normalizedPath);
54917
+ if (uncMatch?.[1]) {
54918
+ const encodedPath = encodeFilePathForUri(uncMatch[2] ?? "/");
54919
+ return `${ideLinkMode}://file//${uncMatch[1]}${encodedPath}`;
54920
+ }
54921
+ const driveMatch = /^([A-Za-z]:)(\/.*)?$/.exec(normalizedPath);
54922
+ if (driveMatch?.[1]) {
54923
+ const encodedPath = encodeFilePathForUri(driveMatch[2] ?? "/");
54924
+ return `${ideLinkMode}://file/${driveMatch[1]}${encodedPath}`;
54925
+ }
54926
+ return `${ideLinkMode}://file${encodeFilePathForUri(normalizedPath)}`;
54927
+ }
54928
+ var IDE_LINK_MODES;
54929
+ var init_hyperlink = __esm(() => {
54930
+ IDE_LINK_MODES = [
54931
+ "vscode",
54932
+ "cursor"
54933
+ ];
54934
+ });
54935
+
54867
54936
  // src/widgets/shared/editor-display.ts
54868
54937
  function makeModifierText(modifiers) {
54869
54938
  return modifiers.length > 0 ? `(${modifiers.join(", ")})` : undefined;
@@ -54930,32 +54999,56 @@ class GitBranchWidget {
54930
54999
  return "Git";
54931
55000
  }
54932
55001
  getEditorDisplay(item) {
55002
+ const isLink = isMetadataFlagEnabled(item, LINK_KEY);
55003
+ const modifiers = [];
55004
+ const noGitText = getHideNoGitModifierText(item);
55005
+ if (noGitText)
55006
+ modifiers.push("hide 'no git'");
55007
+ if (isLink)
55008
+ modifiers.push("GitHub link");
54933
55009
  return {
54934
55010
  displayText: this.getDisplayName(),
54935
- modifierText: getHideNoGitModifierText(item)
55011
+ modifierText: makeModifierText(modifiers)
54936
55012
  };
54937
55013
  }
54938
55014
  handleEditorAction(action, item) {
55015
+ if (action === TOGGLE_LINK_ACTION) {
55016
+ return toggleMetadataFlag(item, LINK_KEY);
55017
+ }
54939
55018
  return handleToggleNoGitAction(action, item);
54940
55019
  }
54941
55020
  render(item, context, settings) {
54942
55021
  const hideNoGit = isHideNoGitEnabled(item);
55022
+ const isLink = isMetadataFlagEnabled(item, LINK_KEY);
54943
55023
  if (context.isPreview) {
54944
- return item.rawValue ? "main" : "⎇ main";
55024
+ const text = item.rawValue ? "main" : "⎇ main";
55025
+ return isLink ? renderOsc8Link("https://github.com/owner/repo/tree/main", text) : text;
54945
55026
  }
54946
55027
  if (!isInsideGitWorkTree(context)) {
54947
55028
  return hideNoGit ? null : "⎇ no git";
54948
55029
  }
54949
55030
  const branch = this.getGitBranch(context);
54950
- if (branch)
54951
- return item.rawValue ? branch : `⎇ ${branch}`;
54952
- return hideNoGit ? null : "⎇ no git";
55031
+ if (!branch) {
55032
+ return hideNoGit ? null : "⎇ no git";
55033
+ }
55034
+ const displayText = item.rawValue ? branch : `⎇ ${branch}`;
55035
+ if (isLink) {
55036
+ const remoteUrl = runGit("remote get-url origin", context);
55037
+ const baseUrl = remoteUrl ? parseGitHubBaseUrl(remoteUrl) : null;
55038
+ if (baseUrl) {
55039
+ return renderOsc8Link(`${baseUrl}/tree/${encodeGitRefForUrlPath(branch)}`, displayText);
55040
+ }
55041
+ }
55042
+ return displayText;
54953
55043
  }
54954
55044
  getGitBranch(context) {
54955
55045
  return runGit("branch --show-current", context);
54956
55046
  }
54957
55047
  getCustomKeybinds() {
54958
- return getHideNoGitKeybinds();
55048
+ return [
55049
+ ...getHideNoGitKeybinds(),
55050
+ { key: "l", label: "(l)ink to GitHub", action: TOGGLE_LINK_ACTION }
55051
+ ];
54959
55052
  }
54960
55053
  supportsRawValue() {
54961
55054
  return true;
@@ -54964,8 +55057,10 @@ class GitBranchWidget {
54964
55057
  return true;
54965
55058
  }
54966
55059
  }
55060
+ var LINK_KEY = "linkToGitHub", TOGGLE_LINK_ACTION = "toggle-link";
54967
55061
  var init_GitBranch = __esm(() => {
54968
55062
  init_git();
55063
+ init_hyperlink();
54969
55064
  init_git_no_git();
54970
55065
  });
54971
55066
 
@@ -55131,27 +55226,43 @@ class GitRootDirWidget {
55131
55226
  return "Git";
55132
55227
  }
55133
55228
  getEditorDisplay(item) {
55229
+ const ideLinkMode = this.getIdeLinkMode(item);
55230
+ const modifiers = [];
55231
+ const noGitText = getHideNoGitModifierText(item);
55232
+ if (noGitText)
55233
+ modifiers.push("hide 'no git'");
55234
+ if (ideLinkMode)
55235
+ modifiers.push(IDE_LINK_LABELS[ideLinkMode]);
55134
55236
  return {
55135
55237
  displayText: this.getDisplayName(),
55136
- modifierText: getHideNoGitModifierText(item)
55238
+ modifierText: makeModifierText(modifiers)
55137
55239
  };
55138
55240
  }
55139
55241
  handleEditorAction(action, item) {
55242
+ if (action === TOGGLE_LINK_ACTION2) {
55243
+ return this.cycleIdeLinkMode(item);
55244
+ }
55140
55245
  return handleToggleNoGitAction(action, item);
55141
55246
  }
55142
55247
  render(item, context, _settings) {
55143
55248
  const hideNoGit = isHideNoGitEnabled(item);
55249
+ const ideLinkMode = this.getIdeLinkMode(item);
55144
55250
  if (context.isPreview) {
55145
- return "my-repo";
55251
+ const name2 = "my-repo";
55252
+ return ideLinkMode ? renderOsc8Link(buildIdeFileUrl("/Users/example/my-repo", ideLinkMode), name2) : name2;
55146
55253
  }
55147
55254
  if (!isInsideGitWorkTree(context)) {
55148
55255
  return hideNoGit ? null : "no git";
55149
55256
  }
55150
55257
  const rootDir = this.getGitRootDir(context);
55151
- if (rootDir) {
55152
- return this.getRootDirName(rootDir);
55258
+ if (!rootDir) {
55259
+ return hideNoGit ? null : "no git";
55153
55260
  }
55154
- return hideNoGit ? null : "no git";
55261
+ const name = this.getRootDirName(rootDir);
55262
+ if (ideLinkMode) {
55263
+ return renderOsc8Link(buildIdeFileUrl(rootDir, ideLinkMode), name);
55264
+ }
55265
+ return name;
55155
55266
  }
55156
55267
  getGitRootDir(context) {
55157
55268
  return runGit("rev-parse --show-toplevel", context);
@@ -55164,7 +55275,10 @@ class GitRootDirWidget {
55164
55275
  return lastPart && lastPart.length > 0 ? lastPart : normalizedRootDir;
55165
55276
  }
55166
55277
  getCustomKeybinds() {
55167
- return getHideNoGitKeybinds();
55278
+ return [
55279
+ ...getHideNoGitKeybinds(),
55280
+ { key: "l", label: "(l)ink to IDE", action: TOGGLE_LINK_ACTION2 }
55281
+ ];
55168
55282
  }
55169
55283
  supportsRawValue() {
55170
55284
  return false;
@@ -55172,10 +55286,43 @@ class GitRootDirWidget {
55172
55286
  supportsColors(item) {
55173
55287
  return true;
55174
55288
  }
55289
+ getIdeLinkMode(item) {
55290
+ const configuredMode = item.metadata?.[IDE_LINK_KEY];
55291
+ if (configuredMode && IDE_LINK_MODES.includes(configuredMode)) {
55292
+ return configuredMode;
55293
+ }
55294
+ if (isMetadataFlagEnabled(item, LEGACY_CURSOR_LINK_KEY)) {
55295
+ return "cursor";
55296
+ }
55297
+ return null;
55298
+ }
55299
+ cycleIdeLinkMode(item) {
55300
+ const currentMode = this.getIdeLinkMode(item);
55301
+ const currentIndex = currentMode ? IDE_LINK_MODES.indexOf(currentMode) : -1;
55302
+ const nextMode = currentIndex === IDE_LINK_MODES.length - 1 ? null : IDE_LINK_MODES[currentIndex + 1] ?? null;
55303
+ const {
55304
+ [IDE_LINK_KEY]: removedIdeLink,
55305
+ [LEGACY_CURSOR_LINK_KEY]: removedLegacyLink,
55306
+ ...restMetadata
55307
+ } = item.metadata ?? {};
55308
+ return {
55309
+ ...item,
55310
+ metadata: nextMode ? {
55311
+ ...restMetadata,
55312
+ [IDE_LINK_KEY]: nextMode
55313
+ } : Object.keys(restMetadata).length > 0 ? restMetadata : undefined
55314
+ };
55315
+ }
55175
55316
  }
55317
+ var IDE_LINK_KEY = "linkToIDE", LEGACY_CURSOR_LINK_KEY = "linkToCursor", TOGGLE_LINK_ACTION2 = "toggle-link", IDE_LINK_LABELS;
55176
55318
  var init_GitRootDir = __esm(() => {
55177
55319
  init_git();
55320
+ init_hyperlink();
55178
55321
  init_git_no_git();
55322
+ IDE_LINK_LABELS = {
55323
+ vscode: "link-vscode",
55324
+ cursor: "link-cursor"
55325
+ };
55179
55326
  });
55180
55327
 
55181
55328
  // src/widgets/GitWorktree.ts
@@ -56027,7 +56174,7 @@ function getTerminalWidth() {
56027
56174
  function canDetectTerminalWidth() {
56028
56175
  return probeTerminalWidth() !== null;
56029
56176
  }
56030
- var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils", PACKAGE_VERSION = "2.2.5";
56177
+ var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils", PACKAGE_VERSION = "2.2.6";
56031
56178
  var init_terminal = () => {};
56032
56179
 
56033
56180
  // src/utils/renderer.ts
@@ -63740,9 +63887,6 @@ function buildMetadata(widget, urlValue, textValue) {
63740
63887
  metadata
63741
63888
  };
63742
63889
  }
63743
- function renderOsc8Link(url2, text) {
63744
- return `\x1B]8;;${url2}\x1B\\${text}\x1B]8;;\x1B\\`;
63745
- }
63746
63890
  function getLinkLabel(item) {
63747
63891
  const url2 = item.metadata?.url?.trim() ?? "";
63748
63892
  const metadataText = item.metadata?.text?.trim();
@@ -63896,6 +64040,7 @@ var import_react31, jsx_dev_runtime5, LinkEditor = ({ widget, onComplete, onCanc
63896
64040
  }, undefined, true, undefined, this);
63897
64041
  };
63898
64042
  var init_Link = __esm(async () => {
64043
+ init_hyperlink();
63899
64044
  init_input_guards();
63900
64045
  await init_build2();
63901
64046
  import_react31 = __toESM(require_react(), 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",