@byokit/accounts 0.14.0 → 0.15.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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.15.1 (2026-10-01)
6
+
7
+ - Dependency update: pins @byokit/usage 0.6.0.
8
+ - FIX: (from @byokit/usage 0.6.0) Claude subscription quota snapshots can now be read through an identity-free ephemeral host callback without credentials or a fabricated account UUID; readings never enter a cache or shared store, and retry state stays local to the source.
9
+
10
+ ## 0.15.0 (2026-10-01)
11
+
12
+ - Dependency update: pins @byokit/usage 0.5.0.
13
+
14
+ - FIX: ChatGPT subscription respond() now retains reported token usage with result: true or tools, including partial results for incomplete answers.
15
+ - Bind an existing ChatGPT subscription login to a member-bound chatgpt() handle without exposing credentials.
16
+ - Script mockOpenAI answers with string, regex or function prompt matchers and optional token usage.
17
+
5
18
  ## 0.14.0 (2026-10-01)
6
19
 
7
20
  - Dependency update: pins @byokit/usage 0.4.0.
package/README.md CHANGED
@@ -118,7 +118,7 @@ and [`examples/pwa`](../../examples/pwa) (browser sign-in).
118
118
 
119
119
  | Export | What it does |
120
120
  |---|---|
121
- | `Accounts` | Sign-in, status, sign-out, asking and limits for each member: `login`, `finished`, `status`, `plan`, `logout`, `respond`, `failed`, `ladder`, `keepFresh` |
121
+ | `Accounts` | Sign-in, status, sign-out, asking and limits for each member: `login`, `finished`, `status`, `plan`, `logout`, `respond`, `chatgpt`, `failed`, `ladder`, `keepFresh` |
122
122
  | `portable`, `computer`, `loopback` | The platform `Accounts` runs on: device code with `fetch` alone, or (Node entry only) Pi's flows and the loopback listener |
123
123
  | `memoryStore`, `fileStore`, `secureStore`, `browserStore`, `recordStore` | One store per person: in memory, a sealed 0600 file (Node entry only), Keychain/Keystore, IndexedDB, or your own load and save |
124
124
  | `offered`, `provider`, `PROVIDERS` | The catalogue: each provider's billing, models and source |
@@ -256,12 +256,15 @@ const accounts = new Accounts({
256
256
 
257
257
  `respond(member, { instructions, input, model?, onText?, signal? })` asks ChatGPT's own answers endpoint with the
258
258
  member's sign-in, refreshed first when due, and returns the whole text (`onText` gets each piece as it streams; the
259
- returned completion is authoritative). A limit or a lapsed sign-in is acted on as `failed()` does, then thrown as a
259
+ returned completion is authoritative). Pass `result: true` to receive `{ text, output, usage? }` even without tools.
260
+ `usage` retains provider-reported `input_tokens`, `output_tokens` and native details in the same shape as the
261
+ Messages route; absent usage stays absent. Subscription token counts do not imply an API-key charge.
262
+ A limit or a lapsed sign-in is acted on as `failed()` does, then thrown as a
260
263
  `ResponseError` with the words to show and the kind acted on. Rules: [conformance fixtures](../../fixtures/README.md).
261
264
 
262
265
  A cut-off answer always throws `IncompleteError` (a `ResponseError` with `kind: null`), with or without tools.
263
266
  Its `reason` preserves the provider's `incomplete_details.reason`, including `max_output_tokens` and
264
- `content_filter` (`unknown` when absent). Its `result` holds the partial `{ text, output }` for apps that want to
267
+ `content_filter` (`unknown` when absent). Its `result` holds the partial `{ text, output, usage? }` for apps that want to
265
268
  show it as unfinished. `onEvent` also receives `{ type: 'incomplete', reason }` before rejection; `onText` may
266
269
  already have shown partial words. This covers `response.incomplete` events and `status: 'incomplete'` envelopes,
267
270
  whether fetch streams SSE, buffers it, or returns JSON. The account stays signed in and is not put to rest.
@@ -332,6 +335,14 @@ tests and demos sign in and ask end to end with no account. Point the kit at it
332
335
 
333
336
  Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](../../NOTICE).
334
337
 
338
+ ## ChatGPT subscription handles
339
+
340
+ After `Accounts.login(member, 'chatgpt')` finishes, `accounts.chatgpt(member)` returns a member-bound
341
+ subscription handle for `openai({ auth: 'account', account: handle, model })` in `@byokit/decide`.
342
+ It routes requests through `Accounts.respond`, which keeps tokens in the app's store and refreshes them
343
+ before use. The handle contains no credentials, follows sign-out, and needs no additional sign-in or
344
+ API key (billed per use). ChatGPT remains offered by default. Use the handle only where the sign-in lives.
345
+
335
346
  ## Official ChatGPT plan token-sharing adapter
336
347
 
337
348
  `chatgptPlan({ session })`, also exported from `@byokit/accounts/chatgpt-plan`, binds a single person's
@@ -589,3 +600,22 @@ Native status reads resolve within 15 seconds. Claude stdout is capped at 256 KB
589
600
  The existing `accounts-v1.json` `{version:1,accounts:[{id,provider,name,folder,found}]}` and `auto-terms-v1.json` `{acknowledged:true}` encodings remain unchanged, with 0600 files and atomic replacement. The kit preserves but excludes `found-*` and `found:true` rows, which belong to the host's default-login adapter. Legacy managed rows without kit completion sidecars retain their native signed-in status; new or re-signing rows require the completion marker. Symlinked account folders and records outside the provider/hex layout are refused.
590
601
 
591
602
  `usageSource(id)` returns a Codex Source for `@byokit/usage`; Claude returns `undefined`, and its usage Source is `{provider:'claude', folder:set.CLAUDE_CONFIG_DIR, headers}` in a usage reader whose `stateDir` is the same managed root. `kinds` serves only the matching native agent (`claude` or `codex`); Pi is excluded until its folder mapping is verified. `resumeArgs` accepts an `id` conversation reference for these kinds. `termsAcknowledged` and `acknowledgeTerms` keep the host's existing terms bit; they do not gate sign-in. `suggestName` uses the first part of an email, falling back to the provider's name.
603
+
604
+
605
+ ## Scripted sign-in stand-in
606
+
607
+ `mockOpenAI({ answers })` from `@byokit/accounts/testing` runs the normal device-code sign-in and
608
+ ChatGPT response path on loopback. Each script has `{ match, text, usage? }`: a string matches a
609
+ substring of joined input text, a regex tests it, and a function receives it and returns a boolean.
610
+ The first match wins, including on repeat requests; unmatched input retains echo/tool behavior.
611
+ Replace `mock.state.answers` between requests to change scripts. Counts are explicit fake data.
612
+
613
+ ```ts
614
+ import { mockOpenAI } from '@byokit/accounts/testing';
615
+
616
+ const mock = await mockOpenAI({ email: 'umer@example.com', answers: [
617
+ { match: 'Umer', text: 'Ready.', usage: { input_tokens: 12, output_tokens: 3 } },
618
+ { match: /finished/i, text: 'Done.' },
619
+ { match: (prompt) => prompt.includes('help'), text: 'I can help.' },
620
+ ] });
621
+ ```
@@ -3,6 +3,7 @@ import { type ClaudePlanOptions } from './claude-plan.ts';
3
3
  import { type AnthropicAsk, type AnthropicResult, type AnthropicTool } from './anthropic.ts';
4
4
  import { type Provider } from './catalogue.ts';
5
5
  import { ResponseError, type Ask, type ResponseResult, type ResponseTool } from './responses.ts';
6
+ import type { ChatGPTRespondAccount } from './chatgpt-plan.ts';
6
7
  import { type EndingStore, type RefreshStore } from './stores.ts';
7
8
  import { type Why } from './words.ts';
8
9
  /** What signing in needs from an engine: Pi's `Models`, or anything shaped like it (the coding agent's `ModelRuntime`). */
@@ -171,12 +172,20 @@ export declare class Accounts<R extends AuthHost = AuthHost, M extends Member =
171
172
  result?: false;
172
173
  }): Promise<string>;
173
174
  respond(member: M, ask: AnthropicAccountAsk): Promise<string | AnthropicResult>;
175
+ respond(member: M, ask: Ask & {
176
+ result: true;
177
+ }): Promise<ResponseResult>;
174
178
  respond(member: M, ask: Ask & {
175
179
  tools?: undefined;
180
+ result?: false;
176
181
  }): Promise<string>;
177
182
  respond(member: M, ask: Ask & {
178
183
  tools: ResponseTool[];
179
184
  }): Promise<ResponseResult>;
185
+ respond(member: M, ask: Ask): Promise<string | ResponseResult>;
186
+ /** Bind this member's existing ChatGPT subscription login for consumers such as decide. Each request uses
187
+ * the current sign-in and Accounts' refresh/limit/sign-out handling; the handle exposes no credentials. */
188
+ chatgpt(member: M): ChatGPTRespondAccount;
180
189
  /** An account's error, acted on. A limit or overload rests it (until when it said, or a default). A plan without this
181
190
  * use is marked so. A refusal is checked: a sign-in that no longer refreshes is signed out for real, one that still
182
191
  * does was a passing refusal and rests a few minutes (kind `overloaded`) rather than loop. Returns the kind acted on,
package/dist/accounts.js CHANGED
@@ -300,7 +300,7 @@ export class Accounts {
300
300
  const { access, accountId } = await this.access(member);
301
301
  try {
302
302
  const base = { ...ask, access, accountId, model: ask.model ?? p.models.strong, base: this.opts.apiBase, fetch: this.opts.fetch, originator: ask.originator ?? this.opts.originator };
303
- return ask.tools ? await respond({ ...base, tools: ask.tools }) : await respond({ ...base, tools: undefined });
303
+ return await respond(base);
304
304
  }
305
305
  catch (e) {
306
306
  if (e instanceof ResponseError && e.kind && e.kind !== 'network') {
@@ -311,6 +311,12 @@ export class Accounts {
311
311
  throw e;
312
312
  }
313
313
  }
314
+ /** Bind this member's existing ChatGPT subscription login for consumers such as decide. Each request uses
315
+ * the current sign-in and Accounts' refresh/limit/sign-out handling; the handle exposes no credentials. */
316
+ chatgpt(member) {
317
+ this.offer('chatgpt');
318
+ return { billing: 'subscription', respond: (ask) => this.respond(member, ask) };
319
+ }
314
320
  /** An account's error, acted on. A limit or overload rests it (until when it said, or a default). A plan without this
315
321
  * use is marked so. A refusal is checked: a sign-in that no longer refreshes is signed out for real, one that still
316
322
  * does was a passing refusal and rests a few minutes (kind `overloaded`) rather than loop. Returns the kind acted on,
@@ -1,3 +1,11 @@
1
+ import type { Ask, ResponseResult } from './responses.ts';
2
+ /** A member-bound Accounts ChatGPT subscription handle. Credentials stay inside Accounts. */
3
+ export type ChatGPTRespondAccount = {
4
+ readonly billing: 'subscription';
5
+ respond(ask: Ask & {
6
+ result: true;
7
+ }): Promise<ResponseResult>;
8
+ };
1
9
  /** A host-owned, validated Sign in with ChatGPT token-sharing session. The host completes the official
2
10
  * OAuth flow (including ID-token verification), stores it per person, and refreshes it before returning it.
3
11
  * This adapter never discovers credentials, starts a login, or substitutes a Codex credential. */
@@ -2,10 +2,10 @@ export { Accounts, planOf, portable, type AccountsOptions, type ClaudePlanAsk, t
2
2
  export { PROVIDERS, offered, provider, type Billing, type MultiAccountTerms, type Provider } from './catalogue.ts';
3
3
  export { PORTABLE, claims, credentialOf, devicePoll, deviceStart, portableEngine, type EngineOptions, type Poll } from './engine.ts';
4
4
  export { REST_MS, classify, classifyFailure, type Failure, type Kind } from './limits.ts';
5
- export { IncompleteError, ResponseError, isFunctionCall, limitResponse, respond, sseReader, type Ask, type ResponseFunctionCall, type ResponseInputItem, type ResponseOutputItem, type ResponseOutputMessage, type ResponseReasoning, type ResponseResult, type ResponseStreamEvent, type ResponseText, type ResponseTextFormat, type ResponseTool, type ResponseToolChoice } from './responses.ts';
5
+ export { IncompleteError, ResponseError, isFunctionCall, limitResponse, respond, sseReader, type Ask, type ResponseFunctionCall, type ResponseInputItem, type ResponseOutputItem, type ResponseOutputMessage, type ResponseReasoning, type ResponseResult, type ResponseUsage, type ResponseStreamEvent, type ResponseText, type ResponseTextFormat, type ResponseTool, type ResponseToolChoice } from './responses.ts';
6
6
  export { browserStore, keystoreStore, memoryStore, recordStore, secureStore, RefreshRequiredError, type RefreshStore, type SecureStoreLike } from './stores.ts';
7
7
  export { WORDS, billingWords, callbackPage, clock, failure, say, signInError, type WordKey, type Why } from './words.ts';
8
- export { chatgptPlan, UnsupportedAccountError, type ChatGPTPlanAccount, type ChatGPTPlanSession } from './chatgpt-plan.ts';
8
+ export { chatgptPlan, UnsupportedAccountError, type ChatGPTPlanAccount, type ChatGPTRespondAccount, type ChatGPTPlanSession } from './chatgpt-plan.ts';
9
9
  export { anthropic, anthropicSseReader, AnthropicIncompleteError, type AnthropicAsk, type AnthropicCacheControl, type AnthropicContent, type AnthropicImage, type AnthropicMessage, type AnthropicOptions, type AnthropicRequest, type AnthropicResponse, type AnthropicResult, type AnthropicStreamEvent, type AnthropicText, type AnthropicThinking, type AnthropicTool, type AnthropicToolChoice, type AnthropicToolUse, type AnthropicUsage } from './anthropic.ts';
10
10
  export { ClaudePlanExpiredError, ClaudePlanPlatformError, claudeAuthorization, claudeCode, type ClaudePlanOptions } from './claude-plan.ts';
11
11
  export { chooseAccount, resolveSelection, roomOf, roomWords, type AccountLike, type AccountPick, type Considered, type Defaults, type PickWhy, type Room, type RoomSpan, type RunSelection, type SignInState } from './multi.ts';
@@ -128,10 +128,17 @@ export type ResponseOutputItem = ResponseOutputMessage | ResponseFunctionCall |
128
128
  };
129
129
  /** Whether an output item is a function call, so an app can answer it with a `function_call_output` turn. */
130
130
  export declare const isFunctionCall: (item: ResponseOutputItem) => item is ResponseFunctionCall;
131
- /** The model's answer: the text (`onText` saw it piece by piece) and every output item. */
131
+ /** Provider-reported token counts, with native details retained. Missing usage is never guessed. */
132
+ export type ResponseUsage = {
133
+ input_tokens: number;
134
+ output_tokens: number;
135
+ [key: string]: unknown;
136
+ };
137
+ /** The model's answer: text, output items and token usage when the provider reports it. */
132
138
  export type ResponseResult = {
133
139
  text: string;
134
140
  output: ResponseOutputItem[];
141
+ usage?: ResponseUsage;
135
142
  };
136
143
  /** What streams besides the words: each text piece, each tool call as it builds and lands, and each output item. */
137
144
  export type ResponseStreamEvent = {
@@ -166,6 +173,8 @@ export declare function sseReader(onText?: (delta: string) => void, onEvent?: (e
166
173
  result(): ResponseResult;
167
174
  };
168
175
  export type Ask = {
176
+ /** Return text, output items and reported usage even without tools. */
177
+ result?: boolean;
169
178
  /** What the model is told to be. */
170
179
  instructions: string;
171
180
  /** The person's words, or the turns so far: messages (with `input_image` where the person attached one) and, after a
@@ -201,10 +210,15 @@ type Access = {
201
210
  /** Ask ChatGPT with a signed-in token. `fetch`: pass one that streams (Expo's `expo/fetch`); any fetch works.
202
211
  * Without `tools` the answer is the plain text, as before; with `tools` it is the text with every output item.
203
212
  * Incomplete answers always throw IncompleteError and notify onEvent, including with tools. */
213
+ export declare function respond(o: Ask & Access & {
214
+ result: true;
215
+ }): Promise<ResponseResult>;
204
216
  export declare function respond(o: Ask & Access & {
205
217
  tools?: undefined;
218
+ result?: false;
206
219
  }): Promise<string>;
207
220
  export declare function respond(o: Ask & Access & {
208
221
  tools: ResponseTool[];
209
222
  }): Promise<ResponseResult>;
223
+ export declare function respond(o: Ask & Access): Promise<string | ResponseResult>;
210
224
  export {};
package/dist/responses.js CHANGED
@@ -58,6 +58,7 @@ export function sseReader(onText, onEvent) {
58
58
  let buffer = '', text = '', completed;
59
59
  let done = false, finished;
60
60
  let incomplete;
61
+ let usage;
61
62
  const output = [];
62
63
  const emitted = new Set();
63
64
  const calls = new Map();
@@ -115,6 +116,8 @@ export function sseReader(onText, onEvent) {
115
116
  }
116
117
  if (e.type === 'response.completed' || e.type === 'response.incomplete' || e.response?.status === 'incomplete') {
117
118
  done = true;
119
+ if (isRecord(e.response?.usage))
120
+ usage = { ...e.response.usage };
118
121
  if (Array.isArray(e.response?.output)) {
119
122
  for (const [i, item] of e.response.output.entries())
120
123
  land(item, i);
@@ -155,7 +158,7 @@ export function sseReader(onText, onEvent) {
155
158
  throw new ResponseError('ChatGPT stopped before completing its answer.', 'network');
156
159
  if (text === '' && whole !== '')
157
160
  onText?.(whole);
158
- finished = { text: whole, output };
161
+ finished = { text: whole, output, ...(usage && { usage }) };
159
162
  }
160
163
  if (incomplete !== undefined)
161
164
  throw new IncompleteError(incomplete, finished);
@@ -214,5 +217,5 @@ export async function respond(o) {
214
217
  else {
215
218
  reader.push(await res.text()); // a fetch that can't stream: the whole answer at once
216
219
  }
217
- return o.tools ? reader.result() : reader.end();
220
+ return o.tools || o.result ? reader.result() : reader.end();
218
221
  }
@@ -18,4 +18,4 @@ export declare function decoy(root: string): {
18
18
  /** Marks left by the decoy's extension or CLIs: something ran code from someone else's setup. */
19
19
  ran: () => string[];
20
20
  };
21
- export { mockJwt, mockOpenAI, type MockOpenAIOptions } from './mock-openai.ts';
21
+ export { mockJwt, mockOpenAI, type MockOpenAIAnswer, type MockOpenAIOptions } from './mock-openai.ts';
@@ -1,14 +1,23 @@
1
+ import type { ResponseUsage } from '../responses.ts';
2
+ /** First matching script wins. Strings match a substring; regexes and predicates inspect the joined input text.
3
+ * Unmatched prompts retain the echo/tool behavior. Counts are supplied explicitly, never estimated. */
4
+ export type MockOpenAIAnswer = {
5
+ match: string | RegExp | ((prompt: string) => boolean);
6
+ text: string;
7
+ usage?: ResponseUsage;
8
+ };
1
9
  export type MockOpenAIOptions = {
2
10
  port?: number;
3
11
  host?: string;
4
12
  plan?: string;
5
13
  email?: string;
6
14
  expiresIn?: number;
15
+ answers?: readonly MockOpenAIAnswer[];
7
16
  log?: (line: string) => void;
8
17
  };
9
18
  /** An access token as OpenAI shapes it: the account, the plan and the email in its claims. */
10
19
  export declare const mockJwt: (plan?: string, email?: string, n?: number) => string;
11
- export declare function mockOpenAI({ port, host, plan, email, expiresIn, log }?: MockOpenAIOptions): Promise<{
20
+ export declare function mockOpenAI({ port, host, plan, email, expiresIn, answers, log }?: MockOpenAIOptions): Promise<{
12
21
  base: string;
13
22
  state: {
14
23
  /** Refresh tokens OpenAI still honours; a refresh spends the old one (rotation), sign-out revokes one. */
@@ -17,6 +26,8 @@ export declare function mockOpenAI({ port, host, plan, email, expiresIn, log }?:
17
26
  path: string;
18
27
  body: string;
19
28
  }[];
29
+ /** Scripts may be replaced between requests, without restarting the sign-in stand-in. */
30
+ answers: MockOpenAIAnswer[];
20
31
  /** Refuse every refresh, as when the person signed out elsewhere. */
21
32
  refuse: boolean;
22
33
  /** Seconds each issued token lives. */
@@ -11,13 +11,15 @@ export const mockJwt = (plan = 'plus', email = 'sara@example.com', n = 0) => ['e
11
11
  // As long as a real one, whose claims fill about 1.5 kB.
12
12
  scp: ['openid', 'profile', 'email', 'offline_access'], pad: 'x'.repeat(1200),
13
13
  })).toString('base64url'), 'sig'].join('.');
14
- export async function mockOpenAI({ port = 0, host = '127.0.0.1', plan = 'plus', email = 'sara@example.com', expiresIn = 864_000, log } = {}) {
14
+ export async function mockOpenAI({ port = 0, host = '127.0.0.1', plan = 'plus', email = 'sara@example.com', expiresIn = 864_000, answers = [], log } = {}) {
15
15
  const codes = new Map();
16
16
  let issued = 0, asked = 0;
17
17
  const state = {
18
18
  /** Refresh tokens OpenAI still honours; a refresh spends the old one (rotation), sign-out revokes one. */
19
19
  live: new Set(),
20
20
  requests: [],
21
+ /** Scripts may be replaced between requests, without restarting the sign-in stand-in. */
22
+ answers: [...answers],
21
23
  /** Refuse every refresh, as when the person signed out elsewhere. */
22
24
  refuse: false,
23
25
  /** Seconds each issued token lives. */
@@ -115,12 +117,20 @@ export async function mockOpenAI({ port = 0, host = '127.0.0.1', plan = 'plus',
115
117
  said.push(part.text);
116
118
  }
117
119
  const words = said.join(' ');
120
+ let scripted;
121
+ try {
122
+ scripted = state.answers.find(({ match }) => typeof match === 'string' ? words.includes(match)
123
+ : typeof match === 'function' ? match(words) : new RegExp(match.source, match.flags).test(words));
124
+ }
125
+ catch {
126
+ return send(500, { error: { message: 'The stand-in could not match this prompt.' } });
127
+ }
118
128
  const schema = asked.text?.format?.type === 'json_schema';
119
- const text = answered !== undefined ? `You did: ${answered}`
129
+ const text = scripted ? scripted.text : answered !== undefined ? `You did: ${answered}`
120
130
  : schema ? JSON.stringify({ echo: words ? `You said: ${words}` : 'You said nothing' })
121
131
  : `You said: ${words}`;
122
132
  const called = Array.isArray(asked.tools) ? asked.tools.filter((t) => t?.type === 'function') : [];
123
- if (called.length > 0 && answered === undefined) {
133
+ if (!scripted && called.length > 0 && answered === undefined) {
124
134
  // A tool turn: the model calls the first function tool, streamed as argument deltas and one finished item,
125
135
  // then the completion with the output list. The app answers with a `function_call_output` turn next.
126
136
  const name = String(called[0].name ?? 'tool');
@@ -140,7 +150,9 @@ export async function mockOpenAI({ port = 0, host = '127.0.0.1', plan = 'plus',
140
150
  res.write(`event: response.output_text.delta\ndata: ${JSON.stringify({ type: 'response.output_text.delta', delta })}\n\n`);
141
151
  await new Promise((r) => setTimeout(r, 5));
142
152
  }
143
- return res.end('event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed"}}\n\ndata: [DONE]\n\n');
153
+ return res.end(`event: response.completed\ndata: ${JSON.stringify({ type: 'response.completed', response: {
154
+ status: 'completed', ...(scripted?.usage && { usage: scripted.usage }),
155
+ } })}\n\ndata: [DONE]\n\n`);
144
156
  }
145
157
  case '/oauth/revoke':
146
158
  state.live.delete(json().token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/accounts",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "Sign in with the AI plan you already pay for, into your app's own store.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -52,7 +52,7 @@
52
52
  "prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
53
53
  },
54
54
  "dependencies": {
55
- "@byokit/usage": "0.4.0",
55
+ "@byokit/usage": "0.6.0",
56
56
  "@earendil-works/pi-ai": "0.87.1"
57
57
  },
58
58
  "publishConfig": {