@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.
- package/README.md +119 -107
- package/assets/banner.svg +18 -43
- package/assets/how-it-works.svg +40 -86
- package/assets/providers/README.md +10 -0
- package/assets/providers/antigravity.png +0 -0
- package/assets/providers/claude.png +0 -0
- package/dist/antigravity-clients/opencode.js +173 -0
- package/dist/antigravity-clients/pi.js +21 -0
- package/dist/antigravitySetup-35pd5xqn.js +191 -0
- package/dist/cli-1jstkdj9.js +30 -0
- package/dist/{cli-9e5cxp89.js → cli-2h3w5n6g.js} +163 -65
- package/dist/cli-3zwfvexc.js +56 -0
- package/dist/{cli-hxxy0m1z.js → cli-82e2m44k.js} +2 -1
- package/dist/cli-8sw5wr9j.js +136 -0
- package/dist/{cli-khhjyk04.js → cli-chsp880k.js} +214 -21
- package/dist/cli-dh0a1bw4.js +619 -0
- package/dist/cli-eehk3052.js +57 -0
- package/dist/{cli-ab7zkp1a.js → cli-fveyj34s.js} +32261 -18952
- package/dist/cli-grfp0s1j.js +330 -0
- package/dist/cli-qpfky8wy.js +133 -0
- package/dist/{cli-hj61zpt7.js → cli-wbsmhxcx.js} +26 -2
- package/dist/cli.js +111 -38
- package/dist/cliDashboard-c0dvkj4y.js +325 -0
- package/dist/configDir.d.ts +30 -0
- package/dist/configDir.d.ts.map +1 -0
- package/dist/organizationName-90f0atkn.js +29 -0
- package/dist/{pluginPage-f5gdmbxc.js → pluginPage-pp72d2j8.js} +1 -1
- package/dist/{profileCli-1eecbmgk.js → profileCli-1bfgc05h.js} +13 -3
- package/dist/{profilePage-mamej7kp.js → profilePage-vs319rvf.js} +252 -60
- package/dist/profileRename-4a4km6g8.js +22 -0
- package/dist/{profiles-wch9h234.js → profiles-0mp32ek9.js} +16 -3
- package/dist/proxy/adapterInstances.d.ts +1 -1
- package/dist/proxy/adapterInstances.d.ts.map +1 -1
- package/dist/proxy/adapters/claudecode.d.ts.map +1 -1
- package/dist/proxy/adapters/droid.d.ts.map +1 -1
- package/dist/proxy/auth.d.ts +2 -0
- package/dist/proxy/auth.d.ts.map +1 -1
- package/dist/proxy/authDiscovery.d.ts +52 -0
- package/dist/proxy/authDiscovery.d.ts.map +1 -0
- package/dist/proxy/backends/antigravity.d.ts +8 -0
- package/dist/proxy/backends/antigravity.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityAttachments.d.ts +16 -0
- package/dist/proxy/backends/antigravityAttachments.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityGrammar.d.ts +12 -0
- package/dist/proxy/backends/antigravityGrammar.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityJobs.d.ts +19 -0
- package/dist/proxy/backends/antigravityJobs.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityMedia.d.ts +9 -0
- package/dist/proxy/backends/antigravityMedia.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityNative.d.ts +8 -0
- package/dist/proxy/backends/antigravityNative.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityOpenai.d.ts +9 -0
- package/dist/proxy/backends/antigravityOpenai.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityOpenaiMedia.d.ts +9 -0
- package/dist/proxy/backends/antigravityOpenaiMedia.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityPlugins.d.ts +41 -0
- package/dist/proxy/backends/antigravityPlugins.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityProbe.d.ts +16 -0
- package/dist/proxy/backends/antigravityProbe.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityProcess.d.ts +4 -0
- package/dist/proxy/backends/antigravityProcess.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityProtocol.d.ts +582 -0
- package/dist/proxy/backends/antigravityProtocol.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityReplay.d.ts +41 -0
- package/dist/proxy/backends/antigravityReplay.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityResponses.d.ts +35 -0
- package/dist/proxy/backends/antigravityResponses.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityRuntime.d.ts +189 -0
- package/dist/proxy/backends/antigravityRuntime.d.ts.map +1 -0
- package/dist/proxy/backends/antigravitySchema.d.ts +10 -0
- package/dist/proxy/backends/antigravitySchema.d.ts.map +1 -0
- package/dist/proxy/backends/antigravitySessions.d.ts +28 -0
- package/dist/proxy/backends/antigravitySessions.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityState.d.ts +22 -0
- package/dist/proxy/backends/antigravityState.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityStops.d.ts +10 -0
- package/dist/proxy/backends/antigravityStops.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityTokens.d.ts +14 -0
- package/dist/proxy/backends/antigravityTokens.d.ts.map +1 -0
- package/dist/proxy/backends/antigravityUrl.d.ts +7 -0
- package/dist/proxy/backends/antigravityUrl.d.ts.map +1 -0
- package/dist/proxy/backends/providerStatus.d.ts +15 -0
- package/dist/proxy/backends/providerStatus.d.ts.map +1 -0
- package/dist/proxy/credentialsMode.d.ts +77 -0
- package/dist/proxy/credentialsMode.d.ts.map +1 -0
- package/dist/proxy/design.d.ts.map +1 -1
- package/dist/proxy/followActive.d.ts +262 -0
- package/dist/proxy/followActive.d.ts.map +1 -0
- package/dist/proxy/limitDetection.d.ts +110 -0
- package/dist/proxy/limitDetection.d.ts.map +1 -0
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/oauthPlan.d.ts +63 -0
- package/dist/proxy/oauthPlan.d.ts.map +1 -0
- package/dist/proxy/oauthUsage.d.ts +65 -2
- package/dist/proxy/oauthUsage.d.ts.map +1 -1
- package/dist/proxy/openai.d.ts +6 -2
- package/dist/proxy/openai.d.ts.map +1 -1
- package/dist/proxy/organizationName.d.ts +71 -0
- package/dist/proxy/organizationName.d.ts.map +1 -0
- package/dist/proxy/passthroughEarlyStop.d.ts +10 -0
- package/dist/proxy/passthroughEarlyStop.d.ts.map +1 -1
- package/dist/proxy/planAllowance.d.ts +75 -0
- package/dist/proxy/planAllowance.d.ts.map +1 -0
- package/dist/proxy/probes.d.ts +85 -0
- package/dist/proxy/probes.d.ts.map +1 -0
- package/dist/proxy/profileCli.d.ts +53 -1
- package/dist/proxy/profileCli.d.ts.map +1 -1
- package/dist/proxy/profileHealth.d.ts +127 -0
- package/dist/proxy/profileHealth.d.ts.map +1 -0
- package/dist/proxy/profileRename.d.ts +83 -0
- package/dist/proxy/profileRename.d.ts.map +1 -0
- package/dist/proxy/profiles.d.ts +72 -6
- package/dist/proxy/profiles.d.ts.map +1 -1
- package/dist/proxy/query.d.ts +7 -0
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/routing.d.ts +59 -1
- package/dist/proxy/routing.d.ts.map +1 -1
- package/dist/proxy/sdkFeatures.d.ts +1 -1
- package/dist/proxy/sdkFeatures.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/session/fingerprint.d.ts +5 -0
- package/dist/proxy/session/fingerprint.d.ts.map +1 -1
- package/dist/proxy/session/lineage.d.ts.map +1 -1
- package/dist/proxy/setup.d.ts.map +1 -1
- package/dist/proxy/supervision.d.ts +41 -0
- package/dist/proxy/supervision.d.ts.map +1 -0
- package/dist/proxy/tokenRefresh.d.ts +38 -4
- package/dist/proxy/tokenRefresh.d.ts.map +1 -1
- package/dist/proxy/types.d.ts +32 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/server.js +11 -7
- package/dist/settings.d.ts +83 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/{setup-ndmjpy23.js → setup-7fr49z92.js} +1 -1
- package/dist/statusProbe-9x9q261e.js +151 -0
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/index.d.ts +20 -2
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/landing.d.ts +1 -1
- package/dist/telemetry/landing.d.ts.map +1 -1
- package/dist/telemetry/logStore.d.ts +11 -0
- package/dist/telemetry/logStore.d.ts.map +1 -1
- package/dist/telemetry/pricingStore.d.ts +2 -2
- package/dist/telemetry/pricingStore.d.ts.map +1 -1
- package/dist/telemetry/profileBar.d.ts +3 -3
- package/dist/telemetry/profileBar.d.ts.map +1 -1
- package/dist/telemetry/profileFacts.d.ts +17 -0
- package/dist/telemetry/profileFacts.d.ts.map +1 -0
- package/dist/telemetry/profileOrder.d.ts +68 -0
- package/dist/telemetry/profileOrder.d.ts.map +1 -0
- package/dist/telemetry/profilePage.d.ts.map +1 -1
- package/dist/telemetry/profileSort.d.ts +35 -0
- package/dist/telemetry/profileSort.d.ts.map +1 -0
- package/dist/telemetry/profileSpent.d.ts +68 -0
- package/dist/telemetry/profileSpent.d.ts.map +1 -0
- package/dist/telemetry/providerPage.d.ts +2 -0
- package/dist/telemetry/providerPage.d.ts.map +1 -0
- package/dist/telemetry/providerSetup.d.ts +5 -0
- package/dist/telemetry/providerSetup.d.ts.map +1 -0
- package/dist/telemetry/providerView.d.ts +45 -0
- package/dist/telemetry/providerView.d.ts.map +1 -0
- package/dist/telemetry/routeChain.d.ts +32 -0
- package/dist/telemetry/routeChain.d.ts.map +1 -0
- package/dist/telemetry/routes.d.ts.map +1 -1
- package/dist/telemetry/settingsPage.d.ts +1 -1
- package/dist/telemetry/settingsPage.d.ts.map +1 -1
- package/dist/telemetry/sqlite.d.ts.map +1 -1
- package/dist/telemetry/store.d.ts +6 -1
- package/dist/telemetry/store.d.ts.map +1 -1
- package/dist/telemetry/types.d.ts +132 -2
- package/dist/telemetry/types.d.ts.map +1 -1
- package/dist/{tokenRefresh-kzz08kea.js → tokenRefresh-eg9h83wd.js} +6 -1
- package/package.json +11 -4
- package/dist/cli-5jxyma6z.js +0 -277
- package/dist/cli-vj9cv18n.js +0 -38
- package/dist/proxy/settings.d.ts +0 -30
- 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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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
|
-
|
|
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>
|