@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,619 @@
|
|
|
1
|
+
import {
|
|
2
|
+
env,
|
|
3
|
+
init_env
|
|
4
|
+
} from "./cli-1jstkdj9.js";
|
|
5
|
+
import {
|
|
6
|
+
configPath,
|
|
7
|
+
defaultConfigDir,
|
|
8
|
+
getSetting,
|
|
9
|
+
init_configDir,
|
|
10
|
+
setSetting
|
|
11
|
+
} from "./cli-eehk3052.js";
|
|
12
|
+
|
|
13
|
+
// src/proxy/profiles.ts
|
|
14
|
+
init_configDir();
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
|
|
18
|
+
// src/proxy/routing.ts
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
var ACTIVE_PRIORITY = "active+priority";
|
|
21
|
+
var ROUTING_MODES = ["active", "sticky", "priority", ACTIVE_PRIORITY];
|
|
22
|
+
function getRoutingMode(raw) {
|
|
23
|
+
const lower = raw?.toLowerCase();
|
|
24
|
+
if (lower === "sticky")
|
|
25
|
+
return "sticky";
|
|
26
|
+
if (lower === "priority")
|
|
27
|
+
return "priority";
|
|
28
|
+
if (lower && lower.replace(/[\s+_-]/g, "") === "activepriority")
|
|
29
|
+
return ACTIVE_PRIORITY;
|
|
30
|
+
return "active";
|
|
31
|
+
}
|
|
32
|
+
function isPoolRouting(mode) {
|
|
33
|
+
return mode === "priority" || mode === ACTIVE_PRIORITY;
|
|
34
|
+
}
|
|
35
|
+
function getPriorityFailbackPolicy(raw) {
|
|
36
|
+
return raw?.toLowerCase() === "next-user-turn" ? "next-user-turn" : "new-conversation";
|
|
37
|
+
}
|
|
38
|
+
function shouldPromotePriorityAssignment(input) {
|
|
39
|
+
return input.policy === "next-user-turn" && input.requestKind === "human" && input.requestId !== undefined && input.requestId !== input.assignment.requestId;
|
|
40
|
+
}
|
|
41
|
+
function classifyRouteKind(input) {
|
|
42
|
+
if (input.priorityHop)
|
|
43
|
+
return input.routingMode === ACTIVE_PRIORITY ? "active+priority-hop" : "priority-hop";
|
|
44
|
+
if (input.pinnedProfileHeader)
|
|
45
|
+
return "pinned";
|
|
46
|
+
if (input.routingMode === "sticky")
|
|
47
|
+
return "sticky";
|
|
48
|
+
if (input.routingMode === "priority")
|
|
49
|
+
return "priority";
|
|
50
|
+
if (input.routingMode === ACTIVE_PRIORITY)
|
|
51
|
+
return ACTIVE_PRIORITY;
|
|
52
|
+
if (input.routingMode === "active")
|
|
53
|
+
return "active";
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
function rendezvousScore(sessionKey, profileId) {
|
|
57
|
+
const digest = createHash("sha256").update(`${sessionKey}\x00${profileId}`).digest();
|
|
58
|
+
return digest.readBigUInt64BE(0);
|
|
59
|
+
}
|
|
60
|
+
function pickStickyProfile(sessionKey, profileIds) {
|
|
61
|
+
if (!sessionKey || profileIds.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
let best;
|
|
64
|
+
let bestScore = -1n;
|
|
65
|
+
for (const id of profileIds) {
|
|
66
|
+
const score = rendezvousScore(sessionKey, id);
|
|
67
|
+
if (score > bestScore) {
|
|
68
|
+
bestScore = score;
|
|
69
|
+
best = id;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return best;
|
|
73
|
+
}
|
|
74
|
+
function resolvePriorityOrder(configuredIds, orderSetting) {
|
|
75
|
+
const existing = new Set(configuredIds);
|
|
76
|
+
const order = [];
|
|
77
|
+
const unknown = [];
|
|
78
|
+
for (const id of orderSetting ?? []) {
|
|
79
|
+
if (!existing.has(id)) {
|
|
80
|
+
unknown.push(id);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!order.includes(id))
|
|
84
|
+
order.push(id);
|
|
85
|
+
}
|
|
86
|
+
for (const id of configuredIds)
|
|
87
|
+
if (!order.includes(id))
|
|
88
|
+
order.push(id);
|
|
89
|
+
return { order, unknown };
|
|
90
|
+
}
|
|
91
|
+
function choosePriorityProfile(order, isExhausted) {
|
|
92
|
+
const preferred = order[0];
|
|
93
|
+
if (preferred === undefined)
|
|
94
|
+
return;
|
|
95
|
+
for (const id of order) {
|
|
96
|
+
if (!isExhausted(id))
|
|
97
|
+
return { id, allExhausted: false };
|
|
98
|
+
}
|
|
99
|
+
return { id: preferred, allExhausted: true };
|
|
100
|
+
}
|
|
101
|
+
function chooseActivePriorityCandidates(activeId, order, isExhausted, assigned) {
|
|
102
|
+
const pool = [...new Set(order.includes(activeId) ? order : [activeId, ...order])];
|
|
103
|
+
let first;
|
|
104
|
+
if (!isExhausted(activeId)) {
|
|
105
|
+
first = activeId;
|
|
106
|
+
} else if (assigned && assigned !== activeId && pool.includes(assigned) && !isExhausted(assigned)) {
|
|
107
|
+
first = assigned;
|
|
108
|
+
} else {
|
|
109
|
+
first = pool.find((id) => !isExhausted(id)) ?? activeId;
|
|
110
|
+
}
|
|
111
|
+
return [first, ...pool.filter((id) => id !== first && !isExhausted(id))];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class ProfileExhaustion {
|
|
115
|
+
now;
|
|
116
|
+
marks = new Map;
|
|
117
|
+
constructor(now = Date.now) {
|
|
118
|
+
this.now = now;
|
|
119
|
+
}
|
|
120
|
+
mark(id, until, reason) {
|
|
121
|
+
const existing = this.marks.get(id);
|
|
122
|
+
if (existing && existing.until >= until)
|
|
123
|
+
return;
|
|
124
|
+
this.marks.set(id, { until, reason });
|
|
125
|
+
}
|
|
126
|
+
isExhausted(id) {
|
|
127
|
+
const entry = this.marks.get(id);
|
|
128
|
+
if (!entry)
|
|
129
|
+
return false;
|
|
130
|
+
if (entry.until <= this.now()) {
|
|
131
|
+
this.marks.delete(id);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
snapshot() {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const [id, entry] of this.marks) {
|
|
139
|
+
if (entry.until <= this.now()) {
|
|
140
|
+
this.marks.delete(id);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
out.push({ id, until: entry.until, reason: entry.reason });
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
class AssignmentStore {
|
|
150
|
+
max;
|
|
151
|
+
entries = new Map;
|
|
152
|
+
constructor(max) {
|
|
153
|
+
this.max = max;
|
|
154
|
+
}
|
|
155
|
+
get(key) {
|
|
156
|
+
const value = this.entries.get(key);
|
|
157
|
+
if (value === undefined)
|
|
158
|
+
return;
|
|
159
|
+
this.entries.delete(key);
|
|
160
|
+
this.entries.set(key, value);
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
set(key, value) {
|
|
164
|
+
this.entries.delete(key);
|
|
165
|
+
this.entries.set(key, value);
|
|
166
|
+
if (this.entries.size > this.max) {
|
|
167
|
+
const oldest = this.entries.keys().next().value;
|
|
168
|
+
if (oldest !== undefined)
|
|
169
|
+
this.entries.delete(oldest);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
compareAndSet(key, expected, value) {
|
|
173
|
+
if (this.entries.get(key) !== expected)
|
|
174
|
+
return false;
|
|
175
|
+
this.set(key, value);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
get size() {
|
|
179
|
+
return this.entries.size;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
var COOLDOWN_WINDOWS = ["seven_day", "five_hour"];
|
|
183
|
+
var COOLDOWN_CAP_MS = {
|
|
184
|
+
five_hour: 6 * 60 * 60000,
|
|
185
|
+
seven_day: 8 * 24 * 60 * 60000
|
|
186
|
+
};
|
|
187
|
+
function cooldownCapMs(type) {
|
|
188
|
+
return COOLDOWN_CAP_MS[type];
|
|
189
|
+
}
|
|
190
|
+
function resolveCooldownUntil(windows, now, defaultMs) {
|
|
191
|
+
return findCooldownReset(windows, now) ?? now + defaultMs;
|
|
192
|
+
}
|
|
193
|
+
function findCooldownReset(windows, now) {
|
|
194
|
+
for (const type of COOLDOWN_WINDOWS) {
|
|
195
|
+
const match = windows.find((w) => w.type === type && w.exhausted && (w.resetsAt ?? 0) > now);
|
|
196
|
+
if (match?.resetsAt) {
|
|
197
|
+
return Math.min(match.resetsAt, now + cooldownCapMs(type));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/proxy/followActive.ts
|
|
204
|
+
init_env();
|
|
205
|
+
var FOLLOW_POLL_INTERVAL_MS = 1e4;
|
|
206
|
+
var FOLLOW_FETCH_TIMEOUT_MS = 2000;
|
|
207
|
+
var FOLLOW_STALE_AFTER_MS = 120000;
|
|
208
|
+
var MAX_PROFILE_ID_LENGTH = 128;
|
|
209
|
+
function parseFollowTarget(raw) {
|
|
210
|
+
const trimmed = raw?.trim();
|
|
211
|
+
if (!trimmed)
|
|
212
|
+
return { kind: "off" };
|
|
213
|
+
const withScheme = trimmed.includes("://") ? trimmed : `http://${trimmed}`;
|
|
214
|
+
let parsed;
|
|
215
|
+
try {
|
|
216
|
+
parsed = new URL(withScheme);
|
|
217
|
+
} catch {
|
|
218
|
+
return { kind: "invalid", raw: trimmed, message: "not a URL" };
|
|
219
|
+
}
|
|
220
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
221
|
+
return { kind: "invalid", raw: trimmed, message: `unsupported scheme "${parsed.protocol}"` };
|
|
222
|
+
}
|
|
223
|
+
if (!parsed.hostname) {
|
|
224
|
+
return { kind: "invalid", raw: trimmed, message: "no host" };
|
|
225
|
+
}
|
|
226
|
+
return { kind: "on", url: `${parsed.protocol}//${parsed.host}` };
|
|
227
|
+
}
|
|
228
|
+
var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0", "::", "[::]"]);
|
|
229
|
+
function isSelfTarget(url, selfHost, selfPort) {
|
|
230
|
+
const target = parseFollowTarget(url);
|
|
231
|
+
if (target.kind !== "on")
|
|
232
|
+
return false;
|
|
233
|
+
const parsed = new URL(target.url);
|
|
234
|
+
const targetPort = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
|
|
235
|
+
if (targetPort !== selfPort)
|
|
236
|
+
return false;
|
|
237
|
+
const targetHost = parsed.hostname.toLowerCase();
|
|
238
|
+
const ownHost = selfHost.toLowerCase();
|
|
239
|
+
if (targetHost === ownHost)
|
|
240
|
+
return true;
|
|
241
|
+
return LOOPBACK_HOSTS.has(targetHost) && LOOPBACK_HOSTS.has(ownHost);
|
|
242
|
+
}
|
|
243
|
+
function readActiveProfile(body) {
|
|
244
|
+
if (typeof body !== "object" || body === null)
|
|
245
|
+
return;
|
|
246
|
+
const value = body.activeProfile;
|
|
247
|
+
if (typeof value !== "string")
|
|
248
|
+
return;
|
|
249
|
+
const trimmed = value.trim();
|
|
250
|
+
if (!trimmed || trimmed.length > MAX_PROFILE_ID_LENGTH)
|
|
251
|
+
return;
|
|
252
|
+
return trimmed;
|
|
253
|
+
}
|
|
254
|
+
var MAX_CREDENTIAL_DIR_LENGTH = 4096;
|
|
255
|
+
function readFollowedRoster(body) {
|
|
256
|
+
if (typeof body !== "object" || body === null)
|
|
257
|
+
return;
|
|
258
|
+
const entries = body.profiles;
|
|
259
|
+
if (!Array.isArray(entries))
|
|
260
|
+
return;
|
|
261
|
+
const adoptable = [];
|
|
262
|
+
const unadoptable = [];
|
|
263
|
+
let sawField = false;
|
|
264
|
+
for (const entry of entries) {
|
|
265
|
+
if (typeof entry !== "object" || entry === null)
|
|
266
|
+
continue;
|
|
267
|
+
const record = entry;
|
|
268
|
+
if (typeof record.id !== "string")
|
|
269
|
+
continue;
|
|
270
|
+
const id = record.id.trim();
|
|
271
|
+
if (!id || id.length > MAX_PROFILE_ID_LENGTH)
|
|
272
|
+
continue;
|
|
273
|
+
if (!("credentialDir" in record))
|
|
274
|
+
continue;
|
|
275
|
+
sawField = true;
|
|
276
|
+
const dir = record.credentialDir;
|
|
277
|
+
if (typeof dir === "string" && dir.trim() && dir.length <= MAX_CREDENTIAL_DIR_LENGTH) {
|
|
278
|
+
adoptable.push({ id, credentialDir: dir.trim() });
|
|
279
|
+
} else {
|
|
280
|
+
unadoptable.push(id);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return sawField ? { adoptable, unadoptable } : undefined;
|
|
284
|
+
}
|
|
285
|
+
function sameRoster(a, b) {
|
|
286
|
+
if (!a || !b)
|
|
287
|
+
return a === b;
|
|
288
|
+
if (a.adoptable.length !== b.adoptable.length)
|
|
289
|
+
return false;
|
|
290
|
+
if (a.unadoptable.length !== b.unadoptable.length)
|
|
291
|
+
return false;
|
|
292
|
+
return a.adoptable.every((p, i) => p.id === b.adoptable[i].id && p.credentialDir === b.adoptable[i].credentialDir) && a.unadoptable.every((id, i) => id === b.unadoptable[i]);
|
|
293
|
+
}
|
|
294
|
+
function logRosterChange(previous, next, url) {
|
|
295
|
+
const before = new Map((previous?.adoptable ?? []).map((p) => [p.id, p.credentialDir]));
|
|
296
|
+
const added = next.adoptable.filter((p) => !before.has(p.id)).map((p) => p.id);
|
|
297
|
+
const moved = next.adoptable.filter((p) => before.has(p.id) && before.get(p.id) !== p.credentialDir).map((p) => p.id);
|
|
298
|
+
const after = new Set(next.adoptable.map((p) => p.id));
|
|
299
|
+
const removed = [...before.keys()].filter((id) => !after.has(id));
|
|
300
|
+
if (added.length || removed.length || moved.length) {
|
|
301
|
+
console.warn(`[PROXY] Profiles from ${url}: now serving ${next.adoptable.length}` + (added.length ? `; added ${added.join(", ")}` : "") + (removed.length ? `; withdrew ${removed.join(", ")}` : "") + (moved.length ? `; relocated ${moved.join(", ")}` : "") + ".");
|
|
302
|
+
}
|
|
303
|
+
const unadoptableChanged = next.unadoptable.length !== (previous?.unadoptable.length ?? 0) || next.unadoptable.some((id, i) => id !== previous?.unadoptable[i]);
|
|
304
|
+
if (next.unadoptable.length && unadoptableChanged) {
|
|
305
|
+
console.warn(`[PROXY] ${url} also has ${next.unadoptable.join(", ")}, which cannot be taken from it: ` + `their credentials are an inline key or token rather than a file on this machine, and a ` + `secret is never sent over this link. Configure them here to serve them here.`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function decideFollowedProfile(state, availableIds, now, staleAfterMs = FOLLOW_STALE_AFTER_MS) {
|
|
309
|
+
const good = state.lastGood;
|
|
310
|
+
if (!good)
|
|
311
|
+
return { follow: false, reason: "no-value" };
|
|
312
|
+
if (!availableIds.includes(good.profileId)) {
|
|
313
|
+
return { follow: false, reason: "unknown-profile", followedValue: good.profileId };
|
|
314
|
+
}
|
|
315
|
+
return { follow: true, profileId: good.profileId, stale: now - good.at >= staleAfterMs };
|
|
316
|
+
}
|
|
317
|
+
var cachedRaw;
|
|
318
|
+
var cachedTarget = { kind: "off" };
|
|
319
|
+
var warnedInvalid;
|
|
320
|
+
var disabledReason;
|
|
321
|
+
var state = {};
|
|
322
|
+
var pollTimer;
|
|
323
|
+
var bannerLogged = false;
|
|
324
|
+
var errorLogged = false;
|
|
325
|
+
function followTarget() {
|
|
326
|
+
if (disabledReason)
|
|
327
|
+
return;
|
|
328
|
+
const raw = env("FOLLOW_ACTIVE");
|
|
329
|
+
if (raw !== cachedRaw) {
|
|
330
|
+
cachedRaw = raw;
|
|
331
|
+
cachedTarget = parseFollowTarget(raw);
|
|
332
|
+
}
|
|
333
|
+
if (cachedTarget.kind === "invalid") {
|
|
334
|
+
if (warnedInvalid !== cachedTarget.raw) {
|
|
335
|
+
warnedInvalid = cachedTarget.raw;
|
|
336
|
+
console.warn(`[PROXY] MERIDIAN_FOLLOW_ACTIVE="${cachedTarget.raw}" is not usable (${cachedTarget.message}). ` + `Expected a Meridian base URL, e.g. http://127.0.0.1:3456. Follow mode is OFF; ` + `this instance uses its own active profile.`);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
return cachedTarget.kind === "on" ? { url: cachedTarget.url } : undefined;
|
|
341
|
+
}
|
|
342
|
+
function isFollowEnabled() {
|
|
343
|
+
return followTarget() !== undefined;
|
|
344
|
+
}
|
|
345
|
+
function followedActiveProfile(availableIds) {
|
|
346
|
+
if (!isFollowEnabled())
|
|
347
|
+
return;
|
|
348
|
+
return decideFollowedProfile(state, availableIds, Date.now());
|
|
349
|
+
}
|
|
350
|
+
function adoptedProfiles() {
|
|
351
|
+
if (!isFollowEnabled())
|
|
352
|
+
return [];
|
|
353
|
+
return state.lastRoster?.roster.adoptable ?? [];
|
|
354
|
+
}
|
|
355
|
+
function followStatus(availableIds) {
|
|
356
|
+
const target = followTarget();
|
|
357
|
+
if (!target)
|
|
358
|
+
return;
|
|
359
|
+
const outcome = decideFollowedProfile(state, availableIds, Date.now());
|
|
360
|
+
const roster = state.lastRoster;
|
|
361
|
+
return {
|
|
362
|
+
url: target.url,
|
|
363
|
+
activeProfile: outcome.follow ? outcome.profileId : null,
|
|
364
|
+
followedValue: state.lastGood?.profileId ?? null,
|
|
365
|
+
reason: outcome.follow ? null : outcome.reason,
|
|
366
|
+
stale: outcome.follow ? outcome.stale : false,
|
|
367
|
+
lastSyncedAt: state.lastGood?.at ?? null,
|
|
368
|
+
lastError: state.lastError?.message ?? null,
|
|
369
|
+
adoptedProfiles: (roster?.roster.adoptable ?? []).map((p) => p.id),
|
|
370
|
+
unadoptableProfiles: roster?.roster.unadoptable ?? [],
|
|
371
|
+
rosterSyncedAt: roster?.at ?? null
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async function pollFollowedActiveProfile() {
|
|
375
|
+
const target = followTarget();
|
|
376
|
+
if (!target)
|
|
377
|
+
return;
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
try {
|
|
380
|
+
const res = await fetch(`${target.url}/profiles/list`, {
|
|
381
|
+
signal: AbortSignal.timeout(FOLLOW_FETCH_TIMEOUT_MS),
|
|
382
|
+
headers: { accept: "application/json" }
|
|
383
|
+
});
|
|
384
|
+
if (!res.ok)
|
|
385
|
+
throw new Error(`HTTP ${res.status}`);
|
|
386
|
+
const body = await res.json();
|
|
387
|
+
const value = readActiveProfile(body);
|
|
388
|
+
if (!value)
|
|
389
|
+
throw new Error("response carried no usable activeProfile");
|
|
390
|
+
const previous = state.lastGood?.profileId;
|
|
391
|
+
const roster = readFollowedRoster(body);
|
|
392
|
+
const previousRoster = state.lastRoster?.roster;
|
|
393
|
+
state = {
|
|
394
|
+
lastGood: { profileId: value, at: now },
|
|
395
|
+
lastRoster: roster ? { roster, at: now } : state.lastRoster,
|
|
396
|
+
lastPollAt: now
|
|
397
|
+
};
|
|
398
|
+
if (roster && !sameRoster(roster, previousRoster))
|
|
399
|
+
logRosterChange(previousRoster, roster, target.url);
|
|
400
|
+
if (previous !== value) {
|
|
401
|
+
console.warn(`[PROXY] Active profile now following "${value}" from ${target.url}` + (previous ? ` (was "${previous}")` : ""));
|
|
402
|
+
} else if (errorLogged) {
|
|
403
|
+
console.warn(`[PROXY] Recovered contact with ${target.url}; still following "${value}".`);
|
|
404
|
+
}
|
|
405
|
+
errorLogged = false;
|
|
406
|
+
} catch (err) {
|
|
407
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
408
|
+
state = { ...state, lastPollAt: now, lastError: { message, at: now } };
|
|
409
|
+
if (!errorLogged) {
|
|
410
|
+
errorLogged = true;
|
|
411
|
+
console.warn(`[PROXY] Could not read the active profile from ${target.url} (${message}). ` + (state.lastGood ? `Continuing on the last known value "${state.lastGood.profileId}".` : `No value has ever been read; using this instance's own active profile.`));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function startFollowPolling(self) {
|
|
416
|
+
if (pollTimer)
|
|
417
|
+
return;
|
|
418
|
+
const target = followTarget();
|
|
419
|
+
if (!target)
|
|
420
|
+
return;
|
|
421
|
+
if (self && isSelfTarget(target.url, self.host, self.port)) {
|
|
422
|
+
disabledReason = `MERIDIAN_FOLLOW_ACTIVE (${target.url}) points at this instance's own address. ` + `Following yourself would freeze the active profile permanently — the followed value ` + `could never change and local switching would be refused. Follow mode is OFF.`;
|
|
423
|
+
console.warn(`[PROXY] ${disabledReason}`);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
pollFollowedActiveProfile();
|
|
427
|
+
pollTimer = setInterval(() => {
|
|
428
|
+
pollFollowedActiveProfile();
|
|
429
|
+
}, FOLLOW_POLL_INTERVAL_MS);
|
|
430
|
+
if (pollTimer.unref)
|
|
431
|
+
pollTimer.unref();
|
|
432
|
+
}
|
|
433
|
+
function stopFollowPolling() {
|
|
434
|
+
if (!pollTimer)
|
|
435
|
+
return;
|
|
436
|
+
clearInterval(pollTimer);
|
|
437
|
+
pollTimer = undefined;
|
|
438
|
+
}
|
|
439
|
+
function logFollowBanner(routingMode) {
|
|
440
|
+
if (bannerLogged)
|
|
441
|
+
return;
|
|
442
|
+
const target = followTarget();
|
|
443
|
+
if (!target)
|
|
444
|
+
return;
|
|
445
|
+
bannerLogged = true;
|
|
446
|
+
console.warn(`[PROXY] FOLLOWING ACTIVE PROFILE (MERIDIAN_FOLLOW_ACTIVE=${target.url}): ` + `this instance takes its active profile from ${target.url}, not from its own settings. ` + `Local switching is refused; the x-meridian-profile header still overrides per request.`);
|
|
447
|
+
if (routingMode === "priority") {
|
|
448
|
+
console.warn(`[PROXY] Routing is "priority", which dispatches unpinned requests across the pool ` + `without consulting the active profile — follow mode will have no visible effect on them.`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/proxy/profiles.ts
|
|
453
|
+
var DISK_CACHE_TTL_MS = 5000;
|
|
454
|
+
var diskProfilesCache = [];
|
|
455
|
+
var diskProfilesCacheAt = 0;
|
|
456
|
+
var diskProfilesCachePath = "";
|
|
457
|
+
function profilesRelocationNotice(configuredFile, defaultFile, defaultFileExists) {
|
|
458
|
+
if (configuredFile === defaultFile || !defaultFileExists)
|
|
459
|
+
return;
|
|
460
|
+
return `[meridian] No profiles at ${configuredFile}, but ${defaultFile} exists. ` + `MERIDIAN_CONFIG_DIR moves the whole config directory, not settings.json alone — ` + `copy profiles.json (and profiles/) across, or unset the variable to use the default.`;
|
|
461
|
+
}
|
|
462
|
+
var warnedProfilesLeftBehind = false;
|
|
463
|
+
function warnIfProfilesLeftBehind(configuredFile) {
|
|
464
|
+
if (warnedProfilesLeftBehind || !diskDiscoveryEnabled)
|
|
465
|
+
return;
|
|
466
|
+
const defaultFile = join(defaultConfigDir(), "profiles.json");
|
|
467
|
+
const notice = profilesRelocationNotice(configuredFile, defaultFile, existsSync(defaultFile));
|
|
468
|
+
if (!notice)
|
|
469
|
+
return;
|
|
470
|
+
warnedProfilesLeftBehind = true;
|
|
471
|
+
console.warn(notice);
|
|
472
|
+
}
|
|
473
|
+
function loadProfilesFromDisk() {
|
|
474
|
+
const file = configPath("profiles.json");
|
|
475
|
+
if (diskProfilesCachePath === file && diskProfilesCacheAt > 0 && Date.now() - diskProfilesCacheAt < DISK_CACHE_TTL_MS) {
|
|
476
|
+
return diskProfilesCache;
|
|
477
|
+
}
|
|
478
|
+
diskProfilesCachePath = file;
|
|
479
|
+
try {
|
|
480
|
+
if (!existsSync(file)) {
|
|
481
|
+
warnIfProfilesLeftBehind(file);
|
|
482
|
+
diskProfilesCache = [];
|
|
483
|
+
} else {
|
|
484
|
+
diskProfilesCache = JSON.parse(readFileSync(file, "utf-8"));
|
|
485
|
+
}
|
|
486
|
+
diskProfilesCacheAt = Date.now();
|
|
487
|
+
return diskProfilesCache;
|
|
488
|
+
} catch (err) {
|
|
489
|
+
console.warn(`[meridian] Failed to read ${file}: ${err instanceof Error ? err.message : err}`);
|
|
490
|
+
diskProfilesCacheAt = Date.now();
|
|
491
|
+
diskProfilesCache = [];
|
|
492
|
+
return [];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function invalidateDiskProfileCache() {
|
|
496
|
+
diskProfilesCacheAt = 0;
|
|
497
|
+
}
|
|
498
|
+
var DEFAULT_PROFILE_ID = "default";
|
|
499
|
+
var activeProfileId;
|
|
500
|
+
function setActiveProfile(profileId) {
|
|
501
|
+
activeProfileId = profileId;
|
|
502
|
+
setSetting("activeProfile", profileId);
|
|
503
|
+
}
|
|
504
|
+
function getActiveProfileId() {
|
|
505
|
+
return activeProfileId;
|
|
506
|
+
}
|
|
507
|
+
var warnedUnknownFollowed;
|
|
508
|
+
function resolveActiveProfileId(availableIds) {
|
|
509
|
+
const outcome = followedActiveProfile(availableIds);
|
|
510
|
+
if (!outcome)
|
|
511
|
+
return activeProfileId;
|
|
512
|
+
if (outcome.follow) {
|
|
513
|
+
warnedUnknownFollowed = undefined;
|
|
514
|
+
return outcome.profileId;
|
|
515
|
+
}
|
|
516
|
+
if (outcome.reason === "unknown-profile" && outcome.followedValue !== warnedUnknownFollowed) {
|
|
517
|
+
warnedUnknownFollowed = outcome.followedValue;
|
|
518
|
+
console.warn(`[meridian] Followed instance is on profile "${outcome.followedValue}", which is not configured here. ` + `Using this instance's own active profile instead.`);
|
|
519
|
+
}
|
|
520
|
+
return activeProfileId;
|
|
521
|
+
}
|
|
522
|
+
function getEffectiveActiveProfileId(configProfiles) {
|
|
523
|
+
return resolveActiveProfileId(getEffectiveProfiles(configProfiles).map((p) => p.id));
|
|
524
|
+
}
|
|
525
|
+
function resetActiveProfile() {
|
|
526
|
+
activeProfileId = undefined;
|
|
527
|
+
warnedUnknownFollowed = undefined;
|
|
528
|
+
}
|
|
529
|
+
function restoreActiveProfile(configProfiles) {
|
|
530
|
+
if (activeProfileId)
|
|
531
|
+
return;
|
|
532
|
+
if (!diskDiscoveryEnabled)
|
|
533
|
+
return;
|
|
534
|
+
const saved = getSetting("activeProfile");
|
|
535
|
+
if (!saved)
|
|
536
|
+
return;
|
|
537
|
+
const effective = getEffectiveProfiles(configProfiles);
|
|
538
|
+
if (effective.length === 0 || effective.some((p) => p.id === saved)) {
|
|
539
|
+
activeProfileId = saved;
|
|
540
|
+
} else {
|
|
541
|
+
console.warn(`[meridian] Saved active profile "${saved}" not found. Using default.`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
var diskDiscoveryEnabled = false;
|
|
545
|
+
function enableDiskProfileDiscovery() {
|
|
546
|
+
diskDiscoveryEnabled = true;
|
|
547
|
+
}
|
|
548
|
+
function getEffectiveProfiles(configProfiles) {
|
|
549
|
+
const fromConfig = configProfiles ?? [];
|
|
550
|
+
const configIds = new Set(fromConfig.map((p) => p.id));
|
|
551
|
+
const fromDisk = diskDiscoveryEnabled ? loadProfilesFromDisk().filter((p) => !configIds.has(p.id)) : [];
|
|
552
|
+
const localIds = new Set([...configIds, ...fromDisk.map((p) => p.id)]);
|
|
553
|
+
const adopted = adoptedProfiles().filter((p) => !localIds.has(p.id)).map((p) => ({ id: p.id, claudeConfigDir: p.credentialDir }));
|
|
554
|
+
return [...fromConfig, ...fromDisk, ...adopted];
|
|
555
|
+
}
|
|
556
|
+
function shareableCredentialDir(resolved) {
|
|
557
|
+
const keys = Object.keys(resolved.env);
|
|
558
|
+
if (keys.length !== 1 || keys[0] !== "CLAUDE_CONFIG_DIR")
|
|
559
|
+
return null;
|
|
560
|
+
return resolved.env.CLAUDE_CONFIG_DIR ?? null;
|
|
561
|
+
}
|
|
562
|
+
function hasProfiles(configProfiles) {
|
|
563
|
+
return getEffectiveProfiles(configProfiles).length > 0;
|
|
564
|
+
}
|
|
565
|
+
function resolveProfile(profiles, defaultProfile, requestedId, options) {
|
|
566
|
+
const effective = getEffectiveProfiles(profiles);
|
|
567
|
+
if (effective.length === 0) {
|
|
568
|
+
return { id: DEFAULT_PROFILE_ID, type: "claude-max", env: {} };
|
|
569
|
+
}
|
|
570
|
+
const stickyId = options?.routingMode === "sticky" && options.stickySessionKey ? pickStickyProfile(options.stickySessionKey, effective.map((p) => p.id)) : undefined;
|
|
571
|
+
const activeId = resolveActiveProfileId(effective.map((p) => p.id));
|
|
572
|
+
const resolvedId = requestedId || stickyId || activeId || defaultProfile || effective[0].id;
|
|
573
|
+
const profile = findProfileByIdOrAlias(effective, resolvedId);
|
|
574
|
+
if (!profile) {
|
|
575
|
+
console.warn(`[meridian] Unknown profile "${resolvedId}". Using first configured profile.`);
|
|
576
|
+
return buildResolvedProfile(effective[0]);
|
|
577
|
+
}
|
|
578
|
+
return buildResolvedProfile(profile);
|
|
579
|
+
}
|
|
580
|
+
function findProfileByIdOrAlias(profiles, id) {
|
|
581
|
+
return profiles.find((p) => p.id === id) ?? profiles.find((p) => p.aliases?.includes(id));
|
|
582
|
+
}
|
|
583
|
+
function buildResolvedProfile(profile) {
|
|
584
|
+
if (profile.oauthToken || profile.type === "oauth-token") {
|
|
585
|
+
const env3 = {};
|
|
586
|
+
if (profile.oauthToken) {
|
|
587
|
+
env3.CLAUDE_CODE_OAUTH_TOKEN = profile.oauthToken;
|
|
588
|
+
env3.CLAUDE_CONFIG_DIR = configPath("profiles", profile.id);
|
|
589
|
+
}
|
|
590
|
+
return { id: profile.id, type: "oauth-token", env: env3 };
|
|
591
|
+
}
|
|
592
|
+
const type = profile.type ?? "claude-max";
|
|
593
|
+
if (type === "api") {
|
|
594
|
+
const env3 = {};
|
|
595
|
+
if (profile.apiKey)
|
|
596
|
+
env3.ANTHROPIC_API_KEY = profile.apiKey;
|
|
597
|
+
if (profile.baseUrl)
|
|
598
|
+
env3.ANTHROPIC_BASE_URL = profile.baseUrl;
|
|
599
|
+
return { id: profile.id, type, env: env3 };
|
|
600
|
+
}
|
|
601
|
+
const env2 = {};
|
|
602
|
+
if (profile.claudeConfigDir)
|
|
603
|
+
env2.CLAUDE_CONFIG_DIR = profile.claudeConfigDir;
|
|
604
|
+
return { id: profile.id, type, env: env2 };
|
|
605
|
+
}
|
|
606
|
+
function listProfiles(profiles, defaultProfile) {
|
|
607
|
+
const effective = getEffectiveProfiles(profiles);
|
|
608
|
+
if (effective.length === 0)
|
|
609
|
+
return [];
|
|
610
|
+
const currentActive = resolveActiveProfileId(effective.map((p) => p.id)) || defaultProfile || effective[0].id;
|
|
611
|
+
return effective.map((p) => ({
|
|
612
|
+
id: p.id,
|
|
613
|
+
type: p.type ?? "claude-max",
|
|
614
|
+
isActive: p.id === currentActive,
|
|
615
|
+
...p.aliases && p.aliases.length > 0 ? { aliases: p.aliases } : {}
|
|
616
|
+
}));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export { ACTIVE_PRIORITY, ROUTING_MODES, getRoutingMode, isPoolRouting, getPriorityFailbackPolicy, shouldPromotePriorityAssignment, classifyRouteKind, resolvePriorityOrder, choosePriorityProfile, chooseActivePriorityCandidates, ProfileExhaustion, AssignmentStore, cooldownCapMs, resolveCooldownUntil, findCooldownReset, FOLLOW_POLL_INTERVAL_MS, followStatus, startFollowPolling, stopFollowPolling, logFollowBanner, profilesRelocationNotice, loadProfilesFromDisk, invalidateDiskProfileCache, setActiveProfile, getActiveProfileId, resolveActiveProfileId, getEffectiveActiveProfileId, resetActiveProfile, restoreActiveProfile, enableDiskProfileDiscovery, getEffectiveProfiles, shareableCredentialDir, hasProfiles, resolveProfile, findProfileByIdOrAlias, listProfiles };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm
|
|
3
|
+
} from "./cli-p9swy5t3.js";
|
|
4
|
+
|
|
5
|
+
// src/configDir.ts
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
function defaultConfigDir() {
|
|
9
|
+
return join(homedir(), ".config", "meridian");
|
|
10
|
+
}
|
|
11
|
+
function configDir() {
|
|
12
|
+
return process.env.MERIDIAN_CONFIG_DIR || defaultConfigDir();
|
|
13
|
+
}
|
|
14
|
+
function configPath(...segments) {
|
|
15
|
+
return join(configDir(), ...segments);
|
|
16
|
+
}
|
|
17
|
+
var init_configDir = () => {};
|
|
18
|
+
|
|
19
|
+
// src/settings.ts
|
|
20
|
+
init_configDir();
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
22
|
+
import { dirname } from "node:path";
|
|
23
|
+
function settingsFile() {
|
|
24
|
+
return configPath("settings.json");
|
|
25
|
+
}
|
|
26
|
+
var TELEMETRY_SETTING_LIMITS = {
|
|
27
|
+
telemetryRetentionDays: { min: 1, max: 3650 },
|
|
28
|
+
telemetrySize: { min: 10, max: 1e6 },
|
|
29
|
+
diagnosticLogSize: { min: 10, max: 1e6 }
|
|
30
|
+
};
|
|
31
|
+
function loadSettings() {
|
|
32
|
+
const file = settingsFile();
|
|
33
|
+
try {
|
|
34
|
+
if (!existsSync(file))
|
|
35
|
+
return {};
|
|
36
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
37
|
+
} catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function saveSettings(patch) {
|
|
42
|
+
const file = settingsFile();
|
|
43
|
+
const current = loadSettings();
|
|
44
|
+
const merged = { ...current, ...patch };
|
|
45
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
46
|
+
writeFileSync(file, JSON.stringify(merged, null, 2) + `
|
|
47
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
48
|
+
return merged;
|
|
49
|
+
}
|
|
50
|
+
function getSetting(key) {
|
|
51
|
+
return loadSettings()[key];
|
|
52
|
+
}
|
|
53
|
+
function setSetting(key, value) {
|
|
54
|
+
return saveSettings({ [key]: value });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { defaultConfigDir, configDir, configPath, init_configDir, TELEMETRY_SETTING_LIMITS, getSetting, setSetting };
|