claude-code-runrate 0.4.0 → 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,114 @@
1
+ # Changelog
2
+
3
+ Every release also has a [GitHub release](https://github.com/bingh0/ccr/releases)
4
+ and a release commit, and those carry the full notes — what was measured, what
5
+ was rejected, and why. This file is the index: enough to decide whether a
6
+ version is worth upgrading to, and where to read the rest.
7
+
8
+ Dates are npm publish dates, because that is when a version reached anyone.
9
+
10
+ ## 0.5.0 — 2026-08-22
11
+
12
+ The Windows release. The things that could not be reasoned about were measured
13
+ on a real Windows 11 machine, because none of them are reachable from CI: every
14
+ Windows test injects the launcher, so the suite proves the argv `ccr` builds and
15
+ nothing about what Windows Terminal does when handed it.
16
+
17
+ - **The panes open where `ccr` was run.** `wt.exe` does not inherit the working
18
+ directory the way `tmux new-session` does, so every pane opened in
19
+ `%USERPROFILE%` while the launch record claimed otherwise — and the sidecar
20
+ prefers that record, so the git pane described the project in full confidence
21
+ for a terminal that was not in it. One source now feeds both.
22
+ - **Measured limits, and they are real.** Past 256 characters `wt` opens *no
23
+ tab at all*; a UNC path opens a tab and silently lands in `%SystemRoot%`; a
24
+ semicolon costs the whole tab; a backtick does not, so refusing it was wrong
25
+ and no longer happens. Each unusable path degrades — the panes open in the
26
+ default directory and stderr names which of the four reasons applied.
27
+ - **The sidecar pane has a cycle key** on Windows Terminal. F3 or Space. F3 has
28
+ three encodings and on Windows which one arrives depends on the *Node*
29
+ version, so `ccr` answers all three rather than pinning a Node floor for a
30
+ keystroke.
31
+ - **A pane config that says what is wrong.** A UTF-8 BOM is stripped; UTF-16 —
32
+ what PowerShell 5.1 writes for `>` and `Out-File` — is named as an encoding
33
+ problem instead of reported as bad JSON; `~\` expands as well as `~/`; and a
34
+ broken config says so on the panel and in `ccr doctor`, which reports pane
35
+ paths as *resolved*.
36
+ - **`ccr sidecar` resolves the instance from the working directory**, which the
37
+ feature files and the README had both described for a while.
38
+ - **The economy screen earns a decimal** where the next tenth is a decision: at
39
+ or past 95% used, one truncated digit, so `floor(shown)` still matches
40
+ `/usage`. A snapshot that stopped refreshing dims rather than blanks.
41
+
42
+ ## 0.4.0 — 2026-08-07
43
+
44
+ [The instance layer and the git pane.](https://github.com/bingh0/ccr/releases/tag/v0.4.0)
45
+
46
+ - **Instances.** Every launch is a named, slotted instance under one container
47
+ home; the name is derived from the project or given with `-i`, and travels
48
+ into the window title, status line and sidebar.
49
+ - **A git pane** on F3: which repository the tab is in, the working tree, and
50
+ recent history in lanes. Read-only — it never writes to the repository.
51
+ - The three launchers (tmux, Windows Terminal, VS Code) take naming, titles and
52
+ retention from one shared path rather than the tmux path owning them.
53
+
54
+ ## 0.3.0 — 2026-08-04
55
+
56
+ [External tool panes in the sidebar.](https://github.com/bingh0/ccr/releases/tag/v0.3.0)
57
+
58
+ - **Panes from other tools.** The sidebar hosts full-height read-only panes
59
+ rendered from small JSON blobs another tool writes beside its own artifacts.
60
+ You list the path in `~/.config/ccr/config.json`; F3 cycles.
61
+ - A rate-limit correctness fix, and `strict` type checking across the codebase.
62
+
63
+ ## 0.2.4 — 2026-07-12
64
+
65
+ [VS Code sidecars stop piling up; profile state-dir fix.](https://github.com/bingh0/ccr/releases/tag/v0.2.4)
66
+
67
+ - One live sidecar per session: relaunching inside VS Code no longer
68
+ accumulates identical panes.
69
+ - Profile sessions write their snapshots to the right state dir.
70
+
71
+ ## 0.2.3 — 2026-07-03
72
+
73
+ [Sidecars agree on 5h/weekly across profiles.](https://github.com/bingh0/ccr/releases/tag/v0.2.3)
74
+
75
+ - The 5h and weekly walls are one account-wide resource, but each profile only
76
+ captures its own; two sidecars on the same account could disagree. They
77
+ reconcile now.
78
+
79
+ ## 0.2.2 — 2026-07-02
80
+
81
+ [Aligned meter bars + a staleness marker.](https://github.com/bingh0/ccr/releases/tag/v0.2.2)
82
+
83
+ - A barely-used window projects an enormous time-to-exhaust, which overflowed
84
+ the fixed time column and shoved that row's bar out of line. Long horizons
85
+ are capped, so the bars stay aligned and the figure reads more honestly.
86
+ - A stale snapshot is annotated (`· updated Nm ago`) rather than silently
87
+ frozen. Never a wipe — Claude Code legitimately stops ticking during a long
88
+ operation.
89
+
90
+ ## 0.2.1 — 2026-06-26
91
+
92
+ [Windows Terminal teardown and sidecar width fixes.](https://github.com/bingh0/ccr/releases/tag/v0.2.1)
93
+
94
+ - ConPTY makes a split pane's reported width unreliable, so the panel
95
+ soft-wrapped. The launcher injects the computed width and the sidecar clamps
96
+ to the smaller of the two.
97
+ - On exit the sidecar pane collapses first and the border sweeps left to right,
98
+ fixing an ordering bug where Claude's pane could close first.
99
+
100
+ ## 0.2.0 — 2026-06-26
101
+
102
+ [Native Windows and VS Code live sidecars.](https://github.com/bingh0/ccr/releases/tag/v0.2.0)
103
+
104
+ - **Native Windows** — the live sidecar hosted in Windows Terminal split panes,
105
+ no tmux, bash or WSL, with a graceful fallback when `wt.exe` is absent.
106
+ - **VS Code integrated terminal on any OS** — split-pane sidecar via a
107
+ clipboard one-liner.
108
+ - `statusLine` injected per launch through a temp settings file, so `~/.claude`
109
+ is never mutated.
110
+
111
+ ## 0.1.0 — 2026-06-24
112
+
113
+ First publish: the CLI and status line — `economy`, `resume`, `statusline` —
114
+ with the live sidebar on tmux. Native Windows arrived in 0.2.0.
package/README.md CHANGED
@@ -173,11 +173,11 @@ small JSON blob beside its own artifacts; you list that file's path in ccr's
173
173
  config; the sidebar cycles between the economy panel, the built-in git pane, and
174
174
  each configured pane.
175
175
 
176
- **Cycling views.** Under tmux the launcher binds **F3**. VS Code and its forks
177
- (Cursor, Positron, Antigravity) bind no key and leave both split panes running a
178
- foreground process, so the pasted sidecar one-liner carries `--keys`: click that
179
- pane and press **Space** (or **F3**). The renderer still reads no input — the key
180
- lives in a separate parent process that runs the panel as a child, the same
176
+ **Cycling views.** Under tmux the launcher binds **F3** at the host. **Windows
177
+ Terminal** binds no key of its own, and neither do VS Code and its forks (Cursor,
178
+ Positron, Antigravity) — so on those the sidecar pane carries its own key: click
179
+ the pane and press **Space** or **F3**. The renderer still reads no input — the
180
+ key lives in a separate parent process that runs the panel as a child, the same
181
181
  separation tmux enforces. Anywhere else, `ccr cycle-view -i <name>` (or
182
182
  `--state-dir <dir>`); a bare `ccr cycle-view` resolves the instance from your
183
183
  working directory, like `ccr economy` does.
@@ -191,6 +191,19 @@ read from a repository:
191
191
  { "panes": [ { "path": "/home/you/project/.your-tool/sidecar.json" } ] }
192
192
  ```
193
193
 
194
+ A leading `~` expands, with either separator (`~/tools/blob.json`,
195
+ `~\tools\blob.json`), and a relative path resolves against the config file's own
196
+ directory. **On Windows, save the file as UTF-8** — `>` and `Out-File` in Windows
197
+ PowerShell 5.1 write UTF-16, which is not JSON as far as any parser is concerned:
198
+
199
+ ```powershell
200
+ Set-Content -Encoding utf8 $env:USERPROFILE\.config\ccr\config.json $json
201
+ ```
202
+
203
+ `ccr doctor` reports what it read out of the config — the paths as **resolved**,
204
+ not as written — and names the problem when there is one, so a pane that never
205
+ appears is a question you can answer rather than a silence.
206
+
194
207
  A pane is a full-height view carrying the producing tool's own rows — here
195
208
  `gherkin-trace`, whose blob ships as the golden example:
196
209
 
@@ -253,10 +266,16 @@ method has followed since, and is honest about which point on that line this
253
266
  repository represents.
254
267
 
255
268
  ```bash
256
- npm test # node --test — harness self-tests + feature scenarios
257
- npm run typecheck # tsc --noEmit over @ts-check'd JS (needs: npm i first)
269
+ npm test # node --test — harness self-tests + feature scenarios
270
+ npm run typecheck # tsc --noEmit over @ts-check'd JS (needs: npm i first)
271
+ npm run install-hooks # copy .githooks/ into this clone (run once)
258
272
  ```
259
273
 
274
+ `install-hooks` installs a fail-closed `pre-push` guard for the public remote:
275
+ only `main`, fast-forward only, and a scan of the commits the push would make
276
+ public. It is worth running in any clone you push from — a push is the moment
277
+ history stops being private, and it is the last moment the check can help.
278
+
260
279
  - **No runtime dependencies**, ever — it's what lets `npx claude-code-runrate` install
261
280
  instantly on every OS, including native Windows.
262
281
  - **Dev-only** tooling (`typescript`, `@types/node`) exists solely for
package/bin/ccr.js CHANGED
@@ -323,8 +323,18 @@ function cmdResume(arg) {
323
323
  * @returns {number | undefined}
324
324
  */
325
325
  function cmdSidecar(stateDir, showHint, exitOnEnd, useKeys, view, target = null) {
326
- if (!stateDir && target != null) {
327
- // -i: attach to a live instance by name, through the resolution chain.
326
+ // Resolve whether or not -i was typed, which is what `ccr cycle-view` has
327
+ // always done. Consulting the chain ONLY for -i meant a bare `ccr sidecar`
328
+ // skipped every other link in it — including the one that matters most here,
329
+ // "the live instance whose launch directory contains the cwd" — and landed on
330
+ // the CONTAINER (~/.ccr) instead. The container holds no session, so the pane
331
+ // sat empty next to a running instance, and the fix looked like naming the
332
+ // instance exactly right when the whole point of the chain is not having to.
333
+ //
334
+ // --hint is left out: it prints the VS Code split instructions, which are
335
+ // worth printing before any instance exists, so requiring a live one to
336
+ // explain how to start one would be backwards.
337
+ if (!stateDir && !showHint) {
328
338
  const res = require('../src/instance-resolve').resolveInstance({ target, command: 'sidecar' });
329
339
  if (!res.ok) { process.stderr.write(res.error + '\n'); return 1; }
330
340
  stateDir = res.stateDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-runrate",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Claude Code run-rate — subscription burn-rate & economy for your Claude Code sessions.",
5
5
  "license": "MIT",
6
6
  "author": "Bing Ho <reps-attic-riot@duck.com>",
@@ -25,6 +25,7 @@
25
25
  "scripts/launch.sh",
26
26
  "sidecar",
27
27
  "README.md",
28
+ "CHANGELOG.md",
28
29
  "LICENSE"
29
30
  ],
30
31
  "scripts": {
@@ -32,7 +33,10 @@
32
33
  "typecheck": "tsc --noEmit -p jsconfig.json",
33
34
  "lint": "oxlint",
34
35
  "release-gate": "node scripts/release-gate.js",
35
- "prepublishOnly": "npm run release-gate && npm run lint && npm run typecheck && npm test"
36
+ "install-hooks": "node scripts/install-hooks.js",
37
+ "probe:wt": "node scripts/probe-wt.js",
38
+ "scan-package": "node scripts/scan-package.js",
39
+ "prepublishOnly": "npm run release-gate && npm run scan-package && npm run lint && npm run typecheck && npm test"
36
40
  },
37
41
  "keywords": [
38
42
  "claude-code",
package/src/doctor.js CHANGED
@@ -9,6 +9,7 @@ const path = require('node:path');
9
9
  const os = require('node:os');
10
10
  const { spawnSync } = require('node:child_process');
11
11
  const { stripControl } = require('./sanitize');
12
+ const { loadPaneConfig } = require('./pane-config');
12
13
 
13
14
  const ok = (/** @type {string} */ s) => `\x1b[32m✓\x1b[0m ${s}`;
14
15
  const bad = (/** @type {string} */ s) => `\x1b[31m✗\x1b[0m ${s}`;
@@ -36,7 +37,8 @@ function isExec(/** @type {string} */ f) {
36
37
 
37
38
  /**
38
39
  * @param {{ platform?: string, has?: (cmd: string) => (string|null),
39
- * homedir?: string, repo?: string, write?: (s: string) => void }} [opts]
40
+ * homedir?: string, repo?: string, write?: (s: string) => void,
41
+ * env?: Record<string, string|undefined> }} [opts]
40
42
  * side effects are injectable for testing; defaults hit the real environment
41
43
  * @returns {number} exit code (0 = healthy)
42
44
  */
@@ -91,6 +93,24 @@ function run(opts = {}) {
91
93
  out.push(dim('· ccs not installed (optional — only for `ccr <profile>`)'));
92
94
  }
93
95
 
96
+ // Pane wiring. This command exists to diagnose "nothing happens", and a pane
97
+ // config the user wrote and got wrong is exactly that — the sidecar has room
98
+ // for a one-line marker and no more. Here there is room for the path, the
99
+ // reason, and what ccr actually read out of the file, which is the question
100
+ // someone whose pane never appeared is really asking.
101
+ const cfg = loadPaneConfig({ env: opts.env, home: homedir });
102
+ if (cfg.error) {
103
+ out.push(bad(`pane config: ${cfg.error} — ${stripControl(cfg.configPath)}`));
104
+ problems++;
105
+ } else if (cfg.panes.length) {
106
+ out.push(ok(`pane config: ${cfg.panes.length} pane(s) (${stripControl(cfg.configPath)})`));
107
+ // The path as ccr resolved it, not as written: a tilde that did not expand
108
+ // is invisible in the source string and obvious in the resolved one.
109
+ for (const pane of cfg.panes) out.push(dim(` · ${stripControl(pane.path)}`));
110
+ } else {
111
+ out.push(dim(`· no panes configured (optional — ${stripControl(cfg.configPath)})`));
112
+ }
113
+
94
114
  // newest captured snapshot across the container. Instances live TWO levels
95
115
  // down under the 0.4.0 layout (~/.ccr/instances/<n>/last-status.json) — the
96
116
  // one-level scan alone would report "no status captured" while instances run