@inbrace-tech/tokenline 1.2.0 → 1.2.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
@@ -9,15 +9,22 @@
9
9
  ## Quickstart
10
10
 
11
11
  ### 1. Install (via npm)
12
- If you have **Node 18+**, run this inside your project repository to configure the statusline locally.
13
12
 
13
+ Run the installer for your AI coding CLI:
14
+
15
+ **Claude Code**
14
16
  ```bash
17
+ # Per-project installation (./.claude/settings.json)
15
18
  npx @inbrace-tech/tokenline init
19
+
20
+ # Global installation (~/.claude/settings.json)
21
+ npx @inbrace-tech/tokenline init --global
16
22
  ```
17
23
 
18
- *Want it globally for all your projects? Add `--global` to the command.*
24
+ **Antigravity CLI**
19
25
  ```bash
20
- npx @inbrace-tech/tokenline init --global
26
+ # Global installation (~/.gemini/antigravity-cli/settings.json)
27
+ npx @inbrace-tech/tokenline init --antigravity
21
28
  ```
22
29
 
23
30
  ### 2. Restart your CLI
@@ -117,7 +124,7 @@ Then restart Claude Code.
117
124
 
118
125
  `npx @inbrace-tech/tokenline init` is deliberately transparent about touching your config:
119
126
 
120
- - **Writes** `tokenline.sh` to `./.claude/` (or `~/.claude/` with `--global`).
127
+ - **Writes** `tokenline.sh` to `./.claude/` (or `~/.claude/` with `--global`, or `~/.gemini/antigravity-cli/` with `--antigravity`).
121
128
  - **Merges** only the `statusLine` key into `settings.json` — every other setting is preserved.
122
129
  - **Backs up** `settings.json` to `settings.json.bak` before writing.
123
130
  - **Never clobbers** invalid JSON: if it can't parse your `settings.json`, it stops and prints the block to paste manually.
@@ -127,8 +134,7 @@ Other commands: `doctor` (check dependencies and config, change nothing) and `un
127
134
 
128
135
  ### Antigravity CLI
129
136
 
130
- `tokenline` detects the Antigravity CLI from the transcript path and switches to its provider equivalents automatically. Point Antigravity's statusline command at the same
131
- `tokenline.sh` — no extra flags needed.
137
+ `tokenline` detects the Antigravity CLI from the transcript path and switches to its provider equivalents automatically. Use `npx @inbrace-tech/tokenline init --antigravity` to write `tokenline.sh` and patch `~/.gemini/antigravity-cli/settings.json`.
132
138
 
133
139
  ## How it works
134
140
 
package/dist/cli.js CHANGED
@@ -1,35 +1,36 @@
1
1
  #!/usr/bin/env node
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),m=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"||e==="darwin"?(m(`platform: ${e} (supported)`),!0):(i(`platform: ${e} \u2014 supported on Linux/WSL2 and macOS; 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(r(`
2
+ "use strict";var F=require("fs"),E=require("path");var L=require("os"),l=require("path"),I=(0,l.join)(__dirname,"..","tokenline.sh"),J=t=>t.targetCli==="antigravity"?(0,l.join)((0,L.homedir)(),".gemini","antigravity-cli"):t.global?(0,l.join)((0,L.homedir)(),".claude"):(0,l.resolve)(".claude");var $=t=>t.dir?(0,l.resolve)(t.dir,"tokenline.sh"):(0,l.join)(J(t),"tokenline.sh"),g=t=>(0,l.join)(J(t),"settings.json"),_=t=>t.targetCli==="antigravity"?"antigravity (global)":t.global?"claude (global)":"claude (project)",q=t=>t.includes(" ")?`bash "${t}"`:`bash ${t}`;var p=require("fs");function f(t){if(!(0,p.existsSync)(t))return{exists:!1,data:{}};let e=(0,p.readFileSync)(t,"utf8");if(e.trim()==="")return{exists:!0,data:{},raw:e};try{return{exists:!0,data:JSON.parse(e),raw:e}}catch{return{exists:!0,data:null,raw:e}}}function x(t){let e=`${t}.bak`;return(0,p.copyFileSync)(t,e),e}var v=t=>typeof t=="string"&&/tokenline\.sh/.test(t);var O=require("child_process"),P=require("os");var G=!!process.stdout.isTTY&&!process.env.NO_COLOR,k=(t,e)=>G?`\x1B[${t}m${e}\x1B[0m`:e,i=t=>k("1",t),K=t=>k("2",t),b=t=>k("32",t),Y=t=>k("31",t),z=t=>k("33",t),m=t=>console.log(`${b("\u2713")} ${t}`),a=t=>console.log(`${z("!")} ${t}`),d=t=>console.error(`${Y("\u2717")} ${t}`),s=t=>console.log(`${K("\u2192")} ${t}`);function S(){let t=(0,P.platform)();return t==="linux"||t==="darwin"?(m(`platform: ${t} (supported)`),!0):(a(`platform: ${t} \u2014 supported on Linux/WSL2 and macOS; the bash statusline likely won't render yet (see roadmap). Use --force to install anyway.`),!1)}function w(){let t=(0,O.spawnSync)("jq",["--version"],{encoding:"utf8"});return t.status===0?(m(String(t.stdout).trim()),!0):(a("jq not found \u2014 required at runtime. Install it (apt install jq / brew install jq)."),!1)}function C(){let t=(0,O.spawnSync)("bash",["--version"],{encoding:"utf8"});if(t.status===0){let e=String(t.stdout).match(/version (\d+)\.(\d+)/);return e&&Number(e[1])>=4?(m(`bash ${e[1]}.${e[2]}`),!0):(a(`bash ${e?`${e[1]}.${e[2]}`:"?"} found \u2014 the script needs bash 4+.`),!1)}return a("bash not found \u2014 the statusline runs as a bash script."),!1}function U(){console.log(i(`
3
3
  tokenline \u2014 environment check
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)?m(`${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(`
4
+ `)),S(),C(),w();let t=[{global:!1,dir:null,targetCli:"claude"},{global:!0,dir:null,targetCli:"claude"},{global:!0,dir:null,targetCli:"antigravity"}];for(let e of t){let r=_(e),n=g(e),o=f(n);o.exists&&o.data&&v(o.data.statusLine?.command)?m(`${r} settings: tokenline configured (${n})`):o.exists&&o.data===null?a(`${r} settings: invalid JSON (${n})`):s(`${r} settings: not configured (${n})`)}console.log()}var c=require("fs"),T=require("path");function A(t){if(console.log(i(`
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),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(`
6
+ `)),!S()&&!t.force){d("Unsupported platform. Re-run with --force to install anyway."),process.exitCode=1;return}C(),w();let r=$(t),n=g(t),o={type:"command",command:q(r),refreshInterval:1},u=f(n);if(u.exists&&u.data===null){d(`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: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(`
8
+ `),console.log(JSON.stringify({statusLine:o},null,2)+`
9
+ `),process.exitCode=1;return}let y=u.data?u.data.statusLine:void 0,R=y!==void 0&&y.command===o.command,N=y!==void 0&&!R;if(N&&!t.force){d(`A different statusLine is already configured in ${n}:`),console.log(` ${JSON.stringify(y)}`),a("Re-run with --force to replace it."),process.exitCode=1;return}if(t.dryRun){console.log(i(`
10
+ [dry-run] would:`)),s(`write script \u2192 ${r}`),s(`${u.exists?"patch":"create"} settings \u2192 ${n}`),u.exists&&s(`backup \u2192 ${n}.bak`),console.log(`
11
11
  statusLine block:
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 g=require("fs");function B(e){console.log(r(`
12
+ ${JSON.stringify(o,null,2)}
13
+ `);return}(0,c.mkdirSync)((0,T.dirname)(r),{recursive:!0}),(0,c.copyFileSync)(I,r),(0,c.chmodSync)(r,493),s(`wrote ${r}`);let j=u.data??{};u.exists?(x(n),s(`backed up ${n} \u2192 settings.json.bak`)):(0,c.mkdirSync)((0,T.dirname)(n),{recursive:!0}),j.statusLine=o,(0,c.writeFileSync)(n,JSON.stringify(j,null,2)+`
14
+ `),s(`${R?"confirmed":N?"replaced":"added"} statusLine in ${n}`);let W=t.targetCli==="antigravity"?"Antigravity CLI":"Claude Code";console.log(`
15
+ ${b("Done.")} Restart ${W} to see the statusline.
16
+ `)}var h=require("fs");function B(t){console.log(i(`
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?s(`[dry-run] would remove statusLine from ${t}`):(x(t),delete o.data.statusLine,(0,g.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,g.existsSync)(n)&&(e.dryRun?s(`[dry-run] would delete ${n}`):((0,g.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
18
+ `));let e=g(t),r=f(e);if(!r.exists||r.data===null?a(`No usable settings at ${e} \u2014 nothing to remove.`):v(r.data.statusLine?.command)?t.dryRun?s(`[dry-run] would remove statusLine from ${e}`):(x(e),delete r.data.statusLine,(0,h.writeFileSync)(e,JSON.stringify(r.data,null,2)+`
19
+ `),s(`removed statusLine from ${e} (backup: settings.json.bak)`)):s(`No tokenline statusLine in ${e} \u2014 left untouched.`),t.purge){let o=$(t);(0,h.existsSync)(o)&&(t.dryRun?s(`[dry-run] would delete ${o}`):((0,h.unlinkSync)(o),s(`deleted ${o}`)))}let n=t.targetCli==="antigravity"?"Antigravity CLI":"Claude Code";console.log(`
20
+ ${b("Done.")} Restart ${n}.
21
+ `)}var H=JSON.parse((0,F.readFileSync)((0,E.join)(__dirname,"..","package.json"),"utf8"));function M(t){let e={_:[],targetCli:"claude",dir:null,global:!1,dryRun:!1,force:!1,purge:!1,help:!1,version:!1,unknown:[]};for(let r=0;r<t.length;r++){let n=t[r];switch(n){case"--dir":e.dir=t[++r]??null;break;case"--antigravity":e.targetCli="antigravity",e.global=!0;break;case"--global":e.global=!0;break;case"--dry-run":e.dryRun=!0;break;case"--force":e.force=!0;break;case"--purge":e.purge=!0;break;case"-h":case"--help":e.help=!0;break;case"-v":case"--version":e.version=!0;break;default:n!==void 0&&n.startsWith("-")?e.unknown.push(n):n!==void 0&&e._.push(n)}}return e}function D(){console.log(`
22
+ ${i("tokenline")} \u2014 a cache-aware statusline for AI coding CLIs
23
23
 
24
- ${r("Usage")}
24
+ ${i("Usage")}
25
25
  npx @inbrace-tech/tokenline <command> [options]
26
26
 
27
- ${r("Commands")}
28
- init Install tokenline.sh and wire it into your Claude Code settings
27
+ ${i("Commands")}
28
+ init Install tokenline.sh and wire it into 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
- ${r("Options")}
32
+ ${i("Options")}
33
+ --antigravity Target Antigravity CLI (~/.gemini/antigravity-cli)
33
34
  --global Target ~/.claude (global) instead of ./.claude (project)
34
35
  --dir <path> Write tokenline.sh to a custom directory
35
36
  --dry-run Show what would happen without writing anything
@@ -38,7 +39,7 @@ ${r("Options")}
38
39
  -h, --help Show this help
39
40
  -v, --version Show version
40
41
 
41
- ${r("Notes")}
42
+ ${i("Notes")}
42
43
  The statusline runs as 'bash tokenline.sh' \u2014 no Node in the per-second hot path.
43
44
  jq is required at runtime; this installer checks for it but cannot install it.
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();
45
+ `)}function Q(){let t=M(process.argv.slice(2));if(t.unknown.length>0&&t.unknown.forEach(e=>a(`ignoring unknown option: ${e}`)),t.version){console.log(H.version);return}if(t.help||t._.length===0){D();return}try{switch(t._[0]){case"init":A(t);break;case"doctor":U();break;case"uninstall":case"remove":B(t);break;default:d(`Unknown command: ${t._[0]}`),D(),process.exitCode=1}}catch(e){d(e instanceof Error?e.message:String(e)),process.exitCode=1}}Q();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inbrace-tech/tokenline",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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"
@@ -39,16 +39,19 @@
39
39
  "devDependencies": {
40
40
  "@changesets/cli": "^2.31.0",
41
41
  "@eslint/js": "10.0.1",
42
- "@types/node": "24.13.2",
43
- "eslint": "10.5.0",
42
+ "@types/node": "24.13.3",
43
+ "@vitest/eslint-plugin": "1.6.23",
44
+ "eslint": "10.7.0",
44
45
  "eslint-config-prettier": "10.1.8",
46
+ "eslint-plugin-n": "18.2.2",
45
47
  "eslint-plugin-prettier": "5.5.6",
46
48
  "eslint-plugin-simple-import-sort": "13.0.0",
47
- "globals": "17.6.0",
48
- "prettier": "3.8.4",
49
+ "globals": "17.7.0",
50
+ "prettier": "3.9.5",
49
51
  "tsup": "^8.5.1",
50
52
  "typescript": "5.9.3",
51
- "typescript-eslint": "8.61.0"
53
+ "typescript-eslint": "8.64.0",
54
+ "vitest": "4.1.10"
52
55
  },
53
56
  "scripts": {
54
57
  "build": "tsup",
@@ -56,6 +59,8 @@
56
59
  "lint": "eslint . --cache --cache-location node_modules/.cache/.eslintcache --fix",
57
60
  "format": "prettier --write \"src/**/*.ts\"",
58
61
  "format:check": "prettier --check \"src/**/*.ts\"",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
59
64
  "changeset": "changeset",
60
65
  "release": "pnpm build && changeset publish"
61
66
  }
package/tokenline.sh CHANGED
@@ -108,9 +108,9 @@ parse_and_prepare_paths() {
108
108
  transcript_path="${_f[3]}"
109
109
  session_id="${_f[4]}"
110
110
  rl_5h_pct="${_f[5]}"
111
- rl_5h_reset="${_f[6]}"
111
+ rl_5h_reset=$(epoch_from_iso "${_f[6]}")
112
112
  rl_7d_pct="${_f[7]}"
113
- rl_7d_reset="${_f[8]}"
113
+ rl_7d_reset=$(epoch_from_iso "${_f[8]}")
114
114
  cur_input="${_f[9]}"
115
115
  cur_output="${_f[10]}"
116
116
  cur_cwrite="${_f[11]}"