@rynfar/meridian 1.27.5 → 1.28.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,3 +1,8 @@
1
+ import {
2
+ profileBarCss,
3
+ profileBarHtml,
4
+ profileBarJs
5
+ } from "./cli-g9ypdz51.js";
1
6
  import {
2
7
  checkPluginConfigured
3
8
  } from "./cli-rtab0qa6.js";
@@ -6,6 +11,14 @@ import {
6
11
  refreshOAuthToken,
7
12
  withClaudeLogContext
8
13
  } from "./cli-m9pfb7h9.js";
14
+ import {
15
+ getActiveProfileId,
16
+ getEffectiveProfiles,
17
+ listProfiles,
18
+ resolveProfile,
19
+ restoreActiveProfile,
20
+ setActiveProfile
21
+ } from "./cli-vdp9s10c.js";
9
22
  import {
10
23
  __export,
11
24
  __require
@@ -2172,7 +2185,9 @@ var DEFAULT_PROXY_CONFIG = {
2172
2185
  host: "127.0.0.1",
2173
2186
  debug: (process.env.MERIDIAN_DEBUG ?? process.env.CLAUDE_PROXY_DEBUG) === "1",
2174
2187
  idleTimeoutSeconds: 120,
2175
- silent: false
2188
+ silent: false,
2189
+ profiles: undefined,
2190
+ defaultProfile: undefined
2176
2191
  };
2177
2192
 
2178
2193
  // src/env.ts
@@ -6421,7 +6436,7 @@ var dashboardHtml = `<!DOCTYPE html>
6421
6436
  }
6422
6437
  * { box-sizing: border-box; margin: 0; padding: 0; }
6423
6438
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
6424
- background: var(--bg); color: var(--text); padding: 24px; line-height: 1.5; }
6439
+ background: var(--bg); color: var(--text); padding: 0; line-height: 1.5; }
6425
6440
  h1 { font-size: 20px; font-weight: 600; margin-bottom: 4px; }
6426
6441
  .subtitle { color: var(--muted); font-size: 13px; margin-bottom: 24px; }
6427
6442
  .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
@@ -6480,9 +6495,12 @@ var dashboardHtml = `<!DOCTYPE html>
6480
6495
  transition: all 0.15s; }
6481
6496
  .log-filter:hover { border-color: var(--accent); color: var(--text); }
6482
6497
  .log-filter.active { background: rgba(88,166,255,0.1); border-color: var(--accent); color: var(--accent); }
6498
+ ` + profileBarCss + `
6483
6499
  </style>
6484
6500
  </head>
6485
6501
  <body>
6502
+ ` + profileBarHtml + `
6503
+ <div style="padding:24px">
6486
6504
  <h1>Meridian</h1>
6487
6505
  <div class="subtitle">Request Performance Telemetry</div>
6488
6506
 
@@ -6507,6 +6525,8 @@ let timer;
6507
6525
  let activeTab = 'requests';
6508
6526
  let activeLogFilter = 'all';
6509
6527
 
6528
+
6529
+
6510
6530
  function ms(v) {
6511
6531
  if (v == null) return '—';
6512
6532
  if (v < 1000) return v + 'ms';
@@ -6728,6 +6748,7 @@ $('#window').addEventListener('change', refresh);
6728
6748
 
6729
6749
  refresh();
6730
6750
  timer = setInterval(refresh, 5000);
6751
+ ` + profileBarJs + `
6731
6752
  </script>
6732
6753
  </body>
6733
6754
  </html>`;
@@ -6845,9 +6866,11 @@ var landingHtml = `<!DOCTYPE html>
6845
6866
  .footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border);
6846
6867
  font-size: 11px; color: var(--muted); text-align: center; }
6847
6868
  .footer a { color: var(--violet); text-decoration: none; }
6869
+ ` + profileBarCss + `
6848
6870
  </style>
6849
6871
  </head>
6850
6872
  <body>
6873
+ ` + profileBarHtml + `
6851
6874
  <div class="container">
6852
6875
  <div class="header">
6853
6876
  <svg width="40" height="40" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -6888,12 +6911,13 @@ function render(h,s){
6888
6911
  o+='</div>';
6889
6912
  if(s.byModel&&Object.keys(s.byModel).length>0){o+='<div class="section"><div class="section-title">Models (24h)</div><div class="grid">';for(const[n,d]of Object.entries(s.byModel))o+=card(n,d.count,'avg '+ms(d.avgTotalMs),'');o+='</div></div>'}
6890
6913
  o+='<div class="section"><div class="section-title">Connect an Agent</div><div class="snippet"><div class="snippet-tabs"><div class="snippet-tab active" onclick="showTab(this,&apos;opencode&apos;)">OpenCode</div><div class="snippet-tab" onclick="showTab(this,&apos;crush&apos;)">Crush</div><div class="snippet-tab" onclick="showTab(this,&apos;generic&apos;)">Any Tool</div></div><div id="tab-opencode"><code>ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://'+location.host+' opencode</code></div><div id="tab-crush" style="display:none"><code>'+JSON.stringify({providers:{meridian:{type:"anthropic",base_url:"http://"+location.host,api_key:"x",models:[{id:"claude-sonnet-4-5-20250514",name:"Sonnet 4.5"}]}}},null,2)+'</code></div><div id="tab-generic" style="display:none"><code>export ANTHROPIC_API_KEY=x\\nexport ANTHROPIC_BASE_URL=http://'+location.host+'</code></div></div></div>';
6891
- o+='<div class="links"><a href="/telemetry" class="link">\uD83D\uDCCA Telemetry</a><a href="/health" class="link">\uD83E\uDE7A Health</a><a href="/telemetry/summary" class="link">\uD83D\uDCC8 Stats API</a><a href="https://github.com/rynfar/meridian" class="link">⚙️ GitHub</a></div>';
6914
+ o+='<div class="links"><a href="/telemetry" class="link">\uD83D\uDCCA Telemetry</a><a href="/profiles" class="link">\uD83D\uDC64 Profiles</a><a href="/health" class="link">\uD83E\uDE7A Health</a><a href="/telemetry/summary" class="link">\uD83D\uDCC8 Stats API</a><a href="https://github.com/rynfar/meridian" class="link">⚙️ GitHub</a></div>';
6892
6915
  o+='<div class="footer">Meridian · Built on the <a href="https://github.com/anthropics/claude-code-sdk-js">Claude Code SDK</a></div>';
6893
6916
  document.getElementById('content').innerHTML=o;
6894
6917
  }
6895
6918
  function showTab(el,id){document.querySelectorAll('.snippet-tab').forEach(t=>t.classList.remove('active'));el.classList.add('active');document.querySelectorAll('[id^="tab-"]').forEach(t=>t.style.display='none');document.getElementById('tab-'+id).style.display='block'}
6896
6919
  refresh();setInterval(refresh,10000);
6920
+ ` + profileBarJs + `
6897
6921
  </script>
6898
6922
  </body>
6899
6923
  </html>`;
@@ -7055,33 +7079,83 @@ function stripExtendedContext(model) {
7055
7079
  function hasExtendedContext(model) {
7056
7080
  return model.endsWith("[1m]");
7057
7081
  }
7058
- async function getClaudeAuthStatusAsync() {
7059
- const ttl = cachedAuthStatusIsFailure ? AUTH_STATUS_FAILURE_TTL_MS : AUTH_STATUS_CACHE_TTL_MS;
7060
- if (cachedAuthStatusAt > 0 && Date.now() - cachedAuthStatusAt < ttl) {
7061
- return cachedAuthStatus ?? lastKnownGoodAuthStatus;
7062
- }
7063
- if (cachedAuthStatusPromise)
7064
- return cachedAuthStatusPromise;
7065
- cachedAuthStatusPromise = (async () => {
7082
+ var profileAuthCaches = new Map;
7083
+ function getAuthCacheInfo(profileId) {
7084
+ if (!profileId) {
7085
+ return { lastCheckedAt: cachedAuthStatusAt, lastSuccessAt: cachedAuthStatusIsFailure ? 0 : cachedAuthStatusAt, isFailure: cachedAuthStatusIsFailure };
7086
+ }
7087
+ const cache = profileAuthCaches.get(profileId);
7088
+ if (!cache)
7089
+ return { lastCheckedAt: 0, lastSuccessAt: 0, isFailure: false };
7090
+ return { lastCheckedAt: cache.at, lastSuccessAt: cache.lastSuccessAt, isFailure: cache.isFailure };
7091
+ }
7092
+ function getAuthCache(key) {
7093
+ let cache = profileAuthCaches.get(key);
7094
+ if (!cache) {
7095
+ cache = { status: null, lastKnownGood: null, at: 0, isFailure: false, promise: null, lastSuccessAt: 0 };
7096
+ profileAuthCaches.set(key, cache);
7097
+ }
7098
+ return cache;
7099
+ }
7100
+ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
7101
+ const isDefault = !profileId;
7102
+ const cache = isDefault ? null : getAuthCache(profileId);
7103
+ const c_status = cache ? cache.status : cachedAuthStatus;
7104
+ const c_lastKnownGood = cache ? cache.lastKnownGood : lastKnownGoodAuthStatus;
7105
+ const c_at = cache ? cache.at : cachedAuthStatusAt;
7106
+ const c_isFailure = cache ? cache.isFailure : cachedAuthStatusIsFailure;
7107
+ let c_promise = cache ? cache.promise : cachedAuthStatusPromise;
7108
+ const ttl = c_isFailure ? AUTH_STATUS_FAILURE_TTL_MS : AUTH_STATUS_CACHE_TTL_MS;
7109
+ if (c_at > 0 && Date.now() - c_at < ttl) {
7110
+ return c_status ?? c_lastKnownGood;
7111
+ }
7112
+ if (c_promise)
7113
+ return c_promise;
7114
+ c_promise = (async () => {
7066
7115
  try {
7067
- const { stdout } = await exec("claude auth status", { timeout: 5000 });
7116
+ const { stdout } = await exec("claude auth status", {
7117
+ timeout: 5000,
7118
+ ...envOverrides ? { env: { ...process.env, ...envOverrides } } : {}
7119
+ });
7068
7120
  const parsed = JSON.parse(stdout);
7069
- cachedAuthStatus = parsed;
7070
- lastKnownGoodAuthStatus = parsed;
7071
- cachedAuthStatusAt = Date.now();
7072
- cachedAuthStatusIsFailure = false;
7121
+ if (cache) {
7122
+ cache.status = parsed;
7123
+ cache.lastKnownGood = parsed;
7124
+ cache.at = Date.now();
7125
+ cache.isFailure = false;
7126
+ cache.lastSuccessAt = Date.now();
7127
+ } else {
7128
+ cachedAuthStatus = parsed;
7129
+ lastKnownGoodAuthStatus = parsed;
7130
+ cachedAuthStatusAt = Date.now();
7131
+ cachedAuthStatusIsFailure = false;
7132
+ }
7073
7133
  return parsed;
7074
7134
  } catch {
7075
- cachedAuthStatusIsFailure = true;
7076
- cachedAuthStatusAt = Date.now();
7077
- cachedAuthStatus = null;
7078
- return lastKnownGoodAuthStatus;
7135
+ if (cache) {
7136
+ cache.isFailure = true;
7137
+ cache.at = Date.now();
7138
+ cache.status = null;
7139
+ return cache.lastKnownGood;
7140
+ } else {
7141
+ cachedAuthStatusIsFailure = true;
7142
+ cachedAuthStatusAt = Date.now();
7143
+ cachedAuthStatus = null;
7144
+ return lastKnownGoodAuthStatus;
7145
+ }
7079
7146
  }
7080
7147
  })();
7148
+ if (cache)
7149
+ cache.promise = c_promise;
7150
+ else
7151
+ cachedAuthStatusPromise = c_promise;
7081
7152
  try {
7082
- return await cachedAuthStatusPromise;
7153
+ return await c_promise;
7083
7154
  } finally {
7084
- cachedAuthStatusPromise = null;
7155
+ if (cache)
7156
+ cache.promise = null;
7157
+ else
7158
+ cachedAuthStatusPromise = null;
7085
7159
  }
7086
7160
  }
7087
7161
  var cachedClaudePath = null;
@@ -13744,7 +13818,8 @@ function buildQueryOptions(ctx) {
13744
13818
  env: {
13745
13819
  ...cleanEnv,
13746
13820
  ENABLE_TOOL_SEARCH: "false",
13747
- ...passthrough ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {}
13821
+ ...passthrough ? { ENABLE_CLAUDEAI_MCP_SERVERS: "false" } : {},
13822
+ ...process.getuid?.() === 0 ? { IS_SANDBOX: "1" } : {}
13748
13823
  },
13749
13824
  ...Object.keys(sdkAgents).length > 0 ? { agents: sdkAgents } : {},
13750
13825
  ...resumeSessionId ? { resume: resumeSessionId } : {},
@@ -14388,6 +14463,7 @@ function logUsage(requestId, usage) {
14388
14463
  }
14389
14464
  function createProxyServer(config = {}) {
14390
14465
  const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config };
14466
+ restoreActiveProfile(finalConfig.profiles);
14391
14467
  const app = new Hono2;
14392
14468
  app.use("*", cors());
14393
14469
  app.get("/", (c) => {
@@ -14451,7 +14527,8 @@ function createProxyServer(config = {}) {
14451
14527
  if (!Array.isArray(body.messages)) {
14452
14528
  return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
14453
14529
  }
14454
- const authStatus = await getClaudeAuthStatusAsync();
14530
+ const profile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, c.req.header("x-meridian-profile") || undefined);
14531
+ const authStatus = await getClaudeAuthStatusAsync(profile.id !== "default" ? profile.id : undefined, Object.keys(profile.env).length > 0 ? profile.env : undefined);
14455
14532
  const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
14456
14533
  let model = mapModelToClaudeModel(body.model || "sonnet", authStatus?.subscriptionType, agentMode);
14457
14534
  const adapterStreamPref = adapter.prefersStreaming?.(body);
@@ -14464,6 +14541,7 @@ function createProxyServer(config = {}) {
14464
14541
  ANTHROPIC_AUTH_TOKEN: _dropAuthToken,
14465
14542
  ...cleanEnv
14466
14543
  } = process.env;
14544
+ const profileEnv = { ...cleanEnv, ...profile.env };
14467
14545
  let systemContext = "";
14468
14546
  if (body.system) {
14469
14547
  if (typeof body.system === "string") {
@@ -14476,7 +14554,7 @@ function createProxyServer(config = {}) {
14476
14554
  const effortHeader = c.req.header("x-opencode-effort");
14477
14555
  const thinkingHeader = c.req.header("x-opencode-thinking");
14478
14556
  const taskBudgetHeader = c.req.header("x-opencode-task-budget");
14479
- const betaHeader = c.req.header("anthropic-beta");
14557
+ const betaHeader = profile.type === "api" ? c.req.header("anthropic-beta") : undefined;
14480
14558
  const effort = effortHeader || body.effort || undefined;
14481
14559
  let thinking = body.thinking || undefined;
14482
14560
  if (thinkingHeader !== undefined) {
@@ -14489,8 +14567,13 @@ function createProxyServer(config = {}) {
14489
14567
  const parsedBudget = taskBudgetHeader ? Number.parseInt(taskBudgetHeader, 10) : NaN;
14490
14568
  const taskBudget = Number.isFinite(parsedBudget) ? { total: parsedBudget } : body.task_budget ? { total: body.task_budget.total ?? body.task_budget } : undefined;
14491
14569
  const betas = betaHeader ? betaHeader.split(",").map((b) => b.trim()).filter(Boolean) : undefined;
14570
+ if (!betaHeader && c.req.header("anthropic-beta")) {
14571
+ console.error(`[PROXY] ${requestMeta.requestId} stripped anthropic-beta header (Max subscription — betas trigger extra usage billing)`);
14572
+ }
14492
14573
  const agentSessionId = adapter.getSessionId(c);
14493
- const lineageResult = lookupSession(agentSessionId, body.messages || [], workingDirectory);
14574
+ const profileSessionId = profile.id !== "default" && agentSessionId ? `${profile.id}:${agentSessionId}` : agentSessionId;
14575
+ const profileScopedCwd = profile.id !== "default" ? `${workingDirectory}::profile=${profile.id}` : workingDirectory;
14576
+ const lineageResult = lookupSession(profileSessionId, body.messages || [], profileScopedCwd);
14494
14577
  const isResume = lineageResult.type === "continuation" || lineageResult.type === "compaction";
14495
14578
  const isUndo = lineageResult.type === "undo";
14496
14579
  const cachedSession = lineageResult.type !== "diverged" ? lineageResult.session : undefined;
@@ -14685,7 +14768,7 @@ function createProxyServer(config = {}) {
14685
14768
  stream: false,
14686
14769
  sdkAgents,
14687
14770
  passthroughMcp,
14688
- cleanEnv,
14771
+ cleanEnv: profileEnv,
14689
14772
  resumeSessionId,
14690
14773
  isUndo,
14691
14774
  undoRollbackUuid,
@@ -14714,7 +14797,7 @@ function createProxyServer(config = {}) {
14714
14797
  resumeSessionId
14715
14798
  });
14716
14799
  console.error(`[PROXY] Stale session UUID, evicting and retrying as fresh session`);
14717
- evictSession(agentSessionId, workingDirectory, allMessages);
14800
+ evictSession(profileSessionId, profileScopedCwd, allMessages);
14718
14801
  sdkUuidMap.length = 0;
14719
14802
  for (let i = 0;i < allMessages.length; i++)
14720
14803
  sdkUuidMap.push(null);
@@ -14728,7 +14811,7 @@ function createProxyServer(config = {}) {
14728
14811
  stream: false,
14729
14812
  sdkAgents,
14730
14813
  passthroughMcp,
14731
- cleanEnv,
14814
+ cleanEnv: profileEnv,
14732
14815
  resumeSessionId: undefined,
14733
14816
  isUndo: false,
14734
14817
  undoRollbackUuid: undefined,
@@ -14927,7 +15010,7 @@ Subprocess stderr: ${stderrOutput}`;
14927
15010
  error: null
14928
15011
  });
14929
15012
  if (currentSessionId) {
14930
- storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
15013
+ storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage);
14931
15014
  }
14932
15015
  const responseSessionId = currentSessionId || resumeSessionId || `session_${Date.now()}`;
14933
15016
  return new Response(JSON.stringify({
@@ -15002,7 +15085,7 @@ Subprocess stderr: ${stderrOutput}`;
15002
15085
  stream: true,
15003
15086
  sdkAgents,
15004
15087
  passthroughMcp,
15005
- cleanEnv,
15088
+ cleanEnv: profileEnv,
15006
15089
  resumeSessionId,
15007
15090
  isUndo,
15008
15091
  undoRollbackUuid,
@@ -15031,7 +15114,7 @@ Subprocess stderr: ${stderrOutput}`;
15031
15114
  resumeSessionId
15032
15115
  });
15033
15116
  console.error(`[PROXY] Stale session UUID, evicting and retrying as fresh session`);
15034
- evictSession(agentSessionId, workingDirectory, allMessages);
15117
+ evictSession(profileSessionId, profileScopedCwd, allMessages);
15035
15118
  sdkUuidMap.length = 0;
15036
15119
  for (let i = 0;i < allMessages.length; i++)
15037
15120
  sdkUuidMap.push(null);
@@ -15045,7 +15128,7 @@ Subprocess stderr: ${stderrOutput}`;
15045
15128
  stream: true,
15046
15129
  sdkAgents,
15047
15130
  passthroughMcp,
15048
- cleanEnv,
15131
+ cleanEnv: profileEnv,
15049
15132
  resumeSessionId: undefined,
15050
15133
  isUndo: false,
15051
15134
  undoRollbackUuid: undefined,
@@ -15269,7 +15352,7 @@ data: ${JSON.stringify({ type: "message_stop" })}
15269
15352
  if (lastUsage)
15270
15353
  logUsage(requestMeta.requestId, lastUsage);
15271
15354
  if (currentSessionId) {
15272
- storeSession(agentSessionId, body.messages || [], currentSessionId, workingDirectory, sdkUuidMap, lastUsage);
15355
+ storeSession(profileSessionId, body.messages || [], currentSessionId, profileScopedCwd, sdkUuidMap, lastUsage);
15273
15356
  }
15274
15357
  if (!streamClosed) {
15275
15358
  const unseenToolUses = capturedToolUses.filter((tu) => !streamedToolUseIds.has(tu.id));
@@ -15362,6 +15445,8 @@ data: {"type":"message_stop"}
15362
15445
  bytesSent,
15363
15446
  durationMs: Date.now() - requestStartAt
15364
15447
  });
15448
+ }
15449
+ {
15365
15450
  const streamTotalDurationMs = Date.now() - requestStartAt;
15366
15451
  claudeLog("response.completed", {
15367
15452
  mode: "stream",
@@ -15524,7 +15609,9 @@ data: ${JSON.stringify({
15524
15609
  app.route("/telemetry", createTelemetryRoutes());
15525
15610
  app.get("/health", async (c) => {
15526
15611
  try {
15527
- const auth = await getClaudeAuthStatusAsync();
15612
+ const healthProfile = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile);
15613
+ const profileEnvOverrides = Object.keys(healthProfile.env).length > 0 ? healthProfile.env : undefined;
15614
+ const auth = await getClaudeAuthStatusAsync(healthProfile.id !== "default" ? healthProfile.id : undefined, profileEnvOverrides);
15528
15615
  if (!auth) {
15529
15616
  return c.json({
15530
15617
  status: "degraded",
@@ -15557,6 +15644,53 @@ data: ${JSON.stringify({
15557
15644
  });
15558
15645
  }
15559
15646
  });
15647
+ app.get("/profiles/list", async (c) => {
15648
+ const profiles = listProfiles(finalConfig.profiles, finalConfig.defaultProfile);
15649
+ const enriched = await Promise.all(profiles.map(async (p) => {
15650
+ const resolved = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, p.id);
15651
+ const envOverrides = Object.keys(resolved.env).length > 0 ? resolved.env : undefined;
15652
+ const auth = await getClaudeAuthStatusAsync(p.id !== "default" ? p.id : undefined, envOverrides);
15653
+ const cacheInfo = getAuthCacheInfo(p.id !== "default" ? p.id : undefined);
15654
+ return {
15655
+ ...p,
15656
+ email: auth?.email || null,
15657
+ subscriptionType: auth?.subscriptionType || null,
15658
+ loggedIn: auth?.loggedIn ?? false,
15659
+ lastCheckedAt: cacheInfo.lastCheckedAt || null,
15660
+ lastSuccessAt: cacheInfo.lastSuccessAt || null
15661
+ };
15662
+ }));
15663
+ return c.json({
15664
+ profiles: enriched,
15665
+ activeProfile: getActiveProfileId() || finalConfig.defaultProfile || profiles[0]?.id || "default"
15666
+ });
15667
+ });
15668
+ app.get("/profiles", async (c) => {
15669
+ const { profilePageHtml } = await import("./profilePage-9nkbct3w.js");
15670
+ return c.html(profilePageHtml);
15671
+ });
15672
+ app.post("/profiles/active", async (c) => {
15673
+ let body;
15674
+ try {
15675
+ body = await c.req.json();
15676
+ } catch {
15677
+ return c.json({ error: "Invalid JSON in request body" }, 400);
15678
+ }
15679
+ if (!body.profile) {
15680
+ return c.json({ error: "Missing 'profile' in request body" }, 400);
15681
+ }
15682
+ const effective = getEffectiveProfiles(finalConfig.profiles);
15683
+ if (effective.length === 0) {
15684
+ return c.json({ error: "No profiles configured" }, 400);
15685
+ }
15686
+ if (!effective.find((p) => p.id === body.profile)) {
15687
+ return c.json({ error: `Unknown profile: ${body.profile}. Available: ${effective.map((p) => p.id).join(", ")}` }, 400);
15688
+ }
15689
+ setActiveProfile(body.profile);
15690
+ clearSessionCache();
15691
+ console.error(`[PROXY] Active profile switched to: ${body.profile} (session cache cleared)`);
15692
+ return c.json({ success: true, activeProfile: body.profile });
15693
+ });
15560
15694
  app.post("/auth/refresh", async (c) => {
15561
15695
  const success = await refreshOAuthToken();
15562
15696
  if (success) {
@@ -15702,10 +15836,29 @@ Or use a different port:`);
15702
15836
  console.error(` MERIDIAN_PORT=4567 meridian`);
15703
15837
  }
15704
15838
  });
15839
+ let authKeepaliveInterval;
15840
+ const effectiveProfiles = getEffectiveProfiles(finalConfig.profiles);
15841
+ if (effectiveProfiles.length > 0) {
15842
+ const AUTH_KEEPALIVE_MS = 45000;
15843
+ authKeepaliveInterval = setInterval(async () => {
15844
+ const currentProfiles = getEffectiveProfiles(finalConfig.profiles);
15845
+ for (const profile of currentProfiles) {
15846
+ const resolved = resolveProfile(finalConfig.profiles, finalConfig.defaultProfile, profile.id);
15847
+ if (Object.keys(resolved.env).length > 0) {
15848
+ getClaudeAuthStatusAsync(resolved.id, resolved.env).catch(() => {});
15849
+ }
15850
+ }
15851
+ getClaudeAuthStatusAsync().catch(() => {});
15852
+ }, AUTH_KEEPALIVE_MS);
15853
+ if (authKeepaliveInterval.unref)
15854
+ authKeepaliveInterval.unref();
15855
+ }
15705
15856
  return {
15706
15857
  server,
15707
15858
  config: finalConfig,
15708
15859
  async close() {
15860
+ if (authKeepaliveInterval)
15861
+ clearInterval(authKeepaliveInterval);
15709
15862
  await new Promise((resolve3, reject) => {
15710
15863
  server.close((err) => err ? reject(err) : resolve3());
15711
15864
  });
package/dist/cli.js CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-td95gtqj.js";
4
+ } from "./cli-zcxn6xmn.js";
5
+ import"./cli-g9ypdz51.js";
5
6
  import"./cli-rtab0qa6.js";
6
7
  import"./cli-m9pfb7h9.js";
8
+ import"./cli-vdp9s10c.js";
9
+ import"./cli-340h1chz.js";
7
10
  import {
8
11
  __require
9
12
  } from "./cli-a05ws7rb.js";
@@ -29,6 +32,7 @@ Usage: meridian [command] [options]
29
32
  Commands:
30
33
  (default) Start the proxy server
31
34
  setup Configure the OpenCode plugin (run once after install)
35
+ profile Manage Claude account profiles (add, list, switch, remove)
32
36
  refresh-token Refresh the Claude Code OAuth token
33
37
 
34
38
  Options:
@@ -44,6 +48,24 @@ Environment variables:
44
48
  See https://github.com/rynfar/meridian for full documentation.`);
45
49
  process.exit(0);
46
50
  }
51
+ if (args[0] === "profile") {
52
+ const { profileAdd, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-pdqrpw0m.js");
53
+ const subcommand = args[1];
54
+ const profileId = args[2];
55
+ if (subcommand === "add" && profileId)
56
+ profileAdd(profileId);
57
+ else if (subcommand === "list" || subcommand === "ls")
58
+ profileList();
59
+ else if (subcommand === "remove" && profileId)
60
+ profileRemove(profileId);
61
+ else if (subcommand === "switch" && profileId)
62
+ await profileSwitch(profileId);
63
+ else if (subcommand === "login" && profileId)
64
+ profileLogin(profileId);
65
+ else
66
+ profileHelp();
67
+ process.exit(0);
68
+ }
47
69
  if (args[0] === "setup") {
48
70
  const { findPluginPath, runSetup } = await import("./setup-5x116vbs.js");
49
71
  const pluginPath = findPluginPath(import.meta.url);
@@ -86,6 +108,17 @@ process.on("unhandledRejection", (reason) => {
86
108
  var port = parseInt(process.env.MERIDIAN_PORT ?? process.env.CLAUDE_PROXY_PORT ?? "3456", 10);
87
109
  var host = process.env.MERIDIAN_HOST ?? process.env.CLAUDE_PROXY_HOST ?? "127.0.0.1";
88
110
  var idleTimeoutSeconds = parseInt(process.env.MERIDIAN_IDLE_TIMEOUT_SECONDS ?? process.env.CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS ?? "120", 10);
111
+ var profiles;
112
+ var defaultProfile;
113
+ try {
114
+ const raw = process.env.MERIDIAN_PROFILES;
115
+ if (raw) {
116
+ profiles = JSON.parse(raw);
117
+ defaultProfile = process.env.MERIDIAN_DEFAULT_PROFILE || undefined;
118
+ }
119
+ } catch (e) {
120
+ console.error(`[meridian] Failed to parse MERIDIAN_PROFILES: ${e instanceof Error ? e.message : e}`);
121
+ }
89
122
  async function runCli(start = startProxyServer, runExec = exec) {
90
123
  try {
91
124
  const { findOpencodeConfigPath, checkPluginConfigured, findPluginPath } = await import("./setup-5x116vbs.js");
@@ -112,7 +145,11 @@ async function runCli(start = startProxyServer, runExec = exec) {
112
145
  } catch {
113
146
  console.error("\x1B[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1B[0m");
114
147
  }
115
- const proxy = await start({ port, host, idleTimeoutSeconds });
148
+ if (!profiles) {
149
+ const { enableDiskProfileDiscovery } = await import("./profiles-ntgacztq.js");
150
+ enableDiskProfileDiscovery();
151
+ }
152
+ const proxy = await start({ port, host, idleTimeoutSeconds, profiles, defaultProfile });
116
153
  proxy.server.on("error", (error) => {
117
154
  if (error.code === "EADDRINUSE") {
118
155
  process.exit(1);