cxstatusline 0.4.0 → 0.4.1

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
@@ -101,66 +101,20 @@ cxstatusline
101
101
  The bare command opens the configuration TUI in an interactive terminal. Choose widgets, colors
102
102
  and themes, preview, then save explicitly or press Ctrl+S. Settings live at
103
103
  `~/.config/cxstatusline/settings.json` (or your XDG config location).
104
- Claude-only widgets without a Codex data source are not included; see the widget inventory in the
105
- project notes for the full list and reasons.
106
104
 
107
105
  For scripts, `cxstatusline render` reads a versioned JSON payload on stdin and prints ANSI rows;
108
106
  it never opens the TUI.
109
107
 
110
- ## Custom widgets
111
-
112
- Three widgets show your own content, with the same settings fields as ccstatusline so presets import unchanged:
113
-
114
- | Widget | Field | Shows |
115
- |---|---|---|
116
- | Custom Text | `customText` | A fixed label such as `[PROD]` |
117
- | Custom Symbol | `customSymbol` | One glyph or emoji such as `⚡` |
118
- | Custom Command | `commandPath` | The first line a shell command prints |
119
-
120
- ### Custom Command execution and caching
121
-
122
- Custom Command runs `commandPath` through your shell (`/bin/sh -c`) with Codex's environment, in
123
- the session's working directory. It receives the Codex status payload as JSON on stdin plus `terminal_width`.
124
- Only the first non-empty line is shown; stderr is discarded. Set `preserveColors` (key `p`) to keep
125
- the command's own colour codes; other escape sequences and control characters are always removed. Configured
126
- background colours apply in both render modes and survive resets within the command output. `maxWidth`
127
- (key `w`) truncates with an ellipsis. The TUI preview never runs commands. Imported presets list their
128
- commands before you confirm.
129
-
130
- #### Synchronous execution (default)
131
-
132
- When `refreshMs` is unset (or `0`), commands run synchronously on **every footer redraw** (up to five
133
- times a second while Codex streams).
134
-
135
- Codex kills the whole statusline renderer after one second and keeps the previous frame, so
136
- cxstatusline caps each synchronous command at **300 ms by default, 600 ms maximum** (`timeout`, editor key
137
- `t`), and all commands on a render share a 600 ms budget. Over budget shows `[Budget]`; a slow
138
- command shows `[Timeout]`, a failing one `[Exit: N]` or `[Cmd not found]`. Commands terminated by
139
- external signals before the deadline report `[Signal: <name>]` rather than `[Timeout]`. Keep synchronous
140
- commands cheap and local: `git status -s | wc -l` and `date +%H:%M` are good fits; network calls are not.
141
-
142
- #### Background caching (`refreshMs`)
143
-
144
- For commands that take longer or should not run on every redraw, configure `refreshMs` (key `f`).
145
-
146
- - **Background refresh:** When `refreshMs` is set, rendering reads the latest result from disk and never
147
- blocks the statusline or consumes renderer timeout budget. When an entry is due for an update,
148
- cxstatusline spawns a detached background process to refresh it; the refresh outlives the render.
149
- - **Cold start:** On the very first render before the initial background run completes, the widget shows
150
- `[Loading]`.
151
- - **Failures and timeouts:** A failing background command caches its exit code or error token; if a
152
- background refresh stays outstanding or fails beyond 60 seconds, the widget settles on `[Error]` and
153
- keeps showing `[Error]` across subsequent renders until a refresh succeeds.
154
- - **Cache location:** Cached documents live under `~/.cache/cxstatusline/commands/` (or
155
- `$XDG_CACHE_HOME/cxstatusline/commands/`). Deleting this cache directory at any time is completely
156
- safe; entries are re-created automatically.
157
- - **Eviction:** Cache entries and temporary files expire and are automatically evicted after 7 days.
158
- - **Session sharing constraint:** The cache key is derived from `(command, cwd)`, so every Codex
159
- session running in the same working directory shares one cache entry and one payload (the payload
160
- written by whichever session triggered the refresh). `refreshMs` is intended for commands whose
161
- output does not depend on specific session payload details.
162
- - **Pipeline processes:** As with synchronous mode, `shell: true` terminates `/bin/sh` on timeout,
163
- so child processes spawned inside a shell pipeline can outlive the cap. Avoid long-running pipelines.
108
+ ## Widgets
109
+
110
+ 34 widgets: model and thinking effort, Git branch and changes, context and token counts, token speed,
111
+ weekly usage and reset, session clock and name, working directory, terminal width, memory, plus three
112
+ custom widgets that show your own text, symbol, or the output of a shell command.
113
+
114
+ Type IDs match ccstatusline's, so a ccstatusline preset imports without translation.
115
+
116
+ 👉 The full catalog, and how Custom Command handles timeouts, colours and background caching, are in
117
+ the **[Usage guide](docs/usage.md)**.
164
118
 
165
119
  ## Update
166
120
 
@@ -55203,6 +55203,47 @@ class GitBranchWidget {
55203
55203
  return true;
55204
55204
  }
55205
55205
  }
55206
+ // src/utils/git.ts
55207
+ import { spawnSync as spawnSync2 } from "node:child_process";
55208
+ function parseDiffShortStat(stat) {
55209
+ const insertMatch = /(\d+)\s+insertions?/.exec(stat);
55210
+ const deleteMatch = /(\d+)\s+deletions?/.exec(stat);
55211
+ return {
55212
+ additions: insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0,
55213
+ deletions: deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0
55214
+ };
55215
+ }
55216
+ var defaultGitRunner = (args, cwd2) => {
55217
+ const result2 = spawnSync2("git", args, {
55218
+ cwd: cwd2,
55219
+ encoding: "utf8",
55220
+ stdio: ["ignore", "pipe", "ignore"],
55221
+ timeout: 300,
55222
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
55223
+ windowsHide: true
55224
+ });
55225
+ if (result2.error || result2.status !== 0) {
55226
+ throw result2.error ?? new Error(`git exited with status ${result2.status}`);
55227
+ }
55228
+ return result2.stdout ?? "";
55229
+ };
55230
+ function getGitDiffChanges(cwd2, runner = defaultGitRunner) {
55231
+ if (!cwd2)
55232
+ return null;
55233
+ try {
55234
+ const unstaged = runner(["diff", "--no-ext-diff", "--shortstat"], cwd2);
55235
+ const staged = runner(["diff", "--cached", "--no-ext-diff", "--shortstat"], cwd2);
55236
+ const unstagedCounts = parseDiffShortStat(unstaged);
55237
+ const stagedCounts = parseDiffShortStat(staged);
55238
+ return {
55239
+ additions: unstagedCounts.additions + stagedCounts.additions,
55240
+ deletions: unstagedCounts.deletions + stagedCounts.deletions
55241
+ };
55242
+ } catch {
55243
+ return null;
55244
+ }
55245
+ }
55246
+
55206
55247
  // src/widgets/GitChanges.ts
55207
55248
  class GitChangesWidget {
55208
55249
  getDefaultColor() {
@@ -55226,7 +55267,10 @@ class GitChangesWidget {
55226
55267
  render(item, context, _settings) {
55227
55268
  if (context.isPreview)
55228
55269
  return "(+42,-10)";
55229
- const changes2 = context.data.git?.changes;
55270
+ const cwd2 = context.data.session?.cwd ?? context.data.session?.project_root;
55271
+ const shouldCheckLive = Boolean(context.commandCacheDir || context.liveGit || !context.data.git?.changes);
55272
+ const liveChanges = shouldCheckLive ? getGitDiffChanges(cwd2) : null;
55273
+ const changes2 = liveChanges ?? context.data.git?.changes;
55230
55274
  if (!changes2)
55231
55275
  return isHideStateEnabled(item, NO_GIT_HIDEABLE_STATE) ? null : "(no git)";
55232
55276
  return "(+" + changes2.additions + ",-" + changes2.deletions + ")";
@@ -58152,19 +58196,19 @@ function runRefreshCommand(cacheKey2, deps) {
58152
58196
  return 0;
58153
58197
  }
58154
58198
  // src/version-info.ts
58155
- var VERSION = "0.4.0";
58156
- var SOURCE_COMMIT = /^[0-9a-f]{40}$/.test("d65580953bb20c0d4c11c817bb55070de49c66e3") ? "d65580953bb20c0d4c11c817bb55070de49c66e3" : null;
58199
+ var VERSION = "0.4.1";
58200
+ var SOURCE_COMMIT = /^[0-9a-f]{40}$/.test("d5cfa7430d7fdb52a032c51c289131d04f714dd4") ? "d5cfa7430d7fdb52a032c51c289131d04f714dd4" : null;
58157
58201
  var SOURCE_DIRTY = false;
58158
58202
 
58159
58203
  // src/context.ts
58160
58204
  import { existsSync as existsSync3, realpathSync } from "node:fs";
58161
- import { spawnSync as spawnSync3 } from "node:child_process";
58205
+ import { spawnSync as spawnSync4 } from "node:child_process";
58162
58206
  import { dirname as dirname5, join as join7 } from "node:path";
58163
58207
 
58164
58208
  // src/patch/preflight.ts
58165
58209
  import { accessSync, constants as constants2, existsSync as existsSync2, statfsSync } from "node:fs";
58166
58210
  import { dirname as dirname4, join as join6 } from "node:path";
58167
- import { spawnSync as spawnSync2 } from "node:child_process";
58211
+ import { spawnSync as spawnSync3 } from "node:child_process";
58168
58212
  var MIN_FREE_BYTES = 20 * 1024 ** 3;
58169
58213
  var MIN_STAGING_FREE_BYTES = 2 * 1024 ** 3;
58170
58214
  var REQUIRED_TOOLCHAIN = "1.95.0";
@@ -58246,7 +58290,7 @@ function realDeps() {
58246
58290
  return {
58247
58291
  which: whichReal,
58248
58292
  run: (cmd, args, opts) => {
58249
- const r = spawnSync2(cmd, args, { encoding: "utf8", cwd: opts?.cwd });
58293
+ const r = spawnSync3(cmd, args, { encoding: "utf8", cwd: opts?.cwd });
58250
58294
  return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
58251
58295
  },
58252
58296
  freeBytes: (path) => {
@@ -58268,7 +58312,7 @@ function realContext(env3, io) {
58268
58312
  const sccache = deps.which("sccache");
58269
58313
  const run = (cmd, args, opts) => {
58270
58314
  const baseEnv = opts?.env ? { ...process.env, ...opts.env } : process.env;
58271
- const r = spawnSync3(cmd, args, {
58315
+ const r = spawnSync4(cmd, args, {
58272
58316
  encoding: "utf8",
58273
58317
  cwd: opts?.cwd,
58274
58318
  stdio: opts?.interactive ? "inherit" : "pipe",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cxstatusline",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Customizable statusline for OpenAI Codex CLI",
5
5
  "license": "MIT",
6
6
  "repository": {