copilot-statusline 0.1.8 → 0.1.10

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
@@ -87,23 +87,24 @@ The interactive configuration tool provides a terminal UI where you can:
87
87
 
88
88
  > 💡 **Tip:** Your settings are automatically saved to `~/.config/copilot-statusline/settings.json`
89
89
 
90
- ### Copilot CLI config.json format
90
+ ### Copilot CLI Integration
91
91
 
92
- When you install from the TUI, copilot-statusline writes a `statusLine` command object to your Copilot CLI config:
92
+ When you install from the TUI, copilot-statusline creates a launcher script at `~/.copilot/statusline.sh` that uses `bunx`/`npx` to always run the latest version — **no manual upgrades needed**.
93
+
94
+ It also writes to your Copilot CLI config:
93
95
 
94
96
  ```json
97
+ // ~/.copilot/config.json
95
98
  {
96
99
  "statusLine": {
97
100
  "type": "command",
98
- "command": "npx -y copilot-statusline@latest",
101
+ "command": "/Users/you/.copilot/statusline.sh",
99
102
  "padding": 0
100
103
  }
101
104
  }
102
105
  ```
103
106
 
104
- Other supported command values:
105
- - `bunx -y copilot-statusline@latest`
106
- - `copilot-statusline` (for global installs)
107
+ > 💡 **Auto-upgrade:** The launcher script runs `bunx -y copilot-statusline@latest` (or `npx` if you used npm to run the TUI) under the hood, so you always get the latest version automatically.
107
108
 
108
109
  **Settings location:** `~/.copilot/config.json` (or use `--config-dir` with Copilot CLI to change)
109
110
 
@@ -513,7 +514,7 @@ opus-4.6 | 3x | Ctx: 18.0% | Reqs: 3 | Session: <1m ⎇ main
513
514
  ### Setup
514
515
 
515
516
  ```bash
516
- git clone https://github.com/user/copilot-statusline.git
517
+ git clone https://github.com/EncodeTS/copilot-statusline.git
517
518
  cd copilot-statusline
518
519
  bun install
519
520
  ```
@@ -52913,7 +52913,7 @@ import { execSync as execSync3 } from "child_process";
52913
52913
  import * as fs5 from "fs";
52914
52914
  import * as path4 from "path";
52915
52915
  var __dirname = "/Users/ts/workspace/active/statusline/copilot_statusline/src/utils";
52916
- var PACKAGE_VERSION = "0.1.8";
52916
+ var PACKAGE_VERSION = "0.1.10";
52917
52917
  function getPackageVersion() {
52918
52918
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
52919
52919
  return PACKAGE_VERSION;
@@ -53915,8 +53915,22 @@ class SessionIdWidget {
53915
53915
  }
53916
53916
  }
53917
53917
  // src/utils/display-name-parser.ts
53918
- var EFFORT_LEVELS = new Set(["low", "medium", "high"]);
53918
+ var THINKING_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
53919
+ var EFFORT_LEVELS = new Set(THINKING_EFFORT_LEVELS);
53920
+ var EFFORT_LEVEL_PATTERN = THINKING_EFFORT_LEVELS.join("|");
53919
53921
  var MULTIPLIER_REGEX = /^(\d+)x$/;
53922
+ var LABELED_EFFORT_REGEX = new RegExp(`^(?:thinking|reasoning|effort)(?:[-_ ](?:effort|level))?\\s*[:=]\\s*(${EFFORT_LEVEL_PATTERN})$`);
53923
+ function normalizeThinkingEffort(value) {
53924
+ if (!value) {
53925
+ return null;
53926
+ }
53927
+ const normalized = value.trim().toLowerCase();
53928
+ if (EFFORT_LEVELS.has(normalized)) {
53929
+ return normalized;
53930
+ }
53931
+ const match = LABELED_EFFORT_REGEX.exec(normalized);
53932
+ return match?.[1] ? match[1] : null;
53933
+ }
53920
53934
  function parseDisplayName(displayName) {
53921
53935
  if (!displayName) {
53922
53936
  return { thinkingEffort: null, multiplier: null, multiplierValue: null };
@@ -53934,9 +53948,19 @@ function parseDisplayName(displayName) {
53934
53948
  let multiplier = null;
53935
53949
  let multiplierValue = null;
53936
53950
  for (let i = groups.length - 1;i >= 0; i--) {
53937
- const group = groups[i]?.toLowerCase();
53938
- if (group && EFFORT_LEVELS.has(group)) {
53939
- thinkingEffort = group;
53951
+ const group = groups[i];
53952
+ if (!group) {
53953
+ continue;
53954
+ }
53955
+ const parts = group.split(/[,;|]/).map((part) => part.trim());
53956
+ for (let partIndex = parts.length - 1;partIndex >= 0; partIndex--) {
53957
+ const effort = normalizeThinkingEffort(parts[partIndex]);
53958
+ if (effort) {
53959
+ thinkingEffort = effort;
53960
+ break;
53961
+ }
53962
+ }
53963
+ if (thinkingEffort) {
53940
53964
  break;
53941
53965
  }
53942
53966
  }
@@ -53944,10 +53968,16 @@ function parseDisplayName(displayName) {
53944
53968
  if (!group) {
53945
53969
  continue;
53946
53970
  }
53947
- const multMatch = MULTIPLIER_REGEX.exec(group);
53948
- if (multMatch?.[1]) {
53949
- multiplier = group;
53950
- multiplierValue = parseInt(multMatch[1], 10);
53971
+ const parts = group.split(/[,;|]/).map((part) => part.trim());
53972
+ for (const part of parts) {
53973
+ const multMatch = MULTIPLIER_REGEX.exec(part);
53974
+ if (multMatch?.[1]) {
53975
+ multiplier = part;
53976
+ multiplierValue = parseInt(multMatch[1], 10);
53977
+ break;
53978
+ }
53979
+ }
53980
+ if (multiplier) {
53951
53981
  break;
53952
53982
  }
53953
53983
  }
@@ -53960,7 +53990,7 @@ class ThinkingEffortWidget {
53960
53990
  return "magenta";
53961
53991
  }
53962
53992
  getDescription() {
53963
- return "Displays the thinking effort level parsed from model.display_name (low, medium, high)";
53993
+ return "Displays the thinking effort level from the Copilot model payload";
53964
53994
  }
53965
53995
  getDisplayName() {
53966
53996
  return "Thinking Effort";
@@ -53975,9 +54005,10 @@ class ThinkingEffortWidget {
53975
54005
  if (context.isPreview) {
53976
54006
  return item.rawValue ? "high" : "Thinking: high";
53977
54007
  }
53978
- const parsed = parseDisplayName(context.data?.model?.display_name);
53979
- if (parsed.thinkingEffort) {
53980
- return item.rawValue ? parsed.thinkingEffort : `Thinking: ${parsed.thinkingEffort}`;
54008
+ const model = context.data?.model;
54009
+ const thinkingEffort = normalizeThinkingEffort(model?.thinking_effort_level) ?? normalizeThinkingEffort(model?.thinking_effort) ?? normalizeThinkingEffort(model?.reasoning_effort) ?? parseDisplayName(model?.display_name).thinkingEffort;
54010
+ if (thinkingEffort) {
54011
+ return item.rawValue ? thinkingEffort : `Thinking: ${thinkingEffort}`;
53981
54012
  }
53982
54013
  return null;
53983
54014
  }
@@ -54076,15 +54107,17 @@ function getContextWindowMetrics(data) {
54076
54107
  }
54077
54108
  const rawUsedPercentage = toFiniteNonNegativeNumber(contextWindow.used_percentage);
54078
54109
  const rawRemainingPercentage = toFiniteNonNegativeNumber(contextWindow.remaining_percentage);
54110
+ const usedTokensFromRemaining = windowSize !== null && remainingTokens !== null ? windowSize - remainingTokens : null;
54079
54111
  const usedTokensFromPercentage = rawUsedPercentage !== null && windowSize !== null ? rawUsedPercentage / 100 * windowSize : null;
54080
- const usedTokens = currentUsageTotalTokens ?? usedTokensFromPercentage;
54112
+ const usedTokens = usedTokensFromRemaining ?? usedTokensFromPercentage ?? currentUsageTotalTokens;
54081
54113
  const usedPercentage = rawUsedPercentage !== null ? clampPercentage(rawUsedPercentage) : usedTokens !== null && windowSize !== null && windowSize > 0 ? clampPercentage(usedTokens / windowSize * 100) : null;
54082
54114
  const remainingPercentage = rawRemainingPercentage !== null ? clampPercentage(rawRemainingPercentage) : usedPercentage !== null ? 100 - usedPercentage : null;
54083
54115
  const totalTokens = currentUsageTotalTokens ?? toFiniteNonNegativeNumber(contextWindow.total_tokens) ?? (totalInputTokens !== null && totalOutputTokens !== null ? totalInputTokens + totalOutputTokens : null);
54116
+ const contextLengthFromAuthoritative = usedTokensFromRemaining ?? usedTokensFromPercentage;
54084
54117
  return {
54085
54118
  windowSize,
54086
54119
  usedTokens,
54087
- contextLengthTokens: contextLengthTokens ?? usedTokens,
54120
+ contextLengthTokens: contextLengthFromAuthoritative ?? contextLengthTokens ?? usedTokens,
54088
54121
  usedPercentage,
54089
54122
  remainingPercentage,
54090
54123
  totalInputTokens,
@@ -54498,17 +54531,14 @@ class ContextBarWidget {
54498
54531
  render(item, context, settings) {
54499
54532
  const displayMode = getDisplayMode(item);
54500
54533
  const barWidth = displayMode === "progress" ? 32 : 16;
54501
- const powerlineMode = settings.powerline?.enabled === true;
54534
+ const powerlineMode = settings.powerline.enabled;
54502
54535
  if (context.isPreview) {
54503
54536
  const previewDisplay = `${makeUsageProgressBar(25, barWidth, powerlineMode)} 50k/200k (25%)`;
54504
54537
  return item.rawValue ? previewDisplay : `Context: ${previewDisplay}`;
54505
54538
  }
54506
54539
  const contextWindowMetrics = getContextWindowMetrics(context.data);
54507
- let total = contextWindowMetrics.windowSize;
54540
+ const total = contextWindowMetrics.windowSize;
54508
54541
  const used = contextWindowMetrics.contextLengthTokens;
54509
- if (total === null) {
54510
- total = contextWindowMetrics.windowSize;
54511
- }
54512
54542
  if (used === null || total === null || total <= 0) {
54513
54543
  return null;
54514
54544
  }
@@ -54984,51 +55014,131 @@ function getGitShortSha(context) {
54984
55014
  return runGit("rev-parse --short HEAD", context);
54985
55015
  }
54986
55016
 
54987
- // src/utils/hyperlink.ts
54988
- var IDE_LINK_MODES = [
54989
- "vscode",
54990
- "cursor"
54991
- ];
54992
- function renderOsc8Link(url2, text) {
54993
- return `\x1B]8;;${url2}\x1B\\${text}\x1B]8;;\x1B\\`;
54994
- }
54995
- function parseGitHubRepositoryPath(pathname) {
54996
- const trimmedPath = pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
54997
- const segments = trimmedPath.split("/").filter(Boolean);
54998
- if (segments.length !== 2) {
54999
- return null;
55000
- }
55001
- return `${segments[0]}/${segments[1]}`;
55002
- }
55003
- function parseGitHubBaseUrl(remoteUrl) {
55004
- const trimmed = remoteUrl.trim();
55017
+ // src/utils/git-remote.ts
55018
+ function parseRemoteUrl(url2) {
55019
+ const trimmed = url2.trim();
55005
55020
  if (trimmed.length === 0) {
55006
55021
  return null;
55007
55022
  }
55008
- const sshMatch = /^(?:[^@]+@)?github\.com:([^/]+\/[^/]+?)(?:\.git)?\/?$/.exec(trimmed);
55009
- if (sshMatch?.[1]) {
55010
- return `https://github.com/${sshMatch[1]}`;
55023
+ const sshMatch = !trimmed.includes("://") ? /^(?:[^@]+@)?([^:]+):(.+?)(?:\.git)?\/?$/.exec(trimmed) : null;
55024
+ if (sshMatch?.[1] && sshMatch[2]) {
55025
+ const pathSegments = sshMatch[2].split("/").filter(Boolean);
55026
+ const repo = pathSegments.at(-1);
55027
+ const owner = pathSegments.slice(0, -1).join("/");
55028
+ if (!owner || !repo) {
55029
+ return null;
55030
+ }
55031
+ return {
55032
+ host: sshMatch[1],
55033
+ owner,
55034
+ repo
55035
+ };
55011
55036
  }
55012
55037
  try {
55013
55038
  const parsedUrl = new URL(trimmed);
55014
- const supportedProtocols = new Set([
55015
- "http:",
55016
- "https:",
55017
- "ssh:",
55018
- "git:"
55019
- ]);
55020
- if (parsedUrl.hostname.toLowerCase() !== "github.com" || !supportedProtocols.has(parsedUrl.protocol)) {
55039
+ const supportedProtocols = new Set(["http:", "https:", "ssh:", "git:"]);
55040
+ if (!supportedProtocols.has(parsedUrl.protocol)) {
55021
55041
  return null;
55022
55042
  }
55023
- const repoPath = parseGitHubRepositoryPath(parsedUrl.pathname);
55024
- if (!repoPath) {
55043
+ const pathname = parsedUrl.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
55044
+ const segments = pathname.split("/").filter(Boolean);
55045
+ const repo = segments.at(-1);
55046
+ const owner = segments.slice(0, -1).join("/");
55047
+ if (!owner || !repo) {
55025
55048
  return null;
55026
55049
  }
55027
- return `https://github.com/${repoPath}`;
55050
+ return {
55051
+ host: parsedUrl.hostname,
55052
+ owner,
55053
+ repo
55054
+ };
55028
55055
  } catch {
55029
55056
  return null;
55030
55057
  }
55031
55058
  }
55059
+ function getRemoteInfo(remoteName, context) {
55060
+ const url2 = runGit(`remote get-url ${remoteName}`, context);
55061
+ if (!url2) {
55062
+ return null;
55063
+ }
55064
+ const parsed = parseRemoteUrl(url2);
55065
+ if (!parsed) {
55066
+ return null;
55067
+ }
55068
+ return {
55069
+ name: remoteName,
55070
+ url: url2,
55071
+ host: parsed.host,
55072
+ owner: parsed.owner,
55073
+ repo: parsed.repo
55074
+ };
55075
+ }
55076
+ function getTrackingRemoteName(context) {
55077
+ const upstreamRef = runGit("rev-parse --abbrev-ref --symbolic-full-name @{upstream}", context);
55078
+ if (!upstreamRef) {
55079
+ return null;
55080
+ }
55081
+ const remotes = listRemotes(context).slice().sort((left, right) => right.length - left.length);
55082
+ return remotes.find((remote) => upstreamRef === remote || upstreamRef.startsWith(`${remote}/`)) ?? null;
55083
+ }
55084
+ function getUpstreamRemoteInfo(context) {
55085
+ const namedUpstream = getRemoteInfo("upstream", context);
55086
+ if (namedUpstream) {
55087
+ return namedUpstream;
55088
+ }
55089
+ const trackingRemoteName = getTrackingRemoteName(context);
55090
+ if (!trackingRemoteName) {
55091
+ return null;
55092
+ }
55093
+ return getRemoteInfo(trackingRemoteName, context);
55094
+ }
55095
+ function getForkStatus(context) {
55096
+ const origin = getRemoteInfo("origin", context);
55097
+ const upstream = getRemoteInfo("upstream", context);
55098
+ const isFork = Boolean(origin && upstream && (origin.owner !== upstream.owner || origin.repo !== upstream.repo));
55099
+ return {
55100
+ isFork,
55101
+ origin,
55102
+ upstream
55103
+ };
55104
+ }
55105
+ function listRemotes(context) {
55106
+ const output = runGit("remote", context);
55107
+ if (!output) {
55108
+ return [];
55109
+ }
55110
+ return output.split(`
55111
+ `).filter(Boolean);
55112
+ }
55113
+ function buildRepoWebUrl(remote) {
55114
+ let webHost = remote.host;
55115
+ try {
55116
+ const parsedUrl = new URL(remote.url);
55117
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
55118
+ webHost = parsedUrl.host;
55119
+ }
55120
+ } catch {}
55121
+ return `https://${webHost}/${remote.owner}/${remote.repo}`;
55122
+ }
55123
+ function getBranchWebPath(remote) {
55124
+ const host = remote.host.toLowerCase().replace(/:\d+$/, "");
55125
+ if (host === "gitlab.com" || /^[^.]+\.gitlab\.com$/.test(host)) {
55126
+ return "/-/tree/";
55127
+ }
55128
+ return "/tree/";
55129
+ }
55130
+ function buildBranchWebUrl(remote, encodedBranch) {
55131
+ return `${buildRepoWebUrl(remote)}${getBranchWebPath(remote)}${encodedBranch}`;
55132
+ }
55133
+
55134
+ // src/utils/hyperlink.ts
55135
+ var IDE_LINK_MODES = [
55136
+ "vscode",
55137
+ "cursor"
55138
+ ];
55139
+ function renderOsc8Link(url2, text) {
55140
+ return `\x1B]8;;${url2}\x1B\\${text}\x1B]8;;\x1B\\`;
55141
+ }
55032
55142
  function encodeGitRefForUrlPath(ref) {
55033
55143
  return ref.split("/").map((segment) => encodeURIComponent(segment)).join("/");
55034
55144
  }
@@ -55075,8 +55185,30 @@ function getHideNoGitKeybinds() {
55075
55185
  }
55076
55186
 
55077
55187
  // src/widgets/GitBranch.ts
55078
- var LINK_KEY = "linkToGitHub";
55188
+ var LINK_KEY = "linkToRepo";
55189
+ var LEGACY_LINK_KEY = "linkToGitHub";
55079
55190
  var TOGGLE_LINK_ACTION = "toggle-link";
55191
+ var PREVIEW_REMOTE = {
55192
+ name: "origin",
55193
+ url: "https://example.com/owner/repo.git",
55194
+ host: "example.com",
55195
+ owner: "owner",
55196
+ repo: "repo"
55197
+ };
55198
+ function isLinkEnabled(item) {
55199
+ return isMetadataFlagEnabled(item, LINK_KEY) || item.metadata?.[LINK_KEY] === undefined && isMetadataFlagEnabled(item, LEGACY_LINK_KEY);
55200
+ }
55201
+ function toggleLink(item) {
55202
+ const nextEnabled = !isLinkEnabled(item);
55203
+ const nextMetadata = Object.fromEntries(Object.entries(item.metadata ?? {}).filter(([key]) => key !== LINK_KEY && key !== LEGACY_LINK_KEY));
55204
+ if (nextEnabled) {
55205
+ nextMetadata[LINK_KEY] = "true";
55206
+ }
55207
+ return {
55208
+ ...item,
55209
+ metadata: Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined
55210
+ };
55211
+ }
55080
55212
 
55081
55213
  class GitBranchWidget {
55082
55214
  getDefaultColor() {
@@ -55092,13 +55224,13 @@ class GitBranchWidget {
55092
55224
  return "Git";
55093
55225
  }
55094
55226
  getEditorDisplay(item) {
55095
- const isLink = isMetadataFlagEnabled(item, LINK_KEY);
55227
+ const isLink = isLinkEnabled(item);
55096
55228
  const modifiers = [];
55097
55229
  const noGitText = getHideNoGitModifierText(item);
55098
55230
  if (noGitText)
55099
55231
  modifiers.push("hide 'no git'");
55100
55232
  if (isLink)
55101
- modifiers.push("GitHub link");
55233
+ modifiers.push("repo link");
55102
55234
  return {
55103
55235
  displayText: this.getDisplayName(),
55104
55236
  modifierText: makeModifierText(modifiers)
@@ -55106,16 +55238,16 @@ class GitBranchWidget {
55106
55238
  }
55107
55239
  handleEditorAction(action, item) {
55108
55240
  if (action === TOGGLE_LINK_ACTION) {
55109
- return toggleMetadataFlag(item, LINK_KEY);
55241
+ return toggleLink(item);
55110
55242
  }
55111
55243
  return handleToggleNoGitAction(action, item);
55112
55244
  }
55113
55245
  render(item, context, settings) {
55114
55246
  const hideNoGit = isHideNoGitEnabled(item);
55115
- const isLink = isMetadataFlagEnabled(item, LINK_KEY);
55247
+ const isLink = isLinkEnabled(item);
55116
55248
  if (context.isPreview) {
55117
55249
  const text = item.rawValue ? "main" : "⎇ main";
55118
- return isLink ? renderOsc8Link("https://github.com/owner/repo/tree/main", text) : text;
55250
+ return isLink ? renderOsc8Link(buildBranchWebUrl(PREVIEW_REMOTE, "main"), text) : text;
55119
55251
  }
55120
55252
  if (!isInsideGitWorkTree(context)) {
55121
55253
  return hideNoGit ? null : "⎇ no git";
@@ -55126,10 +55258,9 @@ class GitBranchWidget {
55126
55258
  }
55127
55259
  const displayText = item.rawValue ? branch : `⎇ ${branch}`;
55128
55260
  if (isLink) {
55129
- const remoteUrl = runGit("remote get-url origin", context);
55130
- const baseUrl = remoteUrl ? parseGitHubBaseUrl(remoteUrl) : null;
55131
- if (baseUrl) {
55132
- return renderOsc8Link(`${baseUrl}/tree/${encodeGitRefForUrlPath(branch)}`, displayText);
55261
+ const origin = getRemoteInfo("origin", context);
55262
+ if (origin) {
55263
+ return renderOsc8Link(buildBranchWebUrl(origin, encodeGitRefForUrlPath(branch)), displayText);
55133
55264
  }
55134
55265
  }
55135
55266
  return displayText;
@@ -55140,7 +55271,7 @@ class GitBranchWidget {
55140
55271
  getCustomKeybinds() {
55141
55272
  return [
55142
55273
  ...getHideNoGitKeybinds(),
55143
- { key: "l", label: "(l)ink to GitHub", action: TOGGLE_LINK_ACTION }
55274
+ { key: "l", label: "(l)ink to repo", action: TOGGLE_LINK_ACTION }
55144
55275
  ];
55145
55276
  }
55146
55277
  supportsRawValue() {
@@ -56065,106 +56196,6 @@ class GitShaWidget {
56065
56196
  return true;
56066
56197
  }
56067
56198
  }
56068
- // src/utils/git-remote.ts
56069
- function parseRemoteUrl(url2) {
56070
- const trimmed = url2.trim();
56071
- if (trimmed.length === 0) {
56072
- return null;
56073
- }
56074
- const sshMatch = !trimmed.includes("://") ? /^(?:[^@]+@)?([^:]+):(.+?)(?:\.git)?\/?$/.exec(trimmed) : null;
56075
- if (sshMatch?.[1] && sshMatch[2]) {
56076
- const pathSegments = sshMatch[2].split("/").filter(Boolean);
56077
- const repo = pathSegments.at(-1);
56078
- const owner = pathSegments.slice(0, -1).join("/");
56079
- if (!owner || !repo) {
56080
- return null;
56081
- }
56082
- return {
56083
- host: sshMatch[1],
56084
- owner,
56085
- repo
56086
- };
56087
- }
56088
- try {
56089
- const parsedUrl = new URL(trimmed);
56090
- const supportedProtocols = new Set(["http:", "https:", "ssh:", "git:"]);
56091
- if (!supportedProtocols.has(parsedUrl.protocol)) {
56092
- return null;
56093
- }
56094
- const pathname = parsedUrl.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
56095
- const segments = pathname.split("/").filter(Boolean);
56096
- const repo = segments.at(-1);
56097
- const owner = segments.slice(0, -1).join("/");
56098
- if (!owner || !repo) {
56099
- return null;
56100
- }
56101
- return {
56102
- host: parsedUrl.hostname,
56103
- owner,
56104
- repo
56105
- };
56106
- } catch {
56107
- return null;
56108
- }
56109
- }
56110
- function getRemoteInfo(remoteName, context) {
56111
- const url2 = runGit(`remote get-url ${remoteName}`, context);
56112
- if (!url2) {
56113
- return null;
56114
- }
56115
- const parsed = parseRemoteUrl(url2);
56116
- if (!parsed) {
56117
- return null;
56118
- }
56119
- return {
56120
- name: remoteName,
56121
- url: url2,
56122
- host: parsed.host,
56123
- owner: parsed.owner,
56124
- repo: parsed.repo
56125
- };
56126
- }
56127
- function getTrackingRemoteName(context) {
56128
- const upstreamRef = runGit("rev-parse --abbrev-ref --symbolic-full-name @{upstream}", context);
56129
- if (!upstreamRef) {
56130
- return null;
56131
- }
56132
- const remotes = listRemotes(context).slice().sort((left, right) => right.length - left.length);
56133
- return remotes.find((remote) => upstreamRef === remote || upstreamRef.startsWith(`${remote}/`)) ?? null;
56134
- }
56135
- function getUpstreamRemoteInfo(context) {
56136
- const namedUpstream = getRemoteInfo("upstream", context);
56137
- if (namedUpstream) {
56138
- return namedUpstream;
56139
- }
56140
- const trackingRemoteName = getTrackingRemoteName(context);
56141
- if (!trackingRemoteName) {
56142
- return null;
56143
- }
56144
- return getRemoteInfo(trackingRemoteName, context);
56145
- }
56146
- function getForkStatus(context) {
56147
- const origin = getRemoteInfo("origin", context);
56148
- const upstream = getRemoteInfo("upstream", context);
56149
- const isFork = Boolean(origin && upstream && (origin.owner !== upstream.owner || origin.repo !== upstream.repo));
56150
- return {
56151
- isFork,
56152
- origin,
56153
- upstream
56154
- };
56155
- }
56156
- function listRemotes(context) {
56157
- const output = runGit("remote", context);
56158
- if (!output) {
56159
- return [];
56160
- }
56161
- return output.split(`
56162
- `).filter(Boolean);
56163
- }
56164
- function buildRepoWebUrl(remote) {
56165
- return `https://${remote.host}/${remote.owner}/${remote.repo}`;
56166
- }
56167
-
56168
56199
  // src/widgets/shared/git-remote.ts
56169
56200
  var HIDE_NO_REMOTE_KEY = "hideNoRemote";
56170
56201
  var TOGGLE_NO_REMOTE_ACTION = "toggle-no-remote";
@@ -58337,7 +58368,7 @@ function List({
58337
58368
  initialSelection = 0,
58338
58369
  showBackButton,
58339
58370
  color,
58340
- wrapNavigation = false,
58371
+ wrapNavigation = true,
58341
58372
  ...boxProps
58342
58373
  }) {
58343
58374
  const [selectedIndex, setSelectedIndex] = import_react34.useState(initialSelection);
@@ -58365,6 +58396,9 @@ function List({
58365
58396
  }
58366
58397
  }, [selectedIndex, selectedValue]);
58367
58398
  use_input_default((_, key) => {
58399
+ if (selectableItems.length === 0) {
58400
+ return;
58401
+ }
58368
58402
  if (key.upArrow) {
58369
58403
  const prev = selectedIndex - 1;
58370
58404
  const prevIndex = prev < 0 ? wrapNavigation ? selectableItems.length - 1 : 0 : prev;
@@ -59533,6 +59567,21 @@ function setPickerState(setWidgetPicker, normalizeState, updater) {
59533
59567
  function getPickerCategories(widgetCategories) {
59534
59568
  return [...widgetCategories];
59535
59569
  }
59570
+ function wrapIndex(index, length) {
59571
+ if (length === 0) {
59572
+ return -1;
59573
+ }
59574
+ if (index < 0) {
59575
+ return length - 1;
59576
+ }
59577
+ if (index > length - 1) {
59578
+ return 0;
59579
+ }
59580
+ return index;
59581
+ }
59582
+ function getAdjacentIndex(currentIndex, length, isForward) {
59583
+ return wrapIndex(currentIndex + (isForward ? 1 : -1), length);
59584
+ }
59536
59585
  function normalizePickerState(state, widgetCatalog, widgetCategories) {
59537
59586
  const filteredCategories = getPickerCategories(widgetCategories);
59538
59587
  const selectedCategory = state.selectedCategory && filteredCategories.includes(state.selectedCategory) ? state.selectedCategory : filteredCategories[0] ?? null;
@@ -59615,7 +59664,7 @@ function handlePickerInputMode({
59615
59664
  if (currentIndex === -1) {
59616
59665
  currentIndex = 0;
59617
59666
  }
59618
- const nextIndex = key.downArrow ? Math.min(topLevelSearchEntries.length - 1, currentIndex + 1) : Math.max(0, currentIndex - 1);
59667
+ const nextIndex = getAdjacentIndex(currentIndex, topLevelSearchEntries.length, Boolean(key.downArrow));
59619
59668
  const nextType = topLevelSearchEntries[nextIndex]?.type ?? null;
59620
59669
  setPickerState(setWidgetPicker, normalizeState, (prev) => ({
59621
59670
  ...prev,
@@ -59629,7 +59678,7 @@ function handlePickerInputMode({
59629
59678
  if (currentIndex === -1) {
59630
59679
  currentIndex = 0;
59631
59680
  }
59632
- const nextIndex = key.downArrow ? Math.min(filteredCategories.length - 1, currentIndex + 1) : Math.max(0, currentIndex - 1);
59681
+ const nextIndex = getAdjacentIndex(currentIndex, filteredCategories.length, Boolean(key.downArrow));
59633
59682
  const nextCategory = filteredCategories[nextIndex] ?? null;
59634
59683
  setPickerState(setWidgetPicker, normalizeState, (prev) => ({
59635
59684
  ...prev,
@@ -59674,7 +59723,7 @@ function handlePickerInputMode({
59674
59723
  if (currentIndex === -1) {
59675
59724
  currentIndex = 0;
59676
59725
  }
59677
- const nextIndex = key.downArrow ? Math.min(filteredWidgets.length - 1, currentIndex + 1) : Math.max(0, currentIndex - 1);
59726
+ const nextIndex = getAdjacentIndex(currentIndex, filteredWidgets.length, Boolean(key.downArrow));
59678
59727
  const nextType = filteredWidgets[nextIndex]?.type ?? null;
59679
59728
  setPickerState(setWidgetPicker, normalizeState, (prev) => ({
59680
59729
  ...prev,
@@ -59703,24 +59752,26 @@ function handleMoveInputMode({
59703
59752
  setSelectedIndex,
59704
59753
  setMoveMode
59705
59754
  }) {
59706
- if (key.upArrow && selectedIndex > 0) {
59755
+ if (key.upArrow && widgets.length > 1) {
59707
59756
  const newWidgets = [...widgets];
59757
+ const targetIndex = getAdjacentIndex(selectedIndex, widgets.length, false);
59708
59758
  const temp = newWidgets[selectedIndex];
59709
- const prev = newWidgets[selectedIndex - 1];
59759
+ const prev = newWidgets[targetIndex];
59710
59760
  if (temp && prev) {
59711
- [newWidgets[selectedIndex], newWidgets[selectedIndex - 1]] = [prev, temp];
59761
+ [newWidgets[selectedIndex], newWidgets[targetIndex]] = [prev, temp];
59712
59762
  }
59713
59763
  onUpdate(newWidgets);
59714
- setSelectedIndex(selectedIndex - 1);
59715
- } else if (key.downArrow && selectedIndex < widgets.length - 1) {
59764
+ setSelectedIndex(targetIndex);
59765
+ } else if (key.downArrow && widgets.length > 1) {
59716
59766
  const newWidgets = [...widgets];
59767
+ const targetIndex = getAdjacentIndex(selectedIndex, widgets.length, true);
59717
59768
  const temp = newWidgets[selectedIndex];
59718
- const next = newWidgets[selectedIndex + 1];
59769
+ const next = newWidgets[targetIndex];
59719
59770
  if (temp && next) {
59720
- [newWidgets[selectedIndex], newWidgets[selectedIndex + 1]] = [next, temp];
59771
+ [newWidgets[selectedIndex], newWidgets[targetIndex]] = [next, temp];
59721
59772
  }
59722
59773
  onUpdate(newWidgets);
59723
- setSelectedIndex(selectedIndex + 1);
59774
+ setSelectedIndex(targetIndex);
59724
59775
  } else if (key.escape || key.return) {
59725
59776
  setMoveMode(false);
59726
59777
  }
@@ -59738,12 +59789,13 @@ function handleNormalInputMode({
59738
59789
  setShowClearConfirm,
59739
59790
  openWidgetPicker,
59740
59791
  getCustomKeybindsForWidget,
59741
- setCustomEditorWidget
59792
+ setCustomEditorWidget,
59793
+ getUniqueBackgroundColor
59742
59794
  }) {
59743
59795
  if (key.upArrow && widgets.length > 0) {
59744
- setSelectedIndex(Math.max(0, selectedIndex - 1));
59796
+ setSelectedIndex(getAdjacentIndex(selectedIndex, widgets.length, false));
59745
59797
  } else if (key.downArrow && widgets.length > 0) {
59746
- setSelectedIndex(Math.min(widgets.length - 1, selectedIndex + 1));
59798
+ setSelectedIndex(getAdjacentIndex(selectedIndex, widgets.length, true));
59747
59799
  } else if (key.leftArrow && widgets.length > 0) {
59748
59800
  openWidgetPicker("change");
59749
59801
  } else if (key.rightArrow && widgets.length > 0) {
@@ -59760,6 +59812,26 @@ function handleNormalInputMode({
59760
59812
  if (selectedIndex >= newWidgets.length && selectedIndex > 0) {
59761
59813
  setSelectedIndex(selectedIndex - 1);
59762
59814
  }
59815
+ } else if (input === "k" && widgets.length > 0) {
59816
+ const source = widgets[selectedIndex];
59817
+ if (!source) {
59818
+ return;
59819
+ }
59820
+ const cloneInsertPosition = selectedIndex + 1;
59821
+ const uniqueBackgroundColor = getUniqueBackgroundColor?.(cloneInsertPosition);
59822
+ const clone3 = {
59823
+ ...source,
59824
+ id: generateGuid(),
59825
+ ...source.metadata ? { metadata: { ...source.metadata } } : {},
59826
+ ...uniqueBackgroundColor && { backgroundColor: uniqueBackgroundColor }
59827
+ };
59828
+ const newWidgets = [
59829
+ ...widgets.slice(0, cloneInsertPosition),
59830
+ clone3,
59831
+ ...widgets.slice(cloneInsertPosition)
59832
+ ];
59833
+ onUpdate(newWidgets);
59834
+ setSelectedIndex(cloneInsertPosition);
59763
59835
  } else if (input === "c") {
59764
59836
  if (widgets.length > 0) {
59765
59837
  setShowClearConfirm(true);
@@ -59960,7 +60032,8 @@ var ItemsEditor = ({ widgets, onUpdate, onBack, lineNumber, settings }) => {
59960
60032
  setShowClearConfirm,
59961
60033
  openWidgetPicker,
59962
60034
  getCustomKeybindsForWidget,
59963
- setCustomEditorWidget
60035
+ setCustomEditorWidget,
60036
+ getUniqueBackgroundColor
59964
60037
  });
59965
60038
  });
59966
60039
  const getWidgetDisplay = (widget) => {
@@ -60008,7 +60081,7 @@ var ItemsEditor = ({ widgets, onUpdate, onBack, lineNumber, settings }) => {
60008
60081
  helpText += ", Space edit separator";
60009
60082
  }
60010
60083
  if (hasWidgets) {
60011
- helpText += ", Enter to move, (a)dd via picker, (i)nsert via picker, (d)elete, (c)lear line";
60084
+ helpText += ", Enter to move, (a)dd via picker, (i)nsert via picker, (d)elete, (k)lone, (c)lear line";
60012
60085
  }
60013
60086
  if (canToggleRaw) {
60014
60087
  helpText += ", (r)aw value";
@@ -60441,26 +60514,28 @@ var LineSelector = ({
60441
60514
  return;
60442
60515
  }
60443
60516
  if (moveMode) {
60444
- if (key.upArrow && selectedIndex > 0) {
60517
+ if (key.upArrow && localLines.length > 1) {
60445
60518
  const newLines = [...localLines];
60519
+ const targetIndex = selectedIndex - 1 < 0 ? localLines.length - 1 : selectedIndex - 1;
60446
60520
  const temp = newLines[selectedIndex];
60447
- const prev = newLines[selectedIndex - 1];
60521
+ const prev = newLines[targetIndex];
60448
60522
  if (temp && prev) {
60449
- [newLines[selectedIndex], newLines[selectedIndex - 1]] = [prev, temp];
60523
+ [newLines[selectedIndex], newLines[targetIndex]] = [prev, temp];
60450
60524
  }
60451
60525
  setLocalLines(newLines);
60452
60526
  onLinesUpdate(newLines);
60453
- setSelectedIndex(selectedIndex - 1);
60454
- } else if (key.downArrow && selectedIndex < localLines.length - 1) {
60527
+ setSelectedIndex(targetIndex);
60528
+ } else if (key.downArrow && localLines.length > 1) {
60455
60529
  const newLines = [...localLines];
60530
+ const targetIndex = selectedIndex + 1 > localLines.length - 1 ? 0 : selectedIndex + 1;
60456
60531
  const temp = newLines[selectedIndex];
60457
- const next = newLines[selectedIndex + 1];
60532
+ const next = newLines[targetIndex];
60458
60533
  if (temp && next) {
60459
- [newLines[selectedIndex], newLines[selectedIndex + 1]] = [next, temp];
60534
+ [newLines[selectedIndex], newLines[targetIndex]] = [next, temp];
60460
60535
  }
60461
60536
  setLocalLines(newLines);
60462
60537
  onLinesUpdate(newLines);
60463
- setSelectedIndex(selectedIndex + 1);
60538
+ setSelectedIndex(targetIndex);
60464
60539
  } else if (key.escape || key.return) {
60465
60540
  setMoveMode(false);
60466
60541
  }
@@ -62878,11 +62953,9 @@ var App2 = () => {
62878
62953
  const [powerlineFontStatus, setPowerlineFontStatus] = import_react45.useState({ installed: false });
62879
62954
  const [installingFonts, setInstallingFonts] = import_react45.useState(false);
62880
62955
  const [fontInstallMessage, setFontInstallMessage] = import_react45.useState(null);
62881
- const [existingStatusLine, setExistingStatusLine] = import_react45.useState(null);
62882
62956
  const [flashMessage, setFlashMessage] = import_react45.useState(null);
62883
62957
  const [previewIsTruncated, setPreviewIsTruncated] = import_react45.useState(false);
62884
62958
  import_react45.useEffect(() => {
62885
- setExistingStatusLine(getExistingStatusLine());
62886
62959
  loadSettings().then((loadedSettings) => {
62887
62960
  source_default.level = loadedSettings.colorLevel;
62888
62961
  setSettings(loadedSettings);
@@ -62951,7 +63024,6 @@ A launcher script will be created at ~/.copilot/statusline.sh`;
62951
63024
  try {
62952
63025
  installStatusLine();
62953
63026
  setIsCopilotInstalled(true);
62954
- setExistingStatusLine(getExistingStatusLine());
62955
63027
  setFlashMessage({ text: "✓ Installed to Copilot CLI", color: "green" });
62956
63028
  } catch (e) {
62957
63029
  const errorMsg = e instanceof Error ? e.message : String(e);
@@ -62976,7 +63048,6 @@ A launcher script will be created at ~/.copilot/statusline.sh`;
62976
63048
  action: async () => {
62977
63049
  uninstallStatusLine();
62978
63050
  setIsCopilotInstalled(false);
62979
- setExistingStatusLine(null);
62980
63051
  setScreen("main");
62981
63052
  setConfirmDialog(null);
62982
63053
  return Promise.resolve();
@@ -63228,11 +63299,12 @@ var CopilotPayloadSchema = exports_external.object({
63228
63299
  transcript_path: exports_external.string().optional(),
63229
63300
  model: exports_external.object({
63230
63301
  id: exports_external.string().nullable().optional(),
63231
- display_name: exports_external.string().nullable().optional()
63232
- }).optional(),
63233
- workspace: exports_external.object({
63234
- current_dir: exports_external.string().optional()
63302
+ display_name: exports_external.string().nullable().optional(),
63303
+ thinking_effort: exports_external.string().nullable().optional(),
63304
+ thinking_effort_level: exports_external.string().nullable().optional(),
63305
+ reasoning_effort: exports_external.string().nullable().optional()
63235
63306
  }).optional(),
63307
+ workspace: exports_external.object({ current_dir: exports_external.string().optional() }).optional(),
63236
63308
  version: exports_external.string().optional(),
63237
63309
  cost: exports_external.object({
63238
63310
  total_api_duration_ms: exports_external.number().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-statusline",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "A customizable status line formatter for GitHub Copilot CLI — based on ccstatusline",
5
5
  "module": "src/copilot-statusline.ts",
6
6
  "type": "module",
@@ -28,8 +28,8 @@
28
28
  "chalk": "^5.5.0",
29
29
  "eslint": "^10.0.0",
30
30
  "eslint-import-resolver-typescript": "^4.4.4",
31
- "eslint-plugin-import": "^2.32.0",
32
31
  "eslint-plugin-import-newlines": "^2.0.0",
32
+ "eslint-plugin-import-x": "^4.16.2",
33
33
  "eslint-plugin-react": "^7.37.5",
34
34
  "eslint-plugin-react-hooks": "^7.0.1",
35
35
  "globals": "^17.3.0",
@@ -55,8 +55,16 @@
55
55
  "powerline",
56
56
  "developer-tools"
57
57
  ],
58
- "author": "",
58
+ "author": "EncodeTS (https://github.com/EncodeTS)",
59
59
  "license": "MIT",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/EncodeTS/copilot-statusline.git"
63
+ },
64
+ "homepage": "https://github.com/EncodeTS/copilot-statusline",
65
+ "bugs": {
66
+ "url": "https://github.com/EncodeTS/copilot-statusline/issues"
67
+ },
60
68
  "engines": {
61
69
  "node": ">=14.0.0"
62
70
  },