@rynfar/meridian 1.72.0 → 1.74.0

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 (177) hide show
  1. package/README.md +119 -107
  2. package/assets/banner.svg +18 -43
  3. package/assets/how-it-works.svg +40 -86
  4. package/assets/providers/README.md +10 -0
  5. package/assets/providers/antigravity.png +0 -0
  6. package/assets/providers/claude.png +0 -0
  7. package/dist/antigravity-clients/opencode.js +173 -0
  8. package/dist/antigravity-clients/pi.js +21 -0
  9. package/dist/antigravitySetup-35pd5xqn.js +191 -0
  10. package/dist/cli-1jstkdj9.js +30 -0
  11. package/dist/{cli-9e5cxp89.js → cli-2h3w5n6g.js} +163 -65
  12. package/dist/cli-3zwfvexc.js +56 -0
  13. package/dist/{cli-hxxy0m1z.js → cli-82e2m44k.js} +2 -1
  14. package/dist/cli-8sw5wr9j.js +136 -0
  15. package/dist/{cli-khhjyk04.js → cli-chsp880k.js} +214 -21
  16. package/dist/cli-dh0a1bw4.js +619 -0
  17. package/dist/cli-eehk3052.js +57 -0
  18. package/dist/{cli-ab7zkp1a.js → cli-fveyj34s.js} +32261 -18952
  19. package/dist/cli-grfp0s1j.js +330 -0
  20. package/dist/cli-qpfky8wy.js +133 -0
  21. package/dist/{cli-hj61zpt7.js → cli-wbsmhxcx.js} +26 -2
  22. package/dist/cli.js +111 -38
  23. package/dist/cliDashboard-c0dvkj4y.js +325 -0
  24. package/dist/configDir.d.ts +30 -0
  25. package/dist/configDir.d.ts.map +1 -0
  26. package/dist/organizationName-90f0atkn.js +29 -0
  27. package/dist/{pluginPage-f5gdmbxc.js → pluginPage-pp72d2j8.js} +1 -1
  28. package/dist/{profileCli-1eecbmgk.js → profileCli-1bfgc05h.js} +13 -3
  29. package/dist/{profilePage-mamej7kp.js → profilePage-vs319rvf.js} +252 -60
  30. package/dist/profileRename-4a4km6g8.js +22 -0
  31. package/dist/{profiles-wch9h234.js → profiles-0mp32ek9.js} +16 -3
  32. package/dist/proxy/adapterInstances.d.ts +1 -1
  33. package/dist/proxy/adapterInstances.d.ts.map +1 -1
  34. package/dist/proxy/adapters/claudecode.d.ts.map +1 -1
  35. package/dist/proxy/adapters/droid.d.ts.map +1 -1
  36. package/dist/proxy/auth.d.ts +2 -0
  37. package/dist/proxy/auth.d.ts.map +1 -1
  38. package/dist/proxy/authDiscovery.d.ts +52 -0
  39. package/dist/proxy/authDiscovery.d.ts.map +1 -0
  40. package/dist/proxy/backends/antigravity.d.ts +8 -0
  41. package/dist/proxy/backends/antigravity.d.ts.map +1 -0
  42. package/dist/proxy/backends/antigravityAttachments.d.ts +16 -0
  43. package/dist/proxy/backends/antigravityAttachments.d.ts.map +1 -0
  44. package/dist/proxy/backends/antigravityGrammar.d.ts +12 -0
  45. package/dist/proxy/backends/antigravityGrammar.d.ts.map +1 -0
  46. package/dist/proxy/backends/antigravityJobs.d.ts +19 -0
  47. package/dist/proxy/backends/antigravityJobs.d.ts.map +1 -0
  48. package/dist/proxy/backends/antigravityMedia.d.ts +9 -0
  49. package/dist/proxy/backends/antigravityMedia.d.ts.map +1 -0
  50. package/dist/proxy/backends/antigravityNative.d.ts +8 -0
  51. package/dist/proxy/backends/antigravityNative.d.ts.map +1 -0
  52. package/dist/proxy/backends/antigravityOpenai.d.ts +9 -0
  53. package/dist/proxy/backends/antigravityOpenai.d.ts.map +1 -0
  54. package/dist/proxy/backends/antigravityOpenaiMedia.d.ts +9 -0
  55. package/dist/proxy/backends/antigravityOpenaiMedia.d.ts.map +1 -0
  56. package/dist/proxy/backends/antigravityPlugins.d.ts +41 -0
  57. package/dist/proxy/backends/antigravityPlugins.d.ts.map +1 -0
  58. package/dist/proxy/backends/antigravityProbe.d.ts +16 -0
  59. package/dist/proxy/backends/antigravityProbe.d.ts.map +1 -0
  60. package/dist/proxy/backends/antigravityProcess.d.ts +4 -0
  61. package/dist/proxy/backends/antigravityProcess.d.ts.map +1 -0
  62. package/dist/proxy/backends/antigravityProtocol.d.ts +582 -0
  63. package/dist/proxy/backends/antigravityProtocol.d.ts.map +1 -0
  64. package/dist/proxy/backends/antigravityReplay.d.ts +41 -0
  65. package/dist/proxy/backends/antigravityReplay.d.ts.map +1 -0
  66. package/dist/proxy/backends/antigravityResponses.d.ts +35 -0
  67. package/dist/proxy/backends/antigravityResponses.d.ts.map +1 -0
  68. package/dist/proxy/backends/antigravityRuntime.d.ts +189 -0
  69. package/dist/proxy/backends/antigravityRuntime.d.ts.map +1 -0
  70. package/dist/proxy/backends/antigravitySchema.d.ts +10 -0
  71. package/dist/proxy/backends/antigravitySchema.d.ts.map +1 -0
  72. package/dist/proxy/backends/antigravitySessions.d.ts +28 -0
  73. package/dist/proxy/backends/antigravitySessions.d.ts.map +1 -0
  74. package/dist/proxy/backends/antigravityState.d.ts +22 -0
  75. package/dist/proxy/backends/antigravityState.d.ts.map +1 -0
  76. package/dist/proxy/backends/antigravityStops.d.ts +10 -0
  77. package/dist/proxy/backends/antigravityStops.d.ts.map +1 -0
  78. package/dist/proxy/backends/antigravityTokens.d.ts +14 -0
  79. package/dist/proxy/backends/antigravityTokens.d.ts.map +1 -0
  80. package/dist/proxy/backends/antigravityUrl.d.ts +7 -0
  81. package/dist/proxy/backends/antigravityUrl.d.ts.map +1 -0
  82. package/dist/proxy/backends/providerStatus.d.ts +15 -0
  83. package/dist/proxy/backends/providerStatus.d.ts.map +1 -0
  84. package/dist/proxy/credentialsMode.d.ts +77 -0
  85. package/dist/proxy/credentialsMode.d.ts.map +1 -0
  86. package/dist/proxy/design.d.ts.map +1 -1
  87. package/dist/proxy/followActive.d.ts +262 -0
  88. package/dist/proxy/followActive.d.ts.map +1 -0
  89. package/dist/proxy/limitDetection.d.ts +110 -0
  90. package/dist/proxy/limitDetection.d.ts.map +1 -0
  91. package/dist/proxy/models.d.ts.map +1 -1
  92. package/dist/proxy/oauthPlan.d.ts +63 -0
  93. package/dist/proxy/oauthPlan.d.ts.map +1 -0
  94. package/dist/proxy/oauthUsage.d.ts +65 -2
  95. package/dist/proxy/oauthUsage.d.ts.map +1 -1
  96. package/dist/proxy/openai.d.ts +6 -2
  97. package/dist/proxy/openai.d.ts.map +1 -1
  98. package/dist/proxy/organizationName.d.ts +71 -0
  99. package/dist/proxy/organizationName.d.ts.map +1 -0
  100. package/dist/proxy/passthroughEarlyStop.d.ts +10 -0
  101. package/dist/proxy/passthroughEarlyStop.d.ts.map +1 -1
  102. package/dist/proxy/planAllowance.d.ts +75 -0
  103. package/dist/proxy/planAllowance.d.ts.map +1 -0
  104. package/dist/proxy/probes.d.ts +85 -0
  105. package/dist/proxy/probes.d.ts.map +1 -0
  106. package/dist/proxy/profileCli.d.ts +53 -1
  107. package/dist/proxy/profileCli.d.ts.map +1 -1
  108. package/dist/proxy/profileHealth.d.ts +127 -0
  109. package/dist/proxy/profileHealth.d.ts.map +1 -0
  110. package/dist/proxy/profileRename.d.ts +83 -0
  111. package/dist/proxy/profileRename.d.ts.map +1 -0
  112. package/dist/proxy/profiles.d.ts +72 -6
  113. package/dist/proxy/profiles.d.ts.map +1 -1
  114. package/dist/proxy/query.d.ts +7 -0
  115. package/dist/proxy/query.d.ts.map +1 -1
  116. package/dist/proxy/routing.d.ts +59 -1
  117. package/dist/proxy/routing.d.ts.map +1 -1
  118. package/dist/proxy/sdkFeatures.d.ts +1 -1
  119. package/dist/proxy/sdkFeatures.d.ts.map +1 -1
  120. package/dist/proxy/server.d.ts.map +1 -1
  121. package/dist/proxy/session/fingerprint.d.ts +5 -0
  122. package/dist/proxy/session/fingerprint.d.ts.map +1 -1
  123. package/dist/proxy/session/lineage.d.ts.map +1 -1
  124. package/dist/proxy/setup.d.ts.map +1 -1
  125. package/dist/proxy/supervision.d.ts +41 -0
  126. package/dist/proxy/supervision.d.ts.map +1 -0
  127. package/dist/proxy/tokenRefresh.d.ts +38 -4
  128. package/dist/proxy/tokenRefresh.d.ts.map +1 -1
  129. package/dist/proxy/types.d.ts +32 -0
  130. package/dist/proxy/types.d.ts.map +1 -1
  131. package/dist/server.js +11 -7
  132. package/dist/settings.d.ts +83 -0
  133. package/dist/settings.d.ts.map +1 -0
  134. package/dist/{setup-ndmjpy23.js → setup-7fr49z92.js} +1 -1
  135. package/dist/statusProbe-9x9q261e.js +151 -0
  136. package/dist/telemetry/dashboard.d.ts.map +1 -1
  137. package/dist/telemetry/index.d.ts +20 -2
  138. package/dist/telemetry/index.d.ts.map +1 -1
  139. package/dist/telemetry/landing.d.ts +1 -1
  140. package/dist/telemetry/landing.d.ts.map +1 -1
  141. package/dist/telemetry/logStore.d.ts +11 -0
  142. package/dist/telemetry/logStore.d.ts.map +1 -1
  143. package/dist/telemetry/pricingStore.d.ts +2 -2
  144. package/dist/telemetry/pricingStore.d.ts.map +1 -1
  145. package/dist/telemetry/profileBar.d.ts +3 -3
  146. package/dist/telemetry/profileBar.d.ts.map +1 -1
  147. package/dist/telemetry/profileFacts.d.ts +17 -0
  148. package/dist/telemetry/profileFacts.d.ts.map +1 -0
  149. package/dist/telemetry/profileOrder.d.ts +68 -0
  150. package/dist/telemetry/profileOrder.d.ts.map +1 -0
  151. package/dist/telemetry/profilePage.d.ts.map +1 -1
  152. package/dist/telemetry/profileSort.d.ts +35 -0
  153. package/dist/telemetry/profileSort.d.ts.map +1 -0
  154. package/dist/telemetry/profileSpent.d.ts +68 -0
  155. package/dist/telemetry/profileSpent.d.ts.map +1 -0
  156. package/dist/telemetry/providerPage.d.ts +2 -0
  157. package/dist/telemetry/providerPage.d.ts.map +1 -0
  158. package/dist/telemetry/providerSetup.d.ts +5 -0
  159. package/dist/telemetry/providerSetup.d.ts.map +1 -0
  160. package/dist/telemetry/providerView.d.ts +45 -0
  161. package/dist/telemetry/providerView.d.ts.map +1 -0
  162. package/dist/telemetry/routeChain.d.ts +32 -0
  163. package/dist/telemetry/routeChain.d.ts.map +1 -0
  164. package/dist/telemetry/routes.d.ts.map +1 -1
  165. package/dist/telemetry/settingsPage.d.ts +1 -1
  166. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  167. package/dist/telemetry/sqlite.d.ts.map +1 -1
  168. package/dist/telemetry/store.d.ts +6 -1
  169. package/dist/telemetry/store.d.ts.map +1 -1
  170. package/dist/telemetry/types.d.ts +132 -2
  171. package/dist/telemetry/types.d.ts.map +1 -1
  172. package/dist/{tokenRefresh-kzz08kea.js → tokenRefresh-eg9h83wd.js} +6 -1
  173. package/package.json +11 -4
  174. package/dist/cli-5jxyma6z.js +0 -277
  175. package/dist/cli-vj9cv18n.js +0 -38
  176. package/dist/proxy/settings.d.ts +0 -30
  177. package/dist/proxy/settings.d.ts.map +0 -1
@@ -1,39 +1,32 @@
1
1
  import {
2
- createPlatformCredentialStore
3
- } from "./cli-khhjyk04.js";
2
+ authFieldPaths,
3
+ claudeLog,
4
+ createPlatformCredentialStore,
5
+ credentialsFilePathForProfile,
6
+ describeAuthFields,
7
+ fetchOAuthPlanFields,
8
+ isCredentialsReadOnly
9
+ } from "./cli-chsp880k.js";
4
10
  import {
5
- setSetting
6
- } from "./cli-vj9cv18n.js";
11
+ env,
12
+ envBool,
13
+ init_env
14
+ } from "./cli-1jstkdj9.js";
7
15
  import {
8
- __esm
9
- } from "./cli-p9swy5t3.js";
10
-
11
- // src/env.ts
12
- function env(suffix) {
13
- return process.env[`MERIDIAN_${suffix}`] ?? process.env[`CLAUDE_PROXY_${suffix}`];
14
- }
15
- function envBool(suffix) {
16
- const val = env(suffix);
17
- return val === "1" || val === "true" || val === "yes";
18
- }
19
- function resolvePassthrough(defaultValue) {
20
- const val = env("PASSTHROUGH");
21
- if (val === "1" || val === "true" || val === "yes")
22
- return true;
23
- if (val === "0" || val === "false" || val === "no")
24
- return false;
25
- return defaultValue;
26
- }
27
- function envInt(suffix, defaultValue) {
28
- const val = env(suffix);
29
- if (!val)
30
- return defaultValue;
31
- const parsed = parseInt(val, 10);
32
- return Number.isFinite(parsed) ? parsed : defaultValue;
33
- }
34
- var init_env = () => {};
16
+ applyProfileRename,
17
+ defaultProfilesConfigFile,
18
+ defaultProfilesDir,
19
+ reclaimAlias
20
+ } from "./cli-qpfky8wy.js";
21
+ import {
22
+ configPath,
23
+ getSetting,
24
+ init_configDir,
25
+ setSetting
26
+ } from "./cli-eehk3052.js";
35
27
 
36
28
  // src/proxy/profileCli.ts
29
+ init_configDir();
37
30
  import { execFileSync, spawnSync } from "node:child_process";
38
31
  import { createHash, randomBytes } from "node:crypto";
39
32
  import { mkdirSync, readFileSync, rmSync, writeFileSync, existsSync as existsSync2 } from "node:fs";
@@ -77,6 +70,16 @@ var lastKnownGoodAuthStatus = null;
77
70
  var cachedAuthStatusAt = 0;
78
71
  var cachedAuthStatusIsFailure = false;
79
72
  var cachedAuthStatusPromise = null;
73
+ var cachedAuthStatusCredMtimeMs = 0;
74
+ function credentialFileMtimeMs(envOverrides) {
75
+ if (!isCredentialsReadOnly())
76
+ return 0;
77
+ try {
78
+ return statSync(credentialsFilePathForProfile(envOverrides?.CLAUDE_CONFIG_DIR)).mtimeMs;
79
+ } catch {
80
+ return 0;
81
+ }
82
+ }
80
83
  var warnedTierOverrides = new Set;
81
84
  function warnUnrecognizedTierOverride(varName, raw, tierBase) {
82
85
  if (warnedTierOverrides.has(varName))
@@ -226,7 +229,7 @@ function getAuthCacheInfo(profileId) {
226
229
  function getAuthCache(key) {
227
230
  let cache = profileAuthCaches.get(key);
228
231
  if (!cache) {
229
- cache = { status: null, lastKnownGood: null, at: 0, isFailure: false, promise: null, lastSuccessAt: 0 };
232
+ cache = { status: null, lastKnownGood: null, at: 0, isFailure: false, promise: null, lastSuccessAt: 0, credMtimeMs: 0 };
230
233
  profileAuthCaches.set(key, cache);
231
234
  }
232
235
  return cache;
@@ -239,8 +242,10 @@ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
239
242
  const c_at = cache ? cache.at : cachedAuthStatusAt;
240
243
  const c_isFailure = cache ? cache.isFailure : cachedAuthStatusIsFailure;
241
244
  let c_promise = cache ? cache.promise : cachedAuthStatusPromise;
245
+ const c_credMtime = cache ? cache.credMtimeMs : cachedAuthStatusCredMtimeMs;
242
246
  const ttl = c_isFailure ? AUTH_STATUS_FAILURE_TTL_MS : AUTH_STATUS_CACHE_TTL_MS;
243
- if (c_at > 0 && Date.now() - c_at < ttl) {
247
+ const credMtime = credentialFileMtimeMs(envOverrides);
248
+ if (c_at > 0 && Date.now() - c_at < ttl && credMtime === c_credMtime) {
244
249
  return c_status ?? c_lastKnownGood;
245
250
  }
246
251
  if (c_promise)
@@ -253,29 +258,45 @@ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
253
258
  ...envOverrides ? { env: { ...process.env, ...envOverrides } } : {}
254
259
  });
255
260
  const parsed = JSON.parse(stdout);
261
+ claudeLog("auth.status_discovered", {
262
+ source: "cli_async",
263
+ profile: profileId ?? "default",
264
+ fields: authFieldPaths(parsed),
265
+ payload: describeAuthFields(parsed)
266
+ });
256
267
  if (cache) {
257
268
  cache.status = parsed;
258
269
  cache.lastKnownGood = parsed;
259
270
  cache.at = Date.now();
260
271
  cache.isFailure = false;
261
272
  cache.lastSuccessAt = Date.now();
273
+ cache.credMtimeMs = credMtime;
262
274
  } else {
263
275
  cachedAuthStatus = parsed;
264
276
  lastKnownGoodAuthStatus = parsed;
265
277
  cachedAuthStatusAt = Date.now();
266
278
  cachedAuthStatusIsFailure = false;
279
+ cachedAuthStatusCredMtimeMs = credMtime;
267
280
  }
268
281
  return parsed;
269
- } catch {
282
+ } catch (err) {
283
+ claudeLog("auth.status_failed", {
284
+ source: "cli_async",
285
+ profile: profileId ?? "default",
286
+ error: String(err),
287
+ servingLastKnownGood: Boolean(cache ? cache.lastKnownGood : lastKnownGoodAuthStatus)
288
+ });
270
289
  if (cache) {
271
290
  cache.isFailure = true;
272
291
  cache.at = Date.now();
273
292
  cache.status = null;
293
+ cache.credMtimeMs = credMtime;
274
294
  return cache.lastKnownGood;
275
295
  } else {
276
296
  cachedAuthStatusIsFailure = true;
277
297
  cachedAuthStatusAt = Date.now();
278
298
  cachedAuthStatus = null;
299
+ cachedAuthStatusCredMtimeMs = credMtime;
279
300
  return lastKnownGoodAuthStatus;
280
301
  }
281
302
  }
@@ -425,8 +446,7 @@ function isClosedControllerError(error) {
425
446
  }
426
447
 
427
448
  // src/proxy/profileCli.ts
428
- var PROFILES_DIR = join2(homedir(), ".config", "meridian", "profiles");
429
- var CONFIG_FILE = join2(homedir(), ".config", "meridian", "profiles.json");
449
+ init_env();
430
450
  var OAUTH_AUTHORIZE_URL = "https://claude.com/cai/oauth/authorize";
431
451
  var OAUTH_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
432
452
  var OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
@@ -439,11 +459,20 @@ var OAUTH_SCOPES = [
439
459
  "user:mcp_servers",
440
460
  "user:file_upload"
441
461
  ];
462
+ function profilesConfigFile() {
463
+ return configPath("profiles.json");
464
+ }
442
465
  function ensureProfilesDir() {
443
- mkdirSync(PROFILES_DIR, { recursive: true });
466
+ mkdirSync(configPath("profiles"), { recursive: true });
444
467
  }
445
468
  function getProfileDir(id) {
446
- return join2(PROFILES_DIR, id);
469
+ return configPath("profiles", id);
470
+ }
471
+ function isValidProfileId(id) {
472
+ return Boolean(id) && !/[^a-zA-Z0-9_-]/.test(id);
473
+ }
474
+ function yellow(text) {
475
+ return process.stdout.isTTY ? `\x1B[33m${text}\x1B[0m` : text;
447
476
  }
448
477
  function buildAuthLoginEnv(configDir, _options = {}, baseEnv = process.env) {
449
478
  const env2 = { ...baseEnv };
@@ -489,18 +518,19 @@ function parseAuthorizationCodeInput(input) {
489
518
  return code ? { code, state } : null;
490
519
  }
491
520
  function loadProfileConfig() {
492
- if (!existsSync2(CONFIG_FILE))
521
+ const file = profilesConfigFile();
522
+ if (!existsSync2(file))
493
523
  return [];
494
524
  try {
495
- return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
525
+ return JSON.parse(readFileSync(file, "utf-8"));
496
526
  } catch (err) {
497
- console.warn(`[meridian] Failed to read ${CONFIG_FILE}: ${err instanceof Error ? err.message : err}`);
527
+ console.warn(`[meridian] Failed to read ${file}: ${err instanceof Error ? err.message : err}`);
498
528
  return [];
499
529
  }
500
530
  }
501
531
  function saveProfileConfig(profiles) {
502
532
  ensureProfilesDir();
503
- writeFileSync(CONFIG_FILE, `${JSON.stringify(profiles, null, 2)}
533
+ writeFileSync(profilesConfigFile(), `${JSON.stringify(profiles, null, 2)}
504
534
  `, { mode: 384 });
505
535
  }
506
536
  function getAuthStatus(configDir) {
@@ -515,12 +545,33 @@ function getAuthStatus(configDir) {
515
545
  env: { ...process.env, CLAUDE_CONFIG_DIR: configDir },
516
546
  stdio: ["pipe", "pipe", "pipe"]
517
547
  });
518
- return JSON.parse(result.toString());
548
+ const status = JSON.parse(result.toString());
549
+ claudeLog("auth.status_discovered", {
550
+ source: "cli_sync",
551
+ fields: authFieldPaths(status),
552
+ payload: describeAuthFields(status)
553
+ });
554
+ return status;
519
555
  } catch (err) {
556
+ claudeLog("auth.status_failed", { source: "cli_sync", error: String(err) });
520
557
  console.warn(`[meridian] Auth check failed for ${configDir}: ${err instanceof Error ? err.message : err}`);
521
558
  return { loggedIn: false };
522
559
  }
523
560
  }
561
+ function hasRequiredTokens(tokenData) {
562
+ return Boolean(tokenData.access_token && tokenData.refresh_token);
563
+ }
564
+ function buildLoginCredentials(tokenData, plan, now = Date.now()) {
565
+ return {
566
+ claudeAiOauth: {
567
+ accessToken: tokenData.access_token,
568
+ refreshToken: tokenData.refresh_token,
569
+ expiresAt: tokenData.expires_at ?? now + (tokenData.expires_in ?? 8 * 60 * 60) * 1000,
570
+ scopes: tokenData.scope?.split(" ").filter(Boolean) ?? OAUTH_SCOPES,
571
+ ...plan
572
+ }
573
+ };
574
+ }
524
575
  async function completeManualOAuthLogin(configDir) {
525
576
  const session = createManualOAuthSession();
526
577
  console.log("\x1B[33m⚠ Headless OAuth login: open this URL in a browser:\x1B[0m");
@@ -554,11 +605,13 @@ async function completeManualOAuthLogin(configDir) {
554
605
  signal: AbortSignal.timeout(30000)
555
606
  });
556
607
  } catch (err) {
608
+ claudeLog("auth.token_request_failed", { error: String(err) });
557
609
  console.error(`\x1B[31m✗ OAuth token exchange failed: ${err instanceof Error ? err.message : err}\x1B[0m`);
558
610
  return false;
559
611
  }
560
612
  if (!response.ok) {
561
613
  const body = await response.text().catch(() => "");
614
+ claudeLog("auth.token_bad_response", { status: response.status, bodyLength: body.length });
562
615
  console.error(`\x1B[31m✗ OAuth token exchange failed (${response.status}).\x1B[0m`);
563
616
  if (body)
564
617
  console.error(` ${body.slice(0, 300)}`);
@@ -568,35 +621,39 @@ async function completeManualOAuthLogin(configDir) {
568
621
  try {
569
622
  tokenData = await response.json();
570
623
  } catch (err) {
624
+ claudeLog("auth.token_parse_failed", { error: String(err) });
571
625
  console.error(`\x1B[31m✗ OAuth token response was invalid: ${err instanceof Error ? err.message : err}\x1B[0m`);
572
626
  return false;
573
627
  }
574
- if (!tokenData.access_token || !tokenData.refresh_token) {
628
+ claudeLog("auth.token_discovered", {
629
+ fields: authFieldPaths(tokenData),
630
+ payload: describeAuthFields(tokenData)
631
+ });
632
+ if (!hasRequiredTokens(tokenData)) {
575
633
  console.error("\x1B[31m✗ OAuth token response did not include the required tokens.\x1B[0m");
576
634
  return false;
577
635
  }
578
- const expiresAt = tokenData.expires_at ?? Date.now() + (tokenData.expires_in ?? 8 * 60 * 60) * 1000;
636
+ const plan = await fetchOAuthPlanFields(tokenData.access_token);
579
637
  const store = createPlatformCredentialStore({ claudeConfigDir: configDir });
580
- return store.write({
581
- claudeAiOauth: {
582
- accessToken: tokenData.access_token,
583
- refreshToken: tokenData.refresh_token,
584
- expiresAt,
585
- scopes: tokenData.scope?.split(" ").filter(Boolean) ?? OAUTH_SCOPES
586
- }
638
+ const credentials = buildLoginCredentials(tokenData, plan);
639
+ claudeLog("auth.credentials_built", {
640
+ fields: authFieldPaths(credentials.claudeAiOauth),
641
+ payload: describeAuthFields(credentials.claudeAiOauth)
587
642
  });
643
+ return store.write(credentials);
588
644
  }
589
645
  async function profileAdd(id, options = {}) {
590
- if (!id || /[^a-zA-Z0-9_-]/.test(id)) {
646
+ if (!isValidProfileId(id)) {
591
647
  console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
592
648
  process.exit(1);
593
649
  }
594
- const profiles = loadProfileConfig();
650
+ let profiles = loadProfileConfig();
595
651
  if (profiles.find((p) => p.id === id)) {
596
652
  console.error(`\x1B[31m✗ Profile "${id}" already exists.\x1B[0m`);
597
653
  console.error(` Run: meridian profile list`);
598
654
  process.exit(1);
599
655
  }
656
+ profiles = reclaimAlias(profiles, id);
600
657
  const defaultClaudeDir = join2(homedir(), ".claude");
601
658
  const alreadyImported = profiles.some((p) => p.claudeConfigDir === defaultClaudeDir);
602
659
  if (!alreadyImported) {
@@ -675,16 +732,17 @@ async function profileAdd(id, options = {}) {
675
732
  printEnvHint(profiles);
676
733
  }
677
734
  async function profileAddOauthToken(id, tokenArg) {
678
- if (!id || /[^a-zA-Z0-9_-]/.test(id)) {
735
+ if (!isValidProfileId(id)) {
679
736
  console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
680
737
  process.exit(1);
681
738
  }
682
- const profiles = loadProfileConfig();
739
+ let profiles = loadProfileConfig();
683
740
  if (profiles.find((p) => p.id === id)) {
684
741
  console.error(`\x1B[31m✗ Profile "${id}" already exists.\x1B[0m`);
685
742
  console.error(` Run: meridian profile list`);
686
743
  process.exit(1);
687
744
  }
745
+ profiles = reclaimAlias(profiles, id);
688
746
  let token = tokenArg?.trim() ?? "";
689
747
  if (!token) {
690
748
  console.log(`\x1B[36mAdding profile: ${id} (OAuth token)\x1B[0m`);
@@ -718,6 +776,9 @@ function profileList() {
718
776
  const auth = getAuthStatus(p.claudeConfigDir ?? "");
719
777
  const status = auth.loggedIn ? `\x1B[32m✓ ${auth.email} (${auth.subscriptionType || "unknown"})\x1B[0m` : "\x1B[31m✗ not logged in\x1B[0m";
720
778
  console.log(` ${p.id.padEnd(20)} ${status}`);
779
+ if (p.aliases && p.aliases.length > 0) {
780
+ console.log(` ${"".padEnd(20)} \x1B[90malso answers to: ${p.aliases.join(", ")}\x1B[0m`);
781
+ }
721
782
  }
722
783
  console.log();
723
784
  printEnvHint(profiles);
@@ -746,7 +807,7 @@ function profileRemove(id) {
746
807
  console.error(`\x1B[31m✗ Profile "${id}" not found.\x1B[0m`);
747
808
  process.exit(1);
748
809
  }
749
- const dirsToRemove = dirsToRemoveOnProfileRemove(removed, PROFILES_DIR);
810
+ const dirsToRemove = dirsToRemoveOnProfileRemove(removed, configPath("profiles"));
750
811
  profiles.splice(idx, 1);
751
812
  saveProfileConfig(profiles);
752
813
  for (const dir of dirsToRemove) {
@@ -758,6 +819,26 @@ function profileRemove(id) {
758
819
  printEnvHint(profiles);
759
820
  }
760
821
  }
822
+ function profileRename(from, to) {
823
+ if (envBool("CREDENTIALS_READONLY")) {
824
+ console.error("\x1B[31m✗ MERIDIAN_CREDENTIALS_READONLY=1 — this instance may not modify credentials.\x1B[0m");
825
+ console.error(" Rename the profile from the instance that owns them.");
826
+ process.exit(1);
827
+ }
828
+ const wasActive = getSetting("activeProfile") === from;
829
+ const result = applyProfileRename(from, to, { profilesDir: defaultProfilesDir(), configFile: defaultProfilesConfigFile() });
830
+ if (!result.ok) {
831
+ console.error(`\x1B[31m✗ ${result.error}\x1B[0m`);
832
+ if (result.hint)
833
+ console.error(` ${result.hint}`);
834
+ process.exit(1);
835
+ }
836
+ console.log(`\x1B[32m✓ Profile "${from}" renamed to "${to}".\x1B[0m`);
837
+ console.log(` Requests naming ${result.aliases.map((a) => `"${a}"`).join(", ")} are served by "${to}" until the name is added again.`);
838
+ if (wasActive)
839
+ console.log(` Active profile is now "${to}".`);
840
+ printEnvHint(result.profiles);
841
+ }
761
842
  async function profileSwitch(id) {
762
843
  const port = process.env.MERIDIAN_PORT ?? process.env.CLAUDE_PROXY_PORT ?? "3456";
763
844
  const host = process.env.MERIDIAN_HOST ?? process.env.CLAUDE_PROXY_HOST ?? "127.0.0.1";
@@ -781,18 +862,32 @@ async function profileSwitch(id) {
781
862
  process.exit(1);
782
863
  }
783
864
  }
784
- async function profileLogin(id, options = {}) {
785
- const profiles = loadProfileConfig();
865
+ function planProfileLogin(id, profiles) {
786
866
  const profile = profiles.find((p) => p.id === id);
787
- if (!profile) {
788
- console.error(`\x1B[31m✗ Profile "${id}" not found.\x1B[0m Run: meridian profile add ${id}`);
867
+ if (!profile)
868
+ return isValidProfileId(id) ? { action: "create" } : { action: "reject-invalid-id" };
869
+ if (profile.oauthToken || profile.type === "oauth-token")
870
+ return { action: "reject-oauth-token" };
871
+ return { action: "login", profile };
872
+ }
873
+ async function profileLogin(id, options = {}) {
874
+ const plan = planProfileLogin(id, loadProfileConfig());
875
+ if (plan.action === "reject-invalid-id") {
876
+ console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
789
877
  process.exit(1);
790
878
  }
791
- if (profile.oauthToken || profile.type === "oauth-token") {
879
+ if (plan.action === "reject-oauth-token") {
792
880
  console.error(`\x1B[31m✗ Profile "${id}" uses an OAuth token; \`claude auth login\` does not apply.\x1B[0m`);
793
881
  console.error(` To replace the token: meridian profile remove ${id} && meridian profile add ${id} --oauth-token`);
794
882
  process.exit(1);
795
883
  }
884
+ if (plan.action === "create") {
885
+ console.log(yellow(`⚠ Profile "${id}" does not exist yet — adding it first.`));
886
+ console.log();
887
+ await profileAdd(id, options);
888
+ return;
889
+ }
890
+ const profile = plan.profile;
796
891
  console.log(`\x1B[36mRe-authenticating profile: ${id}\x1B[0m`);
797
892
  console.log();
798
893
  if (!options.headless) {
@@ -883,7 +978,7 @@ function promptToken(question) {
883
978
  return (result.stdout?.toString() ?? "").trim();
884
979
  }
885
980
  function printEnvHint(_profiles) {
886
- console.log(`\x1B[90mConfig: ${CONFIG_FILE}\x1B[0m`);
981
+ console.log(`\x1B[90mConfig: ${profilesConfigFile()}\x1B[0m`);
887
982
  console.log("\x1B[90mProfiles are picked up automatically — no restart needed.\x1B[0m");
888
983
  }
889
984
  function profileHelp() {
@@ -894,9 +989,11 @@ Commands:
894
989
  meridian profile add <name> --oauth-token [TOKEN] Add a profile from a \`claude setup-token\` value
895
990
  (if TOKEN is omitted, you will be prompted; input is hidden)
896
991
  meridian profile list List profiles and auth status
992
+ meridian profile rename <old> <new> Rename a profile; <old> keeps routing to it until reused
897
993
  meridian profile remove <name> Remove a profile
898
994
  meridian profile switch <name> Switch the active profile (requires running proxy)
899
- meridian profile login <name> [--headless] Re-authenticate an existing profile (claude-max only)
995
+ meridian profile login <name> [--headless] Re-authenticate a profile, adding it first if it does not
996
+ exist yet (claude-max only)
900
997
 
901
998
  Examples:
902
999
  meridian profile add personal # Add personal account (browser login)
@@ -907,7 +1004,8 @@ Examples:
907
1004
  # Add headless CI profile (token from CLI argument)
908
1005
  meridian profile login work --headless # Re-authenticate via OAuth URL/code prompt
909
1006
  meridian profile switch work # Switch to work account
1007
+ meridian profile rename work employer # Rename; "work" still routes to it
910
1008
  meridian profile list # Show all profiles`);
911
1009
  }
912
1010
 
913
- export { env, envBool, resolvePassthrough, envInt, init_env, CANONICAL_SONNET_MODEL, resolveSdkModelDefaults, explicitModelPin, mapModelToClaudeModel, recordExtendedContextUnavailable, recordExtendedContextRateLimited, stripExtendedContext, hasExtendedContext, subscriptionIncludesExtendedContext, getAuthCacheInfo, getClaudeAuthStatusAsync, getResolvedClaudeExecutableInfo, resolveClaudeExecutableAsync, isClosedControllerError, OAUTH_TOKEN_URL, OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, buildAuthLoginEnv, createManualOAuthSession, parseAuthorizationCodeInput, profileAdd, profileAddOauthToken, profileList, dirsToRemoveOnProfileRemove, profileRemove, profileSwitch, profileLogin, profileHelp };
1011
+ export { CANONICAL_SONNET_MODEL, resolveSdkModelDefaults, explicitModelPin, mapModelToClaudeModel, recordExtendedContextUnavailable, recordExtendedContextRateLimited, stripExtendedContext, hasExtendedContext, subscriptionIncludesExtendedContext, getAuthCacheInfo, getClaudeAuthStatusAsync, resolveClaudeExecutableSync, getResolvedClaudeExecutableInfo, resolveClaudeExecutableAsync, isClosedControllerError, OAUTH_TOKEN_URL, OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, isValidProfileId, buildAuthLoginEnv, createManualOAuthSession, parseAuthorizationCodeInput, buildLoginCredentials, profileAdd, profileAddOauthToken, profileList, dirsToRemoveOnProfileRemove, profileRemove, profileRename, profileSwitch, planProfileLogin, profileLogin, profileHelp };
@@ -0,0 +1,56 @@
1
+ // src/telemetry/profileUsage.ts
2
+ var WINDOW_LABELS = {
3
+ five_hour: "5h",
4
+ seven_day: "7d",
5
+ seven_day_opus: "7d Opus",
6
+ seven_day_sonnet: "7d Sonnet",
7
+ seven_day_fable: "7d Fable",
8
+ seven_day_oauth_apps: "7d Apps",
9
+ seven_day_cowork: "7d Cowork",
10
+ seven_day_omelette: "7d Omelette"
11
+ };
12
+ function classifyUtilization(utilization) {
13
+ if (utilization == null || !Number.isFinite(utilization))
14
+ return "ok";
15
+ if (utilization >= 0.85)
16
+ return "high";
17
+ if (utilization >= 0.6)
18
+ return "warn";
19
+ return "ok";
20
+ }
21
+ function formatResetCountdown(resetsAt, now = Date.now()) {
22
+ if (resetsAt == null || !Number.isFinite(resetsAt))
23
+ return "";
24
+ const ms = resetsAt - now;
25
+ if (ms <= 0)
26
+ return "resetting…";
27
+ const minutes = Math.floor(ms / 60000);
28
+ if (minutes < 60)
29
+ return `in ${Math.max(1, minutes)}m`;
30
+ const hours = Math.floor(minutes / 60);
31
+ const remMin = minutes % 60;
32
+ if (hours < 24)
33
+ return remMin > 0 ? `in ${hours}h ${remMin}m` : `in ${hours}h`;
34
+ const days = Math.floor(hours / 24);
35
+ const remHr = hours % 24;
36
+ return remHr > 0 ? `in ${days}d ${remHr}h` : `in ${days}d`;
37
+ }
38
+ var SEVEN_DAYS_MS = 7 * 86400000;
39
+ var PACE_ON_BAND = 7;
40
+ var PROJECT_MIN_ELAPSED = 0.1;
41
+ function computeWeeklyPace(utilization, resetsAt, now = Date.now()) {
42
+ if (utilization == null || !Number.isFinite(utilization))
43
+ return null;
44
+ if (resetsAt == null || !Number.isFinite(resetsAt))
45
+ return null;
46
+ const windowStart = resetsAt - SEVEN_DAYS_MS;
47
+ const elapsedFraction = Math.max(0, Math.min(1, (now - windowStart) / SEVEN_DAYS_MS));
48
+ const actualPct = Math.round(Math.max(0, utilization) * 100);
49
+ const expectedPct = Math.round(elapsedFraction * 100);
50
+ const deltaPct = actualPct - expectedPct;
51
+ const status = deltaPct > PACE_ON_BAND ? "ahead" : deltaPct < -PACE_ON_BAND ? "under" : "on";
52
+ const projectedPct = elapsedFraction >= PROJECT_MIN_ELAPSED ? Math.round(Math.max(0, utilization) / elapsedFraction * 100) : null;
53
+ return { actualPct, expectedPct, deltaPct, projectedPct, status, elapsedFraction };
54
+ }
55
+
56
+ export { WINDOW_LABELS, classifyUtilization, formatResetCountdown, computeWeeklyPace };
@@ -795,7 +795,8 @@ function findPluginPath(fromUrl) {
795
795
  }
796
796
  var SUPPORTED_OPENCODE_V2_VERSIONS = new Set([
797
797
  "0.0.0-beta-18314",
798
- "0.0.0-beta-18866"
798
+ "0.0.0-beta-18866",
799
+ "0.0.0-beta-19271"
799
800
  ]);
800
801
  function hasPluginPackageEntry(path) {
801
802
  try {
@@ -0,0 +1,136 @@
1
+ import {
2
+ claudeLog,
3
+ createPlatformCredentialStore
4
+ } from "./cli-chsp880k.js";
5
+
6
+ // src/proxy/organizationName.ts
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
8
+ import { join, dirname } from "node:path";
9
+ import { homedir } from "node:os";
10
+ var OAUTH_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
11
+ var ORGANIZATION_REFRESH_AFTER_MS = 24 * 60 * 60 * 1000;
12
+ var ORGANIZATION_RETRY_AFTER_MS = 5 * 60 * 1000;
13
+ var MAX_ORGANIZATION_NAME_LENGTH = 200;
14
+ function organizationsFile() {
15
+ const override = process.env.MERIDIAN_CONFIG_DIR;
16
+ return override ? join(override, "organizations.json") : join(homedir(), ".config", "meridian", "organizations.json");
17
+ }
18
+ function readOrganizations(raw) {
19
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
20
+ return {};
21
+ const out = {};
22
+ for (const [id, value] of Object.entries(raw)) {
23
+ if (!id || typeof value !== "object" || value === null)
24
+ continue;
25
+ const record = value;
26
+ if (typeof record.name !== "string")
27
+ continue;
28
+ const name = record.name.trim();
29
+ if (!name || name.length > MAX_ORGANIZATION_NAME_LENGTH)
30
+ continue;
31
+ const fetchedAt = typeof record.fetchedAt === "number" && isFinite(record.fetchedAt) ? record.fetchedAt : 0;
32
+ out[id] = { name, fetchedAt };
33
+ }
34
+ return out;
35
+ }
36
+ function extractOrganizationName(body) {
37
+ if (typeof body !== "object" || body === null)
38
+ return null;
39
+ const organization = body.organization;
40
+ if (typeof organization !== "object" || organization === null)
41
+ return null;
42
+ const name = organization.name;
43
+ if (typeof name !== "string")
44
+ return null;
45
+ const trimmed = name.trim();
46
+ if (!trimmed || trimmed.length > MAX_ORGANIZATION_NAME_LENGTH)
47
+ return null;
48
+ return trimmed;
49
+ }
50
+ function organizationNeedsRefresh(record, now, refreshAfterMs = ORGANIZATION_REFRESH_AFTER_MS) {
51
+ if (!record)
52
+ return true;
53
+ return now - record.fetchedAt >= refreshAfterMs;
54
+ }
55
+ function organizationNames() {
56
+ const file = organizationsFile();
57
+ try {
58
+ if (!existsSync(file))
59
+ return {};
60
+ return readOrganizations(JSON.parse(readFileSync(file, "utf-8")));
61
+ } catch {
62
+ return {};
63
+ }
64
+ }
65
+ function rememberOrganizationName(profileId, name) {
66
+ const file = organizationsFile();
67
+ const merged = { ...organizationNames(), [profileId]: { name, fetchedAt: Date.now() } };
68
+ try {
69
+ mkdirSync(dirname(file), { recursive: true });
70
+ writeFileSync(file, JSON.stringify(merged, null, 2) + `
71
+ `, { mode: 384 });
72
+ } catch (err) {
73
+ console.warn(`[meridian] Failed to write ${file}: ${err instanceof Error ? err.message : err}`);
74
+ }
75
+ }
76
+ async function fetchOrganizationName(store, fetchImpl = globalThis.fetch) {
77
+ try {
78
+ const credentials = await store.read();
79
+ const token = credentials?.claudeAiOauth?.accessToken;
80
+ if (!token)
81
+ return null;
82
+ const response = await fetchImpl(OAUTH_PROFILE_URL, {
83
+ headers: {
84
+ Authorization: `Bearer ${token}`,
85
+ Accept: "application/json",
86
+ "Cache-Control": "no-cache"
87
+ },
88
+ signal: AbortSignal.timeout(1e4)
89
+ });
90
+ if (!response.ok) {
91
+ claudeLog("organization.bad_response", { status: response.status });
92
+ return null;
93
+ }
94
+ return extractOrganizationName(await response.json());
95
+ } catch (err) {
96
+ claudeLog("organization.lookup_failed", { error: err instanceof Error ? err.message : String(err) });
97
+ return null;
98
+ }
99
+ }
100
+ var lookupEnabled = false;
101
+ function enableOrganizationLookup() {
102
+ lookupEnabled = true;
103
+ }
104
+ var lastAttemptAt = new Map;
105
+ var inflight = new Set;
106
+ function refreshOrganizationNameSoon(profileId, opts = {}) {
107
+ if (!lookupEnabled && !opts.store)
108
+ return;
109
+ if (inflight.has(profileId))
110
+ return;
111
+ const now = Date.now();
112
+ const last = lastAttemptAt.get(profileId);
113
+ if (last !== undefined && now - last < (opts.retryAfterMs ?? ORGANIZATION_RETRY_AFTER_MS))
114
+ return;
115
+ lastAttemptAt.set(profileId, now);
116
+ inflight.add(profileId);
117
+ (async () => {
118
+ try {
119
+ const store = opts.store ?? createPlatformCredentialStore({ claudeConfigDir: opts.claudeConfigDir });
120
+ const name = await fetchOrganizationName(store, opts.fetchImpl);
121
+ if (name) {
122
+ rememberOrganizationName(profileId, name);
123
+ claudeLog("organization.discovered", { profile: profileId, nameLength: name.length });
124
+ }
125
+ } finally {
126
+ inflight.delete(profileId);
127
+ }
128
+ })();
129
+ }
130
+ function resetOrganizationLookupForTesting() {
131
+ lookupEnabled = false;
132
+ lastAttemptAt.clear();
133
+ inflight.clear();
134
+ }
135
+
136
+ export { ORGANIZATION_REFRESH_AFTER_MS, ORGANIZATION_RETRY_AFTER_MS, readOrganizations, extractOrganizationName, organizationNeedsRefresh, organizationNames, rememberOrganizationName, fetchOrganizationName, enableOrganizationLookup, refreshOrganizationNameSoon, resetOrganizationLookupForTesting };