@inbrace-tech/tokenline 1.0.1 → 1.1.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 +69 -68
  2. package/dist/cli.js +21 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,40 +6,75 @@
6
6
  ![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20WSL2-blue)
7
7
  ![Shell](https://img.shields.io/badge/shell-bash%204%2B-lightgrey)
8
8
 
9
- `tokenline` turns the one-line status bar of your AI coding CLI into a live cockpit:
10
- which model you're on, how much context you've burned, how long your prompt cache
11
- stays hot, how many tokens you're **saving** by reusing it, and how close you are to
12
- your 5h / 7d rate limits.
9
+ ## Quickstart
10
+
11
+ ### 1. Install (via npm)
12
+ If you have **Node 18+**, run this inside your project repository to configure the statusline locally.
13
+
14
+ ```bash
15
+ npx @inbrace-tech/tokenline init
16
+ ```
17
+
18
+ *Want it globally for all your projects? Add `--global` to the command.*
19
+ ```bash
20
+ npx @inbrace-tech/tokenline init --global
21
+ ```
13
22
 
14
- It is **cross-CLI** (Claude Code, Antigravity) and **cross-provider** (Anthropic,
15
- Gemini) — detecting the active client and model provider at runtime and adjusting
16
- the cost equivalents accordingly.
23
+ ### 2. Restart your CLI
24
+
25
+ Simply restart Claude Code or Antigravity, and your new statusline will be live.
26
+
27
+ ---
17
28
 
18
29
  ## Preview
19
30
 
20
31
  ![Tokenline Preview](https://raw.githubusercontent.com/inbrace-tech/tokenline/main/assets/tokenline.png)
21
32
 
22
33
  - **Line 1** — model · context used (tokens + %) · cache TTL with a live HOT→COLD countdown.
23
- - **Line 2** — per-turn token economics: `read / write / new / output`, the equivalent
24
- billed tokens (`eq`), and the **`saving %`** you get from prompt caching.
25
- - **Line 3** — 5h and 7d rate-limit bars with reset ETA and a **pace marker**
26
- (`!!` = you're burning the window faster than it refills).
34
+ - **Line 2** — per-turn token economics: `read / write / new / output`, the equivalent billed tokens (`eq`), and the **`saving %`** you get from prompt caching.
35
+ - **Line 3** — 5h and 7d rate-limit bars with reset ETA and a **pace marker** (`!!` = you're burning the window faster than it refills).
36
+
37
+ > Lines 2 and 3 appear only when there's something to show (a turn happened, limits exist), so the bar stays quiet when idle.
38
+
39
+ ## About
27
40
 
28
- > Lines 2 and 3 appear only when there's something to show (a turn happened, limits
29
- > exist), so the bar stays quiet when idle.
41
+ `tokenline` turns the one-line status bar of your AI coding CLI into a live cockpit:
42
+ * Which model you're on.
43
+ * How much context you've burned.
44
+ * How long your prompt cache stays hot.
45
+ * How many tokens you're saving by reusing it.
46
+ * How close you are to your 5h / 7d rate limits.
47
+
48
+ It is **cross-CLI** (Claude Code, Antigravity) and **cross-provider** (Anthropic, Gemini) — detecting the active client and model provider at runtime and adjusting the cost equivalents accordingly.
30
49
 
31
50
  ## Why tokenline?
32
51
 
33
- Most statuslines show the model and the context bar. `tokenline` adds the two things
34
- that actually drive cost and flow on long agent sessions:
52
+ Most statuslines show the model and the context bar. `tokenline` adds the two things that actually drive cost and flow on long agent sessions:
53
+
54
+ - **Cache visibility.** Anthropic and Gemini bill cached input tokens at a fraction of the price — but the cache expires. `tokenline` shows the TTL countdown (5m or 1h, detected from the data) so you know whether your next turn lands warm or cold.
55
+ - **Savings, quantified.** The `saving %` makes the value of prompt caching concrete instead of invisible.
56
+ - **Rate-limit pacing.** The `!!` marker warns you when you're on track to hit the 5h or 7d ceiling before it resets.
57
+
58
+ ## A 30-second guide to Prompt Caching
35
59
 
36
- - **Cache visibility.** Anthropic and Gemini bill cached input tokens at a fraction of
37
- the price — but the cache expires. `tokenline` shows the TTL countdown (5m or 1h,
38
- detected from the data) so you know whether your next turn lands warm or cold.
39
- - **Savings, quantified.** The `saving %` makes the value of prompt caching concrete
40
- instead of invisible.
41
- - **Rate-limit pacing.** The `!!` marker warns you when you're on track to hit the 5h
42
- or 7d ceiling before it resets.
60
+ LLMs are stateless — by default, they must reread your entire codebase context (system prompt, tools, file history) on *every single turn* before generating a response. This phase (Prefill) is slow and expensive.
61
+
62
+ **Prompt Caching** solves this by saving that processed state in the provider's memory:
63
+ - **Cache Write (Cold):** The model reads everything and stores the state. This costs slightly more than base tokens.
64
+ - **Cache Hit (Warm):** If your prompt prefix matches the cached state exactly, the model skips the reading phase. This is **~90% cheaper** and starts responding almost instantly.
65
+ - **TTL (Time-To-Live):** The cache operates as a **sliding window**. Every cache hit resets the countdown to its full duration for free. The secret to minimizing rate-limit drain is adjusting your workflow pace to keep the cache continuously **HOT**.
66
+
67
+ ### 💡 Pro-tip: Forcing a 5m TTL to save rate limits
68
+
69
+ Anthropic's rules for assigning cache TTLs are dynamic and can change without notice (e.g., historically granting a 1-hour TTL to Claude.ai Max users). This is exactly why `tokenline` is valuable: it doesn't guess your TTL based on your plan. It parses the actual CLI telemetry in real-time to detect whether your last turn was processed as a 5-minute or 1-hour write, and adapts the UI countdown and token economics accordingly.
70
+
71
+ If `tokenline` reveals you are getting 1-hour cache writes, keep in mind they burn **2x** the tokens from your 5h/7d rate limit (compared to 1.25x for a 5-minute write). If you find this is exhausting your quota too quickly—especially when running multiple subagents that cool down fast—you can force the CLI to fall back to the cheaper 5-minute writes by adding this to your `.claude/settings.json`:
72
+
73
+ ```json
74
+ "env": {
75
+ "FORCE_PROMPT_CACHING_5M": "1"
76
+ }
77
+ ```
43
78
 
44
79
  ## Requirements
45
80
 
@@ -52,29 +87,11 @@ v1 targets **Linux / WSL2**:
52
87
  > macOS and Windows support are on the [roadmap](#roadmap). `install.sh` checks all of
53
88
  > the above and tells you exactly what's missing.
54
89
 
55
- ## Install
56
-
57
- ### With npm (recommended)
58
-
59
- If you have **Node 18+**, one command installs the script and wires it into your
60
- Claude Code settings:
61
-
62
- ```bash
63
- npx @inbrace-tech/tokenline init
64
- ```
65
-
66
- It copies `tokenline.sh` to `~/.claude/` and adds the `statusLine` block to
67
- `~/.claude/settings.json`. Use `--project` to configure the current project
68
- instead of your global settings, and `--dry-run` to preview without writing.
69
- Then restart Claude Code.
70
-
71
- > The statusline runs as `bash tokenline.sh` — Node is used only at install
72
- > time, never in the per-second hot path. `jq` is still required at runtime.
90
+ ## Advanced Installation
73
91
 
74
92
  ### Without Node (clone + install.sh)
75
93
 
76
- No Node? Clone the repo and run the dependency checker, which prints a
77
- ready-to-paste snippet:
94
+ No Node? Clone the repo and run the dependency checker, which prints a ready-to-paste snippet:
78
95
 
79
96
  ```bash
80
97
  git clone https://github.com/inbrace-tech/tokenline.git
@@ -82,8 +99,7 @@ cd tokenline
82
99
  ./install.sh
83
100
  ```
84
101
 
85
- Add the printed block to `~/.claude/settings.json` (global) or your project's
86
- `.claude/settings.json`, inside the top-level object:
102
+ Add the printed block to your project's `.claude/settings.json` (or `~/.claude/settings.json` for global), inside the top-level object:
87
103
 
88
104
  ```json
89
105
  "statusLine": {
@@ -97,34 +113,24 @@ Then restart Claude Code.
97
113
 
98
114
  ### What the installer does
99
115
 
100
- `npx @inbrace-tech/tokenline init` is deliberately transparent about touching
101
- your config:
116
+ `npx @inbrace-tech/tokenline init` is deliberately transparent about touching your config:
102
117
 
103
118
  - **Writes** `tokenline.sh` to `~/.claude/` (or `./.claude/` with `--project`).
104
- - **Merges** only the `statusLine` key into `settings.json` — every other
105
- setting is preserved.
119
+ - **Merges** only the `statusLine` key into `settings.json` — every other setting is preserved.
106
120
  - **Backs up** `settings.json` to `settings.json.bak` before writing.
107
- - **Never clobbers** invalid JSON: if it can't parse your `settings.json`, it
108
- stops and prints the block to paste manually.
109
- - Is **idempotent**, and won't replace a different existing `statusLine` unless
110
- you pass `--force`.
121
+ - **Never clobbers** invalid JSON: if it can't parse your `settings.json`, it stops and prints the block to paste manually.
122
+ - Is **idempotent**, and won't replace a different existing `statusLine` unless you pass `--force`.
111
123
 
112
- Other commands: `doctor` (check dependencies and config, change nothing) and
113
- `uninstall` (remove the block; `--purge` also deletes the script).
124
+ Other commands: `doctor` (check dependencies and config, change nothing) and `uninstall` (remove the block; `--purge` also deletes the script).
114
125
 
115
126
  ### Antigravity CLI
116
127
 
117
- `tokenline` detects the Antigravity CLI from the transcript path and switches to its
118
- provider equivalents automatically. Point Antigravity's statusline command at the same
128
+ `tokenline` detects the Antigravity CLI from the transcript path and switches to its provider equivalents automatically. Point Antigravity's statusline command at the same
119
129
  `tokenline.sh` — no extra flags needed.
120
130
 
121
131
  ## How it works
122
132
 
123
- On every refresh the host CLI pipes a JSON snapshot of the session to the script over
124
- stdin. `tokenline` parses it in a single `jq` pass (to keep the per-second refresh
125
- cheap), reads the last turn's timestamp from the transcript to drive the cache
126
- countdown, and renders up to three lines. Per-turn timestamps are cached in a
127
- per-user `0700` directory under `$XDG_RUNTIME_DIR` (tmpfs, cleared on logout).
133
+ On every refresh the host CLI pipes a JSON snapshot of the session to the script over stdin. `tokenline` parses it in a single `jq` pass (to keep the per-second refresh cheap), reads the last turn's timestamp from the transcript to drive the cache countdown, and renders up to three lines. Per-turn timestamps are cached in a per-user `0700` directory under `$XDG_RUNTIME_DIR` (tmpfs, cleared on logout).
128
134
 
129
135
  ## Troubleshooting
130
136
 
@@ -145,16 +151,11 @@ per-user `0700` directory under `$XDG_RUNTIME_DIR` (tmpfs, cleared on logout).
145
151
 
146
152
  ## Contributing
147
153
 
148
- Issues and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). The CI runs
149
- [ShellCheck](https://www.shellcheck.net/) on every push, so please keep the script
150
- lint-clean.
154
+ Issues and PRs are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). The CI runs [ShellCheck](https://www.shellcheck.net/) on every push, so please keep the script lint-clean.
151
155
 
152
156
  ## About Inbrace
153
157
 
154
- `tokenline` is built and maintained by **Inbrace** — a software house based in
155
- Campinas, Brazil, building software with technical and human responsibility. We work
156
- where security is non-negotiable and architecture is treated as a strategic asset:
157
- intentional engineering, grounded decisions, and systems built to last.
158
+ `tokenline` is built and maintained by **Inbrace** — a software house based in Campinas, Brazil, building software with technical and human responsibility. We work where security is non-negotiable and architecture is treated as a strategic asset: intentional engineering, grounded decisions, and systems built to last.
158
159
 
159
160
  > Inbrace. The human side of software.
160
161
 
package/dist/cli.js CHANGED
@@ -1,36 +1,36 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var A=require("fs"),D=require("path");var N=require("os"),l=require("path"),J=(0,l.join)(__dirname,"..","tokenline.sh"),_=e=>e.project?(0,l.resolve)(".claude"):(0,l.join)((0,N.homedir)(),".claude"),$=e=>e.dir?(0,l.resolve)(e.dir,"tokenline.sh"):(0,l.join)(_(e),"tokenline.sh"),d=e=>(0,l.join)(_(e),"settings.json"),q=e=>e.includes(" ")?`bash "${e}"`:`bash ${e}`;var p=require("fs");function f(e){if(!(0,p.existsSync)(e))return{exists:!1,data:{}};let t=(0,p.readFileSync)(e,"utf8");if(t.trim()==="")return{exists:!0,data:{},raw:t};try{return{exists:!0,data:JSON.parse(t),raw:t}}catch{return{exists:!0,data:null,raw:t}}}function x(e){let t=`${e}.bak`;return(0,p.copyFileSync)(e,t),t}var S=e=>typeof e=="string"&&/tokenline\.sh/.test(e);var O=require("child_process"),I=require("os");var E=!!process.stdout.isTTY&&!process.env.NO_COLOR,h=(e,t)=>E?`\x1B[${e}m${t}\x1B[0m`:t,s=e=>h("1",e),W=e=>h("2",e),k=e=>h("32",e),G=e=>h("31",e),K=e=>h("33",e),m=e=>console.log(`${k("\u2713")} ${e}`),i=e=>console.log(`${K("!")} ${e}`),u=e=>console.error(`${G("\u2717")} ${e}`),r=e=>console.log(`${W("\u2192")} ${e}`);function w(){let e=(0,I.platform)();return e==="linux"?(m(`platform: ${e} (supported)`),!0):(i(`platform: ${e} \u2014 v1 targets Linux/WSL2; the bash statusline likely won't render yet (see roadmap). Use --force to install anyway.`),!1)}function v(){let e=(0,O.spawnSync)("jq",["--version"],{encoding:"utf8"});return e.status===0?(m(String(e.stdout).trim()),!0):(i("jq not found \u2014 required at runtime. Install it (apt install jq / brew install jq)."),!1)}function C(){let e=(0,O.spawnSync)("bash",["--version"],{encoding:"utf8"});if(e.status===0){let t=String(e.stdout).match(/version (\d+)\.(\d+)/);return t&&Number(t[1])>=4?(m(`bash ${t[1]}.${t[2]}`),!0):(i(`bash ${t?`${t[1]}.${t[2]}`:"?"} found \u2014 the script needs bash 4+.`),!1)}return i("bash not found \u2014 the statusline runs as a bash script."),!1}function P(){console.log(s(`
2
+ "use strict";var A=require("fs"),D=require("path");var j=require("os"),c=require("path"),J=(0,c.join)(__dirname,"..","tokenline.sh"),_=e=>e.global?(0,c.join)((0,j.homedir)(),".claude"):(0,c.resolve)(".claude"),$=e=>e.dir?(0,c.resolve)(e.dir,"tokenline.sh"):(0,c.join)(_(e),"tokenline.sh"),d=e=>(0,c.join)(_(e),"settings.json"),q=e=>e.includes(" ")?`bash "${e}"`:`bash ${e}`;var p=require("fs");function f(e){if(!(0,p.existsSync)(e))return{exists:!1,data:{}};let t=(0,p.readFileSync)(e,"utf8");if(t.trim()==="")return{exists:!0,data:{},raw:t};try{return{exists:!0,data:JSON.parse(t),raw:t}}catch{return{exists:!0,data:null,raw:t}}}function x(e){let t=`${e}.bak`;return(0,p.copyFileSync)(e,t),t}var S=e=>typeof e=="string"&&/tokenline\.sh/.test(e);var O=require("child_process"),I=require("os");var E=!!process.stdout.isTTY&&!process.env.NO_COLOR,h=(e,t)=>E?`\x1B[${e}m${t}\x1B[0m`:t,r=e=>h("1",e),W=e=>h("2",e),b=e=>h("32",e),G=e=>h("31",e),K=e=>h("33",e),g=e=>console.log(`${b("\u2713")} ${e}`),i=e=>console.log(`${K("!")} ${e}`),u=e=>console.error(`${G("\u2717")} ${e}`),s=e=>console.log(`${W("\u2192")} ${e}`);function w(){let e=(0,I.platform)();return e==="linux"?(g(`platform: ${e} (supported)`),!0):(i(`platform: ${e} \u2014 v1 targets Linux/WSL2; the bash statusline likely won't render yet (see roadmap). Use --force to install anyway.`),!1)}function v(){let e=(0,O.spawnSync)("jq",["--version"],{encoding:"utf8"});return e.status===0?(g(String(e.stdout).trim()),!0):(i("jq not found \u2014 required at runtime. Install it (apt install jq / brew install jq)."),!1)}function C(){let e=(0,O.spawnSync)("bash",["--version"],{encoding:"utf8"});if(e.status===0){let t=String(e.stdout).match(/version (\d+)\.(\d+)/);return t&&Number(t[1])>=4?(g(`bash ${t[1]}.${t[2]}`),!0):(i(`bash ${t?`${t[1]}.${t[2]}`:"?"} found \u2014 the script needs bash 4+.`),!1)}return i("bash not found \u2014 the statusline runs as a bash script."),!1}function P(){console.log(r(`
3
3
  tokenline \u2014 environment check
4
- `)),w(),C(),v();let e=[{label:"global",project:!1},{label:"project",project:!0}];for(let t of e){let o=d({project:t.project,dir:null}),n=f(o);n.exists&&n.data&&S(n.data.statusLine?.command)?m(`${t.label} settings: tokenline configured (${o})`):n.exists&&n.data===null?i(`${t.label} settings: invalid JSON (${o})`):r(`${t.label} settings: not configured (${o})`)}console.log()}var a=require("fs"),j=require("path");function U(e){if(console.log(s(`
4
+ `)),w(),C(),v();let e=[{label:"project",global:!1},{label:"global",global:!0}];for(let t of e){let o=d({global:t.global,dir:null}),n=f(o);n.exists&&n.data&&S(n.data.statusLine?.command)?g(`${t.label} settings: tokenline configured (${o})`):n.exists&&n.data===null?i(`${t.label} settings: invalid JSON (${o})`):s(`${t.label} settings: not configured (${o})`)}console.log()}var a=require("fs"),L=require("path");function U(e){if(console.log(r(`
5
5
  tokenline \u2014 installing the statusline
6
- `)),!w()&&!e.force){u("Unsupported platform. Re-run with --force to install anyway."),process.exitCode=1;return}C(),v();let o=$(e),n=d(e),b={type:"command",command:q(o),refreshInterval:1},c=f(n);if(c.exists&&c.data===null){u(`Could not parse ${n} (invalid JSON). Leaving it untouched.`),console.log(`
6
+ `)),!w()&&!e.force){u("Unsupported platform. Re-run with --force to install anyway."),process.exitCode=1;return}C(),v();let o=$(e),n=d(e),k={type:"command",command:q(o),refreshInterval:1},l=f(n);if(l.exists&&l.data===null){u(`Could not parse ${n} (invalid JSON). Leaving it untouched.`),console.log(`
7
7
  Add this block manually, inside the top-level object:
8
- `),console.log(JSON.stringify({statusLine:b},null,2)+`
9
- `),process.exitCode=1;return}let y=c.data?c.data.statusLine:void 0,L=y!==void 0&&y.command===b.command,R=y!==void 0&&!L;if(R&&!e.force){u(`A different statusLine is already configured in ${n}:`),console.log(` ${JSON.stringify(y)}`),i("Re-run with --force to replace it."),process.exitCode=1;return}if(e.dryRun){console.log(s(`
10
- [dry-run] would:`)),r(`write script \u2192 ${o}`),r(`${c.exists?"patch":"create"} settings \u2192 ${n}`),c.exists&&r(`backup \u2192 ${n}.bak`),console.log(`
8
+ `),console.log(JSON.stringify({statusLine:k},null,2)+`
9
+ `),process.exitCode=1;return}let y=l.data?l.data.statusLine:void 0,R=y!==void 0&&y.command===k.command,T=y!==void 0&&!R;if(T&&!e.force){u(`A different statusLine is already configured in ${n}:`),console.log(` ${JSON.stringify(y)}`),i("Re-run with --force to replace it."),process.exitCode=1;return}if(e.dryRun){console.log(r(`
10
+ [dry-run] would:`)),s(`write script \u2192 ${o}`),s(`${l.exists?"patch":"create"} settings \u2192 ${n}`),l.exists&&s(`backup \u2192 ${n}.bak`),console.log(`
11
11
  statusLine block:
12
- ${JSON.stringify(b,null,2)}
13
- `);return}(0,a.mkdirSync)((0,j.dirname)(o),{recursive:!0}),(0,a.copyFileSync)(J,o),(0,a.chmodSync)(o,493),r(`wrote ${o}`);let T=c.data??{};c.exists?(x(n),r(`backed up ${n} \u2192 settings.json.bak`)):(0,a.mkdirSync)((0,j.dirname)(n),{recursive:!0}),T.statusLine=b,(0,a.writeFileSync)(n,JSON.stringify(T,null,2)+`
14
- `),r(`${L?"confirmed":R?"replaced":"added"} statusLine in ${n}`),console.log(`
15
- ${k("Done.")} Restart Claude Code to see the statusline.
16
- `)}var g=require("fs");function B(e){console.log(s(`
12
+ ${JSON.stringify(k,null,2)}
13
+ `);return}(0,a.mkdirSync)((0,L.dirname)(o),{recursive:!0}),(0,a.copyFileSync)(J,o),(0,a.chmodSync)(o,493),s(`wrote ${o}`);let N=l.data??{};l.exists?(x(n),s(`backed up ${n} \u2192 settings.json.bak`)):(0,a.mkdirSync)((0,L.dirname)(n),{recursive:!0}),N.statusLine=k,(0,a.writeFileSync)(n,JSON.stringify(N,null,2)+`
14
+ `),s(`${R?"confirmed":T?"replaced":"added"} statusLine in ${n}`),console.log(`
15
+ ${b("Done.")} Restart Claude Code to see the statusline.
16
+ `)}var m=require("fs");function B(e){console.log(r(`
17
17
  tokenline \u2014 uninstall
18
- `));let t=d(e),o=f(t);if(!o.exists||o.data===null?i(`No usable settings at ${t} \u2014 nothing to remove.`):S(o.data.statusLine?.command)?e.dryRun?r(`[dry-run] would remove statusLine from ${t}`):(x(t),delete o.data.statusLine,(0,g.writeFileSync)(t,JSON.stringify(o.data,null,2)+`
19
- `),r(`removed statusLine from ${t} (backup: settings.json.bak)`)):r(`No tokenline statusLine in ${t} \u2014 left untouched.`),e.purge){let n=$(e);(0,g.existsSync)(n)&&(e.dryRun?r(`[dry-run] would delete ${n}`):((0,g.unlinkSync)(n),r(`deleted ${n}`)))}console.log(`
20
- ${k("Done.")} Restart Claude Code.
21
- `)}var Y=JSON.parse((0,A.readFileSync)((0,D.join)(__dirname,"..","package.json"),"utf8"));function z(e){let t={_:[],dir:null,project:!1,dryRun:!1,force:!1,purge:!1,help:!1,version:!1,unknown:[]};for(let o=0;o<e.length;o++){let n=e[o];switch(n){case"--dir":t.dir=e[++o]??null;break;case"--project":case"--local":t.project=!0;break;case"--dry-run":t.dryRun=!0;break;case"--force":t.force=!0;break;case"--purge":t.purge=!0;break;case"-h":case"--help":t.help=!0;break;case"-v":case"--version":t.version=!0;break;default:n!==void 0&&n.startsWith("-")?t.unknown.push(n):n!==void 0&&t._.push(n)}}return t}function F(){console.log(`
22
- ${s("tokenline")} \u2014 a cache-aware statusline for AI coding CLIs
18
+ `));let t=d(e),o=f(t);if(!o.exists||o.data===null?i(`No usable settings at ${t} \u2014 nothing to remove.`):S(o.data.statusLine?.command)?e.dryRun?s(`[dry-run] would remove statusLine from ${t}`):(x(t),delete o.data.statusLine,(0,m.writeFileSync)(t,JSON.stringify(o.data,null,2)+`
19
+ `),s(`removed statusLine from ${t} (backup: settings.json.bak)`)):s(`No tokenline statusLine in ${t} \u2014 left untouched.`),e.purge){let n=$(e);(0,m.existsSync)(n)&&(e.dryRun?s(`[dry-run] would delete ${n}`):((0,m.unlinkSync)(n),s(`deleted ${n}`)))}console.log(`
20
+ ${b("Done.")} Restart Claude Code.
21
+ `)}var Y=JSON.parse((0,A.readFileSync)((0,D.join)(__dirname,"..","package.json"),"utf8"));function z(e){let t={_:[],dir:null,global:!1,dryRun:!1,force:!1,purge:!1,help:!1,version:!1,unknown:[]};for(let o=0;o<e.length;o++){let n=e[o];switch(n){case"--dir":t.dir=e[++o]??null;break;case"--global":t.global=!0;break;case"--dry-run":t.dryRun=!0;break;case"--force":t.force=!0;break;case"--purge":t.purge=!0;break;case"-h":case"--help":t.help=!0;break;case"-v":case"--version":t.version=!0;break;default:n!==void 0&&n.startsWith("-")?t.unknown.push(n):n!==void 0&&t._.push(n)}}return t}function F(){console.log(`
22
+ ${r("tokenline")} \u2014 a cache-aware statusline for AI coding CLIs
23
23
 
24
- ${s("Usage")}
24
+ ${r("Usage")}
25
25
  npx @inbrace-tech/tokenline <command> [options]
26
26
 
27
- ${s("Commands")}
27
+ ${r("Commands")}
28
28
  init Install tokenline.sh and wire it into your Claude Code settings
29
29
  doctor Check dependencies and current config \u2014 changes nothing
30
30
  uninstall Remove the tokenline statusLine block from settings
31
31
 
32
- ${s("Options")}
33
- --project Target ./.claude (project) instead of ~/.claude (global)
32
+ ${r("Options")}
33
+ --global Target ~/.claude (global) instead of ./.claude (project)
34
34
  --dir <path> Write tokenline.sh to a custom directory
35
35
  --dry-run Show what would happen without writing anything
36
36
  --force Proceed on an unsupported platform / replace an existing statusLine
@@ -38,7 +38,7 @@ ${s("Options")}
38
38
  -h, --help Show this help
39
39
  -v, --version Show version
40
40
 
41
- ${s("Notes")}
41
+ ${r("Notes")}
42
42
  The statusline runs as 'bash tokenline.sh' \u2014 no Node in the per-second hot path.
43
43
  jq is required at runtime; this installer checks for it but cannot install it.
44
44
  `)}function H(){let e=z(process.argv.slice(2));if(e.unknown.length>0&&e.unknown.forEach(t=>i(`ignoring unknown option: ${t}`)),e.version){console.log(Y.version);return}if(e.help||e._.length===0){F();return}try{switch(e._[0]){case"init":U(e);break;case"doctor":P();break;case"uninstall":case"remove":B(e);break;default:u(`Unknown command: ${e._[0]}`),F(),process.exitCode=1}}catch(t){u(t instanceof Error?t.message:String(t)),process.exitCode=1}}H();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inbrace-tech/tokenline",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A cache-aware statusline for AI coding CLIs — installs tokenline.sh and wires it into your Claude Code settings.",
5
5
  "bin": {
6
6
  "tokenline": "dist/cli.js"