@schuettc/pi-usage 0.10.0-schuettc.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/dist/index.js +18 -0
  4. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 omega
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @schuettc/pi-usage
2
+
3
+ A [pi](https://github.com/earendil-works/pi) coding agent extension that reports provider
4
+ usage / rate-limit budgets — OpenAI Codex, Anthropic OAuth, and pi-auth-backed providers — via
5
+ `/usage`, with a live statusline widget.
6
+
7
+ ## What it does
8
+
9
+ - Queries the active model's provider usage endpoint (Codex app-server usage/rate-limit-reset
10
+ APIs, or Anthropic's OAuth usage API) and renders remaining budget/reset windows.
11
+ - Caches results on disk (`~/.pi/agent/usage-cache.json`) for a few minutes so multiple
12
+ concurrent pi sessions don't hammer the provider APIs.
13
+ - Pushes a compact usage summary into the statusline, refreshed automatically with retry/backoff
14
+ on rate limits (`429`).
15
+ - Shows the next Codex banked-reset expiry and supports confirmed, interactive consumption.
16
+
17
+ ## Provider support
18
+ - OpenAI Codex through Pi auth, with the Codex app-server fallback.
19
+ - Anthropic OAuth through Pi auth.
20
+ - Optional provider adapters discovered at runtime. No Schuettc package is required.
21
+
22
+ ## Optional provider adapter protocol
23
+
24
+ Adapters and consumers discover the V1 process-local registry through
25
+ `Symbol.for("pi.provider-usage.bus.v1")` (also exported as `PROVIDER_USAGE_BUS_SYMBOL`). An adapter has this structural shape:
26
+
27
+ ```ts
28
+ {
29
+ id: string;
30
+ usageProvider: "claude" | "codex";
31
+ modelProviders: string[];
32
+ refresh(options: { timeoutMs: number; signal?: AbortSignal }): Promise<ProviderUsageSnapshotV1>;
33
+ }
34
+ ```
35
+
36
+ Snapshots use the exported `ProviderUsageSnapshotV1`, `NormalizedUsageWindow`, `UsageScopeV1`, and
37
+ `UsageStateV1` types. Complete account refreshes replace an adapter/provider snapshot; partial passive
38
+ snapshots merge supplied windows and fields by stable scope/window identity. Unknown utilization is omitted,
39
+ not reported as zero. Malformed, incompatible, absent, or throwing optional registries fail open.
40
+
41
+ The usage extension owns once-per-provider warning notifications when subscribed. Providers may publish
42
+ `soft-warning` and `hard-limit` events; hard limits are always shown. A provider running without pi-usage must
43
+ keep its own standalone warning policy. Without any optional adapter, pi-usage remains standalone and uses its
44
+ native Codex and Anthropic OAuth queries and cache.
45
+
46
+ ## Shared state
47
+ Snapshots are cached at `~/.pi/agent/usage-cache.json`. The cache contains normalized usage only, never access
48
+ tokens, raw responses, or account identifiers.
49
+
50
+ ## Commands
51
+
52
+ | Command | Effect |
53
+ |---|---|
54
+ | `/usage` | Show cached usage (fetches fresh data if the cache is stale) |
55
+ | `/usage --refresh` | Force a fresh fetch, bypassing the cache |
56
+ | `/usage --timeout <seconds>` | Set the query timeout |
57
+ | `/usage --raw` | Show raw usage API responses (debugging) |
58
+ | `/usage --consume-banked-reset` | Select and consume a Codex banked reset after confirmation |
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ npm install -g @schuettc/pi-usage
64
+ ```
65
+
66
+ Then add it to your pi `settings.json`:
67
+
68
+ ```json
69
+ {
70
+ "packages": ["npm:@schuettc/pi-usage"]
71
+ }
72
+ ```
73
+
74
+ Or, for local development, point at the file directly:
75
+
76
+ ```json
77
+ {
78
+ "extensions": ["/absolute/path/to/pi-extensions/extensions/pi-usage/src/index.ts"]
79
+ }
80
+ ```
81
+
82
+ ## File layout
83
+
84
+ | File | Responsibility |
85
+ |---|---|
86
+ | `src/index.ts` | Extension entry point + session event wiring |
87
+ | `src/command.ts` | `/usage` command handler |
88
+ | `src/statusline.ts` | Statusline state, timers, background refresh |
89
+ | `src/footer.ts` | Custom footer with right-aligned usage status |
90
+ | `src/query.ts` | Top-level usage query orchestration |
91
+ | `src/codex-query.ts` | Codex pi-auth + app-server fallback queries |
92
+ | `src/anthropic-query.ts` | Anthropic OAuth usage queries |
93
+ | `src/codex-auth.ts` | Codex auth header resolution |
94
+ | `src/anthropic-auth.ts` | Anthropic auth header resolution |
95
+ | `src/codex-app-server.ts` | `codex app-server` RPC client |
96
+ | `src/codex-reset-credits.ts` | Banked reset list/consume/format |
97
+ | `src/normalize-codex.ts` | Codex backend + app-server payload normalization |
98
+ | `src/normalize-anthropic.ts` | Anthropic usage payload normalization |
99
+ | `src/format.ts` | Report/statusline formatting and display |
100
+ | `src/shared-cache.ts` | On-disk shared usage cache |
101
+ | `src/models.ts` | Provider/model matching helpers |
102
+ | `src/args.ts` | `/usage` argument parsing and completions |
103
+ | `src/constants.ts` | Shared constants |
104
+ | `src/types.ts` | Shared TypeScript types |
105
+ | `src/utils.ts` | Small parsing/formatting helpers |
106
+ | `src/errors.ts` | HTTP/rate-limit error helpers |
107
+ | `src/http.ts` | `fetchWithTimeout` wrapper |
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ npm install
113
+ npm run --workspace @schuettc/pi-usage check # biome + typecheck
114
+ npm run --workspace @schuettc/pi-usage format
115
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import{homedir as Rn}from"node:os";import{join as Un}from"node:path";var Kt="usage",ue="openai-codex",ce="anthropic",Ie="https://chatgpt.com/backend-api/wham/usage",Ht="https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",Qt="https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume",Yt="codex-1",tt="https://api.anthropic.com/api/oauth/usage",Te=15e3,R=180*1e3,fe=3e4,rt=3,Gt=1e3,Jt=900*1e3,Xt=30*1e3,Ce=3600*1e3,Zt=Un(Rn(),".pi","agent","usage-cache.json"),nt=2,Re="provider-usage",ot=20,it=29,Ue=600,er="\x1B[39m",tr=new Set(["five_hour","seven_day","extra_usage","model_scoped"]),st=[{value:"--refresh",label:"--refresh",description:"Refresh usage instead of cached data"},{value:"--timeout ",label:"--timeout",description:"Set query timeout in seconds"},{value:"--raw",label:"--raw",description:"Show raw usage API responses (for debugging)"},{value:"--consume-banked-reset",label:"--consume-banked-reset",description:"Select and consume a Codex banked reset"}];function rr(e){return new Promise(r=>{setTimeout(r,e).unref?.()})}function w(e){return e instanceof Error?e.message:String(e)}function le(e,r){let t;try{t=JSON.parse(e)}catch(n){throw new Error(`${r} was not valid JSON: ${w(n)}`)}return y(t,r)}function y(e,r){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${r} was not an object.`);return e}function m(e){return typeof e=="string"?e:void 0}function g(e){if(typeof e=="number"&&Number.isFinite(e))return e;if(typeof e=="string"&&e.trim()){let r=Number(e);return Number.isFinite(r)?r:void 0}}function X(e){return typeof e=="boolean"?e:void 0}function A(e,r){return Object.keys(e).some(t=>t.toLowerCase()===r.toLowerCase())}function dt(e){return Ee(e.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,"Bearer <redacted>").replace(/"(?:access|refresh|id)_token"\s*:\s*"[^"]+"/gi,r=>`${r.slice(0,r.indexOf(":"))}:"<redacted>"`).trim(),Ue)}function Ee(e,r){return e.length<=r?e:`${e.slice(0,r-1)}\u2026`}function ut(e){try{return JSON.stringify(at(JSON.parse(e)),null,2)}catch{return dt(e).slice(0,Ue*4)}}function at(e,r=""){return/(?:token|authorization|headers?|(?:account|organi[sz]ation|org)[_-]?(?:id|uuid))/i.test(r)?"<redacted>":Array.isArray(e)?e.map(t=>at(t)):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).map(([t,n])=>[t,at(n,t)])):typeof e=="string"?e.replace(/Bearer\s+\S+/gi,"Bearer <redacted>"):e}function ve(e){let r=Math.round(e/6e4);if(r<60)return`${Math.max(1,r)}m`;let t=Math.floor(r/60);return t<24?`${t}h`:`${Math.floor(t/24)}d`}function v(e){return Number.isFinite(e)?Math.min(100,Math.max(0,e)):0}function nr(e,r){return Number.isFinite(e)?new Intl.NumberFormat(void 0,{maximumFractionDigits:2}).format(e):r}var En={USD:"$",CAD:"$",AUD:"$",EUR:"\u20AC",GBP:"\xA3",JPY:"\xA5"};function B(e,r,t=2){if(!Number.isFinite(e))return String(e);let n=new Intl.NumberFormat(void 0,{minimumFractionDigits:t,maximumFractionDigits:t}).format(e),o=En[r.toUpperCase()];return o?`${o}${n}`:`${r} ${n}`}function Oe(e){let r=e.replace(/[_-]+/g," ").trim();return(r.match(/\bcodex\s+(.+)$/i)?.[1]?.trim()||r).toLowerCase().replace(/\s+/g," ")}function $(e){return e?.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||void 0}function ct(e,r){let t=$(r);t&&e.add(t)}function or(e,r){return e===r||e.startsWith(`${r}-`)||e.endsWith(`-${r}`)||e.includes(`-${r}-`)}function ft(e){return e.replace(/[\r\n\t]/g," ").replace(/ +/g," ").trim()}function De(e,r){if(ir(e),!Array.isArray(r)||r.length===0||!r.every(t=>L(t)))throw new Error("External adapter modelProviders must be a non-empty array of strings.");return{provider:e.provider,...e.providerLabel===void 0?{}:{providerLabel:e.providerLabel},source:"external-adapter",snapshotSource:e.source,...e.adapterId===void 0?{}:{adapterId:e.adapterId},complete:e.complete,modelProviders:[...r],capturedAt:e.capturedAt,windows:e.windows.map(sr)}}function pt(e){try{return ir(e),!0}catch{return!1}}function ir(e){if(!mt(e))throw new Error("External usage snapshot must be an object.");if(e.version!==1)throw new Error(`Unsupported external usage snapshot version: ${String(e.version)}.`);if(e.provider!=="claude"&&e.provider!=="codex")throw new Error("External usage snapshot provider must be claude or codex.");if(e.providerLabel!==void 0&&!L(e.providerLabel))throw new Error("External usage snapshot providerLabel must be a non-empty string.");if(!L(e.source))throw new Error("External usage snapshot source must be a non-empty string.");if(!lt(e.capturedAt))throw new Error("External usage snapshot capturedAt must be a finite epoch-millisecond value.");if(typeof e.complete!="boolean")throw new Error("External usage snapshot complete must be a boolean.");if(e.adapterId!==void 0&&!L(e.adapterId))throw new Error("External usage snapshot adapterId must be a non-empty string.");if(!Array.isArray(e.windows))throw new Error("External usage snapshot windows must be an array.");e.windows.forEach(sr)}function sr(e,r){if(!mt(e))throw new Error(`External usage window ${r} must be an object.`);if(!L(e.id))throw new Error(`External usage window ${r} id must be a non-empty string.`);if(!L(e.label))throw new Error(`External usage window ${r} label must be a non-empty string.`);if(e.usedPercent!==void 0&&!kn(e.usedPercent))throw new Error(`External usage window ${r} usedPercent must be finite.`);if(e.resetsAt!==void 0&&!lt(e.resetsAt))throw new Error(`External usage window ${r} resetsAt must be a finite epoch-second value.`);if(e.windowMinutes!==void 0&&!Mn(e.windowMinutes))throw new Error(`External usage window ${r} windowMinutes must be finite and positive.`);if(e.state!==void 0&&!Pn(e.state))throw new Error(`External usage window ${r} state is invalid.`);for(let o of["usedAmount","limitAmount"])if(e[o]!==void 0&&!lt(e[o]))throw new Error(`External usage window ${r} ${o} must be finite and non-negative.`);if(e.currency!==void 0&&!L(e.currency))throw new Error(`External usage window ${r} currency must be a non-empty string.`);let t=e.usedAmount,n=e.limitAmount;return{id:e.id,label:e.label,...e.usedPercent===void 0?{}:{usedPercent:v(e.usedPercent)},...e.resetsAt===void 0?{}:{resetsAt:e.resetsAt},...e.windowMinutes===void 0?{}:{windowMinutes:e.windowMinutes},scope:vn(e.scope,r),...e.state===void 0?{}:{state:e.state},...t===void 0?{}:{usedAmount:t},...n===void 0?{}:{limitAmount:n},...e.currency===void 0?{}:{currency:e.currency}}}function vn(e,r){if(!mt(e))throw new Error(`External usage window ${r} scope must be an object.`);if(e.kind==="account")return{kind:"account"};if(e.kind==="overage")return{kind:"overage"};if(e.kind==="provider"){if(!L(e.id))throw new Error(`External usage window ${r} provider scope id is invalid.`);if(e.label!==void 0&&!L(e.label))throw new Error(`External usage window ${r} provider scope label is invalid.`);return{kind:"provider",id:e.id,...e.label===void 0?{}:{label:e.label}}}if(e.kind!=="model")throw new Error(`External usage window ${r} has an unsupported scope kind.`);if(!Array.isArray(e.modelIds)||e.modelIds.length===0||!e.modelIds.every(t=>L(t)))throw new Error(`External usage window ${r} modelIds must be a non-empty array of strings.`);if(!L(e.label))throw new Error(`External usage window ${r} model scope label must be a non-empty string.`);return{kind:"model",modelIds:[...e.modelIds],label:e.label}}function mt(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function L(e){return typeof e=="string"&&e.trim().length>0}function Pn(e){return e==="available"||e==="warning"||e==="rejected"||e==="unknown"}function kn(e){return typeof e=="number"&&Number.isFinite(e)}function lt(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0}function Mn(e){return typeof e=="number"&&Number.isFinite(e)&&e>0}var gt=Symbol.for("pi.provider-usage.bus.v1"),ar=globalThis;function dr(){let e=ar[gt];if(e!==void 0){if(typeof e!="object"||e===null)throw new Error("Incompatible provider usage bus version undefined; expected version 1.");let t=Reflect.get(e,"version");if(t!==1)throw new Error(`Incompatible provider usage bus version ${String(t)}; expected version 1.`);for(let n of["register","adapters","subscribe","publish"])if(typeof Reflect.get(e,n)!="function")throw new Error(`Incompatible provider usage bus version 1: ${n} must be a function.`);return e}let r=_n();return ar[gt]=r,r}function yt(){try{return dr()}catch{return}}function V(){try{let e=yt()?.adapters();return Array.isArray(e)?e.filter(fr):[]}catch{return[]}}function _n(){let e=new Map,r=new Set;return{version:1,register(t){if(!fr(t))return()=>{};let n=Symbol(t.id);return e.set(t.id,{adapter:t,registration:n}),()=>{e.get(t.id)?.registration===n&&e.delete(t.id)}},adapters(){return[...e.values()].map(({adapter:t})=>t)},subscribe(t){return typeof t!="function"?()=>{}:(r.add(t),()=>{r.delete(t)})},publish(t){if(!wt(t))return 0;let n=0;for(let o of[...r]){n+=1;try{o(t)}catch{}}return n}}}function ur(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function ht(e){return typeof e=="string"&&e.trim().length>0}function cr(e){return e==="claude"||e==="codex"}function fr(e){return ur(e)&&ht(e.id)&&cr(e.usageProvider)&&Array.isArray(e.modelProviders)&&e.modelProviders.length>0&&e.modelProviders.every(ht)&&typeof e.refresh=="function"}function wt(e){return!ur(e)||e.version!==1?!1:e.type==="snapshot"?pt(e.snapshot):(e.type==="soft-warning"||e.type==="hard-limit")&&cr(e.provider)&&ht(e.message)&&(e.snapshot===void 0||pt(e.snapshot)&&e.snapshot.provider===e.provider)}function pe(e){return e?.provider===ue}function Pe(e){return e?.provider===ce}function Z(e){return e?pe(e)||Pe(e)?!0:V().some(r=>r.modelProviders.includes(e.provider)):!1}function b(e,r){return r?e.source==="external-adapter"?e.modelProviders.includes(r.provider):e.provider==="codex"?pe(r):Pe(r):!1}function bt(e){return Pe(e)?"claude":"codex"}function ee(e){return pr(e,ue)}function te(e){return pr(e,ce)}function lr(e,r){let t=ee(e).length>0,n=te(e).length>0,o=new Set(V().flatMap(i=>i.modelProviders));return r.filter(i=>i.source==="external-adapter"?i.modelProviders.some(s=>o.has(s)):i.provider==="codex"&&t||i.provider==="claude"&&n)}function pr(e,r){let t=e.modelRegistry.getAvailable(),n=[],o=new Set,i=s=>{if(!s||s.provider!==r)return;let a=`${s.provider}/${s.id}`;o.has(a)||(o.add(a),n.push(s))};e.model&&i(t.find(s=>s.provider===e.model?.provider&&s.id===e.model.id));for(let s of t)i(s);return n}async function xt(e){let r=te(e),t=[];for(let n of r){let o=await e.modelRegistry.getApiKeyAndHeaders(n);if(!o.ok){t.push(o.error);continue}let i={};for(let[s,a]of Object.entries(o.headers??{}))typeof a=="string"&&(i[s]=a);if(!A(i,"Authorization")&&o.apiKey&&(i.Authorization=`Bearer ${o.apiKey}`),A(i,"Accept")||(i.Accept="application/json"),A(i,"User-Agent")||(i["User-Agent"]="pi-usage"),A(i,"Authorization"))return{headers:i}}if(t.length>0)throw new Error(t.join("; "))}import{readFileSync as $n}from"node:fs";import{homedir as Ln}from"node:os";import{join as Nn}from"node:path";async function ke(e){let r=ee(e),t=[];for(let n of r){let o=await e.modelRegistry.getApiKeyAndHeaders(n);if(!o.ok){t.push(o.error);continue}let i={};for(let[s,a]of Object.entries(o.headers??{}))typeof a=="string"&&(i[s]=a);if(!A(i,"Authorization")&&o.apiKey&&(i.Authorization=`Bearer ${o.apiKey}`),A(i,"User-Agent")||(i["User-Agent"]="pi-codex-usage"),A(i,"Authorization"))return{headers:i}}if(t.length>0)throw new Error(t.join("; "))}async function At(e){let r=await ke(e);if(!r)return;let t={...r.headers};if(A(t,"Accept")||(t.Accept="application/json"),A(t,"Content-Type")||(t["Content-Type"]="application/json"),A(t,"OpenAI-Beta")||(t["OpenAI-Beta"]=Yt),A(t,"originator")||(t.originator="Codex Desktop"),!A(t,"ChatGPT-Account-ID")){let n=In();n&&(t["ChatGPT-Account-ID"]=n)}return{headers:t}}function In(){try{let e=JSON.parse($n(Nn(Ln(),".codex","auth.json"),"utf8"));return m(e.tokens?.account_id)??m(e.tokens?.accountId)}catch{return}}function S(e){return e instanceof Error&&e.message.includes("This extension ctx is stale after session replacement or reload")}var Fe=class extends Error{retryAfterMs;constructor(r,t){super(r),this.name="UsageEndpointError",this.retryAfterMs=t}};function me(e){return/\b429\b|rate_limit_error|rate.?limited/i.test(e)}function ge(e,r,t){let n=r.statusText.replace(/[\r\n\t]/g," ").trim(),o=`${e} usage endpoint returned ${r.status}${n?` ${n}`:""}.`;throw new Fe(o,r.status===429?Tn(r):void 0)}function Tn(e){let r=e.headers.get("retry-after");if(!r)return;let t=Number(r);if(Number.isFinite(t)&&t>=0)return t*1e3;let n=Date.parse(r);if(!Number.isNaN(n))return Math.max(0,n-Date.now())}function ze(e){for(let r of e)if(r.cause instanceof Fe&&r.cause.retryAfterMs!==void 0)return Math.min(Math.max(r.cause.retryAfterMs,Xt),Ce);return Jt}async function q(e,r,t,n="usage"){let o=new AbortController,i=setTimeout(()=>o.abort(),t);try{return await fetch(e,{...r,signal:o.signal})}catch(s){throw o.signal.aborted?new Error(`Timed out after ${Math.round(t/1e3)}s while fetching ${n}.`):s}finally{clearTimeout(i)}}function wr(e,r){let t=[" >_ OpenAI Codex Usage",""];for(let n of e.snapshots){let o=n.limitName??n.limitId;We(n)||t.push(` ${o} limit:`),n.primary&&t.push(yr("5h limit:",n.primary)),n.secondary&&t.push(yr("7d limit:",n.secondary)),!n.primary&&!n.secondary&&t.push(" Limits unavailable for this account")}if(e.bankedResetsAvailable!==void 0){t.push("");let n=e.nextBankedResetExpiresAt?` (next expires ${Rt(e.nextBankedResetExpiresAt)})`:"";t.push(` Banked resets available: ${e.bankedResetsAvailable}${n}`)}return t.join(`
2
+ `)}function br(e,r){let t=Wn(e,r);if(!t)return"usage unavailable";let n=["Codex"];return We(t)||(n[0]=`Codex ${Oe(t.limitName??t.limitId)}`),t.primary&&n.push(gr("5h",t.primary)),t.secondary&&n.push(gr("7d",t.secondary)),n.length===1&&t.credits&&n.push(Qn(t.credits)),n.join(" \xB7 ")}function St(e,r){if(!(r&&!b(e,r))){if(e.source==="external-adapter")return mr(e.windows,e.provider,r,e.providerLabel);if(e.provider==="claude"){if(!r)return e.statusline;let t=e.windows.some(o=>Ct(o)&&o.scope.kind==="model"&&xr(o.scope.modelIds,r)),n=e.windows.some(o=>o.scope.kind==="account"&&o.windowMinutes===void 0);return!t&&n?e.statusline:mr(e.windows,e.provider,r)}return br(e,r)}}function On(e,r){return e.source==="external-adapter"?Dn(e):e.provider==="claude"?e.summaryLines.join(`
3
+ `):wr(e,r)}function mr(e,r,t,n){let o=e.filter(Ct),i=t?o.filter(f=>f.scope.kind==="model"&&xr(f.scope.modelIds,t)):Fn(o),s=new Set(i.map(f=>f.windowMinutes).filter(f=>f!==void 0)),a=o.filter(f=>f.scope.kind==="account"&&!s.has(f.windowMinutes??-1)),d=o.filter(f=>f.scope.kind==="overage"||f.scope.kind==="provider"),u=[...a,...i,...d];if(u.length===0)return"usage unavailable";let l=new Map;for(let f of i)f.scope.kind==="model"&&l.set(f.scope.label,(l.get(f.scope.label)??0)+1);let c=u.map(f=>{let p=f.label;f.scope.kind==="model"&&r==="claude"?p=(l.get(f.scope.label)??0)>1?`${f.scope.label} ${f.label}`:f.scope.label:f.scope.kind==="provider"&&(p=f.scope.label??f.label);let W=f.usedPercent===void 0?"":` ${v(f.usedPercent).toFixed(0)}%`,k=Ar(f.resetsAt);return`${p}${W}${k?` \u21BB${k}`:""}`});return[n??(r==="claude"?"Claude":"Codex"),...c].join(" \xB7 ")}function Dn(e){let t=[` >_ ${e.providerLabel??(e.provider==="claude"?"Claude":"OpenAI Codex")} Usage`,""],n=e.windows.filter(Ct);if(n.length===0)return t.push(" Usage unavailable"),t.join(`
4
+ `);let o=new Map;for(let s of n){let a=s.scope.kind==="account"?"account":s.scope.kind==="model"?`model:${JSON.stringify(s.scope.modelIds)}`:s.scope.kind==="overage"?"overage":`provider:${s.scope.id}`,d=s.scope.kind==="account"?"Account":s.scope.kind==="model"?s.scope.label:s.scope.kind==="overage"?"Overage":s.scope.label??s.scope.id,u=o.get(a)??{label:d,windows:[]};u.windows.push(s),o.set(a,u)}let i=!0;for(let s of o.values()){i||t.push(""),i=!1,t.push(` ${s.label} usage:`);for(let a of s.windows)t.push(Kn(`${a.label}:`,a))}return t.join(`
5
+ `)}function Ct(e){return!!e.label.trim()&&(e.usedPercent!==void 0||e.resetsAt!==void 0||e.usedAmount!==void 0)}function xr(e,r){let t=new Set([$(r.id),$(r.name)].filter(n=>n!==void 0));return e.some(n=>{let o=$(n);return o!==void 0&&t.has(o)})}function Fn(e){let r=e.find(n=>n.scope.kind==="model");if(r?.scope.kind!=="model")return[];let t=new Set(r.scope.modelIds.map(n=>$(n)));return e.filter(n=>n.scope.kind==="model"&&n.scope.modelIds.some(o=>t.has($(o))))}function gr(e,r){let t=Ar(r.resetsAt);return`${e} ${v(r.usedPercent).toFixed(0)}%${t?` \u21BB${t}`:""}`}function Ar(e){if(e===void 0||!Number.isFinite(e))return;let r=e*1e3-Date.now();if(r<=0)return;let t=Math.floor(r/6e4),n=Math.floor(r/36e5),o=Math.floor(r/864e5);return o>0?`${o}d`:n>0?`${n}h`:t>0?`${t}m`:"<1m"}function he(e,r,t){let o=Vn(r,e.model).map(i=>On(i,t?Date.now()-i.capturedAt:void 0)).join(`
6
+
7
+ `);e.ui.notify(e.hasUI?qn(o):o,"info")}function je(e,r=!1){if(e.length===0)return"No logged-in Codex or Anthropic providers. Run /login to connect one.";let t=[r?"Some provider usage is unavailable:":"Usage unavailable:"];for(let n of e){let o=n.source==="pi-auth"?"Codex":n.source==="codex-app-server"?"Codex fallback":n.source==="anthropic-oauth"?"Anthropic":"External adapter";t.push(`- ${o}: ${zn(n.message)}`)}return t.join(`
8
+ `)}function zn(e){let r=e.replace(/\s+/g," ").trim();if(/invalid_grant|refresh token not found|token refresh request failed/i.test(r))return"login expired or invalid; run /login to reconnect.";if(/no .*auth|no api key|not logged in/i.test(r))return"not logged in; run /login to connect.";let t=r.split(/;\s*(?:details|stack)=/i,1)[0]??r;return Ee(t,180)}function ye(e){let r=Math.round(v(e)/100*ot);return`[${"\u2588".repeat(r)}${"\u2591".repeat(ot-r)}]`}function Wn(e,r){let t=e.snapshots.find(We);if(!r||!pe(r))return t??e.snapshots[0];let n=jn(r),o=e.snapshots.find(s=>hr(s).some(a=>n.has(a)));if(o)return o;let i=Bn(n);for(let s of i){let a=e.snapshots.filter(d=>!We(d)&&hr(d).some(u=>or(u,s)));if(a.length===1)return a[0]}return t??e.snapshots[0]}function jn(e){let r=new Set;ct(r,e.id),ct(r,e.name);for(let t of[...r]){let n=t.indexOf("codex");n>=0&&r.add(t.slice(n))}return r}function hr(e){return[$(e.limitId),$(e.limitName)].filter(r=>r!==void 0)}function Bn(e){let r=new Set;for(let t of e){let n=t.match(/(?:^|-)codex-(.+)$/);n?.[1]&&r.add(n[1])}return[...r]}function Vn(e,r){return[...e].sort((t,n)=>{let o=b(t,r)?0:1,i=b(n,r)?0:1;return o-i})}function qn(e){return`${er}${e}`}function We(e){return $(e.limitId)==="codex"||$(e.limitName)==="codex"}function yr(e,r){return` ${e.padEnd(it)}${Hn(r)}`}function Kn(e,r){let t=r.usedPercent===void 0?"usage unavailable":`${ye(r.usedPercent)} ${v(r.usedPercent).toFixed(0)}% used`,n=r.resetsAt?` (resets ${Rt(r.resetsAt)})`:"",o=r.usedAmount===void 0?"":` (${r.currency?`${r.currency} `:""}${r.usedAmount}${r.limitAmount===void 0?"":`/${r.limitAmount}`})`;return` ${e.padEnd(it)}${t}${o}${n}`}function Hn(e){let r=v(e.usedPercent),t=e.resetsAt?` (resets ${Rt(e.resetsAt)})`:"";return`${ye(r)} ${r.toFixed(0)}% used${t}`}function Qn(e){if(!e.hasCredits)return"no credits";if(e.unlimited)return"unlimited credits";let r=e.balance?.trim();return r?`${nr(Number(r),r)} credits`:"credits available"}function Rt(e){let r=new Date(e*1e3);if(Number.isNaN(r.getTime()))return"at an unknown time";let t=new Date,n=`${r.getHours().toString().padStart(2,"0")}:${r.getMinutes().toString().padStart(2,"0")}`;if(r.toDateString()===t.toDateString())return n;let o=r.getDate().toString(),i=r.toLocaleDateString(void 0,{month:"short"});return`${n} on ${o} ${i}`}var Cr={kind:"account"},Rr={five_hour:{label:"5h",windowMinutes:300},seven_day:{label:"7d",windowMinutes:10080}};function Ur(e,r){let t=Er(e,Cr),n=Yn(e.model_scoped),o=Gn(e),i=Jn(e.extra_usage),s=[...t,...n,...o.map(({window:a})=>a),...i?[i.window]:[]];if(s.length===0)throw new Error("Anthropic usage endpoint returned no displayable usage data.");return{provider:"claude",source:"anthropic-oauth",capturedAt:r,windows:s,summaryLines:Xn(t,n,o,i),statusline:Zn(t,n,o,i)}}function Er(e,r,t){let n=[];for(let[o,i]of Object.entries(Rr)){let s=vr(e[o],t?`${t}:${o}`:o,i.label,r);s&&n.push({...s,windowMinutes:i.windowMinutes})}return n}function Yn(e){if(e==null)return[];let r=y(e,"Anthropic model-scoped usage"),t=[];for(let[n,o]of Object.entries(r)){if(!o||typeof o!="object"||Array.isArray(o))continue;let i=o,s={kind:"model",modelIds:[n],label:Ut(n)},a=Er(i,s,n);t.push(...a);for(let[d,u]of Object.entries(i)){if(d in Rr||!u||typeof u!="object"||Array.isArray(u))continue;let l=vr(u,`${n}:${d}`,Ut(d),s);l&&t.push(l)}}return t}function vr(e,r,t,n){if(e==null)return;let o=y(e,"Anthropic rolling usage window"),i=g(o.utilization);if(i===void 0)return;let s=vt(m(o.resets_at));return{id:r,label:t,usedPercent:v(i),...s===void 0?{}:{resetsAt:s},scope:n}}function Gn(e){let r=[];for(let[t,n]of Object.entries(e)){if(tr.has(t)||!n||typeof n!="object"||Array.isArray(n))continue;let o=n,i=g(o.limit_dollars),s=g(o.used_dollars);if(i===void 0||s===void 0)continue;let a=vt(m(o.resets_at));r.push({window:{id:t,label:Ut(t),...g(o.utilization)===void 0?{}:{usedPercent:v(g(o.utilization))},...a===void 0?{}:{resetsAt:a},scope:Cr,usedAmount:s,limitAmount:i,currency:"USD"},usedDollars:s,limitDollars:i})}return r.sort((t,n)=>t.limitDollars-n.limitDollars)}function Jn(e){if(e==null)return;let r=y(e,"Anthropic extra usage"),t=X(r.is_enabled),n=g(r.used_credits);if(!t||n===void 0)return;let o=g(r.monthly_limit),i=vt(m(r.reset_at)??m(r.resets_at));return{window:{id:"extra_usage",label:"overage",...g(r.utilization)===void 0?{}:{usedPercent:v(g(r.utilization))},...i===void 0?{}:{resetsAt:i},scope:{kind:"overage"},state:"available",usedAmount:n/100,...o===void 0?{}:{limitAmount:o/100},currency:m(r.currency)??"USD"},usedMajor:n/100,monthlyLimitMajor:o===void 0?void 0:o/100,currency:m(r.currency)??"USD"}}function Xn(e,r,t,n){let o=[" >_ Anthropic Usage",""],i=!1;if(t.length>0){i=!0,o.push(" Enterprise budget windows:");for(let{window:a,usedDollars:d,limitDollars:u}of t){let l=a.resetsAt?` (resets ${Et(a.resetsAt)})`:"",c=a.usedPercent===void 0?"usage unavailable":`${ye(a.usedPercent)} ${a.usedPercent.toFixed(0)}% used`;o.push(` ${a.label}: ${c} (${B(d,"USD")}/${B(u,"USD")})${l}`)}}if(n){i&&o.push(""),i=!0;let{window:a,usedMajor:d,monthlyLimitMajor:u,currency:l}=n,c=`${B(d,l)}${u!==void 0?`/${B(u,l)}`:""}`,f=a.resetsAt?` (resets ${Et(a.resetsAt)})`:"";o.push(" Monthly extra usage:");let p=a.usedPercent===void 0?"usage unavailable":`${ye(a.usedPercent)} ${a.usedPercent.toFixed(0)}% used`;o.push(` ${p} ${c}${f}`)}if(e.length>0){i&&o.push(""),i=!0,o.push(" Subscription usage:");for(let a of e)o.push(` ${Sr(a)}`)}let s=eo(r);for(let{label:a,windows:d}of s){i&&o.push(""),i=!0,o.push(` ${a} usage:`);for(let u of d)o.push(` ${Sr(u)}`)}return o}function Zn(e,r,t,n){let o=["claude"];if(t.length>0){let i=[...t].sort((s,a)=>(a.window.usedPercent??-1)-(s.window.usedPercent??-1))[0];i.window.usedPercent!==void 0&&o.push(`${i.window.usedPercent.toFixed(0)}%`),o.push(`${B(i.usedDollars,"USD",0)}/${B(i.limitDollars,"USD",0)}`)}if(n){let i=o.length>1,{window:s,usedMajor:a,monthlyLimitMajor:d,currency:u}=n,l=`${B(a,u,0)}${d!==void 0?`/${B(d,u,0)}`:""}`;s.usedPercent!==void 0&&o.push(`${s.usedPercent.toFixed(0)}%`),o.push(l),i&&o.push("extra")}if(o.length===1){let i=e.length>0?e:r;for(let s of i)s.usedPercent!==void 0&&o.push(`${s.usedPercent.toFixed(0)}% ${s.label}`)}return o.join(" ")}function eo(e){let r=new Map;for(let t of e){if(t.scope.kind!=="model")continue;let n=JSON.stringify(t.scope.modelIds),o=r.get(n)??{label:t.scope.label,windows:[]};o.windows.push(t),r.set(n,o)}return[...r.values()]}function Sr(e){let r=e.resetsAt?` (resets ${Et(e.resetsAt,e.label!=="5h")})`:"",t=e.usedPercent===void 0?"usage unavailable":`${ye(e.usedPercent)} ${e.usedPercent.toFixed(0)}% used`;return`${e.label}: ${t}${r}`}function vt(e){if(!e)return;let r=Date.parse(e);return Number.isFinite(r)?r/1e3:void 0}function Ut(e){return Oe(e).replace(/\b\w/g,r=>r.toUpperCase())}function Et(e,r=!0){let t=new Date(e*1e3),n=new Date,o=`${t.getHours().toString().padStart(2,"0")}:${t.getMinutes().toString().padStart(2,"0")}`;if(!r&&t.toDateString()===n.toDateString())return o;let i=t.getDate().toString(),s=t.toLocaleDateString(void 0,{month:"short"});return`${o} on ${i} ${s}`}async function Pt(e,r){let t=await xt(e);if(!t)throw new Error("No Pi Anthropic subscription auth was available. Use a Pi Anthropic model and run /login for Anthropic.");let n=await q(tt,{headers:t.headers},r,"Anthropic usage"),o=await n.text();n.ok||ge("Anthropic",n,o);let i=le(o,"Anthropic usage endpoint response");return Ur(i,Date.now())}async function Pr(e,r){let t=[];if(te(e).length>0)try{let n=await xt(e);if(!n)throw new Error("No Anthropic auth available.");let o=await q(tt,{headers:n.headers},r,"Anthropic usage"),i=await o.text();t.push(`>_ Anthropic raw (${o.status}):
9
+ ${ut(i)}`)}catch(n){if(S(n))throw n;t.push(`>_ Anthropic raw: error \u2014 ${w(n)}`)}if(ee(e).length>0)try{let n=await ke(e);if(!n)throw new Error("No Codex auth available.");let o=await q(Ie,{headers:n.headers},r,"Codex usage"),i=await o.text();t.push(`>_ Codex raw (${o.status}):
10
+ ${ut(i)}`)}catch(n){if(S(n))throw n;t.push(`>_ Codex raw: error \u2014 ${w(n)}`)}return t.length===0?"No providers with usable auth found.":t.join(`
11
+
12
+ `)}function kt(e){let r=e.trimStart();if(r==="")return[...st];let t=/\s$/.test(r),n=r.trimEnd().split(/\s+/).filter(Boolean),o=n.at(-1);if(o==="--timeout"&&t||o==="--consume-banked-reset"&&t||!t&&n.at(-2)==="--timeout"||!t&&n.at(-2)==="--consume-banked-reset")return null;let i=t?"":o??"";if(i&&!i.startsWith("-"))return null;let s=t?"":r.match(/\S+$/)?.[0]??"",a=t?r:r.slice(0,r.length-s.length),d=st.filter(u=>u.value.startsWith(i));return d.length>0?d.map(u=>({...u,value:`${a}${u.value}`})):null}function Mt(e){let r=e.trim().split(/\s+/).filter(Boolean),t=!1,n,o=!1,i=!1,s=Te;for(let a=0;a<r.length;a++){let d=r[a];if(d==="--raw"){o=!0;continue}if(d==="--consume-banked-reset"){t=!0;let u=r[a+1];u&&!u.startsWith("-")&&(n=u,a+=1);continue}if(d==="--refresh"){i=!0;continue}if(d==="--timeout"){let u=r[a+1];if(!u)return{ok:!1,error:"Usage: /usage [--refresh] [--raw] [--consume-banked-reset [id]] [--timeout seconds]"};let l=Number(u);if(!Number.isFinite(l)||l<=0||l>120)return{ok:!1,error:"--timeout must be a number of seconds between 1 and 120."};s=Math.round(l*1e3),a+=1;continue}return{ok:!1,error:`Unknown option: ${d}. Usage: /usage [--refresh] [--raw] [--consume-banked-reset [id]] [--timeout seconds]`}}return o&&t?{ok:!1,error:"Choose only one of: --raw, --consume-banked-reset."}:{ok:!0,value:{consumeBankedReset:t,consumeBankedResetId:n,raw:o,refresh:i,timeoutMs:s}}}import{randomUUID as to}from"node:crypto";async function Be(e,r){let t=await At(e);if(!t)throw new Error("No Pi OpenAI Codex auth was available. Use a Pi OpenAI Codex model or run /login for OpenAI ChatGPT Plus/Pro (Codex).");let n=await q(Ht,{headers:t.headers},r,"Codex banked resets"),o=await n.text();n.ok||ge("Codex banked resets",n,o);let i=le(o,"Codex banked reset credits response");return ro(i)}async function kr(e,r,t){let n=await At(e);if(!n)throw new Error("No Pi OpenAI Codex auth was available. Use a Pi OpenAI Codex model or run /login for OpenAI ChatGPT Plus/Pro (Codex).");let o=to(),i=[{id:t,redeem_request_id:o},{credit_id:t,redeem_request_id:o},{rate_limit_reset_credit_id:t,redeem_request_id:o},{id:t,redeemRequestId:o},{credit_id:t,redeemRequestId:o},{rate_limit_reset_credit_id:t,redeemRequestId:o}],s;for(let a of i){let d=await q(Qt,{method:"POST",headers:n.headers,body:JSON.stringify(a)},r,"Codex consume banked reset");if(await d.text(),d.ok)return;if(s=new Error(`Codex consume banked reset returned ${d.status}${d.statusText?` ${d.statusText}`:""}.`),d.status!==400&&d.status!==422)break}throw s??new Error("Failed to consume Codex banked reset.")}function ro(e){let t=(Array.isArray(e.credits)?e.credits:[]).map(o=>no(o)).filter(o=>o!==void 0),n=g(e.available_count)??g(e.availableCount)??t.filter(o=>(o.status??"available")==="available").length;return{availableCount:Math.max(0,Math.trunc(n)),credits:t}}function no(e){if(!e)return;let r=y(e,"Codex reset credit"),t=m(r.id);if(t)return{id:t,resetType:m(r.reset_type)??m(r.resetType),status:m(r.status),grantedAt:m(r.granted_at)??m(r.grantedAt),expiresAt:m(r.expires_at)??m(r.expiresAt)}}function Ve(e){let r=e.expiresAt?`expires ${oo(e.expiresAt)}`:"no expiry shown";return`${e.id} \u2014 ${r}`}function Mr(e){let r=Date.now(),t=e.credits.filter(n=>(n.status??"available").toLowerCase()==="available").map(n=>n.expiresAt?Date.parse(n.expiresAt):Number.NaN).filter(n=>Number.isFinite(n)&&n>r);if(t.length!==0)return Math.min(...t)/1e3}function oo(e,r=!0){let t=new Date(e);if(Number.isNaN(t.getTime()))return"at an unknown time";let n=new Date,o=`${t.getHours().toString().padStart(2,"0")}:${t.getMinutes().toString().padStart(2,"0")}`;if(!r&&t.toDateString()===n.toDateString())return o;let i=t.getDate().toString(),s=t.toLocaleDateString(void 0,{month:"short"});return`${o} on ${i} ${s}`}import{spawn as co}from"node:child_process";import{createInterface as fo}from"node:readline";function _t(e,r,t){let n=[],o=m(e.plan_type),i=Ir(e.rate_limit_reset_credits),s=_r("codex",void 0,e.rate_limit,e.credits);s&&n.push(s);let a=Array.isArray(e.additional_rate_limits)?e.additional_rate_limits:[];for(let d of a){let u=y(d,"additional rate limit"),l=m(u.metered_feature)??m(u.limit_name);if(!l)continue;let c=_r(l,m(u.limit_name),u.rate_limit,void 0);c&&n.push(c)}if(n.length===0)throw new Error("Codex usage endpoint returned no displayable rate-limit windows.");return{provider:"codex",source:t,capturedAt:r,planType:o,bankedResetsAvailable:i,snapshots:n}}function _r(e,r,t,n){if(t==null){let d=Lr(n);return d?{limitId:e,limitName:r,credits:d}:void 0}let o=y(t,"rate limit"),i=$r(o.primary_window),s=$r(o.secondary_window),a=Lr(n);if(!(!i&&!s&&!a))return{limitId:e,limitName:r,primary:i,secondary:s,credits:a}}function $r(e){if(e==null)return;let r=y(e,"rate-limit window"),t=g(r.used_percent);if(t===void 0)return;let n=g(r.limit_window_seconds),o=g(r.reset_at);return{usedPercent:t,windowMinutes:n&&n>0?Math.ceil(n/60):void 0,resetsAt:o}}function Lr(e){if(e==null)return;let r=y(e,"credits"),t=X(r.has_credits),n=X(r.unlimited);if(!(t===void 0||n===void 0))return{hasCredits:t,unlimited:n,balance:m(r.balance)}}function $t(e,r){let t=[],n=Ir(e.rateLimitResetCredits),o=(s,a)=>{let d=so(s,a);if(!d)return;let u=t.findIndex(l=>l.limitId===d.limitId);u>=0?t[u]=uo(t[u],d):t.push(d)};if(o(e.rateLimits,"codex"),e.rateLimitsByLimitId&&typeof e.rateLimitsByLimitId=="object")for(let[s,a]of Object.entries(e.rateLimitsByLimitId))o(a,s);if(t.length===0)throw new Error("codex app-server returned no displayable rate-limit windows.");let i=io(e.rateLimits);return{provider:"codex",source:"codex-app-server",capturedAt:r,planType:i,bankedResetsAvailable:n,snapshots:t}}function io(e){if(e==null)return;let r=y(e,"app-server rate-limit snapshot");return m(r.planType)}function so(e,r){if(e==null)return;let t=y(e,"app-server rate-limit snapshot"),n=m(t.limitId)??r,o=m(t.limitName),i=Nr(t.primary),s=Nr(t.secondary),a=ao(t.credits);if(!(!i&&!s&&!a))return{limitId:n,limitName:o,primary:i,secondary:s,credits:a}}function Nr(e){if(e==null)return;let r=y(e,"app-server rate-limit window"),t=g(r.usedPercent);if(t!==void 0)return{usedPercent:t,windowMinutes:g(r.windowDurationMins),resetsAt:g(r.resetsAt)}}function ao(e){if(e==null)return;let r=y(e,"app-server credits"),t=X(r.hasCredits),n=X(r.unlimited);if(!(t===void 0||n===void 0))return{hasCredits:t,unlimited:n,balance:m(r.balance)}}function Ir(e){if(e==null)return;let r=y(e,"rate limit reset credits"),t=g(r.available_count)??g(r.availableCount);return t!==void 0?Math.max(0,Math.trunc(t)):void 0}function uo(e,r){return{limitId:r.limitId||e.limitId,limitName:r.limitName??e.limitName,primary:r.primary??e.primary,secondary:r.secondary??e.secondary,credits:r.credits??e.credits}}async function qe(e){let r=new Lt(e);try{await r.start(),await r.request("initialize",{clientInfo:{name:"pi_codex_usage",title:"Pi Codex Usage",version:"0.1.0"},capabilities:{experimentalApi:!1,requestAttestation:!1,optOutNotificationMethods:[]}}),r.notify("initialized");let t=await r.request("account/rateLimits/read",void 0);return $t(y(t,"account/rateLimits/read result"),Date.now())}finally{r.dispose()}}var Lt=class{child;nextId=1;stderr="";pending=new Map;startPromise;exitError;timeoutMs;constructor(r){this.timeoutMs=r}start(){return this.startPromise?this.startPromise:(this.startPromise=new Promise((r,t)=>{let n=co("codex",["app-server","--listen","stdio://"],{stdio:["pipe","pipe","pipe"]});this.child=n;let o=setTimeout(()=>{t(new Error(`Timed out after ${Math.round(this.timeoutMs/1e3)}s starting codex app-server.`))},this.timeoutMs);n.once("spawn",()=>{clearTimeout(o),r()}),n.once("error",s=>{clearTimeout(o),t(new Error(`Failed to start codex app-server: ${s.message}`)),this.rejectAll(s)}),n.once("exit",(s,a)=>{let d=this.stderr?` stderr: ${dt(this.stderr)}`:"";this.exitError=new Error(`codex app-server exited before completing the request (code ${s??"unknown"}, signal ${a??"none"}).${d}`),this.rejectAll(this.exitError)}),n.stderr.setEncoding("utf8"),n.stderr.on("data",s=>{this.stderr=Ee(this.stderr+s,Ue)}),fo({input:n.stdout}).on("line",s=>this.handleLine(s))}),this.startPromise)}request(r,t){let n=this.child;if(!n?.stdin.writable)throw new Error("codex app-server is not running.");if(this.exitError)throw this.exitError;let o=this.nextId++,i=t===void 0?{method:r,id:o}:{method:r,id:o,params:t},s=new Promise((a,d)=>{let u=setTimeout(()=>{this.pending.delete(o),d(new Error(`Timed out after ${Math.round(this.timeoutMs/1e3)}s waiting for ${r}.`))},this.timeoutMs);this.pending.set(o,{resolve:l=>{clearTimeout(u),a(l)},reject:l=>{clearTimeout(u),d(l)}})});return n.stdin.write(`${JSON.stringify(i)}
13
+ `),s}notify(r){let t=this.child;t?.stdin.writable&&t.stdin.write(`${JSON.stringify({method:r})}
14
+ `)}dispose(){for(let[t,n]of this.pending)n.reject(new Error(`codex app-server request ${t} cancelled.`));this.pending.clear();let r=this.child;r&&(r.stdin.end(),r.killed||r.kill(),this.child=void 0)}handleLine(r){let t;try{t=JSON.parse(r)}catch{return}if(typeof t.id!="number")return;let n=this.pending.get(t.id);if(n){if(this.pending.delete(t.id),t.error){let o=typeof t.error.code=="string"||typeof t.error.code=="number"?` (code ${String(t.error.code)})`:"";n.reject(new Error(`codex app-server request failed${o}.`));return}n.resolve(t.result)}}rejectAll(r){for(let t of this.pending.values())t.reject(r);this.pending.clear()}};async function Nt(e,r){let t=await ke(e);if(!t)throw new Error("No Pi OpenAI Codex subscription auth was available. Use a Pi OpenAI Codex model or run /login for OpenAI ChatGPT Plus/Pro (Codex).");let n=await q(Ie,{headers:t.headers},r),o=await n.text();n.ok||ge("Codex",n,o);let i=le(o,"Codex usage endpoint response"),s=_t(i,Date.now(),"pi-auth");return Or(e,r,s)}async function Tr(e,r){try{return{report:await Nt(e,r),errors:[]}}catch(t){if(S(t))throw t;let n=[{source:"pi-auth",message:w(t),cause:t}];try{let o=await qe(r);return{report:await Or(e,r,o),errors:[]}}catch(o){if(S(o))throw o;return n.push({source:"codex-app-server",message:w(o),cause:o}),{errors:n}}}}async function Or(e,r,t){if(t.bankedResetsAvailable===0)return t;try{let n=await Be(e,r);return{...t,bankedResetsAvailable:n.availableCount,nextBankedResetExpiresAt:Mr(n)}}catch{return t}}async function It(e,r){let t=ee(e).length>0,n=te(e).length>0,o={errors:[]},i=[...new Map(V().map(c=>[c.id,c])).values()],s=[{source:"pi-auth",promise:t?Tr(e,r.timeoutMs):Promise.resolve(o)},{source:"anthropic-oauth",promise:n?Pt(e,r.timeoutMs).then(c=>({report:c,errors:[]})):Promise.resolve(o)},...i.map(c=>({source:"external-adapter",promise:Promise.resolve().then(()=>c.refresh({timeoutMs:r.timeoutMs})).then(f=>({report:Fr(f,c.id,c.usageProvider,c.modelProviders),errors:[]}))}))],a=await Promise.allSettled(s.map(({promise:c})=>c)),d=[],u=[],l=(c,f)=>{if(c.status==="rejected"){if(S(c.reason))throw c.reason;u.push({source:f,message:w(c.reason),cause:c.reason})}else c.value.report&&d.push(c.value.report),u.push(...c.value.errors)};return a.forEach((c,f)=>{l(c,s[f]?.source??"external-adapter")}),{reports:d,errors:u}}async function Dr(e,r){let t;for(let n=1;n<=rt;n+=1){if(t=await lo(e,r),t.ok||t.errors.some(o=>me(o.message)))return t;n<rt&&await rr(Gt)}return t??{ok:!1,errors:[{source:"pi-auth",message:"Usage query failed."}]}}async function lo(e,r){if(Pe(e.model))try{return{ok:!0,report:await Pt(e,r.timeoutMs)}}catch(n){if(S(n))throw n;return{ok:!1,errors:[{source:"anthropic-oauth",message:w(n),cause:n}]}}if(!pe(e.model)){let n=e.model?.provider,o=n===void 0?void 0:V().find(i=>i.modelProviders.includes(n));if(!o)return{ok:!1,errors:[{source:"pi-auth",message:"Current model provider is not supported."}]};try{let i=await o.refresh({timeoutMs:r.timeoutMs});return{ok:!0,report:Fr(i,o.id,o.usageProvider,o.modelProviders)}}catch(i){return{ok:!1,errors:[{source:"external-adapter",message:w(i),cause:i}]}}}let t=[];try{return{ok:!0,report:await Nt(e,r.timeoutMs)}}catch(n){if(S(n))throw n;t.push({source:"pi-auth",message:w(n),cause:n})}try{return{ok:!0,report:await qe(r.timeoutMs)}}catch(n){if(S(n))throw n;t.push({source:"codex-app-server",message:w(n),cause:n})}return{ok:!1,errors:t}}function Fr(e,r,t,n){if(e.provider!==t)throw new Error(`External adapter ${r} returned the wrong provider.`);if(e.adapterId!==void 0&&e.adapterId!==r)throw new Error(`External adapter ${r} returned a mismatched adapterId.`);return De(e.adapterId===void 0?{...e,adapterId:r}:e,n)}import{randomUUID as go}from"node:crypto";import{closeSync as Kr,mkdirSync as ho,openSync as yo,readFileSync as wo,renameSync as bo,rmSync as xo,writeFileSync as Ao}from"node:fs";import{dirname as So}from"node:path";import{createRequire as po}from"node:module";var mo=po;function zr(){return mo}var Co=5,Wr=11,Ro=new Int32Array(new SharedArrayBuffer(4)),Ke,jr=!1;function Hr(){if(jr)return Ke;jr=!0;try{let t=zr()(import.meta.url)("fs-ext-extra-prebuilt");if(typeof t!="object"||t===null)return;let n=Reflect.get(t,"flockSync");if(typeof n!="function")return;Ke=n}catch{Ke=void 0}return Ke}var Uo={cacheFile:Zt,now:Date.now,rename:bo,randomUUID:go,mutationLockPhase:void 0},I=Uo;function P(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Br(e){if(!P(e)||!N(e.id)||!N(e.label))return!1;for(let r of["usedPercent","resetsAt","usedAmount","limitAmount"])if(e[r]!==void 0&&!Vr(e[r]))return!1;return e.windowMinutes!==void 0&&(!Vr(e.windowMinutes)||e.windowMinutes===0)||e.state!==void 0&&e.state!=="available"&&e.state!=="warning"&&e.state!=="rejected"&&e.state!=="unknown"||e.currency!==void 0&&!N(e.currency)||!P(e.scope)?!1:e.scope.kind==="account"||e.scope.kind==="overage"?!0:e.scope.kind==="provider"?N(e.scope.id)&&(e.scope.label===void 0||N(e.scope.label)):e.scope.kind==="model"&&N(e.scope.label)&&Array.isArray(e.scope.modelIds)&&e.scope.modelIds.length>0&&e.scope.modelIds.every(N)}function N(e){return typeof e=="string"&&e.trim().length>0}function Vr(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0}function qr(e){return P(e)&&typeof e.usedPercent=="number"&&Number.isFinite(e.usedPercent)&&(e.windowMinutes===void 0||typeof e.windowMinutes=="number"&&Number.isFinite(e.windowMinutes))&&(e.resetsAt===void 0||typeof e.resetsAt=="number"&&Number.isFinite(e.resetsAt))}function Eo(e){return P(e)&&typeof e.hasCredits=="boolean"&&typeof e.unlimited=="boolean"&&(e.balance===void 0||typeof e.balance=="string")}function vo(e){return!P(e)||typeof e.limitId!="string"||e.limitName!==void 0&&typeof e.limitName!="string"||e.primary!==void 0&&!qr(e.primary)||e.secondary!==void 0&&!qr(e.secondary)?!1:e.credits===void 0||Eo(e.credits)}function Po(e,r){return!P(e)||e.provider!==r||typeof e.capturedAt!="number"||!Number.isFinite(e.capturedAt)?!1:e.source==="external-adapter"?(e.providerLabel===void 0||N(e.providerLabel))&&N(e.snapshotSource)&&(e.adapterId===void 0||N(e.adapterId))&&typeof e.complete=="boolean"&&Array.isArray(e.modelProviders)&&e.modelProviders.length>0&&e.modelProviders.every(N)&&Array.isArray(e.windows)&&e.windows.every(Br):r==="claude"&&e.source==="anthropic-oauth"?Array.isArray(e.windows)&&e.windows.every(Br)&&Array.isArray(e.summaryLines)&&e.summaryLines.every(t=>typeof t=="string")&&typeof e.statusline=="string":r==="codex"&&(e.source==="pi-auth"||e.source==="codex-app-server")&&Array.isArray(e.snapshots)&&e.snapshots.every(vo)}function ko(e,r){return P(e)&&typeof e.createdAt=="number"&&Number.isFinite(e.createdAt)&&Po(e.report,r)}function Mo(e){return P(e)?["codex","claude"].every(r=>{let t=e[r];return t===void 0||typeof t=="number"&&Number.isFinite(t)}):!1}function _o(e){return P(e)?["codex","claude"].every(r=>{let t=e[r];return t===void 0||P(t)&&typeof t.owner=="string"&&typeof t.expiresAt=="number"&&Number.isFinite(t.expiresAt)}):!1}function $o(e){if(!P(e)||e.version!==nt||!P(e.entries))return!1;for(let r of["codex","claude"]){let t=e.entries[r];if(t!==void 0&&!ko(t,r))return!1}return!(e.backoffUntil!==void 0&&!Mo(e.backoffUntil)||e.refreshLeases!==void 0&&!_o(e.refreshLeases))}function K(){try{let e=JSON.parse(wo(I.cacheFile,"utf8"));return $o(e)?e:void 0}catch{return}}function Lo(){let e=Hr();if(!e)return;try{ho(So(I.cacheFile),{recursive:!0})}catch{return}let r;try{r=yo(`${I.cacheFile}.lock`,"a+"),I.mutationLockPhase?.("after-open")}catch{return}for(let t=0;t<Wr;t+=1)try{return e(r,"exnb"),I.mutationLockPhase?.("after-acquire"),{descriptor:r,flockSync:e}}catch{if(t+1>=Wr){try{Kr(r)}catch{}return}Atomics.wait(Ro,0,0,Co)}}function No(e){try{return e.flockSync(e.descriptor,"exnb"),!0}catch{return!1}}function Io(e){try{Kr(e.descriptor)}catch{}}function To(e,r){let t=`${I.cacheFile}.${process.pid}.${I.randomUUID()}.tmp`;try{return Ao(t,JSON.stringify(e)),No(r)?(I.mutationLockPhase?.("before-cache-replace"),I.rename(t,I.cacheFile),I.mutationLockPhase?.("after-cache-replace"),!0):!1}catch{return!1}finally{try{xo(t,{force:!0})}catch{}}}function re(e,r){let t=Lo();if(!t)return e;try{let n=K()??{version:nt,entries:{}},o=r(n);return o.changed?To(n,t)?o.value:e:o.value}catch{return e}finally{Io(t)}}function we(e,r=Date.now(),t=e.provider){Number.isFinite(r)&&re(void 0,n=>(n.entries[e.provider]={createdAt:r,report:e},n.backoffUntil&&(delete n.backoffUntil[t],Object.keys(n.backoffUntil).length===0&&(n.backoffUntil=void 0)),{changed:!0,value:void 0}))}function Qr(e){re(void 0,r=>r.entries[e]?(delete r.entries[e],{changed:!0,value:void 0}):{changed:!1,value:void 0})}function He(e,r,t=Date.now()){if(!Number.isFinite(r)||!Number.isFinite(t))return;let n=Math.min(r,t+Ce);re(void 0,o=>(o.backoffUntil={...o.backoffUntil??{},[e]:n},{changed:!0,value:void 0}))}function Yr(){re(void 0,e=>e.backoffUntil===void 0?{changed:!1,value:void 0}:(e.backoffUntil=void 0,{changed:!0,value:void 0}))}function Gr(e,r=Date.now()){try{let t=K()?.backoffUntil?.[e]??0;return Math.min(t-r,Ce)}catch{return 0}}function Jr(){return Hr()!==void 0}function Xr(e,r,t){let n=t+fe;return!r||!Number.isFinite(t)||!Number.isFinite(n)?!1:re(!1,o=>{let i=o.refreshLeases?.[e];return i&&i.expiresAt>t?{changed:!1,value:!1}:(o.refreshLeases={...o.refreshLeases??{},[e]:{owner:r,expiresAt:n}},{changed:!0,value:!0})})}function Zr(e,r,t){return!r||!Number.isFinite(t)?!1:re(!1,n=>{let o=n.refreshLeases?.[e];return!o||o.owner!==r?{changed:!1,value:!1}:(o.expiresAt=t+fe,{changed:!0,value:!0})})}function en(e,r){re(void 0,t=>{let n=t.refreshLeases?.[e];return!n||n.owner!==r?{changed:!1,value:void 0}:(delete t.refreshLeases?.[e],t.refreshLeases&&Object.keys(t.refreshLeases).length===0&&(t.refreshLeases=void 0),{changed:!0,value:void 0})})}function tn(e,r){try{let t;for(let n of Object.values(K()?.entries??{})){if(!n||!b(n.report,e))continue;let o=r-n.createdAt;o<0||o>=R||(!t||n.createdAt>t.createdAt)&&(t=n)}return t}catch{return}}import{randomUUID as Oo}from"node:crypto";var Do={now:Date.now,queryUsage:Dr,setTimeout:(e,r)=>setTimeout(e,r),clearTimeout:e=>clearTimeout(e)},Tt=`${process.pid}:${Oo()}`,h=Do,T,Q,O,D,F,E=0,ie=!1,Me,oe,Ot=new Set;function Dt(){return ie}function Ft(e){ie=e}function nn(){return Q}function se(e){Q=e}function on(){T=void 0}var Ye=()=>{O&&h.clearTimeout(O),D&&h.clearTimeout(D),F&&h.clearTimeout(F),O=void 0,D=void 0,F=void 0};function z(e,r){return S(r)?(e===Me&&(E+=1,Ye(),Me=void 0,oe=void 0),!0):!1}var _e=e=>r=>{if(!z(e,r))throw r},U=(e,r)=>{try{return e.ui.setStatus(Re,r),!0}catch(t){if(z(e,t))return!1;throw t}};function be(e){E+=1,Ye(),Me=void 0,oe=void 0,U(e,void 0)}var Fo=e=>{O&&h.clearTimeout(O);let r=E;O=h.setTimeout(()=>{O=void 0,!(!ie||r!==E)&&U(e,void 0)},R),O.unref?.()},Qe=(e,r,t=R)=>{D&&h.clearTimeout(D);let n=E;D=h.setTimeout(()=>{D=void 0,!(!ie||n!==E)&&$e(e,r).catch(_e(e))},Math.max(0,t)),D.unref?.()},zo=(e,r,t,n)=>{F&&h.clearTimeout(F);let o=E,i=()=>{if(F=void 0,!ie||o!==E)return;let s=St(r,t);s!==void 0&&(n&&(s=`${s} (${ve(Math.max(0,h.now()-r.capturedAt))} old)`),U(e,s)&&(F=h.setTimeout(i,6e4),F.unref?.()))};F=h.setTimeout(i,6e4),F.unref?.()},ne=(e,r,t)=>{let n=St(r,t.model);if(n===void 0)return U(e,void 0),!1;let o=t.forceStale===!0||t.staleAgeMs!==void 0&&t.staleAgeMs>=R;return o&&(n=`${n} (${ve(t.staleAgeMs)} old)`),U(e,n)?(Me=e,O&&h.clearTimeout(O),O=void 0,zo(e,r,t.model,o),t.schedule===!1||(t.autoRefresh?Qe(e,t.model,t.refreshDelayMs):Fo(e)),!0):!1},Wo=(e,r)=>{try{let t=T&&b(T.report,e)?T:void 0;if(Q){let o=Q.reports.find(i=>b(i,e));if(o){let i={createdAt:Q.createdAt,report:o};(!t||i.createdAt>t.createdAt)&&(t=i)}}let n=tn(e,r);for(let o of Object.values(K()?.entries??{}))!o?.report||!b(o.report,e)||(!n||o.createdAt>n.createdAt)&&(n=o);return n&&(!t||n.createdAt>t.createdAt)&&(t=n),t}catch{return T&&b(T.report,e)?T:void 0}};function jo(){D&&(h.clearTimeout(D),D=void 0)}function Bo(e){return e.provider===ce||e.provider===ue?bt(e):V().find(r=>r.modelProviders.includes(e.provider))?.usageProvider??bt(e)}async function $e(e,r){if(!ie)return;Me=e;let t=r??e.model;if(!t||!Z(t)){be(e);return}if(oe!==void 0&&oe!==t.provider){if(E+=1,Ye(),!U(e,void 0))return}else jo();oe=t.provider;let n=E+1;E=n;let o=h.now(),i=Wo(t,o),s=i?o-i.createdAt:Number.POSITIVE_INFINITY,a=i?.report.source!=="external-adapter"||i.report.complete,d=i&&a&&s>=0&&s<R?i:void 0;if(d){ne(e,d.report,{autoRefresh:!0,model:t,refreshDelayMs:R-s});return}let u=Bo(t),l=Gr(u,o);if(l>0){i?ne(e,i.report,{autoRefresh:!0,model:t,staleAgeMs:Math.max(0,o-i.report.capturedAt),refreshDelayMs:Math.max(l,1e4)}):U(e,`usage rate-limited (${ve(l)})`)&&Qe(e,t,Math.max(l,1e4));return}let c=Ot.has(u),f=Jr(),p=f&&!c?Xr(u,Tt,o):!1;if(c||f&&!p){let C=K()?.refreshLeases?.[u]?.expiresAt,H=c&&(C===void 0||C<=o)?fe:Math.max(1e4,(C??o+fe)-o);i?ne(e,i.report,{autoRefresh:!0,model:t,staleAgeMs:Math.max(0,o-i.report.capturedAt),refreshDelayMs:H}):U(e,"checking")&&Qe(e,t,H);return}Ot.add(u);let W,k=()=>{p&&(W=h.setTimeout(()=>{Zr(u,Tt,h.now()),k()},1e4),W.unref?.())};k();let M,Y=!1,x=R;try{if(i){if(!ne(e,i.report,{autoRefresh:!0,model:t,staleAgeMs:Math.max(0,o-i.report.capturedAt),schedule:!1}))return}else if(!U(e,"checking"))return;M=await h.queryUsage(e,{timeoutMs:Te});let C=h.now();M.ok?(T={createdAt:C,report:M.report},we(M.report,C,u)):(Y=M.errors.some(H=>me(H.message)),x=Y?ze(M.errors):R,Y&&He(u,C+x,C))}finally{W&&h.clearTimeout(W),p&&en(u,Tt),Ot.delete(u)}if(!(!ie||n!==E)){if(!M.ok){if(i){ne(e,i.report,{autoRefresh:!0,model:t,staleAgeMs:Math.max(0,h.now()-i.report.capturedAt),refreshDelayMs:x});return}let C=Y?`usage rate-limited (${ve(x)})`:"usage error";U(e,C)&&Qe(e,t,x);return}ne(e,M.report,{autoRefresh:!0,model:t})}}function xe(e,r,t){let n=r.find(o=>b(o,e.model));return n?(T={createdAt:t?.createdAt??h.now(),report:n},oe=e.model?.provider,ne(e,n,{autoRefresh:Z(e.model),model:e.model,...t?{staleAgeMs:Math.max(0,h.now()-t.createdAt),forceStale:t.stale}:{}}),!0):(U(e,void 0),!1)}function sn(e,r){try{let t=V(),n=(r.adapterId?t.find(c=>c.id===r.adapterId):void 0)??t.find(c=>c.usageProvider===r.provider&&e.model!==void 0&&c.modelProviders.includes(e.model.provider));if(n&&n.usageProvider!==r.provider)return!1;let o=Vo(r.provider,r.adapterId??n?.id),i=r.provider==="claude"?ce:ue,s=[...new Set([...o?.modelProviders??[],...n?.modelProviders??[],i])],a=De(r.adapterId===void 0&&n?{...r,adapterId:n.id}:r,s),d=!a.complete&&o?qo(o,a):a,u=h.now(),l=(Q?.reports??[]).filter(c=>c.source!=="external-adapter"||c.provider!==d.provider||c.adapterId!==d.adapterId);return Q={createdAt:u,reports:[...l,d]},we(d,u),b(d,e.model)?(E+=1,Ye(),T={createdAt:u,report:d},oe=e.model?.provider,xe(e,[d])):!1}catch{return!1}}function Vo(e,r){return[T?.report,...Q?.reports??[],K()?.entries[e]?.report].find(n=>n?.source==="external-adapter"&&n.provider===e&&(r===void 0||n.adapterId===r))}function qo(e,r){let t=new Map(e.windows.map(n=>[rn(n),n]));for(let n of r.windows){let o=rn(n);t.set(o,{...t.get(o)??{},...n})}return{...r,providerLabel:r.providerLabel??e.providerLabel,adapterId:r.adapterId??e.adapterId,modelProviders:[...new Set([...e.modelProviders,...r.modelProviders])],windows:[...t.values()]}}function rn(e){return`${e.scope.kind==="model"?`model:${[...e.scope.modelIds].sort().join(",")}`:e.scope.kind==="provider"?`provider:${e.scope.id}`:e.scope.kind}:${e.id}`}function an(e){return U(e,"checking")}function Ge(e){return U(e,void 0)}function un(e){e.registerCommand(Kt,{description:"Show usage for all configured providers (Codex and Anthropic)",getArgumentCompletions:kt,handler:async(r,t)=>{try{let n=Mt(r);if(!n.ok){t.ui.notify(n.error,"warning");return}if(n.value.raw){let a=n.value.timeoutMs;Pr(t,a).then(d=>{Dt()&&t.ui.notify(d,"info")}).catch(d=>{z(t,d)||t.ui.notify(w(d),"error")});return}if(n.value.consumeBankedReset){let a=await Be(t,n.value.timeoutMs),d=a.credits.filter(f=>(f.status??"available").toLowerCase()==="available");if(a.availableCount<=0||d.length===0){t.ui.notify("No Codex banked resets are available.","info");return}if(!t.hasUI){t.ui.notify("Consuming a Codex banked reset requires interactive confirmation.","warning");return}let u=n.value.consumeBankedResetId;if(!u){let f=await t.ui.select("Choose a Codex banked reset to consume",d.map(p=>Ve(p)));if(!f)return;u=d.find(p=>Ve(p)===f)?.id}if(!u){t.ui.notify("Could not determine which banked reset to consume.","warning");return}let l=d.find(f=>f.id===u);if(!l){t.ui.notify(`Codex banked reset ${u} is not available.`,"warning");return}if(!await t.ui.confirm("Consume Codex banked reset?",`${Ve(l)}
15
+
16
+ This action cannot be undone.`))return;await kr(t,n.value.timeoutMs,u),on(),se(void 0),Qr("codex"),t.ui.notify(`Consumed Codex banked reset ${u}.`,"info"),It(t,{timeoutMs:n.value.timeoutMs}).then(f=>{if(f.reports.length>0){se({createdAt:Date.now(),reports:f.reports});for(let p of f.reports)we(p);he(t,f.reports,!1)}}).catch(()=>{});return}let o=nn(),i=o&&(n.value.refresh||Date.now()-o.createdAt<R)?o:void 0;if(!i){let a=K();if(a){let d=Object.values(a.entries).filter(u=>!!u&&(n.value.refresh||Date.now()-u.createdAt<R));d.length>0&&(i={createdAt:Math.min(...d.map(u=>u.createdAt)),reports:d.map(u=>u.report)})}}if(i){let a=lr(t,i.reports);i=a.length>0?{...i,reports:a}:void 0}if(i&&!n.value.refresh){xe(t,i.reports),he(t,i.reports,!0);return}let s=n.value;s.refresh&&Yr(),an(t),It(t,s).then(a=>{if(!Dt())return;if(a.reports.length===0){i?(se(i),xe(t,i.reports,{createdAt:i.createdAt,stale:!0}),he(t,i.reports,!0),t.ui.notify(`Stale usage data retained.
17
+ ${je(a.errors)}`,"warning")):(se(void 0),Ge(t),t.ui.notify(je(a.errors),"warning"));return}let d=new Set(a.reports.map(dn)),u=s.refresh&&i?i.reports.filter(p=>!d.has(dn(p))):[],l=[...a.reports,...u];se({createdAt:Date.now(),reports:l});for(let p of a.reports)we(p);for(let p of a.errors)me(p.message)&&He(p.source==="anthropic-oauth"?"claude":"codex",Date.now()+ze([p]));let c=u.some(p=>b(p,t.model));xe(t,l,c&&i?{createdAt:i.createdAt,stale:!0}:void 0)||Ge(t),he(t,l,u.length>0),a.errors.length>0&&t.ui.notify(je(a.errors,!0),"warning")}).catch(a=>{i?(se(i),xe(t,i.reports,{createdAt:i.createdAt,stale:!0}),he(t,i.reports,!0),t.ui.notify(`Stale usage data retained.
18
+ ${w(a)}`,"warning")):(Ge(t),z(t,a)||t.ui.notify(w(a),"error"))})}catch(n){if(z(t,n))return;throw n}}})}function dn(e){return e.source==="external-adapter"?`external:${e.adapterId??e.modelProviders.slice().sort().join(",")}`:e.source}import{isAbsolute as Ko,relative as Ho,resolve as cn,sep as fn}from"node:path";import{truncateToWidth as Le,visibleWidth as ae}from"@earendil-works/pi-tui";var ln=!1;function pn(){return ln}function mn(e){ln=e}function gn(e,r){r.ui.setFooter((t,n,o)=>{let i=t,s=n,a=o;return{dispose:a.onBranchChange(()=>i.requestRender()),invalidate(){},render(u){return Qo(e,r,s,a,u)}}})}function Qo(e,r,t,n,o){let i=0,s=0,a=0,d=0,u=0,l;for(let _ of r.sessionManager.getEntries()){let Se=_.message;if(_.type!=="message"||Se?.role!=="assistant")continue;let J=Se.usage??{},Bt=g(J.input)??0,Sn=g(J.output)??0,et=g(J.cacheRead)??0,Vt=g(J.cacheWrite)??0,Cn=J.cost;i+=Bt,s+=Sn,a+=et,d+=Vt,u+=g(Cn?.total)??0;let qt=Bt+et+Vt;l=qt>0?et/qt*100:void 0}let c=r.getContextUsage(),f=c?.contextWindow??r.model?.contextWindow??0,p=c?.percent??0,W=c&&c.percent!==null?p.toFixed(1):"?",k=Go(r.sessionManager.getCwd(),process.env.HOME||process.env.USERPROFILE),M=n.getGitBranch();M&&(k=`${k} (${M})`);let Y=r.sessionManager.getSessionName();Y&&(k=`${k} \u2022 ${Y}`);let x=[];i&&x.push(`\u2191${Ae(i)}`),s&&x.push(`\u2193${Ae(s)}`),a&&x.push(`R${Ae(a)}`),d&&x.push(`W${Ae(d)}`),(a>0||d>0)&&l!==void 0&&x.push(`CH${l.toFixed(1)}%`);let C=r.model?r.modelRegistry.isUsingOAuth(r.model):!1;(u||C)&&x.push(`$${u.toFixed(3)}${C?" (sub)":""}`);let H=W==="?"?`?/${Ae(f)}`:`${W}%/${Ae(f)}`,Xe=H;p>90?Xe=t.fg("error",H):p>70&&(Xe=t.fg("warning",H)),x.push(Xe);let j=x.join(" "),G=ae(j);G>o&&(j=Le(j,o,"..."),G=ae(j));let Ze=r.model?.id||"no-model",de=Ze;if(r.model?.reasoning){let _=e.getThinkingLevel?.()??"off";de=_==="off"?`${Ze} \u2022 thinking off`:`${Ze} \u2022 ${_}`}if(n.getAvailableProviderCount()>1&&r.model){let _=`(${r.model.provider}) ${de}`;G+2+ae(_)<=o&&(de=_)}let zt=ae(de),Ne;if(G+2+zt<=o)Ne=j+" ".repeat(o-G-zt)+de;else{let _=o-G-2;if(_>0){let Se=Le(de,_,""),J=Math.max(0,o-G-ae(Se));Ne=j+" ".repeat(J)+Se}else Ne=j}let xn=t.fg("dim",j),An=t.fg("dim",Ne.slice(j.length)),Wt=[Le(t.fg("dim",k),o,t.fg("dim","...")),xn+An],jt=Yo(t,n,o);return jt!==void 0&&Wt.push(jt),Wt}function Yo(e,r,t){let n=r.getExtensionStatuses();if(n.size===0)return;let o=n.get(Re),s=Array.from(n.entries()).filter(([c])=>c!==Re).sort(([c],[f])=>c.localeCompare(f)).map(([,c])=>ft(c)).join(" ");if(!o)return s?Le(s,t,e.fg("dim","...")):void 0;let a=ft(o),d=ae(s),u=ae(a);if(d+1+u>t)return Le(s?`${s} ${a}`:a,t,e.fg("dim","..."));let l=" ".repeat(Math.max(1,t-d-u));return s+l+a}function Ae(e){return e<1e3?e.toString():e<1e4?`${(e/1e3).toFixed(1)}k`:e<1e6?`${Math.round(e/1e3)}k`:e<1e7?`${(e/1e6).toFixed(1)}M`:`${Math.round(e/1e6)}M`}function Go(e,r){if(!r)return e;let t=cn(e),n=cn(r),o=Ho(n,t);return o===""||o!==".."&&!o.startsWith(`..${fn}`)&&!Ko(o)?o===""?"~":`~${fn}${o}`:e}var hn="provider-usage:warning-v1",Je=new WeakMap;function Jo(e){return e==="claude"||e==="codex"}function yn(e,r){let t=new Set;for(let n of r.sessionManager.getEntries()){if(n.type!=="custom"||n.customType!==hn)continue;let o=n.data;if(typeof o!="object"||o===null)continue;let i=Reflect.get(o,"provider"),s=Reflect.get(o,"shownAt");Jo(i)&&typeof s=="number"&&Number.isFinite(s)&&t.add(i)}Je.set(e,t)}function wn(e){Je.set(e,new Set)}function bn(e,r,t){let n=t.snapshot;if(n&&sn(r,n),t.type==="snapshot")return;if(t.type==="hard-limit"){r.ui.notify(t.message,"warning");return}let o=Je.get(e)??new Set;if(o.has(t.provider))return;let i={provider:t.provider,shownAt:Date.now()};e.appendEntry(hn,i),o.add(t.provider),Je.set(e,o),r.ui.notify(t.message,"warning")}function Xo(e){un(e);let r,t=()=>{let i=r;r=void 0;try{i?.unsubscribe?.()}catch{}},n=(i,s)=>{t();try{s?yn(e,i):wn(e);let a=yt();if(!a)return;let d={};r=d;let u=a.subscribe(l=>{if(!(r!==d||!wt(l)))try{i.sessionManager,bn(e,i,l)}catch(c){z(i,c)&&r===d&&t()}});d.unsubscribe=u,r!==d&&u()}catch(a){z(i,a)}},o=i=>{if(!(pn()||!i.hasUI))try{gn(e,i),mn(!0)}catch(s){if(!z(i,s))throw s}};e.on("session_start",(i,s)=>{Ft(!0),o(s),n(s,i.reason!=="fork"),Z(s.model)?$e(s,s.model).catch(_e(s)):be(s)}),e.on("session_tree",(i,s)=>{Z(s.model)?$e(s,s.model).catch(_e(s)):be(s)}),e.on("model_select",(i,s)=>{Z(i.model)?$e(s,i.model).catch(_e(s)):be(s)}),e.on("session_shutdown",(i,s)=>{t(),Ft(!1),be(s)})}export{gt as PROVIDER_USAGE_BUS_SYMBOL,kt as completeCodexStatusArguments,Xo as default,wr as formatCodexUsageReport,br as formatCodexUsageStatusline,dr as getUsageBusV1,S as isStaleExtensionContextError,$t as normalizeAppServerResponse,_t as normalizeBackendPayload,Mt as parseArgs};
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@schuettc/pi-usage",
3
+ "version": "0.10.0-schuettc.1",
4
+ "description": "Pi extension that reports provider usage/rate-limit budgets (Codex, Anthropic OAuth, etc.) via /usage, with a live statusline widget.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "keywords": [
9
+ "pi-package",
10
+ "pi-extension",
11
+ "pi",
12
+ "usage",
13
+ "rate-limit",
14
+ "statusline"
15
+ ],
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "pi": {
22
+ "extensions": [
23
+ "./dist/index.js"
24
+ ]
25
+ },
26
+ "scripts": {
27
+ "build": "node ../../scripts/build-extension.mjs",
28
+ "prepublishOnly": "npm run build",
29
+ "check": "biome check . && npm run typecheck",
30
+ "format": "biome check --write .",
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "node --import tsx --test tests/*.test.ts"
33
+ },
34
+ "devDependencies": {
35
+ "@biomejs/biome": "^2.5.3",
36
+ "@earendil-works/pi-coding-agent": "^0.84.2",
37
+ "@earendil-works/pi-tui": "^0.84.2",
38
+ "tsx": "4.20.5",
39
+ "typescript": "^5.7.3"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/schuettc/pi-usage",
44
+ "directory": "extensions/pi-usage"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "provenance": true
49
+ },
50
+ "dependencies": {
51
+ "fs-ext-extra-prebuilt": "^2.2.14"
52
+ }
53
+ }