@rynfar/meridian 1.71.1 → 1.73.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 (125) hide show
  1. package/README.md +115 -162
  2. package/assets/desktop-dashboard.jpg +0 -0
  3. package/assets/trayTemplate.png +0 -0
  4. package/assets/trayTemplate@2x.png +0 -0
  5. package/assets/trayTemplate@3x.png +0 -0
  6. package/dist/cli-1jstkdj9.js +30 -0
  7. package/dist/{cli-9e5cxp89.js → cli-2h3w5n6g.js} +163 -65
  8. package/dist/cli-3zwfvexc.js +56 -0
  9. package/dist/{cli-hxxy0m1z.js → cli-82e2m44k.js} +2 -1
  10. package/dist/cli-8sw5wr9j.js +136 -0
  11. package/dist/{cli-khhjyk04.js → cli-chsp880k.js} +214 -21
  12. package/dist/cli-dh0a1bw4.js +619 -0
  13. package/dist/cli-eehk3052.js +57 -0
  14. package/dist/cli-grfp0s1j.js +330 -0
  15. package/dist/{cli-ryt69ryf.js → cli-p5zbk952.js} +2185 -358
  16. package/dist/{cli-8yp89fan.js → cli-p9ca4p13.js} +17 -2
  17. package/dist/cli-qpfky8wy.js +133 -0
  18. package/dist/cli.js +47 -12
  19. package/dist/cliDashboard-c0dvkj4y.js +325 -0
  20. package/dist/configDir.d.ts +30 -0
  21. package/dist/configDir.d.ts.map +1 -0
  22. package/dist/organizationName-90f0atkn.js +29 -0
  23. package/dist/{pluginPage-n3gdtbmj.js → pluginPage-3kxsrdpj.js} +1 -1
  24. package/dist/{profileCli-1eecbmgk.js → profileCli-1bfgc05h.js} +13 -3
  25. package/dist/{profilePage-vfj2dbhn.js → profilePage-etxqg3vn.js} +252 -60
  26. package/dist/profileRename-4a4km6g8.js +22 -0
  27. package/dist/{profiles-wch9h234.js → profiles-0mp32ek9.js} +16 -3
  28. package/dist/proxy/adapterInstances.d.ts +1 -1
  29. package/dist/proxy/adapterInstances.d.ts.map +1 -1
  30. package/dist/proxy/adapters/claudecode.d.ts.map +1 -1
  31. package/dist/proxy/adapters/droid.d.ts.map +1 -1
  32. package/dist/proxy/authDiscovery.d.ts +52 -0
  33. package/dist/proxy/authDiscovery.d.ts.map +1 -0
  34. package/dist/proxy/credentialsMode.d.ts +77 -0
  35. package/dist/proxy/credentialsMode.d.ts.map +1 -0
  36. package/dist/proxy/design.d.ts.map +1 -1
  37. package/dist/proxy/errors.d.ts.map +1 -1
  38. package/dist/proxy/followActive.d.ts +262 -0
  39. package/dist/proxy/followActive.d.ts.map +1 -0
  40. package/dist/proxy/limitDetection.d.ts +110 -0
  41. package/dist/proxy/limitDetection.d.ts.map +1 -0
  42. package/dist/proxy/models.d.ts.map +1 -1
  43. package/dist/proxy/oauthPlan.d.ts +63 -0
  44. package/dist/proxy/oauthPlan.d.ts.map +1 -0
  45. package/dist/proxy/oauthUsage.d.ts +65 -2
  46. package/dist/proxy/oauthUsage.d.ts.map +1 -1
  47. package/dist/proxy/openai.d.ts +4 -2
  48. package/dist/proxy/openai.d.ts.map +1 -1
  49. package/dist/proxy/organizationName.d.ts +71 -0
  50. package/dist/proxy/organizationName.d.ts.map +1 -0
  51. package/dist/proxy/passthroughEarlyStop.d.ts +10 -0
  52. package/dist/proxy/passthroughEarlyStop.d.ts.map +1 -1
  53. package/dist/proxy/planAllowance.d.ts +75 -0
  54. package/dist/proxy/planAllowance.d.ts.map +1 -0
  55. package/dist/proxy/probes.d.ts +85 -0
  56. package/dist/proxy/probes.d.ts.map +1 -0
  57. package/dist/proxy/profileCli.d.ts +53 -1
  58. package/dist/proxy/profileCli.d.ts.map +1 -1
  59. package/dist/proxy/profileHealth.d.ts +127 -0
  60. package/dist/proxy/profileHealth.d.ts.map +1 -0
  61. package/dist/proxy/profileRename.d.ts +83 -0
  62. package/dist/proxy/profileRename.d.ts.map +1 -0
  63. package/dist/proxy/profiles.d.ts +72 -6
  64. package/dist/proxy/profiles.d.ts.map +1 -1
  65. package/dist/proxy/query.d.ts +7 -0
  66. package/dist/proxy/query.d.ts.map +1 -1
  67. package/dist/proxy/routing.d.ts +59 -1
  68. package/dist/proxy/routing.d.ts.map +1 -1
  69. package/dist/proxy/sdkFeatures.d.ts +1 -1
  70. package/dist/proxy/sdkFeatures.d.ts.map +1 -1
  71. package/dist/proxy/server.d.ts.map +1 -1
  72. package/dist/proxy/session/fingerprint.d.ts +5 -0
  73. package/dist/proxy/session/fingerprint.d.ts.map +1 -1
  74. package/dist/proxy/session/lineage.d.ts.map +1 -1
  75. package/dist/proxy/session/processIncarnation.d.ts +7 -0
  76. package/dist/proxy/session/processIncarnation.d.ts.map +1 -1
  77. package/dist/proxy/sessionLifecycle.d.ts +8 -1
  78. package/dist/proxy/sessionLifecycle.d.ts.map +1 -1
  79. package/dist/proxy/sessionStore.d.ts.map +1 -1
  80. package/dist/proxy/setup.d.ts.map +1 -1
  81. package/dist/proxy/supervision.d.ts +41 -0
  82. package/dist/proxy/supervision.d.ts.map +1 -0
  83. package/dist/proxy/tokenRefresh.d.ts +38 -4
  84. package/dist/proxy/tokenRefresh.d.ts.map +1 -1
  85. package/dist/server.js +11 -7
  86. package/dist/settings.d.ts +83 -0
  87. package/dist/settings.d.ts.map +1 -0
  88. package/dist/{setup-ndmjpy23.js → setup-7fr49z92.js} +1 -1
  89. package/dist/statusProbe-9x9q261e.js +151 -0
  90. package/dist/telemetry/dashboard.d.ts.map +1 -1
  91. package/dist/telemetry/index.d.ts +20 -2
  92. package/dist/telemetry/index.d.ts.map +1 -1
  93. package/dist/telemetry/landing.d.ts +1 -1
  94. package/dist/telemetry/landing.d.ts.map +1 -1
  95. package/dist/telemetry/logStore.d.ts +11 -0
  96. package/dist/telemetry/logStore.d.ts.map +1 -1
  97. package/dist/telemetry/pricingStore.d.ts +2 -2
  98. package/dist/telemetry/pricingStore.d.ts.map +1 -1
  99. package/dist/telemetry/profileBar.d.ts +10 -2
  100. package/dist/telemetry/profileBar.d.ts.map +1 -1
  101. package/dist/telemetry/profileFacts.d.ts +17 -0
  102. package/dist/telemetry/profileFacts.d.ts.map +1 -0
  103. package/dist/telemetry/profileOrder.d.ts +68 -0
  104. package/dist/telemetry/profileOrder.d.ts.map +1 -0
  105. package/dist/telemetry/profilePage.d.ts.map +1 -1
  106. package/dist/telemetry/profileSort.d.ts +35 -0
  107. package/dist/telemetry/profileSort.d.ts.map +1 -0
  108. package/dist/telemetry/profileSpent.d.ts +68 -0
  109. package/dist/telemetry/profileSpent.d.ts.map +1 -0
  110. package/dist/telemetry/routeChain.d.ts +32 -0
  111. package/dist/telemetry/routeChain.d.ts.map +1 -0
  112. package/dist/telemetry/routes.d.ts.map +1 -1
  113. package/dist/telemetry/settingsPage.d.ts +1 -1
  114. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  115. package/dist/telemetry/sqlite.d.ts.map +1 -1
  116. package/dist/telemetry/store.d.ts +6 -1
  117. package/dist/telemetry/store.d.ts.map +1 -1
  118. package/dist/telemetry/types.d.ts +132 -2
  119. package/dist/telemetry/types.d.ts.map +1 -1
  120. package/dist/{tokenRefresh-kzz08kea.js → tokenRefresh-eg9h83wd.js} +6 -1
  121. package/package.json +2 -1
  122. package/dist/cli-5jxyma6z.js +0 -277
  123. package/dist/cli-vj9cv18n.js +0 -38
  124. package/dist/proxy/settings.d.ts +0 -30
  125. package/dist/proxy/settings.d.ts.map +0 -1
@@ -118,7 +118,9 @@ var themeCss = `
118
118
  background: rgba(188,140,255,0.12);
119
119
  border: 1px solid rgba(188,140,255,0.35);
120
120
  cursor: default;
121
- }
121
+ .meridian-header .mh-profile.following { border-color: var(--accent2, #bc8cff); }
122
+ .meridian-header .mh-profile .mh-profile-follow {
123
+ color: var(--accent2, #bc8cff); font-size: 10px; }
122
124
  .meridian-header .mh-status {
123
125
  display: inline-flex; align-items: center; gap: 6px;
124
126
  font-size: 11px; color: var(--muted, #8b949e); white-space: nowrap;
@@ -193,7 +195,20 @@ var themeCss = `
193
195
  fetch('/profiles/list').then(function(r) { return r.json(); }).then(function(data) {
194
196
  var current = (data.profiles || []).find(function(p) { return p.isActive; });
195
197
  if (!current) { profileChip.classList.remove('visible'); return; }
196
- profileChip.innerHTML = esc(current.id) + ' <span class="mh-profile-type">' + esc(current.type || '') + '</span>';
198
+ // Follow mode: say so on every page. An instance quietly taking its
199
+ // active profile from another one, with a picker that won't stick, is
200
+ // otherwise an hour of confusion.
201
+ var follow = data.follow;
202
+ var followLabel = follow ? (follow.activeProfile ? 'following' : 'follow: local') : '';
203
+ if (follow && follow.stale) followLabel += ' (stale)';
204
+ profileChip.innerHTML = esc(current.id) + ' <span class="mh-profile-type">' + esc(current.type || '') + '</span>'
205
+ + (follow ? ' <span class="mh-profile-follow">' + esc(followLabel) + '</span>' : '');
206
+ profileChip.classList.toggle('following', !!follow);
207
+ profileChip.title = follow
208
+ ? 'Active profile follows ' + follow.url + ' (MERIDIAN_FOLLOW_ACTIVE)'
209
+ + (follow.activeProfile ? '' : ' — no usable value from it, using the local profile')
210
+ + '. Switching here is refused; switch on the followed instance.'
211
+ : 'Active profile — switch from the home page';
197
212
  profileChip.classList.add('visible');
198
213
  }).catch(function() {});
199
214
  }
@@ -0,0 +1,133 @@
1
+ import {
2
+ configPath,
3
+ getSetting,
4
+ init_configDir,
5
+ setSetting
6
+ } from "./cli-eehk3052.js";
7
+
8
+ // src/proxy/profileRename.ts
9
+ init_configDir();
10
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ var INVALID_PROFILE_ID = /[^a-zA-Z0-9_-]/;
13
+ function isValidProfileId(id) {
14
+ return id.length > 0 && !INVALID_PROFILE_ID.test(id);
15
+ }
16
+ function defaultProfilesDir() {
17
+ return configPath("profiles");
18
+ }
19
+ function defaultProfilesConfigFile() {
20
+ return configPath("profiles.json");
21
+ }
22
+ function loadProfileConfigFrom(file) {
23
+ if (!existsSync(file))
24
+ return [];
25
+ try {
26
+ const parsed = JSON.parse(readFileSync(file, "utf-8"));
27
+ return Array.isArray(parsed) ? parsed : [];
28
+ } catch (err) {
29
+ console.warn(`[meridian] Failed to read ${file}: ${err instanceof Error ? err.message : err}`);
30
+ return [];
31
+ }
32
+ }
33
+ function saveProfileConfigTo(file, profiles) {
34
+ mkdirSync(dirname(file), { recursive: true });
35
+ writeFileSync(file, `${JSON.stringify(profiles, null, 2)}
36
+ `, { mode: 384 });
37
+ }
38
+ function reclaimAlias(profiles, name) {
39
+ return profiles.map((p) => {
40
+ if (!p.aliases?.includes(name))
41
+ return p;
42
+ const remaining = p.aliases.filter((a) => a !== name);
43
+ const next = { ...p };
44
+ if (remaining.length > 0)
45
+ next.aliases = remaining;
46
+ else
47
+ delete next.aliases;
48
+ return next;
49
+ });
50
+ }
51
+ function planProfileRename(profiles, from, to, profilesDir) {
52
+ if (!isValidProfileId(to)) {
53
+ return {
54
+ ok: false,
55
+ error: `Invalid profile name "${to}".`,
56
+ hint: "Use only letters, numbers, hyphens, underscores."
57
+ };
58
+ }
59
+ const target = profiles.find((p) => p.id === from);
60
+ if (!target) {
61
+ return { ok: false, error: `Profile "${from}" not found.`, hint: "Run: meridian profile list" };
62
+ }
63
+ if (from === to) {
64
+ return { ok: false, error: `Profile "${from}" is already called that.` };
65
+ }
66
+ if (profiles.some((p) => p.id === to)) {
67
+ return { ok: false, error: `Profile "${to}" already exists.`, hint: "Run: meridian profile list" };
68
+ }
69
+ const oldDir = join(profilesDir, from);
70
+ const newDir = join(profilesDir, to);
71
+ const ownsDir = target.claudeConfigDir === oldDir;
72
+ const isOauthToken = Boolean(target.oauthToken) || target.type === "oauth-token";
73
+ const aliases = [...new Set([...target.aliases ?? [], from])].filter((a) => a !== to);
74
+ const renamed = { ...target, id: to, aliases };
75
+ if (ownsDir)
76
+ renamed.claudeConfigDir = newDir;
77
+ const next = reclaimAlias(profiles, to).map((p) => p.id === from ? renamed : p);
78
+ const plan = { profiles: next, aliases };
79
+ if (ownsDir || isOauthToken)
80
+ plan.dirMove = { from: oldDir, to: newDir };
81
+ return { ok: true, plan };
82
+ }
83
+ function applyProfileRename(from, to, options = {}) {
84
+ const profilesDir = options.profilesDir ?? defaultProfilesDir();
85
+ const configFile = options.configFile ?? defaultProfilesConfigFile();
86
+ const profiles = loadProfileConfigFrom(configFile);
87
+ const planned = planProfileRename(profiles, from, to, profilesDir);
88
+ if (!planned.ok)
89
+ return planned;
90
+ const { plan } = planned;
91
+ let movedDir;
92
+ if (plan.dirMove && existsSync(plan.dirMove.from)) {
93
+ if (existsSync(plan.dirMove.to)) {
94
+ return {
95
+ ok: false,
96
+ error: `Cannot rename to "${to}": ${plan.dirMove.to} already exists.`,
97
+ hint: "Remove or move that directory, then retry."
98
+ };
99
+ }
100
+ try {
101
+ renameSync(plan.dirMove.from, plan.dirMove.to);
102
+ movedDir = plan.dirMove;
103
+ } catch (err) {
104
+ return {
105
+ ok: false,
106
+ error: `Could not move credentials for "${from}": ${err instanceof Error ? err.message : err}`
107
+ };
108
+ }
109
+ }
110
+ try {
111
+ saveProfileConfigTo(configFile, plan.profiles);
112
+ } catch (err) {
113
+ const reason = err instanceof Error ? err.message : String(err);
114
+ if (movedDir) {
115
+ try {
116
+ renameSync(movedDir.to, movedDir.from);
117
+ } catch (undoErr) {
118
+ const undoReason = undoErr instanceof Error ? undoErr.message : String(undoErr);
119
+ return {
120
+ ok: false,
121
+ error: `Could not write ${configFile} (${reason}), and could not move credentials back (${undoReason}).`,
122
+ hint: `Profile "${from}" still expects its credentials at ${movedDir.from} — move ${movedDir.to} back by hand.`
123
+ };
124
+ }
125
+ }
126
+ return { ok: false, error: `Could not write ${configFile}: ${reason}` };
127
+ }
128
+ if (getSetting("activeProfile") === from)
129
+ setSetting("activeProfile", to);
130
+ return { ok: true, from, to, aliases: plan.aliases, profiles: plan.profiles };
131
+ }
132
+
133
+ export { isValidProfileId, defaultProfilesDir, defaultProfilesConfigFile, loadProfileConfigFrom, saveProfileConfigTo, reclaimAlias, planProfileRename, applyProfileRename };
package/dist/cli.js CHANGED
@@ -1,16 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startProxyServer
4
- } from "./cli-ryt69ryf.js";
5
- import"./cli-5jxyma6z.js";
4
+ } from "./cli-p5zbk952.js";
5
+ import"./cli-82e2m44k.js";
6
+ import"./cli-dh0a1bw4.js";
7
+ import"./cli-8sw5wr9j.js";
8
+ import"./cli-grfp0s1j.js";
6
9
  import"./cli-sry5aqdj.js";
7
- import"./cli-8yp89fan.js";
10
+ import"./cli-p9ca4p13.js";
8
11
  import {
9
12
  resolveClaudeExecutableAsync
10
- } from "./cli-9e5cxp89.js";
11
- import"./cli-khhjyk04.js";
12
- import"./cli-vj9cv18n.js";
13
- import"./cli-hxxy0m1z.js";
13
+ } from "./cli-2h3w5n6g.js";
14
+ import"./cli-chsp880k.js";
15
+ import"./cli-1jstkdj9.js";
16
+ import"./cli-qpfky8wy.js";
17
+ import"./cli-eehk3052.js";
14
18
  import {
15
19
  __require
16
20
  } from "./cli-p9swy5t3.js";
@@ -35,6 +39,7 @@ Usage: meridian [command] [options]
35
39
 
36
40
  Commands:
37
41
  (default) Start the proxy server
42
+ status Show what a running instance is doing (the / page, in the terminal)
38
43
  setup Configure the OpenCode plugin (run once after install)
39
44
  profile Manage Claude account profiles (add, list, switch, remove)
40
45
  refresh-token Refresh the Claude Code OAuth token
@@ -60,7 +65,7 @@ See https://github.com/rynfar/meridian for full documentation.`);
60
65
  process.exit(0);
61
66
  }
62
67
  if (args[0] === "profile") {
63
- const { profileAdd, profileAddOauthToken, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-1eecbmgk.js");
68
+ const { profileAdd, profileAddOauthToken, profileList, profileRemove, profileRename, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-1bfgc05h.js");
64
69
  const subcommand = args[1];
65
70
  const profileId = args[2];
66
71
  const headless = args.includes("--headless");
@@ -76,6 +81,8 @@ if (args[0] === "profile") {
76
81
  profileList();
77
82
  else if (subcommand === "remove" && profileId)
78
83
  profileRemove(profileId);
84
+ else if (subcommand === "rename" && profileId && args[3])
85
+ profileRename(profileId, args[3]);
79
86
  else if (subcommand === "switch" && profileId)
80
87
  await profileSwitch(profileId);
81
88
  else if (subcommand === "login" && profileId)
@@ -93,7 +100,7 @@ if (args[0] === "setup") {
93
100
  runSetup,
94
101
  SUPPORTED_OPENCODE_V2_VERSIONS,
95
102
  UnparseableConfigError
96
- } = await import("./setup-ndmjpy23.js");
103
+ } = await import("./setup-7fr49z92.js");
97
104
  const forceV1 = args.includes("--v1");
98
105
  const forceV2 = args.includes("--v2");
99
106
  if (forceV1 && forceV2) {
@@ -172,7 +179,7 @@ Restart OpenCode for the plugin to take effect.`);
172
179
  process.exit(0);
173
180
  }
174
181
  if (args[0] === "refresh-token") {
175
- const { refreshOAuthToken } = await import("./tokenRefresh-kzz08kea.js");
182
+ const { refreshOAuthToken } = await import("./tokenRefresh-eg9h83wd.js");
176
183
  const success = await refreshOAuthToken();
177
184
  if (success) {
178
185
  console.log("Token refreshed successfully");
@@ -189,6 +196,23 @@ var host = process.env.MERIDIAN_HOST ?? process.env.CLAUDE_PROXY_HOST ?? "127.0.
189
196
  var idleTimeoutSeconds = parseInt(process.env.MERIDIAN_IDLE_TIMEOUT_SECONDS ?? process.env.CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS ?? "120", 10);
190
197
  var pluginDir = process.env.MERIDIAN_PLUGIN_DIR;
191
198
  var pluginConfigPath = process.env.MERIDIAN_PLUGIN_CONFIG;
199
+ async function printRunningInstance() {
200
+ const { probeMeridian } = await import("./statusProbe-9x9q261e.js");
201
+ const result = await probeMeridian(host, port, { apiKey: process.env.MERIDIAN_API_KEY });
202
+ if (result.kind === "meridian") {
203
+ const { renderCliDashboard } = await import("./cliDashboard-c0dvkj4y.js");
204
+ process.stdout.write(renderCliDashboard({ host, port, ...result.snapshot }, { color: process.stdout.isTTY === true }));
205
+ }
206
+ return result;
207
+ }
208
+ if (args[0] === "status") {
209
+ const result = await printRunningInstance();
210
+ if (result.kind === "meridian")
211
+ process.exit(0);
212
+ const { formatStatusMessage } = await import("./statusProbe-9x9q261e.js");
213
+ console.error(formatStatusMessage(result, host, port));
214
+ process.exit(1);
215
+ }
192
216
  var profiles;
193
217
  var defaultProfile;
194
218
  try {
@@ -205,7 +229,7 @@ async function runCli(start = startProxyServer, runAuthCheck = async () => {
205
229
  return execFile(claudePath, ["auth", "status"], { timeout: 5000 });
206
230
  }) {
207
231
  try {
208
- const { findOpencodeConfigPath, checkPluginConfigured, findPluginPath } = await import("./setup-ndmjpy23.js");
232
+ const { findOpencodeConfigPath, checkPluginConfigured, findPluginPath } = await import("./setup-7fr49z92.js");
209
233
  const configPath = findOpencodeConfigPath();
210
234
  const { existsSync } = await import("fs");
211
235
  if (existsSync(configPath) && !checkPluginConfigured(configPath)) {
@@ -230,9 +254,11 @@ async function runCli(start = startProxyServer, runAuthCheck = async () => {
230
254
  console.error("\x1B[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1B[0m");
231
255
  }
232
256
  if (!profiles) {
233
- const { enableDiskProfileDiscovery } = await import("./profiles-wch9h234.js");
257
+ const { enableDiskProfileDiscovery } = await import("./profiles-0mp32ek9.js");
234
258
  enableDiskProfileDiscovery();
235
259
  }
260
+ const { enableOrganizationLookup } = await import("./organizationName-90f0atkn.js");
261
+ enableOrganizationLookup();
236
262
  const proxy = await start({ port, host, idleTimeoutSeconds, pluginDir, pluginConfigPath, profiles, defaultProfile, version, installProcessErrorHandlers: true });
237
263
  proxy.server.on("error", (error) => {
238
264
  if (error.code === "EADDRINUSE") {
@@ -258,6 +284,15 @@ async function runCli(start = startProxyServer, runAuthCheck = async () => {
258
284
  process.on("SIGINT", handleShutdownSignal);
259
285
  }
260
286
  if (__require.main == __require.module) {
287
+ const { isPortAvailable } = await import("./statusProbe-9x9q261e.js");
288
+ if (!await isPortAvailable(host, port)) {
289
+ const result = await printRunningInstance();
290
+ if (result.kind === "meridian")
291
+ process.exit(0);
292
+ const { formatConflictMessage } = await import("./statusProbe-9x9q261e.js");
293
+ console.error(formatConflictMessage(result, host, port));
294
+ process.exit(1);
295
+ }
261
296
  await runCli();
262
297
  }
263
298
  export {
@@ -0,0 +1,325 @@
1
+ import {
2
+ WINDOW_LABELS,
3
+ classifyUtilization,
4
+ computeWeeklyPace,
5
+ formatResetCountdown
6
+ } from "./cli-3zwfvexc.js";
7
+ import"./cli-p9swy5t3.js";
8
+
9
+ // src/telemetry/cliDashboard.ts
10
+ var PALETTE = {
11
+ green: 71,
12
+ yellow: 178,
13
+ red: 203,
14
+ muted: 246,
15
+ accent: 75
16
+ };
17
+ var colorPaint = (text, color) => `\x1B[38;5;${PALETTE[color]}m${text}\x1B[0m`;
18
+ var plainPaint = (text) => text;
19
+ var UNKNOWN = "—";
20
+ function isNum(v) {
21
+ return typeof v === "number" && Number.isFinite(v);
22
+ }
23
+ function usd(v) {
24
+ if (!isNum(v))
25
+ return UNKNOWN;
26
+ if (v > 0 && v < 0.01)
27
+ return `$${v.toFixed(4)}`;
28
+ if (v < 100)
29
+ return `$${v.toFixed(2)}`;
30
+ return `$${Math.round(v).toLocaleString("en-US")}`;
31
+ }
32
+ function tokens(v) {
33
+ if (!isNum(v))
34
+ return UNKNOWN;
35
+ if (v >= 1e6)
36
+ return `${(v / 1e6).toFixed(1)}M`;
37
+ if (v >= 1000)
38
+ return `${(v / 1000).toFixed(1)}k`;
39
+ return String(v);
40
+ }
41
+ function millis(v) {
42
+ if (!isNum(v) || v === 0)
43
+ return UNKNOWN;
44
+ return v < 1000 ? `${v}ms` : `${(v / 1000).toFixed(1)}s`;
45
+ }
46
+ function windowLabel(type) {
47
+ const known = WINDOW_LABELS[type];
48
+ if (known)
49
+ return known;
50
+ return type.replace(/^seven_day_/, "7d ").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
51
+ }
52
+ function utilizationColor(utilization) {
53
+ const status = classifyUtilization(utilization);
54
+ return status === "high" ? "red" : status === "warn" ? "yellow" : "green";
55
+ }
56
+ function paceStatus(pace) {
57
+ return pace.projectedPct != null && pace.projectedPct >= 100 ? "over" : pace.status;
58
+ }
59
+ function paceColor(status) {
60
+ return status === "over" ? "red" : status === "ahead" ? "yellow" : "green";
61
+ }
62
+ var BAR_WIDTH = 28;
63
+ var BAR_FILL = "█";
64
+ var BAR_TRACK = "░";
65
+ var BAR_MARKER = "┃";
66
+ function clampPct(pct) {
67
+ return Math.max(0, Math.min(100, pct));
68
+ }
69
+ function renderBar(pct, color, paint, markerPct) {
70
+ const filled = Math.round(clampPct(pct) / 100 * BAR_WIDTH);
71
+ const marker = markerPct == null ? -1 : Math.min(Math.round(clampPct(markerPct) / 100 * BAR_WIDTH), BAR_WIDTH - 1);
72
+ const cells = [];
73
+ for (let i2 = 0;i2 < BAR_WIDTH; i2++) {
74
+ if (i2 === marker)
75
+ cells.push({ ch: BAR_MARKER, role: "marker" });
76
+ else if (i2 < filled)
77
+ cells.push({ ch: BAR_FILL, role: "fill" });
78
+ else
79
+ cells.push({ ch: BAR_TRACK, role: "track" });
80
+ }
81
+ let out = "";
82
+ let i = 0;
83
+ while (i < cells.length) {
84
+ const role = cells[i].role;
85
+ let run = "";
86
+ while (i < cells.length && cells[i].role === role) {
87
+ run += cells[i].ch;
88
+ i++;
89
+ }
90
+ out += role === "fill" ? paint(run, color) : role === "track" ? paint(run, "muted") : run;
91
+ }
92
+ return out;
93
+ }
94
+ function buildAccounts(input, now) {
95
+ const byProfile = input.summary?.costEstimate?.byProfile ?? {};
96
+ const quotaByProfile = {};
97
+ for (const entry of input.quota?.profiles ?? []) {
98
+ quotaByProfile[entry.id || entry.profile || "default"] = entry.windows ?? [];
99
+ }
100
+ const configured = input.profileList?.profiles ?? [];
101
+ const candidates = [];
102
+ if (configured.length > 0) {
103
+ for (const p of configured) {
104
+ const id = p.id ?? "default";
105
+ candidates.push({ id, label: id, isActive: p.isActive === true, configured: true });
106
+ }
107
+ } else {
108
+ const auth = input.health?.auth;
109
+ const email = auth?.loggedIn && auth.email || "";
110
+ const seen = new Set;
111
+ for (const key of [...Object.keys(quotaByProfile), ...Object.keys(byProfile)]) {
112
+ if (seen.has(key))
113
+ continue;
114
+ seen.add(key);
115
+ candidates.push({
116
+ id: key,
117
+ label: key === "default" ? email || "account" : key,
118
+ isActive: false,
119
+ configured: false
120
+ });
121
+ }
122
+ }
123
+ const isPriority = input.profileList?.routing === "priority";
124
+ const blocks = [];
125
+ for (const candidate of candidates) {
126
+ const cost = byProfile[candidate.id];
127
+ const windows = (quotaByProfile[candidate.id] ?? []).filter((w) => w.utilization != null);
128
+ if (!candidate.configured && windows.length === 0 && !cost)
129
+ continue;
130
+ const rows = [];
131
+ for (const w of windows) {
132
+ const utilization = w.utilization;
133
+ if (!isNum(utilization))
134
+ continue;
135
+ rows.push({
136
+ label: windowLabel(w.type ?? ""),
137
+ pct: Math.round(utilization * 100),
138
+ figure: `${Math.round(utilization * 100)}%`,
139
+ color: utilizationColor(utilization),
140
+ note: formatResetCountdown(w.resetsAt, now)
141
+ });
142
+ }
143
+ let weekly;
144
+ for (const w of windows)
145
+ if (w.type === "seven_day")
146
+ weekly = w;
147
+ const pace = weekly ? computeWeeklyPace(weekly.utilization, weekly.resetsAt, now) : null;
148
+ if (pace) {
149
+ const status = paceStatus(pace);
150
+ const projected = pace.projectedPct;
151
+ const figure = status === "over" ? projected != null ? `${projected}%` : "100%" : `${pace.deltaPct >= 0 ? "+" : "-"}${Math.abs(pace.deltaPct)}%`;
152
+ rows.push({
153
+ label: "pace",
154
+ pct: pace.actualPct,
155
+ markerPct: pace.expectedPct,
156
+ figure,
157
+ color: paceColor(status),
158
+ note: status === "over" ? "runs out before reset" : projected != null ? `~${projected}% by reset` : ""
159
+ });
160
+ }
161
+ const badges = [];
162
+ if (isPriority) {
163
+ const order = input.profileList?.profileOrder ?? [];
164
+ const idx = order.indexOf(candidate.id);
165
+ if (idx >= 0)
166
+ badges.push({ text: `#${idx + 1} in pool`, color: "muted" });
167
+ const exhausted = (input.profileList?.exhausted ?? []).find((e) => e.id === candidate.id);
168
+ if (exhausted) {
169
+ badges.push({
170
+ text: `exhausted · resets ${formatResetCountdown(exhausted.until, now)}`,
171
+ color: "red"
172
+ });
173
+ }
174
+ } else if (candidate.isActive) {
175
+ badges.push({ text: "ACTIVE", color: "accent" });
176
+ }
177
+ const requests = cost?.requests;
178
+ blocks.push({
179
+ label: candidate.label,
180
+ isActive: candidate.isActive,
181
+ badges,
182
+ cost: usd(cost ? cost.estimatedUsd : 0),
183
+ sub: isNum(requests) ? `${requests} request${requests === 1 ? "" : "s"} · est. API value · 24h` : "no traffic · 24h",
184
+ rows
185
+ });
186
+ }
187
+ return blocks;
188
+ }
189
+ function buildStrip(summary) {
190
+ const tu = summary.tokenUsage ?? {};
191
+ const hitRate = tu.avgCacheHitRate;
192
+ const errors = summary.errorCount;
193
+ const items = [
194
+ {
195
+ label: "Requests",
196
+ value: isNum(summary.totalRequests) ? String(summary.totalRequests) : UNKNOWN,
197
+ detail: !isNum(errors) ? UNKNOWN : errors > 0 ? `${errors} error${errors === 1 ? "" : "s"}` : "no errors",
198
+ detailColor: isNum(errors) && errors > 0 ? "red" : undefined
199
+ },
200
+ {
201
+ label: "Tokens Out",
202
+ value: tokens(tu.totalOutputTokens),
203
+ detail: `${tokens(tu.totalInputTokens)} in`
204
+ },
205
+ {
206
+ label: "Cache Hit",
207
+ value: isNum(hitRate) ? `${Math.round(hitRate * 100)}%` : UNKNOWN,
208
+ valueColor: isNum(hitRate) && hitRate >= 0.5 ? "green" : undefined,
209
+ detail: "prompt cache"
210
+ },
211
+ {
212
+ label: "Est. API Value",
213
+ value: usd(summary.costEstimate?.totalUsd),
214
+ detail: "list prices"
215
+ },
216
+ {
217
+ label: "Median Response",
218
+ value: millis(summary.totalDuration?.p50),
219
+ detail: `p95 ${millis(summary.totalDuration?.p95)}`
220
+ }
221
+ ];
222
+ const violations = summary.envelopeViolationCount;
223
+ if (isNum(violations) && violations > 0) {
224
+ items.push({
225
+ label: "Envelope",
226
+ value: String(violations),
227
+ valueColor: "red",
228
+ detail: "wire-contract violations",
229
+ detailColor: "red"
230
+ });
231
+ }
232
+ return items;
233
+ }
234
+ function healthPill(health) {
235
+ const status = health?.status;
236
+ if (status === "healthy")
237
+ return { label: "Operational", color: "green" };
238
+ if (status === "degraded")
239
+ return { label: "Degraded", color: "yellow" };
240
+ return { label: "Offline", color: "red" };
241
+ }
242
+ function activeProfileChip(profileList) {
243
+ const active = (profileList?.profiles ?? []).find((p) => p.isActive === true);
244
+ if (!active?.id)
245
+ return null;
246
+ return active.type ? `${active.id} ${active.type}` : active.id;
247
+ }
248
+ var INDENT = " ";
249
+ function renderCliDashboard(input, options = {}) {
250
+ const paint = options.color ? colorPaint : plainPaint;
251
+ const bold = options.color ? (t) => `\x1B[1m${t}\x1B[22m` : (t) => t;
252
+ const now = input.now ?? Date.now();
253
+ const out = [];
254
+ const pill = healthPill(input.health);
255
+ const chip = activeProfileChip(input.profileList);
256
+ out.push(`${bold("MERIDIAN")} ${paint("●", pill.color)} ${pill.label}` + (chip ? ` ${paint("·", "muted")} ${chip}` : ""));
257
+ out.push("");
258
+ out.push(`${INDENT}ANTHROPIC_BASE_URL=http://${input.host}:${input.port}`);
259
+ const meta = [];
260
+ const auth = input.health?.auth;
261
+ if (auth?.loggedIn) {
262
+ meta.push(`${auth.email ?? ""}${auth.subscriptionType ? ` (${auth.subscriptionType})` : ""}`);
263
+ }
264
+ meta.push(input.health?.mode ?? "internal");
265
+ meta.push(`port ${input.port}`);
266
+ out.push(`${INDENT}${paint(meta.join(" · "), "muted")}`);
267
+ out.push("");
268
+ const accounts = buildAccounts(input, now);
269
+ const accountsNote = input.unavailable?.accounts;
270
+ if (accounts.length > 0 || accountsNote) {
271
+ out.push(paint(accounts.length === 1 ? "ACCOUNT" : "ACCOUNTS", "muted"));
272
+ out.push("");
273
+ if (accountsNote) {
274
+ out.push(`${INDENT}${paint(accountsNote, "yellow")}`);
275
+ out.push("");
276
+ }
277
+ const allRows = accounts.flatMap((a) => a.rows);
278
+ const labelWidth = Math.max(0, ...allRows.map((r) => r.label.length));
279
+ const figureWidth = Math.max(0, ...allRows.map((r) => r.figure.length));
280
+ const costColumn = INDENT.length + 2 + labelWidth + 2 + BAR_WIDTH + 2 + figureWidth;
281
+ for (const account of accounts) {
282
+ const dot = paint("●", account.isActive ? "accent" : "muted");
283
+ const badges = account.badges.map((b) => b.color ? paint(b.text, b.color) : b.text).join(" ");
284
+ const headLeft = `${account.label}${badges ? ` ${badges}` : ""}`;
285
+ const headLeftPlain = account.label + (account.badges.length > 0 ? ` ${account.badges.map((b) => b.text).join(" ")}` : "");
286
+ const gap = Math.max(1, costColumn - INDENT.length - 2 - headLeftPlain.length - account.cost.length);
287
+ out.push(`${INDENT}${dot} ${headLeft}${" ".repeat(gap)}${bold(account.cost)}`);
288
+ out.push(`${INDENT} ${paint(account.sub, "muted")}`);
289
+ if (account.rows.length === 0) {
290
+ out.push(`${INDENT} ${paint("no usage data yet", "muted")}`);
291
+ }
292
+ for (const row of account.rows) {
293
+ const label = row.label.padEnd(labelWidth);
294
+ const bar = renderBar(row.pct, row.color, paint, row.markerPct);
295
+ const figure = row.figure.padStart(figureWidth);
296
+ const note = row.note ? ` ${paint(row.note, "muted")}` : "";
297
+ out.push(`${INDENT} ${paint(label, "muted")} ${bar} ${paint(figure, row.color)}${note}`);
298
+ }
299
+ out.push("");
300
+ }
301
+ }
302
+ const trafficNote = input.unavailable?.traffic;
303
+ out.push(paint("LAST 24 HOURS", "muted"));
304
+ out.push("");
305
+ if (trafficNote) {
306
+ out.push(`${INDENT}${paint(trafficNote, "yellow")}`);
307
+ } else {
308
+ const items = buildStrip(input.summary ?? {});
309
+ const labelWidth = Math.max(...items.map((i) => i.label.length));
310
+ const valueWidth = Math.max(...items.map((i) => i.value.length));
311
+ for (const item of items) {
312
+ const label = paint(item.label.padEnd(labelWidth), "muted");
313
+ const valuePlain = item.value.padStart(valueWidth);
314
+ const value = item.valueColor ? paint(valuePlain, item.valueColor) : valuePlain;
315
+ const detail = item.detailColor ? paint(item.detail, item.detailColor) : paint(item.detail, "muted");
316
+ out.push(`${INDENT}${label} ${value} ${detail}`);
317
+ }
318
+ }
319
+ return `${out.join(`
320
+ `)}
321
+ `;
322
+ }
323
+ export {
324
+ renderCliDashboard
325
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Where Meridian keeps its own configuration.
3
+ *
4
+ * One directory holds everything Meridian persists for itself — settings.json,
5
+ * profiles.json, the per-profile CLAUDE_CONFIG_DIRs under profiles/,
6
+ * adapter-instances.json, sdk-features.json, model-pricing.json, telemetry.db.
7
+ * `MERIDIAN_CONFIG_DIR` moves that directory, so a second instance pointed at
8
+ * an empty one gets its own everything rather than inheriting the first
9
+ * instance's accounts.
10
+ *
11
+ * Resolved per call, never frozen at import time. A module-level constant is
12
+ * computed before the CLI has parsed argv or a test has set the variable, and
13
+ * can then be changed by neither.
14
+ *
15
+ * NOTE: deliberately does NOT honour XDG_CONFIG_HOME — anyone who has that set
16
+ * would silently relocate to a different config directory and appear to lose
17
+ * everything in it.
18
+ *
19
+ * NOTE: read straight from process.env rather than through env.ts, which would
20
+ * also accept a CLAUDE_PROXY_CONFIG_DIR alias. This variable postdates that
21
+ * prefix, so there is no legacy deployment to stay compatible with and no
22
+ * reason to start accepting a second name for it.
23
+ */
24
+ /** The directory used when MERIDIAN_CONFIG_DIR is unset. */
25
+ export declare function defaultConfigDir(): string;
26
+ /** The directory Meridian's configuration lives in. */
27
+ export declare function configDir(): string;
28
+ /** A path inside the config directory, e.g. `configPath("profiles.json")`. */
29
+ export declare function configPath(...segments: string[]): string;
30
+ //# sourceMappingURL=configDir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configDir.d.ts","sourceRoot":"","sources":["../src/configDir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,4DAA4D;AAC5D,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,uDAAuD;AACvD,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAExD"}
@@ -0,0 +1,29 @@
1
+ import {
2
+ ORGANIZATION_REFRESH_AFTER_MS,
3
+ ORGANIZATION_RETRY_AFTER_MS,
4
+ enableOrganizationLookup,
5
+ extractOrganizationName,
6
+ fetchOrganizationName,
7
+ organizationNames,
8
+ organizationNeedsRefresh,
9
+ readOrganizations,
10
+ refreshOrganizationNameSoon,
11
+ rememberOrganizationName,
12
+ resetOrganizationLookupForTesting
13
+ } from "./cli-8sw5wr9j.js";
14
+ import"./cli-chsp880k.js";
15
+ import"./cli-1jstkdj9.js";
16
+ import"./cli-p9swy5t3.js";
17
+ export {
18
+ resetOrganizationLookupForTesting,
19
+ rememberOrganizationName,
20
+ refreshOrganizationNameSoon,
21
+ readOrganizations,
22
+ organizationNeedsRefresh,
23
+ organizationNames,
24
+ fetchOrganizationName,
25
+ extractOrganizationName,
26
+ enableOrganizationLookup,
27
+ ORGANIZATION_RETRY_AFTER_MS,
28
+ ORGANIZATION_REFRESH_AFTER_MS
29
+ };