@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
@@ -0,0 +1,330 @@
1
+ // src/telemetry/profileFacts.ts
2
+ var profileFactsJs = `
3
+ function timeAgo(ts) {
4
+ if (!ts) return '\\u2014';
5
+ var s = Math.floor((Date.now() - ts) / 1000);
6
+ if (s < 5) return 'just now';
7
+ if (s < 60) return s + 's ago';
8
+ if (s < 3600) return Math.floor(s / 60) + 'm ago';
9
+ if (s < 86400) return Math.floor(s / 3600) + 'h ago';
10
+ return new Date(ts).toLocaleString();
11
+ }
12
+
13
+ function profileFacts(p) {
14
+ var facts = [];
15
+ var authStale = p.authProvenance && p.authProvenance !== 'live';
16
+ var statusValue = p.authProvenance === 'never' ? 'never read' : (p.loggedIn ? '\\u2713 Authenticated' : '\\u2717 Not logged in');
17
+ facts.push({
18
+ label: 'Status',
19
+ value: statusValue,
20
+ tone: p.loggedIn ? 'ok' : 'err',
21
+ cached: authStale && p.authProvenance !== 'never'
22
+ });
23
+ if (p.email) facts.push({ label: 'Email', value: p.email, tone: '', cached: authStale });
24
+ if (p.organizationName) facts.push({ label: 'Organization', value: p.organizationName, tone: '' });
25
+ if (p.accountType) facts.push({ label: 'Account', value: p.accountType, tone: '' });
26
+ var plan = p.planName || p.planLabel || p.subscriptionType;
27
+ if (plan) facts.push({ label: 'Plan', value: plan, tone: '', cached: authStale, title: p.seatTier || p.rateLimitTier || '' });
28
+ if (p.allowance) facts.push({ label: 'Allowance', value: p.allowance + ' of a Pro plan\\u2019s Claude Code usage', shortValue: p.allowance, tone: '', title: p.rateLimitTier || '' });
29
+ if (p.aliases && p.aliases.length > 0) facts.push({ label: 'Former names', value: p.aliases.join(', '), tone: '', title: 'Requests naming these are served by this profile, until the name is added again' });
30
+ if (p.lastSuccessAt) facts.push({ label: 'Last Verified', value: timeAgo(p.lastSuccessAt), tone: 'ok' });
31
+ if (p.lastCheckedAt && p.lastCheckedAt !== p.lastSuccessAt) {
32
+ facts.push({ label: 'Last Checked', value: timeAgo(p.lastCheckedAt), tone: '' });
33
+ }
34
+ return facts;
35
+ }
36
+ `;
37
+
38
+ // src/telemetry/profileOrder.ts
39
+ var EDGE_ZONE_PX = 96;
40
+ var MAX_SCROLL_PX_PER_FRAME = 20;
41
+ var reorderCss = `
42
+ .profile-card.dragging { opacity: 0.45; border-color: var(--accent); }
43
+ .profile-card.drop-target { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
44
+ .drag-handle {
45
+ background: none; border: 1px solid transparent; border-radius: 6px;
46
+ color: var(--muted); cursor: grab; padding: 2px 6px; font-size: 15px;
47
+ line-height: 1; user-select: none; flex-shrink: 0;
48
+ }
49
+ .drag-handle:hover { color: var(--accent); border-color: var(--border); }
50
+ .drag-handle:focus-visible { outline: none; color: var(--accent); border-color: var(--accent); }
51
+ .drag-handle:active { cursor: grabbing; }
52
+ .order-index {
53
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 11px;
54
+ color: var(--muted); min-width: 16px; text-align: right; flex-shrink: 0;
55
+ }
56
+ .order-note { font-size: 12px; color: var(--muted); margin-bottom: 12px; max-width: 620px; }
57
+ .order-note code {
58
+ font-family: 'SF Mono', SFMono-Regular, Consolas, monospace; font-size: 11px;
59
+ background: var(--bg); padding: 1px 5px; border-radius: 4px; color: var(--accent2);
60
+ }
61
+ .order-note.locked { color: var(--yellow); }
62
+ .sr-only {
63
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
64
+ overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
65
+ }
66
+ `;
67
+ var reorderLiveRegionHtml = `<div id="orderStatus" class="sr-only" role="status" aria-live="polite"></div>`;
68
+ var reorderClientJs = `
69
+ window.meridianReorder = (function () {
70
+ function moveInOrder(order, from, to) {
71
+ var next = order.slice();
72
+ if (from < 0 || from >= next.length) return next;
73
+ if (to < 0 || to >= next.length) return next;
74
+ if (from === to) return next;
75
+ var moved = next.splice(from, 1)[0];
76
+ next.splice(to, 0, moved);
77
+ return next;
78
+ }
79
+
80
+ function sortByOrder(items, order) {
81
+ // Null-prototype: a profile called "constructor" or "toString" would
82
+ // otherwise test true against Object.prototype and rank as position 0.
83
+ var rank = Object.create(null);
84
+ for (var i = 0; i < order.length; i++) {
85
+ if (!(order[i] in rank)) rank[order[i]] = i;
86
+ }
87
+ return items
88
+ .map(function (item, index) { return { item: item, index: index }; })
89
+ .sort(function (a, b) {
90
+ var ra = a.item.id in rank ? rank[a.item.id] : Number.MAX_SAFE_INTEGER;
91
+ var rb = b.item.id in rank ? rank[b.item.id] : Number.MAX_SAFE_INTEGER;
92
+ if (ra !== rb) return ra - rb;
93
+ return a.index - b.index;
94
+ })
95
+ .map(function (entry) { return entry.item; });
96
+ }
97
+
98
+ // Escapes the attribute delimiter too. The profiles page's own esc() goes
99
+ // through textContent, which leaves a double quote intact and would break
100
+ // out of the aria-label this builds.
101
+ function escAttr(s) {
102
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (ch) {
103
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[ch];
104
+ });
105
+ }
106
+
107
+ var EDGE_ZONE_PX = ${EDGE_ZONE_PX};
108
+ var MAX_SCROLL_PX_PER_FRAME = ${MAX_SCROLL_PX_PER_FRAME};
109
+
110
+ // Mirrors edgeScrollVelocity in src/telemetry/profileOrder.ts, unit-tested
111
+ // in profile-order.test.ts.
112
+ function edgeScrollVelocity(clientY, viewportHeight) {
113
+ if (!isFinite(clientY) || !isFinite(viewportHeight) || viewportHeight <= 0) return 0;
114
+ var zone = Math.min(EDGE_ZONE_PX, viewportHeight / 2);
115
+ if (zone <= 0) return 0;
116
+ if (clientY < zone) return -Math.round(MAX_SCROLL_PX_PER_FRAME * Math.min(1, (zone - clientY) / zone));
117
+ var fromBottom = viewportHeight - clientY;
118
+ if (fromBottom < zone) return Math.round(MAX_SCROLL_PX_PER_FRAME * Math.min(1, (zone - fromBottom) / zone));
119
+ return 0;
120
+ }
121
+
122
+ var velocity = 0;
123
+ var frame = null;
124
+ var pointerSeenAt = 0;
125
+ // A drag that leaves the window stops producing dragover events while the
126
+ // last velocity is still in force, which would scroll to the end on its
127
+ // own. dragend covers the ordinary paths; this covers the rest.
128
+ var POINTER_STALE_MS = 700;
129
+
130
+ function step() {
131
+ frame = null;
132
+ if (velocity === 0) return;
133
+ if (Date.now() - pointerSeenAt > POINTER_STALE_MS) { velocity = 0; return; }
134
+ window.scrollBy(0, velocity);
135
+ frame = window.requestAnimationFrame(step);
136
+ }
137
+
138
+ function trackPointer(e) {
139
+ if (fromIndex === null) return;
140
+ pointerSeenAt = Date.now();
141
+ velocity = edgeScrollVelocity(e.clientY, window.innerHeight);
142
+ if (velocity !== 0 && frame === null) frame = window.requestAnimationFrame(step);
143
+ }
144
+
145
+ function stopAutoScroll() {
146
+ velocity = 0;
147
+ if (frame !== null) { window.cancelAnimationFrame(frame); frame = null; }
148
+ }
149
+
150
+ var state = { order: [], envPinned: false };
151
+ var fromIndex = null;
152
+ var pendingFocusId = null;
153
+ var onSaved = function () {};
154
+
155
+ function adopt(cfg) {
156
+ if (!cfg) return;
157
+ if (Array.isArray(cfg.profileOrder)) state.order = cfg.profileOrder;
158
+ state.envPinned = !!(cfg.envOverride && cfg.envOverride.profileOrder);
159
+ }
160
+
161
+ function sortProfiles(items) { return sortByOrder(items || [], state.order || []); }
162
+
163
+ function announce(message) {
164
+ var live = document.getElementById('orderStatus');
165
+ if (live) live.textContent = message;
166
+ }
167
+
168
+ function noteHtml(reorderable) {
169
+ return reorderable
170
+ ? '<div class="order-note">Drag a card by its handle to reorder, or focus a handle and press \\u2191 / \\u2193. '
171
+ + 'The order is saved, and drives <a href="/settings" style="color:var(--accent)">Priority routing</a> \\u2014 the pool drains from the top.</div>'
172
+ : '<div class="order-note locked">Order is pinned by the <code>MERIDIAN_PROFILE_ORDER</code> environment variable. Unset it to reorder from here.</div>';
173
+ }
174
+
175
+ function handleHtml(id, index, total) {
176
+ return '<span class="order-index">' + (index + 1) + '</span>'
177
+ + '<button type="button" class="drag-handle" draggable="true" data-index="' + index + '"'
178
+ + ' title="Drag to reorder, or press \\u2191 / \\u2193"'
179
+ + ' aria-label="Reorder ' + escAttr(id) + ', position ' + (index + 1) + ' of ' + total
180
+ + '. Press arrow up or arrow down to move.">\\u283f</button>';
181
+ }
182
+
183
+ // The rendered order is the truth the server must agree with — read it back
184
+ // off the DOM rather than from the cached config, which may list ids the
185
+ // current /profiles/list no longer has (PUT rejects unknown ids outright).
186
+ function currentOrderIds() {
187
+ var ids = [];
188
+ var cards = document.querySelectorAll('.profile-card[data-id]');
189
+ for (var i = 0; i < cards.length; i++) ids.push(cards[i].dataset.id);
190
+ return ids;
191
+ }
192
+
193
+ function clearMarks() {
194
+ var marked = document.querySelectorAll('.profile-card.dragging, .profile-card.drop-target');
195
+ for (var i = 0; i < marked.length; i++) marked[i].classList.remove('dragging', 'drop-target');
196
+ }
197
+
198
+ function save(order, message) {
199
+ var previous = state.order;
200
+ state.order = order;
201
+ return fetch('/settings/api/routing', {
202
+ method: 'PUT',
203
+ headers: { 'Content-Type': 'application/json' },
204
+ body: JSON.stringify({ profileOrder: order })
205
+ }).catch(function () { return null; }).then(function (res) {
206
+ if (!res || !res.ok) {
207
+ state.order = previous;
208
+ announce('Could not save the new order.');
209
+ } else if (message) {
210
+ announce(message);
211
+ }
212
+ return onSaved();
213
+ });
214
+ }
215
+
216
+ function moveTo(from, to) {
217
+ var order = currentOrderIds();
218
+ if (to < 0 || to >= order.length || from === to) return;
219
+ save(moveInOrder(order, from, to), order[from] + ' moved to position ' + (to + 1) + ' of ' + order.length + '.');
220
+ }
221
+
222
+ function init(opts) {
223
+ if (opts && opts.onSaved) onSaved = opts.onSaved;
224
+ var content = document.getElementById('content');
225
+
226
+ content.addEventListener('dragstart', function (e) {
227
+ var handle = e.target.closest && e.target.closest('.drag-handle');
228
+ if (!handle) return;
229
+ var card = handle.closest('.profile-card');
230
+ fromIndex = Number(handle.dataset.index);
231
+ pointerSeenAt = Date.now();
232
+ e.dataTransfer.effectAllowed = 'move';
233
+ // Firefox will not start a drag at all unless the payload is set.
234
+ e.dataTransfer.setData('text/plain', card.dataset.id || '');
235
+ if (e.dataTransfer.setDragImage) e.dataTransfer.setDragImage(card, 24, 24);
236
+ card.classList.add('dragging');
237
+ });
238
+
239
+ content.addEventListener('dragover', function (e) {
240
+ if (fromIndex === null) return;
241
+ var card = e.target.closest && e.target.closest('.profile-card');
242
+ if (!card) return;
243
+ // preventDefault is what makes an element a drop target at all.
244
+ e.preventDefault();
245
+ e.dataTransfer.dropEffect = 'move';
246
+ var marked = document.querySelectorAll('.profile-card.drop-target');
247
+ for (var i = 0; i < marked.length; i++) marked[i].classList.remove('drop-target');
248
+ if (Number(card.dataset.index) !== fromIndex) card.classList.add('drop-target');
249
+ });
250
+
251
+ content.addEventListener('drop', function (e) {
252
+ if (fromIndex === null) return;
253
+ var card = e.target.closest && e.target.closest('.profile-card');
254
+ if (!card) return;
255
+ e.preventDefault();
256
+ var from = fromIndex;
257
+ var to = Number(card.dataset.index);
258
+ fromIndex = null;
259
+ stopAutoScroll();
260
+ clearMarks();
261
+ moveTo(from, to);
262
+ });
263
+
264
+ content.addEventListener('keydown', function (e) {
265
+ if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
266
+ var handle = e.target.closest && e.target.closest('.drag-handle');
267
+ if (!handle) return;
268
+ e.preventDefault();
269
+ var order = currentOrderIds();
270
+ var from = Number(handle.dataset.index);
271
+ var to = e.key === 'ArrowUp' ? from - 1 : from + 1;
272
+ if (to < 0 || to >= order.length) return;
273
+ pendingFocusId = order[from];
274
+ moveTo(from, to);
275
+ });
276
+
277
+ // The page scrolls only while the pointer sits near a viewport edge, and
278
+ // the pointer is over the header or the gap between cards as often as it
279
+ // is over one — so this listens on the document, not on #content.
280
+ document.addEventListener('dragover', trackPointer);
281
+ document.addEventListener('dragend', function () {
282
+ fromIndex = null;
283
+ stopAutoScroll();
284
+ clearMarks();
285
+ });
286
+ document.addEventListener('drop', stopAutoScroll);
287
+ }
288
+
289
+ // A poll landing mid-drag would replace the very cards being dragged.
290
+ function dragging() { return fromIndex !== null; }
291
+
292
+ // A re-render replaces the focused handle; without this the keyboard path
293
+ // loses focus mid-reorder and the next arrow key goes nowhere.
294
+ function focusAnchor() {
295
+ var pending = pendingFocusId;
296
+ pendingFocusId = null;
297
+ if (pending) return pending;
298
+ var active = document.activeElement;
299
+ if (!active || !active.closest || !active.closest('.drag-handle')) return null;
300
+ var card = active.closest('.profile-card');
301
+ return card ? card.dataset.id : null;
302
+ }
303
+
304
+ function restoreFocus(id) {
305
+ if (!id) return;
306
+ var cards = document.querySelectorAll('.profile-card[data-id]');
307
+ for (var i = 0; i < cards.length; i++) {
308
+ if (cards[i].dataset.id !== id) continue;
309
+ var handle = cards[i].querySelector('.drag-handle');
310
+ if (handle) handle.focus();
311
+ return;
312
+ }
313
+ }
314
+
315
+ return {
316
+ adopt: adopt,
317
+ sortProfiles: sortProfiles,
318
+ envPinned: function () { return state.envPinned; },
319
+ noteHtml: noteHtml,
320
+ handleHtml: handleHtml,
321
+ init: init,
322
+ dragging: dragging,
323
+ focusAnchor: focusAnchor,
324
+ restoreFocus: restoreFocus,
325
+ announce: announce
326
+ };
327
+ })();
328
+ `;
329
+
330
+ export { profileFactsJs, reorderCss, reorderLiveRegionHtml, reorderClientJs };
@@ -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 };
@@ -119,6 +119,10 @@ var themeCss = `
119
119
  border: 1px solid rgba(188,140,255,0.35);
120
120
  cursor: default;
121
121
  }
122
+ .meridian-header .mh-profile.following { border-color: var(--accent2, #bc8cff); }
123
+ .meridian-header .mh-profile .mh-profile-follow {
124
+ color: var(--accent2, #bc8cff); font-size: 10px;
125
+ }
122
126
  .meridian-header .mh-status {
123
127
  display: inline-flex; align-items: center; gap: 6px;
124
128
  font-size: 11px; color: var(--muted, #8b949e); white-space: nowrap;
@@ -133,6 +137,8 @@ var themeCss = `
133
137
  @media (max-width: 720px) {
134
138
  .meridian-header { gap: 10px; padding: 10px 16px; flex-wrap: wrap; }
135
139
  .meridian-header .mh-name { display: none; }
140
+ .meridian-header .mh-nav { order: 3; flex-basis: 100%; min-width: 0; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
141
+ .meridian-header .mh-nav a { flex-shrink: 0; }
136
142
  .meridian-header .mh-status .mh-status-text { display: none; }
137
143
  }
138
144
  `, profileBarHtml, profileBarJs = `
@@ -185,6 +191,10 @@ var themeCss = `
185
191
  statusDot.className = 'mh-dot ' + st;
186
192
  statusText.textContent = st === 'healthy' ? 'Operational' : st === 'degraded' ? 'Degraded' : 'Offline';
187
193
  renderBuild(h.build);
194
+ if (h.backend === 'antigravity') {
195
+ ['nav-telemetry','nav-profiles','nav-settings','nav-plugins'].forEach(function(id) { document.getElementById(id).hidden = true; });
196
+ profileChip.removeAttribute('href');
197
+ }
188
198
  }).catch(function() {
189
199
  statusDot.className = 'mh-dot unhealthy';
190
200
  statusText.textContent = 'Offline';
@@ -193,7 +203,20 @@ var themeCss = `
193
203
  fetch('/profiles/list').then(function(r) { return r.json(); }).then(function(data) {
194
204
  var current = (data.profiles || []).find(function(p) { return p.isActive; });
195
205
  if (!current) { profileChip.classList.remove('visible'); return; }
196
- profileChip.innerHTML = esc(current.id) + ' <span class="mh-profile-type">' + esc(current.type || '') + '</span>';
206
+ // Follow mode: say so on every page. An instance quietly taking its
207
+ // active profile from another one, with a picker that won't stick, is
208
+ // otherwise an hour of confusion.
209
+ var follow = data.follow;
210
+ var followLabel = follow ? (follow.activeProfile ? 'following' : 'follow: local') : '';
211
+ if (follow && follow.stale) followLabel += ' (stale)';
212
+ profileChip.innerHTML = esc(current.id) + ' <span class="mh-profile-type">' + esc(current.type || '') + '</span>'
213
+ + (follow ? ' <span class="mh-profile-follow">' + esc(followLabel) + '</span>' : '');
214
+ profileChip.classList.toggle('following', !!follow);
215
+ profileChip.title = follow
216
+ ? 'Active profile follows ' + follow.url + ' (MERIDIAN_FOLLOW_ACTIVE)'
217
+ + (follow.activeProfile ? '' : ' — no usable value from it, using the local profile')
218
+ + '. Switching here is refused; switch on the followed instance.'
219
+ : 'Active profile — switch from the home page';
197
220
  profileChip.classList.add('visible');
198
221
  }).catch(function() {});
199
222
  }
@@ -208,12 +231,13 @@ var themeCss = `
208
231
  var init_profileBar = __esm(() => {
209
232
  profileBarHtml = `
210
233
  <header class="meridian-header" id="meridianHeader">
211
- <a class="mh-brand" href="/">
234
+ <a class="mh-brand" href="/" aria-label="Meridian home">
212
235
  ${meridianLogoSvg}
213
236
  <span class="mh-name">Meridian</span>
214
237
  </a>
215
238
  <nav class="mh-nav">
216
239
  <a href="/" id="nav-home">Home</a>
240
+ <a href="/providers" id="nav-providers">Providers</a>
217
241
  <a href="/telemetry" id="nav-telemetry">Telemetry</a>
218
242
  <a href="/profiles" id="nav-profiles">Profiles</a>
219
243
  <a href="/settings" id="nav-settings">Settings</a>