@rynfar/meridian 1.40.0 → 1.41.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.
@@ -1,7 +1,9 @@
1
1
  // src/proxy/tokenRefresh.ts
2
2
  import { execFile as execFileCb } from "child_process";
3
- import { existsSync, readFileSync, writeFileSync } from "fs";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
4
4
  import { homedir, platform, userInfo } from "os";
5
+ import { join, dirname, resolve } from "path";
6
+ import { createHash } from "crypto";
5
7
  import { promisify } from "util";
6
8
 
7
9
  // src/logger.ts
@@ -72,6 +74,20 @@ var OAUTH_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
72
74
  var OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
73
75
  var KEYCHAIN_SERVICE = "Claude Code-credentials";
74
76
  var CREDENTIALS_FILE = `${homedir()}/.claude/.credentials.json`;
77
+ var DEFAULT_CLAUDE_DIR = `${homedir()}/.claude`;
78
+ function configDirToKeychainService(claudeConfigDir) {
79
+ const abs = resolve(claudeConfigDir);
80
+ if (abs === resolve(DEFAULT_CLAUDE_DIR))
81
+ return KEYCHAIN_SERVICE;
82
+ const hash = createHash("sha256").update(abs).digest("hex").slice(0, 8);
83
+ return `${KEYCHAIN_SERVICE}-${hash}`;
84
+ }
85
+ function configDirToCredentialsFile(claudeConfigDir) {
86
+ return join(resolve(claudeConfigDir), ".credentials.json");
87
+ }
88
+ function serializeCredentials(credentials) {
89
+ return JSON.stringify(credentials);
90
+ }
75
91
  function parseKeychainValue(raw) {
76
92
  const trimmed = raw.trim();
77
93
  try {
@@ -83,57 +99,74 @@ function parseKeychainValue(raw) {
83
99
  } catch {}
84
100
  return null;
85
101
  }
86
- var keychainWasHex = false;
87
- var macosStore = {
88
- async read() {
89
- try {
90
- const { stdout } = await execFile("/usr/bin/security", ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-a", userInfo().username, "-w"], { timeout: 5000 });
91
- const parsed = parseKeychainValue(stdout);
92
- if (!parsed)
93
- throw new Error("Could not parse keychain value as JSON or hex-encoded JSON");
94
- keychainWasHex = parsed.wasHex;
95
- return parsed.credentials;
96
- } catch (err) {
97
- claudeLog("token_refresh.keychain_read_failed", { error: String(err) });
98
- return null;
99
- }
100
- },
101
- async write(credentials) {
102
- const json = JSON.stringify(credentials, null, 2);
103
- const value = keychainWasHex ? Buffer.from(json).toString("hex") : json;
104
- try {
105
- await execFile("/usr/bin/security", ["add-generic-password", "-U", "-s", KEYCHAIN_SERVICE, "-a", userInfo().username, "-w", value], { timeout: 5000 });
106
- return true;
107
- } catch (err) {
108
- claudeLog("token_refresh.keychain_write_failed", { error: String(err) });
109
- return false;
102
+ var keychainWasHexByService = new Map;
103
+ function buildMacosStore(serviceName) {
104
+ return {
105
+ async read() {
106
+ try {
107
+ const { stdout } = await execFile("/usr/bin/security", ["find-generic-password", "-s", serviceName, "-a", userInfo().username, "-w"], { timeout: 5000 });
108
+ const parsed = parseKeychainValue(stdout);
109
+ if (!parsed)
110
+ throw new Error("Could not parse keychain value as JSON or hex-encoded JSON");
111
+ keychainWasHexByService.set(serviceName, parsed.wasHex);
112
+ return parsed.credentials;
113
+ } catch (err) {
114
+ claudeLog("token_refresh.keychain_read_failed", { service: serviceName, error: String(err) });
115
+ return null;
116
+ }
117
+ },
118
+ async write(credentials) {
119
+ const json = serializeCredentials(credentials);
120
+ const wasHex = keychainWasHexByService.get(serviceName) ?? false;
121
+ const value = wasHex ? Buffer.from(json).toString("hex") : json;
122
+ try {
123
+ await execFile("/usr/bin/security", ["add-generic-password", "-U", "-s", serviceName, "-a", userInfo().username, "-w", value], { timeout: 5000 });
124
+ return true;
125
+ } catch (err) {
126
+ claudeLog("token_refresh.keychain_write_failed", { service: serviceName, error: String(err) });
127
+ return false;
128
+ }
110
129
  }
111
- }
112
- };
113
- var fileStore = {
114
- async read() {
115
- try {
116
- if (!existsSync(CREDENTIALS_FILE))
130
+ };
131
+ }
132
+ var macosStore = buildMacosStore(KEYCHAIN_SERVICE);
133
+ function buildFileStore(filePath) {
134
+ return {
135
+ async read() {
136
+ try {
137
+ if (!existsSync(filePath))
138
+ return null;
139
+ return JSON.parse(readFileSync(filePath, "utf-8"));
140
+ } catch (err) {
141
+ claudeLog("token_refresh.file_read_failed", { path: filePath, error: String(err) });
117
142
  return null;
118
- return JSON.parse(readFileSync(CREDENTIALS_FILE, "utf-8"));
119
- } catch (err) {
120
- claudeLog("token_refresh.file_read_failed", { error: String(err) });
121
- return null;
143
+ }
144
+ },
145
+ async write(credentials) {
146
+ try {
147
+ mkdirSync(dirname(filePath), { recursive: true });
148
+ writeFileSync(filePath, serializeCredentials(credentials), "utf-8");
149
+ return true;
150
+ } catch (err) {
151
+ claudeLog("token_refresh.file_write_failed", { path: filePath, error: String(err) });
152
+ return false;
153
+ }
122
154
  }
123
- },
124
- async write(credentials) {
125
- try {
126
- writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), "utf-8");
127
- return true;
128
- } catch (err) {
129
- claudeLog("token_refresh.file_write_failed", { error: String(err) });
130
- return false;
155
+ };
156
+ }
157
+ var fileStore = buildFileStore(CREDENTIALS_FILE);
158
+ function createPlatformCredentialStore(opts) {
159
+ if (opts?.claudeConfigDir) {
160
+ if (platform() === "darwin") {
161
+ return buildMacosStore(configDirToKeychainService(opts.claudeConfigDir));
131
162
  }
163
+ return buildFileStore(configDirToCredentialsFile(opts.claudeConfigDir));
132
164
  }
133
- };
134
- function createPlatformCredentialStore() {
135
165
  return platform() === "darwin" ? macosStore : fileStore;
136
166
  }
167
+ function credentialsFilePathForProfile(claudeConfigDir) {
168
+ return claudeConfigDir ? configDirToCredentialsFile(claudeConfigDir) : CREDENTIALS_FILE;
169
+ }
137
170
  var inflightRefresh = null;
138
171
  async function refreshOAuthToken(store) {
139
172
  if (inflightRefresh)
@@ -200,4 +233,4 @@ function resetInflightRefresh() {
200
233
  inflightRefresh = null;
201
234
  }
202
235
 
203
- export { withClaudeLogContext, claudeLog, createPlatformCredentialStore, refreshOAuthToken, resetInflightRefresh };
236
+ export { withClaudeLogContext, claudeLog, configDirToKeychainService, configDirToCredentialsFile, serializeCredentials, createPlatformCredentialStore, credentialsFilePathForProfile, refreshOAuthToken, resetInflightRefresh };
package/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-3azh7s3k.js";
4
+ } from "./cli-51a9sav0.js";
5
5
  import"./cli-vdp9s10c.js";
6
6
  import"./cli-sry5aqdj.js";
7
7
  import"./cli-4rqtm83g.js";
8
8
  import"./cli-340h1chz.js";
9
9
  import"./cli-rtab0qa6.js";
10
- import"./cli-m9pfb7h9.js";
10
+ import"./cli-e289rj3k.js";
11
11
  import {
12
12
  __require
13
13
  } from "./cli-p9swy5t3.js";
@@ -89,7 +89,7 @@ Restart OpenCode for the plugin to take effect.`);
89
89
  process.exit(0);
90
90
  }
91
91
  if (args[0] === "refresh-token") {
92
- const { refreshOAuthToken } = await import("./tokenRefresh-y7d1qvb3.js");
92
+ const { refreshOAuthToken } = await import("./tokenRefresh-psq94r54.js");
93
93
  const success = await refreshOAuthToken();
94
94
  if (success) {
95
95
  console.log("Token refreshed successfully");
@@ -9,6 +9,19 @@ import"./cli-p9swy5t3.js";
9
9
 
10
10
  // src/telemetry/profilePage.ts
11
11
  init_profileBar();
12
+
13
+ // src/telemetry/profileUsage.ts
14
+ var WINDOW_LABELS = {
15
+ five_hour: "5h",
16
+ seven_day: "7d",
17
+ seven_day_opus: "7d Opus",
18
+ seven_day_sonnet: "7d Sonnet",
19
+ seven_day_oauth_apps: "7d Apps",
20
+ seven_day_cowork: "7d Cowork",
21
+ seven_day_omelette: "7d Omelette"
22
+ };
23
+
24
+ // src/telemetry/profilePage.ts
12
25
  var profilePageHtml = `<!DOCTYPE html>
13
26
  <html lang="en">
14
27
  <head>
@@ -92,6 +105,48 @@ var profilePageHtml = `<!DOCTYPE html>
92
105
  }
93
106
  .copy-btn:hover { border-color: var(--accent); color: var(--accent); }
94
107
  .copy-btn.copied { color: var(--green); border-color: var(--green); }
108
+
109
+ /* OAuth usage panel — one block per profile, mirrors pylon's quota strip. */
110
+ .usage-section { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
111
+ .usage-section-title {
112
+ font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px;
113
+ margin-bottom: 10px; display: flex; align-items: center; gap: 8px;
114
+ }
115
+ .usage-as-of { font-size: 10px; color: var(--muted); text-transform: none; letter-spacing: 0; opacity: 0.7; }
116
+ .usage-grid {
117
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
118
+ gap: 8px;
119
+ }
120
+ .usage-card {
121
+ background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
122
+ padding: 8px 10px; min-width: 0;
123
+ }
124
+ .usage-row {
125
+ display: flex; justify-content: space-between; align-items: baseline;
126
+ font-size: 11px; gap: 8px; margin-bottom: 6px;
127
+ }
128
+ .usage-label { color: var(--muted); font-weight: 500; white-space: nowrap; }
129
+ .usage-pct { font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-weight: 600; font-size: 12px; }
130
+ .usage-bar {
131
+ height: 4px; background: rgba(127,127,127,0.18); border-radius: 2px; overflow: hidden;
132
+ margin-bottom: 4px;
133
+ }
134
+ .usage-fill { height: 100%; transition: width 0.4s ease; background: var(--green); }
135
+ .usage-card.status-warn .usage-fill,
136
+ .usage-card.status-warn .usage-pct { color: var(--yellow); }
137
+ .usage-card.status-warn .usage-fill { background: var(--yellow); }
138
+ .usage-card.status-high .usage-fill,
139
+ .usage-card.status-high .usage-pct { color: var(--red); }
140
+ .usage-card.status-high .usage-fill { background: var(--red); }
141
+ .usage-reset { font-size: 10px; color: var(--muted); white-space: nowrap; }
142
+ .usage-extra {
143
+ margin-top: 8px; padding: 8px 10px; background: var(--bg); border: 1px solid var(--border);
144
+ border-radius: 6px; font-size: 11px;
145
+ }
146
+ .usage-extra-row { display: flex; justify-content: space-between; gap: 8px; }
147
+ .usage-empty {
148
+ font-size: 11px; color: var(--muted); padding: 6px 0; font-style: italic;
149
+ }
95
150
  ` + profileBarCss + `
96
151
  </style>
97
152
  </head>
@@ -145,11 +200,73 @@ var profilePageHtml = `<!DOCTYPE html>
145
200
  </div>
146
201
 
147
202
  <script>
203
+ // Inlined from src/telemetry/profileUsage.ts. The TS source is unit-tested
204
+ // (see profile-usage.test.ts) and the labels object is interpolated here so
205
+ // the browser script and TS module share their data.
206
+ var WINDOW_LABELS = ${JSON.stringify(WINDOW_LABELS)};
207
+
208
+ function labelForWindow(type) {
209
+ if (WINDOW_LABELS[type]) return WINDOW_LABELS[type];
210
+ return String(type || '').split('_').map(function (p) {
211
+ return p.length > 0 ? p[0].toUpperCase() + p.slice(1) : p;
212
+ }).join(' ');
213
+ }
214
+
215
+ function classifyUtilization(u) {
216
+ if (u == null || !isFinite(u)) return 'ok';
217
+ if (u >= 0.85) return 'high';
218
+ if (u >= 0.6) return 'warn';
219
+ return 'ok';
220
+ }
221
+
222
+ function formatResetCountdown(resetsAt) {
223
+ if (resetsAt == null || !isFinite(resetsAt)) return '';
224
+ var ms = resetsAt - Date.now();
225
+ if (ms <= 0) return 'resetting…';
226
+ var minutes = Math.floor(ms / 60000);
227
+ if (minutes < 60) return 'in ' + Math.max(1, minutes) + 'm';
228
+ var hours = Math.floor(minutes / 60);
229
+ var remMin = minutes % 60;
230
+ if (hours < 24) return remMin > 0 ? 'in ' + hours + 'h ' + remMin + 'm' : 'in ' + hours + 'h';
231
+ var days = Math.floor(hours / 24);
232
+ var remHr = hours % 24;
233
+ return remHr > 0 ? 'in ' + days + 'd ' + remHr + 'h' : 'in ' + days + 'd';
234
+ }
235
+
236
+ function formatExtraUsage(eu) {
237
+ if (!eu || !eu.isEnabled) return null;
238
+ var monthlyLimit = isFinite(eu.monthlyLimit) ? eu.monthlyLimit : 0;
239
+ if (monthlyLimit <= 0) return null;
240
+ var used = isFinite(eu.usedCredits) ? eu.usedCredits : 0;
241
+ var utilization = (eu.utilization != null && isFinite(eu.utilization))
242
+ ? Math.max(0, Math.min(1, eu.utilization))
243
+ : (monthlyLimit > 0 ? Math.max(0, Math.min(1, used / monthlyLimit)) : 0);
244
+ var currency = eu.currency || '';
245
+ return {
246
+ used: (currency + used.toFixed(2)).trim(),
247
+ limit: (currency + monthlyLimit.toFixed(2)).trim(),
248
+ utilizationPct: Math.round(utilization * 100),
249
+ status: classifyUtilization(utilization),
250
+ };
251
+ }
252
+
253
+ // Cache the last seen quota response so the /profiles/list refresh can
254
+ // keep showing usage even if a single /v1/usage/quota/all call fails.
255
+ var lastQuota = null;
256
+
148
257
  async function refresh() {
149
258
  try {
150
- const res = await fetch('/profiles/list');
151
- const data = await res.json();
152
- render(data);
259
+ var [profilesRes, quotaRes] = await Promise.all([
260
+ fetch('/profiles/list'),
261
+ fetch('/v1/usage/quota/all').catch(function () { return null; }),
262
+ ]);
263
+ var profiles = await profilesRes.json();
264
+ var quota = null;
265
+ if (quotaRes && quotaRes.ok) {
266
+ try { quota = await quotaRes.json(); } catch (_) { quota = null; }
267
+ }
268
+ if (quota) lastQuota = quota;
269
+ render(profiles, lastQuota);
153
270
  } catch {
154
271
  document.getElementById('content').innerHTML = '<div class="empty-state"><h2>Could not load profiles</h2><p>Is Meridian running?</p></div>';
155
272
  }
@@ -157,9 +274,82 @@ async function refresh() {
157
274
 
158
275
  function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
159
276
 
160
- function render(data) {
277
+ function renderUsageSection(profileQuota) {
278
+ // No quota data for this profile yet (cold start or fetch failed) — hide
279
+ // entirely so we don't render an empty box.
280
+ if (!profileQuota) return '';
281
+ // API-key profiles cannot use OAuth usage — silently omit.
282
+ if (profileQuota.error === 'not_oauth') return '';
283
+
284
+ var windows = (profileQuota.windows || []).filter(function (w) {
285
+ return typeof w.utilization === 'number';
286
+ });
287
+ var extra = formatExtraUsage(profileQuota.extraUsage);
288
+
289
+ if (windows.length === 0 && !extra) {
290
+ if (profileQuota.error === 'no_token') {
291
+ return '<div class="usage-section">'
292
+ + '<div class="usage-section-title">Usage</div>'
293
+ + '<div class="usage-empty">Run <code style="background:var(--bg);padding:1px 5px;border-radius:3px">claude login</code> to see usage.</div>'
294
+ + '</div>';
295
+ }
296
+ return ''; // nothing fetched yet
297
+ }
298
+
299
+ var asOf = profileQuota.fetchedAt
300
+ ? '<span class="usage-as-of">updated ' + timeAgo(profileQuota.fetchedAt) + '</span>'
301
+ : '';
302
+
303
+ var cards = windows.map(function (w) {
304
+ var pct = Math.max(0, Math.min(1, w.utilization));
305
+ var pctRound = Math.round(pct * 100);
306
+ var status = classifyUtilization(pct);
307
+ var label = labelForWindow(w.type);
308
+ var reset = formatResetCountdown(w.resetsAt);
309
+ var tip = label + ' — ' + pctRound + '%' + (reset ? ' (resets ' + reset + ')' : '');
310
+ return '<div class="usage-card status-' + esc(status) + '" title="' + esc(tip) + '">'
311
+ + '<div class="usage-row">'
312
+ + '<span class="usage-label">' + esc(label) + '</span>'
313
+ + '<span class="usage-pct">' + pctRound + '%</span>'
314
+ + '</div>'
315
+ + '<div class="usage-bar"><div class="usage-fill" style="width:' + (pct * 100).toFixed(1) + '%"></div></div>'
316
+ + (reset ? '<div class="usage-reset">' + esc(reset) + '</div>' : '')
317
+ + '</div>';
318
+ }).join('');
319
+
320
+ var extraBlock = '';
321
+ if (extra) {
322
+ extraBlock = '<div class="usage-extra status-' + esc(extra.status) + '">'
323
+ + '<div class="usage-extra-row">'
324
+ + '<span class="usage-label">Extra usage</span>'
325
+ + '<span class="usage-pct">' + extra.utilizationPct + '%</span>'
326
+ + '</div>'
327
+ + '<div class="usage-bar"><div class="usage-fill" style="width:' + extra.utilizationPct + '%"></div></div>'
328
+ + '<div class="usage-extra-row" style="margin-top:4px">'
329
+ + '<span class="usage-reset">' + esc(extra.used) + ' / ' + esc(extra.limit) + '</span>'
330
+ + '</div>'
331
+ + '</div>';
332
+ }
333
+
334
+ return '<div class="usage-section">'
335
+ + '<div class="usage-section-title">Usage' + asOf + '</div>'
336
+ + (cards ? '<div class="usage-grid">' + cards + '</div>' : '')
337
+ + extraBlock
338
+ + '</div>';
339
+ }
340
+
341
+ function render(data, quotaData) {
161
342
  const profiles = data.profiles || [];
162
343
  const active = data.activeProfile;
344
+ // Build quick lookup: profileId -> per-profile quota entry from
345
+ // /v1/usage/quota/all. Endpoint may be unavailable (older Meridian)
346
+ // or have errored — in that case quotaById is empty and the per-card
347
+ // renderer simply hides its usage section.
348
+ const quotaProfiles = (quotaData && Array.isArray(quotaData.profiles)) ? quotaData.profiles : [];
349
+ const quotaById = {};
350
+ for (var qi = 0; qi < quotaProfiles.length; qi++) {
351
+ quotaById[quotaProfiles[qi].id] = quotaProfiles[qi];
352
+ }
163
353
 
164
354
  if (profiles.length === 0) {
165
355
  document.getElementById('content').innerHTML = '<div class="empty-state">'
@@ -218,6 +408,8 @@ function render(data) {
218
408
  html += '</button>';
219
409
  html += '</div>';
220
410
 
411
+ html += renderUsageSection(quotaById[p.id]);
412
+
221
413
  if (!isActive) {
222
414
  html += '<button class="switch-btn" onclick="switchProfile(&quot;'+esc(p.id)+'&quot;)">Switch to ' + esc(p.id) + '</button>';
223
415
  } else {
@@ -1 +1 @@
1
- {"version":3,"file":"droid.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/droid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAwC9C,eAAO,MAAM,YAAY,EAAE,YA8E1B,CAAA;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"droid.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/droid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAwC9C,eAAO,MAAM,YAAY,EAAE,YAmF1B,CAAA;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,CAAA"}
@@ -50,4 +50,6 @@ export declare function mapModelTier(model?: string): "sonnet" | "opus" | "opus[
50
50
  * @param mcpToolNames - Optional list of MCP tool names to make available to agents
51
51
  */
52
52
  export declare function buildAgentDefinitions(taskDescription: string, mcpToolNames?: string[]): Record<string, AgentDefinition>;
53
+ export declare function parseAgentNamesFromSchema(taskTool: unknown): string[];
54
+ export declare function buildAgentDefinitionsFromTool(taskTool: unknown, mcpToolNames?: string[]): Record<string, AgentDefinition>;
53
55
  //# sourceMappingURL=agentDefs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agentDefs.d.ts","sourceRoot":"","sources":["../../src/proxy/agentDefs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,YAAY,CAAA;AAc5C,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IAC/C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcnF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAOjG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAsBjC"}
1
+ {"version":3,"file":"agentDefs.d.ts","sourceRoot":"","sources":["../../src/proxy/agentDefs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,YAAY,CAAA;AAc5C,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IAC/C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAcnF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAOjG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAsBjC;AA4ED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAIrE;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,OAAO,EACjB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAuBjC"}
@@ -38,4 +38,46 @@ export declare function isRateLimitError(errMsg: string): boolean;
38
38
  * sonnet[1m] or opus[1m]. The fix is to fall back to the base model.
39
39
  */
40
40
  export declare function isExtraUsageRequiredError(errMsg: string): boolean;
41
+ /**
42
+ * Structured SDK-termination metadata extracted from raw error text.
43
+ * Used by diagnosticLog to surface why the SDK subprocess ended (max_turns,
44
+ * exit, abort) plus the captured stderr tail — info that classifyError
45
+ * collapses into a generic api_error.
46
+ */
47
+ export interface SdkTermination {
48
+ reason: "max_turns" | "process_exit" | "aborted" | "unknown";
49
+ /** Turn count when reason=max_turns and parseable. */
50
+ turns?: number;
51
+ /** Exit code when reason=process_exit and parseable. */
52
+ exitCode?: number;
53
+ /** Captured "Subprocess stderr: …" tail (truncated). */
54
+ stderrTail?: string;
55
+ /** Truncated raw error message — set only when reason="unknown" so the log
56
+ * line stays self-contained for unrecognized SDK errors (e.g. so we can
57
+ * add a new pattern next time). */
58
+ rawTail?: string;
59
+ }
60
+ /**
61
+ * Parse the raw error message thrown by the Claude Agent SDK into structured
62
+ * termination metadata. Pure function — no I/O.
63
+ *
64
+ * Returns reason="unknown" when the message doesn't match any recognized
65
+ * pattern; callers can still log it with whatever surrounding context they have.
66
+ */
67
+ export declare function extractSdkTermination(errMsg: string): SdkTermination;
68
+ /**
69
+ * Render an SdkTermination plus request context as a single greppable log line.
70
+ * Matches the key=value style used by token-health diagnostic messages so all
71
+ * /telemetry/logs entries are uniform.
72
+ *
73
+ * Session IDs are truncated to 8 chars to keep lines short — full IDs are
74
+ * already on the parent telemetry record.
75
+ */
76
+ export declare function formatSdkTermination(t: SdkTermination, ctx: {
77
+ model?: string;
78
+ requestSource?: string;
79
+ isResume?: boolean;
80
+ hasDeferredTools?: boolean;
81
+ sdkSessionId?: string;
82
+ }): string;
41
83
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CA+G7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGjE"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CA+G7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAA;IAC5D,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;wCAEoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAuBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAuCpE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GACA,MAAM,CAYR"}
@@ -82,6 +82,31 @@ export declare function getClaudeAuthStatusAsync(profileId?: string, envOverride
82
82
  * The promise is cleared in `finally` to allow retry on failure while
83
83
  * cachedClaudePath prevents re-resolution on success.
84
84
  */
85
+ /**
86
+ * Resolver step contract — each tries one source, returns a path on success
87
+ * or null on miss. Failures (thrown errors) are caught by the caller and
88
+ * treated as misses so unresolved sources never block subsequent steps.
89
+ */
90
+ type ResolverDeps = {
91
+ existsSync: (p: string) => boolean;
92
+ statSync: (p: string) => {
93
+ size: number;
94
+ };
95
+ exec: (cmd: string) => Promise<{
96
+ stdout: string;
97
+ }>;
98
+ resolvePackage: (specifier: string) => string;
99
+ envGet: (name: string) => string | undefined;
100
+ platform: NodeJS.Platform;
101
+ arch: string;
102
+ isBun: boolean;
103
+ };
104
+ /**
105
+ * Pure resolver — runs each step and returns the first hit, or null when
106
+ * all steps miss. Exported for unit tests; production callers use
107
+ * resolveClaudeExecutableAsync, which adds caching on top.
108
+ */
109
+ export declare function resolveClaudeExecutable(deps?: ResolverDeps): Promise<string | null>;
85
110
  export declare function resolveClaudeExecutableAsync(): Promise<string>;
86
111
  /** Reset cached path — for testing only */
87
112
  export declare function resetCachedClaudePath(): void;
@@ -96,4 +121,5 @@ export declare function expireAuthStatusCache(): void;
96
121
  * This happens when the client disconnects mid-stream.
97
122
  */
98
123
  export declare function isClosedControllerError(error: unknown): boolean;
124
+ export {};
99
125
  //# sourceMappingURL=models.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AAEjF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,oBAAoB,CAAA;AACrD,eAAO,MAAM,sBAAsB,sBAAsB,CAAA;AACzD,eAAO,MAAM,qBAAqB,qBAAqB,CAAA;AAEvD;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMhE;AACD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA0BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CA8B7H;AAWD;;;;;;GAMG;AACH,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAG3D;AAED,0EAA0E;AAC1E,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAIpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAE9D;AAaD;gFACgF;AAChF,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAOzH;AAWD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAuD1I;AAOD;;;;;;;;;;GAUG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,CAyDpE;AAED,2CAA2C;AAC3C,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,IAAI,IAAI,CAOlD;AAED;;6DAE6D;AAC7D,wBAAgB,qBAAqB,IAAI,IAAI,CAO5C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG/D"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAoBH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AAEjF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,oBAAoB,CAAA;AACrD,eAAO,MAAM,sBAAsB,sBAAsB,CAAA;AACzD,eAAO,MAAM,qBAAqB,qBAAqB,CAAA;AAEvD;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMhE;AACD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA0BD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CA8B7H;AAWD;;;;;;GAMG;AACH,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,IAAI,OAAO,CAG3D;AAED,0EAA0E;AAC1E,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAIpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAE9D;AAaD;gFACgF;AAChF,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAOzH;AAWD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAuD1I;AAOD;;;;;;;;;;GAUG;AACH;;;;GAIG;AACH,KAAK,YAAY,GAAG;IAClB,UAAU,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAClC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACzC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClD,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;IAC7C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC5C,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AA4HD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,GAAE,YAA2B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQvG;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,MAAM,CAAC,CAqBpE;AAED,2CAA2C;AAC3C,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,IAAI,IAAI,CAOlD;AAED;;6DAE6D;AAC7D,wBAAgB,qBAAqB,IAAI,IAAI,CAO5C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG/D"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Continuous OAuth usage fetching from Anthropic's private OAuth endpoint.
3
+ *
4
+ * Anthropic exposes `GET https://api.anthropic.com/api/oauth/usage` for OAuth
5
+ * (Claude Max) subscribers. Unlike the SDK's `rate_limit_event` (which only
6
+ * populates `utilization` near `allowed_warning` / `rejected`), this endpoint
7
+ * always returns continuous percentage values for every active rate-limit
8
+ * window — exactly what claude.ai's own UI uses.
9
+ *
10
+ * Headers required:
11
+ * Authorization: Bearer <oauth-access-token>
12
+ * anthropic-beta: oauth-2025-04-20
13
+ *
14
+ * We reuse `tokenRefresh.ts`'s cross-platform credential store (macOS Keychain
15
+ * or `~/.claude/.credentials.json`) to read the access token, and trigger a
16
+ * background refresh on 401.
17
+ *
18
+ * Per-profile caching: each profile has its own 30s TTL cache so multi-account
19
+ * setups can be queried independently without cross-contamination. Concurrent
20
+ * callers for the same profile share a single in-flight request.
21
+ */
22
+ import { type CredentialStore } from "./tokenRefresh";
23
+ export interface OAuthUsageWindow {
24
+ type: string;
25
+ utilization: number | null;
26
+ resetsAt: number | null;
27
+ }
28
+ export interface OAuthExtraUsageInfo {
29
+ isEnabled: boolean;
30
+ monthlyLimit: number;
31
+ usedCredits: number;
32
+ utilization: number | null;
33
+ currency: string;
34
+ }
35
+ export interface OAuthUsageSnapshot {
36
+ windows: OAuthUsageWindow[];
37
+ extraUsage: OAuthExtraUsageInfo | null;
38
+ fetchedAt: number;
39
+ }
40
+ /**
41
+ * Fetch latest OAuth usage for a specific profile (or the default OAuth
42
+ * account if none specified). Returns null if no OAuth token is available
43
+ * or the upstream call fails (after one refresh attempt).
44
+ *
45
+ * Per-profile in-process cache (30s TTL by default) prevents hammering
46
+ * Anthropic's endpoint when many clients poll concurrently. Concurrent
47
+ * callers for the same profile share a single in-flight request.
48
+ *
49
+ * @param ttlMs Override the cache TTL (default 30s).
50
+ * @param force Bypass the cache and fetch fresh.
51
+ * @param store Override the credential store (for testing).
52
+ * @param profileId Logical profile identifier used as the cache key.
53
+ * Pass null/undefined for the default OAuth account.
54
+ * @param claudeConfigDir When provided, reads credentials from this dir's
55
+ * keychain entry (macOS) or `.credentials.json`
56
+ * (Linux) instead of the platform default.
57
+ */
58
+ export declare function fetchOAuthUsage(opts?: {
59
+ ttlMs?: number;
60
+ force?: boolean;
61
+ store?: CredentialStore;
62
+ profileId?: string | null;
63
+ claudeConfigDir?: string;
64
+ }): Promise<OAuthUsageSnapshot | null>;
65
+ /** Test-only / shutdown helper — clears all cached snapshots and pending fetches. */
66
+ export declare function resetOAuthUsageCache(): void;
67
+ //# sourceMappingURL=oauthUsage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthUsage.d.ts","sourceRoot":"","sources":["../../src/proxy/oauthUsage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAoD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAgCvG,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB;AA0ED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,eAAe,CAAC,IAAI,CAAC,EAAE;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAgDrC;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}