@inbrace-tech/tokenline 1.2.0 → 1.2.2

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,17 +9,23 @@
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 Per-project installation (`./.claude/settings.json`)**
14
16
  ```bash
15
17
  npx @inbrace-tech/tokenline init
16
18
  ```
17
-
18
- *Want it globally for all your projects? Add `--global` to the command.*
19
+ **Claude Code Global installation (`~/.claude/settings.json`)**
19
20
  ```bash
20
21
  npx @inbrace-tech/tokenline init --global
21
22
  ```
22
23
 
24
+ **Antigravity CLI Global installation (`~/.gemini/antigravity-cli/settings.json`)**
25
+ ```bash
26
+ npx @inbrace-tech/tokenline init --antigravity
27
+ ```
28
+
23
29
  ### 2. Restart your CLI
24
30
 
25
31
  Simply restart Claude Code or Antigravity, and your new statusline will be live.
@@ -61,7 +67,7 @@ LLMs are stateless — by default, they must reread your entire codebase context
61
67
 
62
68
  **Prompt Caching** solves this by saving that processed state in the provider's memory:
63
69
  - **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.
70
+ - **Cache Hit (Warm):** If your prompt prefix matches the cached state exactly, the model skips the reading phase. This is **~90% cheaper** (**97.5%** on Claude Fable 5.1 / Mythos 5.1, which bill cache hits at 0.025x base input) and starts responding almost instantly. `tokenline` picks the right multiplier from the active model.
65
71
  - **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
72
 
67
73
  ### 💡 Pro-tip: Forcing a 5m TTL to save rate limits
@@ -117,7 +123,7 @@ Then restart Claude Code.
117
123
 
118
124
  `npx @inbrace-tech/tokenline init` is deliberately transparent about touching your config:
119
125
 
120
- - **Writes** `tokenline.sh` to `./.claude/` (or `~/.claude/` with `--global`).
126
+ - **Writes** `tokenline.sh` to `./.claude/` (or `~/.claude/` with `--global`, or `~/.gemini/antigravity-cli/` with `--antigravity`).
121
127
  - **Merges** only the `statusLine` key into `settings.json` — every other setting is preserved.
122
128
  - **Backs up** `settings.json` to `settings.json.bak` before writing.
123
129
  - **Never clobbers** invalid JSON: if it can't parse your `settings.json`, it stops and prints the block to paste manually.
@@ -127,8 +133,7 @@ Other commands: `doctor` (check dependencies and config, change nothing) and `un
127
133
 
128
134
  ### Antigravity CLI
129
135
 
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.
136
+ `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
137
 
133
138
  ## How it works
134
139
 
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.2",
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,23 +39,29 @@
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",
55
- "typecheck": "tsc --noEmit -p tsconfig.json",
58
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p scripts/tsconfig.json",
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
+ "audit:lockfile": "node --experimental-strip-types scripts/check-lockfile-release-age.mts",
64
+ "test:watch": "vitest",
59
65
  "changeset": "changeset",
60
66
  "release": "pnpm build && changeset publish"
61
67
  }
package/tokenline.sh CHANGED
@@ -95,7 +95,8 @@ parse_and_prepare_paths() {
95
95
  (.context_window.current_usage.input_tokens // 0),
96
96
  (.context_window.current_usage.output_tokens // 0),
97
97
  (.context_window.current_usage.cache_creation_input_tokens // 0),
98
- (.context_window.current_usage.cache_read_input_tokens // 0)' 2>/dev/null)
98
+ (.context_window.current_usage.cache_read_input_tokens // 0),
99
+ (.model.id // "")' 2>/dev/null)
99
100
 
100
101
  # Malformed or empty stdin: jq emits nothing, so the array is empty. Degrade to
101
102
  # a silent no-op render rather than leaking parse errors or rendering garbage —
@@ -108,13 +109,14 @@ parse_and_prepare_paths() {
108
109
  transcript_path="${_f[3]}"
109
110
  session_id="${_f[4]}"
110
111
  rl_5h_pct="${_f[5]}"
111
- rl_5h_reset="${_f[6]}"
112
+ rl_5h_reset=$(epoch_from_iso "${_f[6]}")
112
113
  rl_7d_pct="${_f[7]}"
113
- rl_7d_reset="${_f[8]}"
114
+ rl_7d_reset=$(epoch_from_iso "${_f[8]}")
114
115
  cur_input="${_f[9]}"
115
116
  cur_output="${_f[10]}"
116
117
  cur_cwrite="${_f[11]}"
117
118
  cur_cread="${_f[12]}"
119
+ model_id="${_f[13]}"
118
120
 
119
121
  # Computed: total input-only tokens used in the current context window
120
122
  tokens_used=$((cur_input + cur_cwrite + cur_cread))
@@ -141,6 +143,15 @@ parse_and_prepare_paths() {
141
143
  is_gemini=true
142
144
  fi
143
145
 
146
+ # Claude Fable 5.1 and Mythos 5.1 bill cache hits at 0.025x base input instead of
147
+ # the standard 0.1x. Match the API id (claude-fable-5-1[1m]) or the display name
148
+ # (Fable 5.1) so either field alone is enough.
149
+ discounted_cache_read=false
150
+ local discounted_re='(fable|mythos)[- ]5[-.]1([^0-9]|$)'
151
+ if [[ "${model_id,,}" =~ $discounted_re ]] || [[ "${model,,}" =~ $discounted_re ]]; then
152
+ discounted_cache_read=true
153
+ fi
154
+
144
155
  # Get the current epoch timestamp once to be reused across all calculations
145
156
  now=$(date +%s)
146
157
  }
@@ -402,6 +413,7 @@ compute_turn_breakdown() {
402
413
  output_mult="4"
403
414
  else
404
415
  read_mult="0.1"
416
+ [ "$discounted_cache_read" = true ] && read_mult="0.025"
405
417
  write_mult="1.25"
406
418
  [ "${ttl_label:-5m}" = "1h" ] && write_mult="2"
407
419
  input_mult="1"