@mrciphersmith/keryx 0.2.82 → 0.2.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +403 -73
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -233,7 +233,8 @@ What is in it today:
233
233
  `/resume`, `/sessions`, `/status`, `/flows`, `/compact`, `/new`, and
234
234
  `keryx sessions list|fork|export` — `fork` branches a conversation into a new
235
235
  session that keeps its ancestry, without editing a transcript by hand.
236
- `/status` is the session inspector; `/session-info` and `/info` are not aliases.
236
+ `/status` is the session inspector (identity, context window and limits when
237
+ the provider reported them); `/session-info` and `/info` are not aliases.
237
238
  - **Responsive busy-turn UX.** A running main turn can be interrupted with
238
239
  `/interrupt`; additional prompts are queued and answered as read-only side workers
239
240
  (`side-1`) in the TUI so the shell stays usable under long-running turns.
package/dist/cli.js CHANGED
@@ -70776,7 +70776,7 @@ import { spawnSync as spawnSync2 } from "child_process";
70776
70776
  // package.json
70777
70777
  var package_default = {
70778
70778
  name: "@mrciphersmith/keryx",
70779
- version: "0.2.82",
70779
+ version: "0.2.83",
70780
70780
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
70781
70781
  private: false,
70782
70782
  publishConfig: {
@@ -74085,6 +74085,345 @@ function openMcpTools(otui, chrome, options) {
74085
74085
  // src/tui/tui-shell.ts
74086
74086
  init_slate();
74087
74087
 
74088
+ // src/commands/model-limits.ts
74089
+ init_guard2();
74090
+ init_shell_config();
74091
+ init_providers();
74092
+ var MODEL_LIMITS_TIMEOUT_MS = 8000;
74093
+ var DEFAULT_OLLAMA_BASE_URL2 = "http://localhost:11434";
74094
+ var CONTEXT_WINDOW_KEYS = [
74095
+ "context_length",
74096
+ "context_window",
74097
+ "contextWindow",
74098
+ "max_model_len",
74099
+ "max_input_tokens",
74100
+ "maxInputTokens"
74101
+ ];
74102
+ var NESTED_LIMIT_KEYS = ["architecture", "top_provider", "topProvider", "meta", "limits", "capabilities"];
74103
+ function asPositiveInt(value) {
74104
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
74105
+ return Math.floor(value);
74106
+ }
74107
+ if (typeof value === "string" && value.trim().length > 0) {
74108
+ const parsed = Number(value);
74109
+ if (Number.isFinite(parsed) && parsed > 0) {
74110
+ return Math.floor(parsed);
74111
+ }
74112
+ }
74113
+ return;
74114
+ }
74115
+ function extractContextWindow(entry) {
74116
+ if (typeof entry !== "object" || entry === null) {
74117
+ return;
74118
+ }
74119
+ const rec = entry;
74120
+ for (const key of CONTEXT_WINDOW_KEYS) {
74121
+ const n = asPositiveInt(rec[key]);
74122
+ if (n !== undefined) {
74123
+ return n;
74124
+ }
74125
+ }
74126
+ for (const nested of NESTED_LIMIT_KEYS) {
74127
+ const inner = extractContextWindow(rec[nested]);
74128
+ if (inner !== undefined) {
74129
+ return inner;
74130
+ }
74131
+ }
74132
+ return;
74133
+ }
74134
+ function entryId(entry) {
74135
+ if (typeof entry !== "object" || entry === null) {
74136
+ return "";
74137
+ }
74138
+ const rec = entry;
74139
+ if (typeof rec.id === "string" && rec.id.length > 0) {
74140
+ return rec.id;
74141
+ }
74142
+ if (typeof rec.name === "string" && rec.name.length > 0) {
74143
+ return rec.name;
74144
+ }
74145
+ return "";
74146
+ }
74147
+ function findModelEntry(data, modelId) {
74148
+ const wanted = modelId.trim();
74149
+ if (wanted.length === 0) {
74150
+ return;
74151
+ }
74152
+ const lower = wanted.toLowerCase();
74153
+ const exact = data.find((entry) => entryId(entry).toLowerCase() === lower);
74154
+ if (exact !== undefined) {
74155
+ return exact;
74156
+ }
74157
+ const short = lower.includes("/") ? lower.split("/").pop() ?? lower : lower;
74158
+ return data.find((entry) => {
74159
+ const id = entryId(entry).toLowerCase();
74160
+ if (id.length === 0) {
74161
+ return false;
74162
+ }
74163
+ return id.endsWith(`/${short}`) || id.split("/").pop() === short;
74164
+ });
74165
+ }
74166
+ var REQUEST_LIMIT_HEADERS = [
74167
+ "x-ratelimit-limit-requests",
74168
+ "x-ratelimit-limit",
74169
+ "anthropic-ratelimit-requests-limit"
74170
+ ];
74171
+ var REQUEST_REMAINING_HEADERS = [
74172
+ "x-ratelimit-remaining-requests",
74173
+ "x-ratelimit-remaining",
74174
+ "anthropic-ratelimit-requests-remaining"
74175
+ ];
74176
+ var TOKEN_LIMIT_HEADERS = ["x-ratelimit-limit-tokens", "anthropic-ratelimit-tokens-limit"];
74177
+ var TOKEN_REMAINING_HEADERS = [
74178
+ "x-ratelimit-remaining-tokens",
74179
+ "anthropic-ratelimit-tokens-remaining"
74180
+ ];
74181
+ var RESET_HEADERS = [
74182
+ "x-ratelimit-reset-requests",
74183
+ "x-ratelimit-reset",
74184
+ "anthropic-ratelimit-requests-reset",
74185
+ "retry-after"
74186
+ ];
74187
+ function firstHeaderInt(headers, names) {
74188
+ for (const name of names) {
74189
+ const n = asPositiveInt(headers.get(name));
74190
+ if (n !== undefined) {
74191
+ return n;
74192
+ }
74193
+ }
74194
+ return;
74195
+ }
74196
+ function firstHeaderText(headers, names) {
74197
+ for (const name of names) {
74198
+ const raw = headers.get(name)?.trim();
74199
+ if (raw !== undefined && raw.length > 0) {
74200
+ return raw;
74201
+ }
74202
+ }
74203
+ return;
74204
+ }
74205
+ function parseRateLimitHeaders(headers) {
74206
+ const snapshot = {};
74207
+ const requestsLimit = firstHeaderInt(headers, REQUEST_LIMIT_HEADERS);
74208
+ const requestsRemaining = firstHeaderInt(headers, REQUEST_REMAINING_HEADERS);
74209
+ const tokensLimit = firstHeaderInt(headers, TOKEN_LIMIT_HEADERS);
74210
+ const tokensRemaining = firstHeaderInt(headers, TOKEN_REMAINING_HEADERS);
74211
+ const reset = firstHeaderText(headers, RESET_HEADERS);
74212
+ if (requestsLimit !== undefined)
74213
+ snapshot.requestsLimit = requestsLimit;
74214
+ if (requestsRemaining !== undefined)
74215
+ snapshot.requestsRemaining = requestsRemaining;
74216
+ if (tokensLimit !== undefined)
74217
+ snapshot.tokensLimit = tokensLimit;
74218
+ if (tokensRemaining !== undefined)
74219
+ snapshot.tokensRemaining = tokensRemaining;
74220
+ if (reset !== undefined)
74221
+ snapshot.reset = reset;
74222
+ return Object.keys(snapshot).length === 0 ? undefined : snapshot;
74223
+ }
74224
+ function formatRateLimit(limit) {
74225
+ if (limit === undefined) {
74226
+ return;
74227
+ }
74228
+ const parts = [];
74229
+ if (limit.requestsRemaining !== undefined && limit.requestsLimit !== undefined) {
74230
+ parts.push(`${limit.requestsRemaining}/${limit.requestsLimit} requests`);
74231
+ } else if (limit.requestsRemaining !== undefined) {
74232
+ parts.push(`${limit.requestsRemaining} requests left`);
74233
+ } else if (limit.requestsLimit !== undefined) {
74234
+ parts.push(`${limit.requestsLimit} request cap`);
74235
+ }
74236
+ if (limit.tokensRemaining !== undefined && limit.tokensLimit !== undefined) {
74237
+ parts.push(`${limit.tokensRemaining}/${limit.tokensLimit} tokens`);
74238
+ } else if (limit.tokensRemaining !== undefined) {
74239
+ parts.push(`${limit.tokensRemaining} tokens left`);
74240
+ }
74241
+ if (limit.reset !== undefined) {
74242
+ parts.push(`reset ${limit.reset}`);
74243
+ }
74244
+ return parts.length === 0 ? undefined : parts.join(" \xB7 ");
74245
+ }
74246
+ function extractOllamaContextWindow(body) {
74247
+ if (typeof body !== "object" || body === null) {
74248
+ return;
74249
+ }
74250
+ const rec = body;
74251
+ if (typeof rec.parameters === "string") {
74252
+ const match = /\bnum_ctx\s+(\d+)/.exec(rec.parameters);
74253
+ if (match?.[1] !== undefined) {
74254
+ const n = asPositiveInt(match[1]);
74255
+ if (n !== undefined) {
74256
+ return n;
74257
+ }
74258
+ }
74259
+ }
74260
+ const info = rec.model_info;
74261
+ if (typeof info === "object" && info !== null) {
74262
+ for (const [key, value] of Object.entries(info)) {
74263
+ if (key === "context_length" || key.endsWith(".context_length")) {
74264
+ const n = asPositiveInt(value);
74265
+ if (n !== undefined) {
74266
+ return n;
74267
+ }
74268
+ }
74269
+ }
74270
+ }
74271
+ return extractContextWindow(body);
74272
+ }
74273
+ async function timedFetch(fetchFn, url, init, timeoutMs) {
74274
+ const controller = new AbortController;
74275
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
74276
+ try {
74277
+ return await fetchFn(url, { ...init, signal: controller.signal });
74278
+ } catch {
74279
+ return;
74280
+ } finally {
74281
+ clearTimeout(timer);
74282
+ }
74283
+ }
74284
+ function ollamaShowAllowed(baseUrl) {
74285
+ let host;
74286
+ try {
74287
+ host = new URL(baseUrl).hostname;
74288
+ } catch {
74289
+ return false;
74290
+ }
74291
+ if (isPrivateEgressHost(host) && !isLoopbackHost(host)) {
74292
+ return false;
74293
+ }
74294
+ return true;
74295
+ }
74296
+ async function fetchCompatLimits(fetchFn, providerName, model, baseUrl, env, timeoutMs) {
74297
+ const compat = providerByName(providerName);
74298
+ if (compat === undefined) {
74299
+ return {};
74300
+ }
74301
+ const resolved = resolveProviderBaseUrl({ ...compat, ...baseUrl !== undefined ? { baseUrl } : {} }, env);
74302
+ const url = `${resolved.replace(/\/+$/, "")}${compat.modelsPath ?? DEFAULT_MODELS_PATH}`;
74303
+ const apiKey = providerApiKey(compat, env) ?? compat.apiKey;
74304
+ const init = {};
74305
+ if (apiKey !== undefined && apiKey.length > 0) {
74306
+ init.headers = { authorization: `Bearer ${apiKey}` };
74307
+ }
74308
+ const res = await timedFetch(fetchFn, url, init, timeoutMs);
74309
+ if (res === undefined || !res.ok) {
74310
+ return {};
74311
+ }
74312
+ const rateLimit = parseRateLimitHeaders(res.headers);
74313
+ let body;
74314
+ try {
74315
+ body = await res.json();
74316
+ } catch {
74317
+ return rateLimit === undefined ? {} : { rateLimit };
74318
+ }
74319
+ const data = body?.data;
74320
+ const entries = Array.isArray(data) ? data : Array.isArray(body) ? body : [];
74321
+ const entry = findModelEntry(entries, model);
74322
+ const contextWindow = extractContextWindow(entry);
74323
+ return {
74324
+ ...contextWindow !== undefined ? { contextWindow, contextSource: "live-models" } : {},
74325
+ ...rateLimit !== undefined ? { rateLimit } : {}
74326
+ };
74327
+ }
74328
+ async function fetchOllamaLimits(fetchFn, model, baseUrl, timeoutMs) {
74329
+ const resolved = (baseUrl ?? DEFAULT_OLLAMA_BASE_URL2).replace(/\/+$/, "");
74330
+ if (!ollamaShowAllowed(resolved)) {
74331
+ return {};
74332
+ }
74333
+ const res = await timedFetch(fetchFn, `${resolved}/api/show`, {
74334
+ method: "POST",
74335
+ headers: { "content-type": "application/json" },
74336
+ body: JSON.stringify({ name: model })
74337
+ }, timeoutMs);
74338
+ if (res === undefined || !res.ok) {
74339
+ return {};
74340
+ }
74341
+ let body;
74342
+ try {
74343
+ body = await res.json();
74344
+ } catch {
74345
+ return {};
74346
+ }
74347
+ const contextWindow = extractOllamaContextWindow(body);
74348
+ return contextWindow === undefined ? {} : { contextWindow, contextSource: "ollama-show" };
74349
+ }
74350
+ async function loadSessionLimits(input) {
74351
+ const fetchFn = input.fetch ?? globalThis.fetch;
74352
+ const env = envWithSavedApiKeys(input.env ?? process.env);
74353
+ const timeoutMs = input.timeoutMs ?? MODEL_LIMITS_TIMEOUT_MS;
74354
+ const provider = input.provider.trim();
74355
+ const model = input.model.trim();
74356
+ const limitsPromise = provider === "ollama" ? fetchOllamaLimits(fetchFn, model, input.baseUrl, timeoutMs) : providerByName(provider) !== undefined ? fetchCompatLimits(fetchFn, provider, model, input.baseUrl, env, timeoutMs) : Promise.resolve({});
74357
+ const capable = balanceCapableProvider(provider);
74358
+ const balancePromise = capable === undefined ? Promise.resolve(undefined) : fetchProviderBalance(fetchFn, { ...capable, ...input.baseUrl !== undefined ? { baseUrl: input.baseUrl } : {} }, providerApiKey(capable, env) ?? capable.apiKey, { timeoutMs });
74359
+ const [limits, balance] = await Promise.all([limitsPromise, balancePromise]);
74360
+ return {
74361
+ ...limits,
74362
+ ...balance !== undefined ? { balance } : {}
74363
+ };
74364
+ }
74365
+
74366
+ // src/tui/balance-panel.ts
74367
+ init_providers();
74368
+ init_shell_config();
74369
+ function formatBalance(balance) {
74370
+ if (balance === undefined) {
74371
+ return "\u2014";
74372
+ }
74373
+ const amount = balance.remaining ?? balance.total;
74374
+ const symbol = balance.currency === "USD" ? "$" : balance.currency === "EUR" ? "\u20AC" : balance.currency === "GBP" ? "\xA3" : `${balance.currency} `;
74375
+ return `${symbol}${amount.toFixed(2)}`;
74376
+ }
74377
+ function mountBalancePanel(sidebarTop, otui, renderer, options) {
74378
+ const core = otui;
74379
+ const r = renderer;
74380
+ const env = envWithSavedApiKeys(options.env ?? process.env);
74381
+ let current;
74382
+ let textNode;
74383
+ const capable = balanceCapableProvider(options.provider) !== undefined;
74384
+ if (capable) {
74385
+ const label = new core.TextRenderable(r, {
74386
+ id: "sb-balance-k",
74387
+ content: core.t`${core.dim("Balance")}`,
74388
+ marginTop: 1
74389
+ });
74390
+ sidebarTop.add(label);
74391
+ const value = new core.TextRenderable(r, {
74392
+ id: "sb-balance-v",
74393
+ content: core.t`${core.dim("\u2026")}`,
74394
+ onMouseDown: () => {
74395
+ refresh();
74396
+ }
74397
+ });
74398
+ textNode = value;
74399
+ sidebarTop.add(value);
74400
+ }
74401
+ const paint = (balance) => {
74402
+ current = balance;
74403
+ if (textNode !== undefined) {
74404
+ textNode.content = core.t`${core.dim(formatBalance(balance))}`;
74405
+ }
74406
+ };
74407
+ const refresh = async () => {
74408
+ const provider = balanceCapableProvider(options.provider);
74409
+ if (provider === undefined) {
74410
+ paint(undefined);
74411
+ return;
74412
+ }
74413
+ const apiKey = providerApiKey(provider, env);
74414
+ if (apiKey === undefined) {
74415
+ paint(undefined);
74416
+ return;
74417
+ }
74418
+ const base = resolveProviderBaseUrl(provider, env);
74419
+ const withBase = { ...provider, baseUrl: base };
74420
+ const balance = await fetchProviderBalance(options.fetch ?? globalThis.fetch, withBase, apiKey, { ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {} });
74421
+ paint(balance);
74422
+ };
74423
+ refresh();
74424
+ return { refresh, current: () => current };
74425
+ }
74426
+
74088
74427
  // src/tui/context-usage.ts
74089
74428
  var CONTEXT_BAR_WIDTH = 28;
74090
74429
  function lastTurnUsed(usage) {
@@ -74103,6 +74442,14 @@ function renderUsageBar(filled, width = CONTEXT_BAR_WIDTH) {
74103
74442
  const n = Math.max(0, Math.min(width, filled > 0 ? width : 0));
74104
74443
  return `[${"\u2588".repeat(n)}${"\u2591".repeat(width - n)}]`;
74105
74444
  }
74445
+ function renderWindowBar(used, window, width = CONTEXT_BAR_WIDTH) {
74446
+ if (window <= 0) {
74447
+ return renderUsageBar(used, width);
74448
+ }
74449
+ const ratio = Math.max(0, Math.min(1, used / window));
74450
+ const filled = Math.round(ratio * width);
74451
+ return `[${"\u2588".repeat(filled)}${"\u2591".repeat(width - filled)}]`;
74452
+ }
74106
74453
  function buildContextUsage(source) {
74107
74454
  const lastIn = source.usage?.inputTokens;
74108
74455
  const lastOut = source.usage?.outputTokens;
@@ -74110,6 +74457,7 @@ function buildContextUsage(source) {
74110
74457
  const hasEstimate = source.estimateTokens !== undefined;
74111
74458
  const total = hasEstimate ? source.estimateTokens ?? 0 : last ?? 0;
74112
74459
  const estimated = hasEstimate || last === undefined;
74460
+ const window = source.contextWindow !== undefined && source.contextWindow > 0 ? source.contextWindow : undefined;
74113
74461
  const segments = [];
74114
74462
  if (hasEstimate) {
74115
74463
  segments.push({ id: "history", label: "history (est.)", tokens: source.estimateTokens ?? 0 });
@@ -74120,12 +74468,16 @@ function buildContextUsage(source) {
74120
74468
  if (lastOut !== undefined) {
74121
74469
  segments.push({ id: "last-out", label: "last out", tokens: lastOut });
74122
74470
  }
74471
+ if (window !== undefined) {
74472
+ segments.push({ id: "free", label: "free", tokens: Math.max(0, window - total) });
74473
+ }
74123
74474
  return {
74124
74475
  total,
74125
74476
  estimated,
74477
+ ...window !== undefined ? { window, usedPct: Math.min(100, total / window * 100), free: Math.max(0, window - total) } : {},
74126
74478
  segments,
74127
- bar: renderUsageBar(total),
74128
- note: total === 0 ? "No context usage yet." : "No model context window is known. The bar is relative to used tokens, not a billed limit."
74479
+ bar: window === undefined ? renderUsageBar(total) : renderWindowBar(total, window),
74480
+ note: total === 0 ? "No context usage yet." : window === undefined ? "No model context window is known. The bar is relative to used tokens, not a billed limit." : `Context window ${window.toLocaleString()} tokens (provider-reported).`
74129
74481
  };
74130
74482
  }
74131
74483
  function formatContextUsageText(view) {
@@ -74137,7 +74489,8 @@ function formatContextUsageText(view) {
74137
74489
  const kind = view.estimated ? "tokens (estimate)" : "tokens";
74138
74490
  const width = view.segments.reduce((max, segment) => Math.max(max, segment.label.length), 0);
74139
74491
  const rows = view.segments.map((segment) => ` ${segment.label.padEnd(width)} ${segment.tokens}`);
74140
- return [`Context`, ` Used ${view.total} ${kind}`, ` ${view.bar}`, ...rows, "", ` ${view.note}`, ""].join(`
74492
+ const usedLine = view.window !== undefined ? ` Used ${view.total.toLocaleString()} / ${view.window.toLocaleString()} ${kind} (${(view.usedPct ?? 0).toFixed(1)}%)` : ` Used ${view.total} ${kind}`;
74493
+ return [`Context`, usedLine, ` ${view.bar}`, ...rows, "", ` ${view.note}`, ""].join(`
74141
74494
  `);
74142
74495
  }
74143
74496
 
@@ -74182,12 +74535,17 @@ function usageUsed(usage) {
74182
74535
  return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
74183
74536
  }
74184
74537
  function contextRow(source) {
74538
+ const window = source.limits?.contextWindow;
74185
74539
  const used = usageUsed(source.usage);
74186
- if (used !== undefined) {
74187
- return `${used} tokens`;
74540
+ const usedLabel = used !== undefined ? `${used} tokens` : source.estimateTokens !== undefined ? `${source.estimateTokens} tokens (estimate)` : undefined;
74541
+ if (usedLabel !== undefined && window !== undefined) {
74542
+ return `${usedLabel} / ${window.toLocaleString()}`;
74543
+ }
74544
+ if (usedLabel !== undefined) {
74545
+ return usedLabel;
74188
74546
  }
74189
- if (source.estimateTokens !== undefined) {
74190
- return `${source.estimateTokens} tokens (estimate)`;
74547
+ if (window !== undefined) {
74548
+ return `window ${window.toLocaleString()}`;
74191
74549
  }
74192
74550
  return MISSING;
74193
74551
  }
@@ -74218,11 +74576,23 @@ function buildSessionInfoSnapshot(source) {
74218
74576
  label: "Last turn output",
74219
74577
  value: source.usage?.outputTokens === undefined ? MISSING : String(source.usage.outputTokens)
74220
74578
  },
74221
- { label: "Context estimate", value: estimate }
74579
+ { label: "Context estimate", value: estimate },
74580
+ {
74581
+ label: "Context window",
74582
+ value: source.limits?.contextWindow === undefined ? MISSING : source.limits.contextWindow.toLocaleString()
74583
+ }
74222
74584
  ];
74585
+ const rate = formatRateLimit(source.limits?.rateLimit);
74586
+ if (rate !== undefined) {
74587
+ usageRows.push({ label: "Rate limit", value: rate });
74588
+ }
74589
+ if (source.limits?.balance !== undefined) {
74590
+ usageRows.push({ label: "Balance", value: formatBalance(source.limits.balance) });
74591
+ }
74223
74592
  const context = buildContextUsage({
74224
74593
  estimateTokens: source.estimateTokens,
74225
- usage: source.usage
74594
+ usage: source.usage,
74595
+ ...source.limits?.contextWindow !== undefined ? { contextWindow: source.limits.contextWindow } : {}
74226
74596
  });
74227
74597
  const sessionWorkspaces = workspacesInSession(source.workspaces ?? [], {
74228
74598
  sessionId: id,
@@ -74350,7 +74720,7 @@ function presentSessionInfo(openModal, otui, chrome, options) {
74350
74720
  `), width);
74351
74721
  return;
74352
74722
  }
74353
- paintRows(otui, renderer, body, snapshot.sessionRows, width);
74723
+ paintRows(otui, renderer, body, [...snapshot.sessionRows, ...snapshot.usageRows], width);
74354
74724
  },
74355
74725
  onClose: () => {
74356
74726
  unsubscribeKey?.();
@@ -74437,7 +74807,7 @@ var AGENT_SLASH_COMMANDS = [
74437
74807
  { name: "/sessions", description: "Open the session list and switch to one", modes: AGENT_ONLY },
74438
74808
  {
74439
74809
  name: "/status",
74440
- description: "Show session identity, context, workspaces, and flows",
74810
+ description: "Show session identity, context window, limits, workspaces, and flows",
74441
74811
  modes: BOTH
74442
74812
  },
74443
74813
  { name: "/flows", description: "Browse project flows and inspect one", modes: BOTH },
@@ -75568,67 +75938,6 @@ function presentGamesModal(openModalFn, otui, chrome, options = {}, registry = c
75568
75938
  function openGamesModal(otui, chrome, options = {}) {
75569
75939
  return presentGamesModal((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, options);
75570
75940
  }
75571
- // src/tui/balance-panel.ts
75572
- init_providers();
75573
- init_shell_config();
75574
- function formatBalance(balance) {
75575
- if (balance === undefined) {
75576
- return "\u2014";
75577
- }
75578
- const amount = balance.remaining ?? balance.total;
75579
- const symbol = balance.currency === "USD" ? "$" : balance.currency === "EUR" ? "\u20AC" : balance.currency === "GBP" ? "\xA3" : `${balance.currency} `;
75580
- return `${symbol}${amount.toFixed(2)}`;
75581
- }
75582
- function mountBalancePanel(sidebarTop, otui, renderer, options) {
75583
- const core = otui;
75584
- const r = renderer;
75585
- const env = envWithSavedApiKeys(options.env ?? process.env);
75586
- let current;
75587
- let textNode;
75588
- const capable = balanceCapableProvider(options.provider) !== undefined;
75589
- if (capable) {
75590
- const label = new core.TextRenderable(r, {
75591
- id: "sb-balance-k",
75592
- content: core.t`${core.dim("Balance")}`,
75593
- marginTop: 1
75594
- });
75595
- sidebarTop.add(label);
75596
- const value = new core.TextRenderable(r, {
75597
- id: "sb-balance-v",
75598
- content: core.t`${core.dim("\u2026")}`,
75599
- onMouseDown: () => {
75600
- refresh();
75601
- }
75602
- });
75603
- textNode = value;
75604
- sidebarTop.add(value);
75605
- }
75606
- const paint = (balance) => {
75607
- current = balance;
75608
- if (textNode !== undefined) {
75609
- textNode.content = core.t`${core.dim(formatBalance(balance))}`;
75610
- }
75611
- };
75612
- const refresh = async () => {
75613
- const provider = balanceCapableProvider(options.provider);
75614
- if (provider === undefined) {
75615
- paint(undefined);
75616
- return;
75617
- }
75618
- const apiKey = providerApiKey(provider, env);
75619
- if (apiKey === undefined) {
75620
- paint(undefined);
75621
- return;
75622
- }
75623
- const base = resolveProviderBaseUrl(provider, env);
75624
- const withBase = { ...provider, baseUrl: base };
75625
- const balance = await fetchProviderBalance(options.fetch ?? globalThis.fetch, withBase, apiKey, { ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {} });
75626
- paint(balance);
75627
- };
75628
- refresh();
75629
- return { refresh, current: () => current };
75630
- }
75631
-
75632
75941
  // src/tui/tui-shell.ts
75633
75942
  init_providers();
75634
75943
  init_patch_risk();
@@ -81369,12 +81678,18 @@ Staying in the current session.
81369
81678
  (async () => {
81370
81679
  const cwd = inspectorCwd();
81371
81680
  const [workspaces, flows] = await Promise.all([loadInspectorWorkspaces(cwd), loadInspectorFlows(cwd)]);
81681
+ const limits = await loadSessionLimits({
81682
+ provider: currentSel.provider,
81683
+ model: currentSel.model,
81684
+ ...currentSel.baseUrl !== undefined ? { baseUrl: currentSel.baseUrl } : {}
81685
+ });
81372
81686
  const snapshot = buildSessionInfoSnapshot({
81373
81687
  summary: liveSession.summary,
81374
81688
  selection: currentSel,
81375
81689
  version: package_default.version,
81376
81690
  usage: lastUsage,
81377
81691
  estimateTokens: estimateContextTokens(history),
81692
+ limits,
81378
81693
  sessionText: history.map((message) => message.content).join(`
81379
81694
  `),
81380
81695
  workspaces,
@@ -82922,11 +83237,17 @@ ${formatThemeList(getThemeId())}`)}`);
82922
83237
  return;
82923
83238
  }
82924
83239
  const summary = cwd !== undefined ? latestSession(cwd) : undefined;
83240
+ const limits = await loadSessionLimits({
83241
+ provider: selection.provider,
83242
+ model: selection.model,
83243
+ ...selection.baseUrl !== undefined ? { baseUrl: selection.baseUrl } : {}
83244
+ });
82925
83245
  const snapshot = buildSessionInfoSnapshot({
82926
83246
  summary,
82927
83247
  selection,
82928
83248
  version: package_default.version,
82929
83249
  estimateTokens: estimateContextTokens(seen),
83250
+ limits,
82930
83251
  sessionText: seen.map((message) => message.content).join(`
82931
83252
  `),
82932
83253
  workspaces,
@@ -83161,6 +83482,11 @@ Starting a new session.
83161
83482
  selection: { provider: providerName, model: modelName },
83162
83483
  version: package_default.version,
83163
83484
  estimateTokens: estimateContextTokens(history),
83485
+ limits: await loadSessionLimits({
83486
+ provider: providerName,
83487
+ model: modelName,
83488
+ ...baseUrl !== undefined ? { baseUrl } : {}
83489
+ }),
83164
83490
  sessionText: history.map((message) => message.content).join(`
83165
83491
  `),
83166
83492
  workspaces,
@@ -83990,6 +84316,10 @@ New session ${shortSessionId(live.summary.id)}.
83990
84316
  version: package_default.version,
83991
84317
  usage: lastUsage,
83992
84318
  estimateTokens: estimateContextTokens(history),
84319
+ limits: await loadSessionLimits({
84320
+ provider: deps.providerId,
84321
+ model: deps.modelId
84322
+ }),
83993
84323
  sessionText: history.map((message) => message.content).join(`
83994
84324
  `),
83995
84325
  workspaces,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.82",
3
+ "version": "0.2.83",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {