@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
|
@@ -1,39 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
authFieldPaths,
|
|
3
|
+
claudeLog,
|
|
4
|
+
createPlatformCredentialStore,
|
|
5
|
+
credentialsFilePathForProfile,
|
|
6
|
+
describeAuthFields,
|
|
7
|
+
fetchOAuthPlanFields,
|
|
8
|
+
isCredentialsReadOnly
|
|
9
|
+
} from "./cli-chsp880k.js";
|
|
4
10
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
env,
|
|
12
|
+
envBool,
|
|
13
|
+
init_env
|
|
14
|
+
} from "./cli-1jstkdj9.js";
|
|
7
15
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
function resolvePassthrough(defaultValue) {
|
|
20
|
-
const val = env("PASSTHROUGH");
|
|
21
|
-
if (val === "1" || val === "true" || val === "yes")
|
|
22
|
-
return true;
|
|
23
|
-
if (val === "0" || val === "false" || val === "no")
|
|
24
|
-
return false;
|
|
25
|
-
return defaultValue;
|
|
26
|
-
}
|
|
27
|
-
function envInt(suffix, defaultValue) {
|
|
28
|
-
const val = env(suffix);
|
|
29
|
-
if (!val)
|
|
30
|
-
return defaultValue;
|
|
31
|
-
const parsed = parseInt(val, 10);
|
|
32
|
-
return Number.isFinite(parsed) ? parsed : defaultValue;
|
|
33
|
-
}
|
|
34
|
-
var init_env = () => {};
|
|
16
|
+
applyProfileRename,
|
|
17
|
+
defaultProfilesConfigFile,
|
|
18
|
+
defaultProfilesDir,
|
|
19
|
+
reclaimAlias
|
|
20
|
+
} from "./cli-qpfky8wy.js";
|
|
21
|
+
import {
|
|
22
|
+
configPath,
|
|
23
|
+
getSetting,
|
|
24
|
+
init_configDir,
|
|
25
|
+
setSetting
|
|
26
|
+
} from "./cli-eehk3052.js";
|
|
35
27
|
|
|
36
28
|
// src/proxy/profileCli.ts
|
|
29
|
+
init_configDir();
|
|
37
30
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
38
31
|
import { createHash, randomBytes } from "node:crypto";
|
|
39
32
|
import { mkdirSync, readFileSync, rmSync, writeFileSync, existsSync as existsSync2 } from "node:fs";
|
|
@@ -77,6 +70,16 @@ var lastKnownGoodAuthStatus = null;
|
|
|
77
70
|
var cachedAuthStatusAt = 0;
|
|
78
71
|
var cachedAuthStatusIsFailure = false;
|
|
79
72
|
var cachedAuthStatusPromise = null;
|
|
73
|
+
var cachedAuthStatusCredMtimeMs = 0;
|
|
74
|
+
function credentialFileMtimeMs(envOverrides) {
|
|
75
|
+
if (!isCredentialsReadOnly())
|
|
76
|
+
return 0;
|
|
77
|
+
try {
|
|
78
|
+
return statSync(credentialsFilePathForProfile(envOverrides?.CLAUDE_CONFIG_DIR)).mtimeMs;
|
|
79
|
+
} catch {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
80
83
|
var warnedTierOverrides = new Set;
|
|
81
84
|
function warnUnrecognizedTierOverride(varName, raw, tierBase) {
|
|
82
85
|
if (warnedTierOverrides.has(varName))
|
|
@@ -226,7 +229,7 @@ function getAuthCacheInfo(profileId) {
|
|
|
226
229
|
function getAuthCache(key) {
|
|
227
230
|
let cache = profileAuthCaches.get(key);
|
|
228
231
|
if (!cache) {
|
|
229
|
-
cache = { status: null, lastKnownGood: null, at: 0, isFailure: false, promise: null, lastSuccessAt: 0 };
|
|
232
|
+
cache = { status: null, lastKnownGood: null, at: 0, isFailure: false, promise: null, lastSuccessAt: 0, credMtimeMs: 0 };
|
|
230
233
|
profileAuthCaches.set(key, cache);
|
|
231
234
|
}
|
|
232
235
|
return cache;
|
|
@@ -239,8 +242,10 @@ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
|
|
|
239
242
|
const c_at = cache ? cache.at : cachedAuthStatusAt;
|
|
240
243
|
const c_isFailure = cache ? cache.isFailure : cachedAuthStatusIsFailure;
|
|
241
244
|
let c_promise = cache ? cache.promise : cachedAuthStatusPromise;
|
|
245
|
+
const c_credMtime = cache ? cache.credMtimeMs : cachedAuthStatusCredMtimeMs;
|
|
242
246
|
const ttl = c_isFailure ? AUTH_STATUS_FAILURE_TTL_MS : AUTH_STATUS_CACHE_TTL_MS;
|
|
243
|
-
|
|
247
|
+
const credMtime = credentialFileMtimeMs(envOverrides);
|
|
248
|
+
if (c_at > 0 && Date.now() - c_at < ttl && credMtime === c_credMtime) {
|
|
244
249
|
return c_status ?? c_lastKnownGood;
|
|
245
250
|
}
|
|
246
251
|
if (c_promise)
|
|
@@ -253,29 +258,45 @@ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
|
|
|
253
258
|
...envOverrides ? { env: { ...process.env, ...envOverrides } } : {}
|
|
254
259
|
});
|
|
255
260
|
const parsed = JSON.parse(stdout);
|
|
261
|
+
claudeLog("auth.status_discovered", {
|
|
262
|
+
source: "cli_async",
|
|
263
|
+
profile: profileId ?? "default",
|
|
264
|
+
fields: authFieldPaths(parsed),
|
|
265
|
+
payload: describeAuthFields(parsed)
|
|
266
|
+
});
|
|
256
267
|
if (cache) {
|
|
257
268
|
cache.status = parsed;
|
|
258
269
|
cache.lastKnownGood = parsed;
|
|
259
270
|
cache.at = Date.now();
|
|
260
271
|
cache.isFailure = false;
|
|
261
272
|
cache.lastSuccessAt = Date.now();
|
|
273
|
+
cache.credMtimeMs = credMtime;
|
|
262
274
|
} else {
|
|
263
275
|
cachedAuthStatus = parsed;
|
|
264
276
|
lastKnownGoodAuthStatus = parsed;
|
|
265
277
|
cachedAuthStatusAt = Date.now();
|
|
266
278
|
cachedAuthStatusIsFailure = false;
|
|
279
|
+
cachedAuthStatusCredMtimeMs = credMtime;
|
|
267
280
|
}
|
|
268
281
|
return parsed;
|
|
269
|
-
} catch {
|
|
282
|
+
} catch (err) {
|
|
283
|
+
claudeLog("auth.status_failed", {
|
|
284
|
+
source: "cli_async",
|
|
285
|
+
profile: profileId ?? "default",
|
|
286
|
+
error: String(err),
|
|
287
|
+
servingLastKnownGood: Boolean(cache ? cache.lastKnownGood : lastKnownGoodAuthStatus)
|
|
288
|
+
});
|
|
270
289
|
if (cache) {
|
|
271
290
|
cache.isFailure = true;
|
|
272
291
|
cache.at = Date.now();
|
|
273
292
|
cache.status = null;
|
|
293
|
+
cache.credMtimeMs = credMtime;
|
|
274
294
|
return cache.lastKnownGood;
|
|
275
295
|
} else {
|
|
276
296
|
cachedAuthStatusIsFailure = true;
|
|
277
297
|
cachedAuthStatusAt = Date.now();
|
|
278
298
|
cachedAuthStatus = null;
|
|
299
|
+
cachedAuthStatusCredMtimeMs = credMtime;
|
|
279
300
|
return lastKnownGoodAuthStatus;
|
|
280
301
|
}
|
|
281
302
|
}
|
|
@@ -425,8 +446,7 @@ function isClosedControllerError(error) {
|
|
|
425
446
|
}
|
|
426
447
|
|
|
427
448
|
// src/proxy/profileCli.ts
|
|
428
|
-
|
|
429
|
-
var CONFIG_FILE = join2(homedir(), ".config", "meridian", "profiles.json");
|
|
449
|
+
init_env();
|
|
430
450
|
var OAUTH_AUTHORIZE_URL = "https://claude.com/cai/oauth/authorize";
|
|
431
451
|
var OAUTH_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
432
452
|
var OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
@@ -439,11 +459,20 @@ var OAUTH_SCOPES = [
|
|
|
439
459
|
"user:mcp_servers",
|
|
440
460
|
"user:file_upload"
|
|
441
461
|
];
|
|
462
|
+
function profilesConfigFile() {
|
|
463
|
+
return configPath("profiles.json");
|
|
464
|
+
}
|
|
442
465
|
function ensureProfilesDir() {
|
|
443
|
-
mkdirSync(
|
|
466
|
+
mkdirSync(configPath("profiles"), { recursive: true });
|
|
444
467
|
}
|
|
445
468
|
function getProfileDir(id) {
|
|
446
|
-
return
|
|
469
|
+
return configPath("profiles", id);
|
|
470
|
+
}
|
|
471
|
+
function isValidProfileId(id) {
|
|
472
|
+
return Boolean(id) && !/[^a-zA-Z0-9_-]/.test(id);
|
|
473
|
+
}
|
|
474
|
+
function yellow(text) {
|
|
475
|
+
return process.stdout.isTTY ? `\x1B[33m${text}\x1B[0m` : text;
|
|
447
476
|
}
|
|
448
477
|
function buildAuthLoginEnv(configDir, _options = {}, baseEnv = process.env) {
|
|
449
478
|
const env2 = { ...baseEnv };
|
|
@@ -489,18 +518,19 @@ function parseAuthorizationCodeInput(input) {
|
|
|
489
518
|
return code ? { code, state } : null;
|
|
490
519
|
}
|
|
491
520
|
function loadProfileConfig() {
|
|
492
|
-
|
|
521
|
+
const file = profilesConfigFile();
|
|
522
|
+
if (!existsSync2(file))
|
|
493
523
|
return [];
|
|
494
524
|
try {
|
|
495
|
-
return JSON.parse(readFileSync(
|
|
525
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
496
526
|
} catch (err) {
|
|
497
|
-
console.warn(`[meridian] Failed to read ${
|
|
527
|
+
console.warn(`[meridian] Failed to read ${file}: ${err instanceof Error ? err.message : err}`);
|
|
498
528
|
return [];
|
|
499
529
|
}
|
|
500
530
|
}
|
|
501
531
|
function saveProfileConfig(profiles) {
|
|
502
532
|
ensureProfilesDir();
|
|
503
|
-
writeFileSync(
|
|
533
|
+
writeFileSync(profilesConfigFile(), `${JSON.stringify(profiles, null, 2)}
|
|
504
534
|
`, { mode: 384 });
|
|
505
535
|
}
|
|
506
536
|
function getAuthStatus(configDir) {
|
|
@@ -515,12 +545,33 @@ function getAuthStatus(configDir) {
|
|
|
515
545
|
env: { ...process.env, CLAUDE_CONFIG_DIR: configDir },
|
|
516
546
|
stdio: ["pipe", "pipe", "pipe"]
|
|
517
547
|
});
|
|
518
|
-
|
|
548
|
+
const status = JSON.parse(result.toString());
|
|
549
|
+
claudeLog("auth.status_discovered", {
|
|
550
|
+
source: "cli_sync",
|
|
551
|
+
fields: authFieldPaths(status),
|
|
552
|
+
payload: describeAuthFields(status)
|
|
553
|
+
});
|
|
554
|
+
return status;
|
|
519
555
|
} catch (err) {
|
|
556
|
+
claudeLog("auth.status_failed", { source: "cli_sync", error: String(err) });
|
|
520
557
|
console.warn(`[meridian] Auth check failed for ${configDir}: ${err instanceof Error ? err.message : err}`);
|
|
521
558
|
return { loggedIn: false };
|
|
522
559
|
}
|
|
523
560
|
}
|
|
561
|
+
function hasRequiredTokens(tokenData) {
|
|
562
|
+
return Boolean(tokenData.access_token && tokenData.refresh_token);
|
|
563
|
+
}
|
|
564
|
+
function buildLoginCredentials(tokenData, plan, now = Date.now()) {
|
|
565
|
+
return {
|
|
566
|
+
claudeAiOauth: {
|
|
567
|
+
accessToken: tokenData.access_token,
|
|
568
|
+
refreshToken: tokenData.refresh_token,
|
|
569
|
+
expiresAt: tokenData.expires_at ?? now + (tokenData.expires_in ?? 8 * 60 * 60) * 1000,
|
|
570
|
+
scopes: tokenData.scope?.split(" ").filter(Boolean) ?? OAUTH_SCOPES,
|
|
571
|
+
...plan
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
524
575
|
async function completeManualOAuthLogin(configDir) {
|
|
525
576
|
const session = createManualOAuthSession();
|
|
526
577
|
console.log("\x1B[33m⚠ Headless OAuth login: open this URL in a browser:\x1B[0m");
|
|
@@ -554,11 +605,13 @@ async function completeManualOAuthLogin(configDir) {
|
|
|
554
605
|
signal: AbortSignal.timeout(30000)
|
|
555
606
|
});
|
|
556
607
|
} catch (err) {
|
|
608
|
+
claudeLog("auth.token_request_failed", { error: String(err) });
|
|
557
609
|
console.error(`\x1B[31m✗ OAuth token exchange failed: ${err instanceof Error ? err.message : err}\x1B[0m`);
|
|
558
610
|
return false;
|
|
559
611
|
}
|
|
560
612
|
if (!response.ok) {
|
|
561
613
|
const body = await response.text().catch(() => "");
|
|
614
|
+
claudeLog("auth.token_bad_response", { status: response.status, bodyLength: body.length });
|
|
562
615
|
console.error(`\x1B[31m✗ OAuth token exchange failed (${response.status}).\x1B[0m`);
|
|
563
616
|
if (body)
|
|
564
617
|
console.error(` ${body.slice(0, 300)}`);
|
|
@@ -568,35 +621,39 @@ async function completeManualOAuthLogin(configDir) {
|
|
|
568
621
|
try {
|
|
569
622
|
tokenData = await response.json();
|
|
570
623
|
} catch (err) {
|
|
624
|
+
claudeLog("auth.token_parse_failed", { error: String(err) });
|
|
571
625
|
console.error(`\x1B[31m✗ OAuth token response was invalid: ${err instanceof Error ? err.message : err}\x1B[0m`);
|
|
572
626
|
return false;
|
|
573
627
|
}
|
|
574
|
-
|
|
628
|
+
claudeLog("auth.token_discovered", {
|
|
629
|
+
fields: authFieldPaths(tokenData),
|
|
630
|
+
payload: describeAuthFields(tokenData)
|
|
631
|
+
});
|
|
632
|
+
if (!hasRequiredTokens(tokenData)) {
|
|
575
633
|
console.error("\x1B[31m✗ OAuth token response did not include the required tokens.\x1B[0m");
|
|
576
634
|
return false;
|
|
577
635
|
}
|
|
578
|
-
const
|
|
636
|
+
const plan = await fetchOAuthPlanFields(tokenData.access_token);
|
|
579
637
|
const store = createPlatformCredentialStore({ claudeConfigDir: configDir });
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
expiresAt,
|
|
585
|
-
scopes: tokenData.scope?.split(" ").filter(Boolean) ?? OAUTH_SCOPES
|
|
586
|
-
}
|
|
638
|
+
const credentials = buildLoginCredentials(tokenData, plan);
|
|
639
|
+
claudeLog("auth.credentials_built", {
|
|
640
|
+
fields: authFieldPaths(credentials.claudeAiOauth),
|
|
641
|
+
payload: describeAuthFields(credentials.claudeAiOauth)
|
|
587
642
|
});
|
|
643
|
+
return store.write(credentials);
|
|
588
644
|
}
|
|
589
645
|
async function profileAdd(id, options = {}) {
|
|
590
|
-
if (!
|
|
646
|
+
if (!isValidProfileId(id)) {
|
|
591
647
|
console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
|
|
592
648
|
process.exit(1);
|
|
593
649
|
}
|
|
594
|
-
|
|
650
|
+
let profiles = loadProfileConfig();
|
|
595
651
|
if (profiles.find((p) => p.id === id)) {
|
|
596
652
|
console.error(`\x1B[31m✗ Profile "${id}" already exists.\x1B[0m`);
|
|
597
653
|
console.error(` Run: meridian profile list`);
|
|
598
654
|
process.exit(1);
|
|
599
655
|
}
|
|
656
|
+
profiles = reclaimAlias(profiles, id);
|
|
600
657
|
const defaultClaudeDir = join2(homedir(), ".claude");
|
|
601
658
|
const alreadyImported = profiles.some((p) => p.claudeConfigDir === defaultClaudeDir);
|
|
602
659
|
if (!alreadyImported) {
|
|
@@ -675,16 +732,17 @@ async function profileAdd(id, options = {}) {
|
|
|
675
732
|
printEnvHint(profiles);
|
|
676
733
|
}
|
|
677
734
|
async function profileAddOauthToken(id, tokenArg) {
|
|
678
|
-
if (!
|
|
735
|
+
if (!isValidProfileId(id)) {
|
|
679
736
|
console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
|
|
680
737
|
process.exit(1);
|
|
681
738
|
}
|
|
682
|
-
|
|
739
|
+
let profiles = loadProfileConfig();
|
|
683
740
|
if (profiles.find((p) => p.id === id)) {
|
|
684
741
|
console.error(`\x1B[31m✗ Profile "${id}" already exists.\x1B[0m`);
|
|
685
742
|
console.error(` Run: meridian profile list`);
|
|
686
743
|
process.exit(1);
|
|
687
744
|
}
|
|
745
|
+
profiles = reclaimAlias(profiles, id);
|
|
688
746
|
let token = tokenArg?.trim() ?? "";
|
|
689
747
|
if (!token) {
|
|
690
748
|
console.log(`\x1B[36mAdding profile: ${id} (OAuth token)\x1B[0m`);
|
|
@@ -718,6 +776,9 @@ function profileList() {
|
|
|
718
776
|
const auth = getAuthStatus(p.claudeConfigDir ?? "");
|
|
719
777
|
const status = auth.loggedIn ? `\x1B[32m✓ ${auth.email} (${auth.subscriptionType || "unknown"})\x1B[0m` : "\x1B[31m✗ not logged in\x1B[0m";
|
|
720
778
|
console.log(` ${p.id.padEnd(20)} ${status}`);
|
|
779
|
+
if (p.aliases && p.aliases.length > 0) {
|
|
780
|
+
console.log(` ${"".padEnd(20)} \x1B[90malso answers to: ${p.aliases.join(", ")}\x1B[0m`);
|
|
781
|
+
}
|
|
721
782
|
}
|
|
722
783
|
console.log();
|
|
723
784
|
printEnvHint(profiles);
|
|
@@ -746,7 +807,7 @@ function profileRemove(id) {
|
|
|
746
807
|
console.error(`\x1B[31m✗ Profile "${id}" not found.\x1B[0m`);
|
|
747
808
|
process.exit(1);
|
|
748
809
|
}
|
|
749
|
-
const dirsToRemove = dirsToRemoveOnProfileRemove(removed,
|
|
810
|
+
const dirsToRemove = dirsToRemoveOnProfileRemove(removed, configPath("profiles"));
|
|
750
811
|
profiles.splice(idx, 1);
|
|
751
812
|
saveProfileConfig(profiles);
|
|
752
813
|
for (const dir of dirsToRemove) {
|
|
@@ -758,6 +819,26 @@ function profileRemove(id) {
|
|
|
758
819
|
printEnvHint(profiles);
|
|
759
820
|
}
|
|
760
821
|
}
|
|
822
|
+
function profileRename(from, to) {
|
|
823
|
+
if (envBool("CREDENTIALS_READONLY")) {
|
|
824
|
+
console.error("\x1B[31m✗ MERIDIAN_CREDENTIALS_READONLY=1 — this instance may not modify credentials.\x1B[0m");
|
|
825
|
+
console.error(" Rename the profile from the instance that owns them.");
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
const wasActive = getSetting("activeProfile") === from;
|
|
829
|
+
const result = applyProfileRename(from, to, { profilesDir: defaultProfilesDir(), configFile: defaultProfilesConfigFile() });
|
|
830
|
+
if (!result.ok) {
|
|
831
|
+
console.error(`\x1B[31m✗ ${result.error}\x1B[0m`);
|
|
832
|
+
if (result.hint)
|
|
833
|
+
console.error(` ${result.hint}`);
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
console.log(`\x1B[32m✓ Profile "${from}" renamed to "${to}".\x1B[0m`);
|
|
837
|
+
console.log(` Requests naming ${result.aliases.map((a) => `"${a}"`).join(", ")} are served by "${to}" until the name is added again.`);
|
|
838
|
+
if (wasActive)
|
|
839
|
+
console.log(` Active profile is now "${to}".`);
|
|
840
|
+
printEnvHint(result.profiles);
|
|
841
|
+
}
|
|
761
842
|
async function profileSwitch(id) {
|
|
762
843
|
const port = process.env.MERIDIAN_PORT ?? process.env.CLAUDE_PROXY_PORT ?? "3456";
|
|
763
844
|
const host = process.env.MERIDIAN_HOST ?? process.env.CLAUDE_PROXY_HOST ?? "127.0.0.1";
|
|
@@ -781,18 +862,32 @@ async function profileSwitch(id) {
|
|
|
781
862
|
process.exit(1);
|
|
782
863
|
}
|
|
783
864
|
}
|
|
784
|
-
|
|
785
|
-
const profiles = loadProfileConfig();
|
|
865
|
+
function planProfileLogin(id, profiles) {
|
|
786
866
|
const profile = profiles.find((p) => p.id === id);
|
|
787
|
-
if (!profile)
|
|
788
|
-
|
|
867
|
+
if (!profile)
|
|
868
|
+
return isValidProfileId(id) ? { action: "create" } : { action: "reject-invalid-id" };
|
|
869
|
+
if (profile.oauthToken || profile.type === "oauth-token")
|
|
870
|
+
return { action: "reject-oauth-token" };
|
|
871
|
+
return { action: "login", profile };
|
|
872
|
+
}
|
|
873
|
+
async function profileLogin(id, options = {}) {
|
|
874
|
+
const plan = planProfileLogin(id, loadProfileConfig());
|
|
875
|
+
if (plan.action === "reject-invalid-id") {
|
|
876
|
+
console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
|
|
789
877
|
process.exit(1);
|
|
790
878
|
}
|
|
791
|
-
if (
|
|
879
|
+
if (plan.action === "reject-oauth-token") {
|
|
792
880
|
console.error(`\x1B[31m✗ Profile "${id}" uses an OAuth token; \`claude auth login\` does not apply.\x1B[0m`);
|
|
793
881
|
console.error(` To replace the token: meridian profile remove ${id} && meridian profile add ${id} --oauth-token`);
|
|
794
882
|
process.exit(1);
|
|
795
883
|
}
|
|
884
|
+
if (plan.action === "create") {
|
|
885
|
+
console.log(yellow(`⚠ Profile "${id}" does not exist yet — adding it first.`));
|
|
886
|
+
console.log();
|
|
887
|
+
await profileAdd(id, options);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const profile = plan.profile;
|
|
796
891
|
console.log(`\x1B[36mRe-authenticating profile: ${id}\x1B[0m`);
|
|
797
892
|
console.log();
|
|
798
893
|
if (!options.headless) {
|
|
@@ -883,7 +978,7 @@ function promptToken(question) {
|
|
|
883
978
|
return (result.stdout?.toString() ?? "").trim();
|
|
884
979
|
}
|
|
885
980
|
function printEnvHint(_profiles) {
|
|
886
|
-
console.log(`\x1B[90mConfig: ${
|
|
981
|
+
console.log(`\x1B[90mConfig: ${profilesConfigFile()}\x1B[0m`);
|
|
887
982
|
console.log("\x1B[90mProfiles are picked up automatically — no restart needed.\x1B[0m");
|
|
888
983
|
}
|
|
889
984
|
function profileHelp() {
|
|
@@ -894,9 +989,11 @@ Commands:
|
|
|
894
989
|
meridian profile add <name> --oauth-token [TOKEN] Add a profile from a \`claude setup-token\` value
|
|
895
990
|
(if TOKEN is omitted, you will be prompted; input is hidden)
|
|
896
991
|
meridian profile list List profiles and auth status
|
|
992
|
+
meridian profile rename <old> <new> Rename a profile; <old> keeps routing to it until reused
|
|
897
993
|
meridian profile remove <name> Remove a profile
|
|
898
994
|
meridian profile switch <name> Switch the active profile (requires running proxy)
|
|
899
|
-
meridian profile login <name> [--headless] Re-authenticate
|
|
995
|
+
meridian profile login <name> [--headless] Re-authenticate a profile, adding it first if it does not
|
|
996
|
+
exist yet (claude-max only)
|
|
900
997
|
|
|
901
998
|
Examples:
|
|
902
999
|
meridian profile add personal # Add personal account (browser login)
|
|
@@ -907,7 +1004,8 @@ Examples:
|
|
|
907
1004
|
# Add headless CI profile (token from CLI argument)
|
|
908
1005
|
meridian profile login work --headless # Re-authenticate via OAuth URL/code prompt
|
|
909
1006
|
meridian profile switch work # Switch to work account
|
|
1007
|
+
meridian profile rename work employer # Rename; "work" still routes to it
|
|
910
1008
|
meridian profile list # Show all profiles`);
|
|
911
1009
|
}
|
|
912
1010
|
|
|
913
|
-
export {
|
|
1011
|
+
export { CANONICAL_SONNET_MODEL, resolveSdkModelDefaults, explicitModelPin, mapModelToClaudeModel, recordExtendedContextUnavailable, recordExtendedContextRateLimited, stripExtendedContext, hasExtendedContext, subscriptionIncludesExtendedContext, getAuthCacheInfo, getClaudeAuthStatusAsync, resolveClaudeExecutableSync, getResolvedClaudeExecutableInfo, resolveClaudeExecutableAsync, isClosedControllerError, OAUTH_TOKEN_URL, OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, isValidProfileId, buildAuthLoginEnv, createManualOAuthSession, parseAuthorizationCodeInput, buildLoginCredentials, profileAdd, profileAddOauthToken, profileList, dirsToRemoveOnProfileRemove, profileRemove, profileRename, profileSwitch, planProfileLogin, profileLogin, profileHelp };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/telemetry/profileUsage.ts
|
|
2
|
+
var WINDOW_LABELS = {
|
|
3
|
+
five_hour: "5h",
|
|
4
|
+
seven_day: "7d",
|
|
5
|
+
seven_day_opus: "7d Opus",
|
|
6
|
+
seven_day_sonnet: "7d Sonnet",
|
|
7
|
+
seven_day_fable: "7d Fable",
|
|
8
|
+
seven_day_oauth_apps: "7d Apps",
|
|
9
|
+
seven_day_cowork: "7d Cowork",
|
|
10
|
+
seven_day_omelette: "7d Omelette"
|
|
11
|
+
};
|
|
12
|
+
function classifyUtilization(utilization) {
|
|
13
|
+
if (utilization == null || !Number.isFinite(utilization))
|
|
14
|
+
return "ok";
|
|
15
|
+
if (utilization >= 0.85)
|
|
16
|
+
return "high";
|
|
17
|
+
if (utilization >= 0.6)
|
|
18
|
+
return "warn";
|
|
19
|
+
return "ok";
|
|
20
|
+
}
|
|
21
|
+
function formatResetCountdown(resetsAt, now = Date.now()) {
|
|
22
|
+
if (resetsAt == null || !Number.isFinite(resetsAt))
|
|
23
|
+
return "";
|
|
24
|
+
const ms = resetsAt - now;
|
|
25
|
+
if (ms <= 0)
|
|
26
|
+
return "resetting…";
|
|
27
|
+
const minutes = Math.floor(ms / 60000);
|
|
28
|
+
if (minutes < 60)
|
|
29
|
+
return `in ${Math.max(1, minutes)}m`;
|
|
30
|
+
const hours = Math.floor(minutes / 60);
|
|
31
|
+
const remMin = minutes % 60;
|
|
32
|
+
if (hours < 24)
|
|
33
|
+
return remMin > 0 ? `in ${hours}h ${remMin}m` : `in ${hours}h`;
|
|
34
|
+
const days = Math.floor(hours / 24);
|
|
35
|
+
const remHr = hours % 24;
|
|
36
|
+
return remHr > 0 ? `in ${days}d ${remHr}h` : `in ${days}d`;
|
|
37
|
+
}
|
|
38
|
+
var SEVEN_DAYS_MS = 7 * 86400000;
|
|
39
|
+
var PACE_ON_BAND = 7;
|
|
40
|
+
var PROJECT_MIN_ELAPSED = 0.1;
|
|
41
|
+
function computeWeeklyPace(utilization, resetsAt, now = Date.now()) {
|
|
42
|
+
if (utilization == null || !Number.isFinite(utilization))
|
|
43
|
+
return null;
|
|
44
|
+
if (resetsAt == null || !Number.isFinite(resetsAt))
|
|
45
|
+
return null;
|
|
46
|
+
const windowStart = resetsAt - SEVEN_DAYS_MS;
|
|
47
|
+
const elapsedFraction = Math.max(0, Math.min(1, (now - windowStart) / SEVEN_DAYS_MS));
|
|
48
|
+
const actualPct = Math.round(Math.max(0, utilization) * 100);
|
|
49
|
+
const expectedPct = Math.round(elapsedFraction * 100);
|
|
50
|
+
const deltaPct = actualPct - expectedPct;
|
|
51
|
+
const status = deltaPct > PACE_ON_BAND ? "ahead" : deltaPct < -PACE_ON_BAND ? "under" : "on";
|
|
52
|
+
const projectedPct = elapsedFraction >= PROJECT_MIN_ELAPSED ? Math.round(Math.max(0, utilization) / elapsedFraction * 100) : null;
|
|
53
|
+
return { actualPct, expectedPct, deltaPct, projectedPct, status, elapsedFraction };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { WINDOW_LABELS, classifyUtilization, formatResetCountdown, computeWeeklyPace };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
claudeLog,
|
|
3
|
+
createPlatformCredentialStore
|
|
4
|
+
} from "./cli-chsp880k.js";
|
|
5
|
+
|
|
6
|
+
// src/proxy/organizationName.ts
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
8
|
+
import { join, dirname } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
var OAUTH_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
11
|
+
var ORGANIZATION_REFRESH_AFTER_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
var ORGANIZATION_RETRY_AFTER_MS = 5 * 60 * 1000;
|
|
13
|
+
var MAX_ORGANIZATION_NAME_LENGTH = 200;
|
|
14
|
+
function organizationsFile() {
|
|
15
|
+
const override = process.env.MERIDIAN_CONFIG_DIR;
|
|
16
|
+
return override ? join(override, "organizations.json") : join(homedir(), ".config", "meridian", "organizations.json");
|
|
17
|
+
}
|
|
18
|
+
function readOrganizations(raw) {
|
|
19
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
20
|
+
return {};
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
23
|
+
if (!id || typeof value !== "object" || value === null)
|
|
24
|
+
continue;
|
|
25
|
+
const record = value;
|
|
26
|
+
if (typeof record.name !== "string")
|
|
27
|
+
continue;
|
|
28
|
+
const name = record.name.trim();
|
|
29
|
+
if (!name || name.length > MAX_ORGANIZATION_NAME_LENGTH)
|
|
30
|
+
continue;
|
|
31
|
+
const fetchedAt = typeof record.fetchedAt === "number" && isFinite(record.fetchedAt) ? record.fetchedAt : 0;
|
|
32
|
+
out[id] = { name, fetchedAt };
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function extractOrganizationName(body) {
|
|
37
|
+
if (typeof body !== "object" || body === null)
|
|
38
|
+
return null;
|
|
39
|
+
const organization = body.organization;
|
|
40
|
+
if (typeof organization !== "object" || organization === null)
|
|
41
|
+
return null;
|
|
42
|
+
const name = organization.name;
|
|
43
|
+
if (typeof name !== "string")
|
|
44
|
+
return null;
|
|
45
|
+
const trimmed = name.trim();
|
|
46
|
+
if (!trimmed || trimmed.length > MAX_ORGANIZATION_NAME_LENGTH)
|
|
47
|
+
return null;
|
|
48
|
+
return trimmed;
|
|
49
|
+
}
|
|
50
|
+
function organizationNeedsRefresh(record, now, refreshAfterMs = ORGANIZATION_REFRESH_AFTER_MS) {
|
|
51
|
+
if (!record)
|
|
52
|
+
return true;
|
|
53
|
+
return now - record.fetchedAt >= refreshAfterMs;
|
|
54
|
+
}
|
|
55
|
+
function organizationNames() {
|
|
56
|
+
const file = organizationsFile();
|
|
57
|
+
try {
|
|
58
|
+
if (!existsSync(file))
|
|
59
|
+
return {};
|
|
60
|
+
return readOrganizations(JSON.parse(readFileSync(file, "utf-8")));
|
|
61
|
+
} catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function rememberOrganizationName(profileId, name) {
|
|
66
|
+
const file = organizationsFile();
|
|
67
|
+
const merged = { ...organizationNames(), [profileId]: { name, fetchedAt: Date.now() } };
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
70
|
+
writeFileSync(file, JSON.stringify(merged, null, 2) + `
|
|
71
|
+
`, { mode: 384 });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.warn(`[meridian] Failed to write ${file}: ${err instanceof Error ? err.message : err}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function fetchOrganizationName(store, fetchImpl = globalThis.fetch) {
|
|
77
|
+
try {
|
|
78
|
+
const credentials = await store.read();
|
|
79
|
+
const token = credentials?.claudeAiOauth?.accessToken;
|
|
80
|
+
if (!token)
|
|
81
|
+
return null;
|
|
82
|
+
const response = await fetchImpl(OAUTH_PROFILE_URL, {
|
|
83
|
+
headers: {
|
|
84
|
+
Authorization: `Bearer ${token}`,
|
|
85
|
+
Accept: "application/json",
|
|
86
|
+
"Cache-Control": "no-cache"
|
|
87
|
+
},
|
|
88
|
+
signal: AbortSignal.timeout(1e4)
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
claudeLog("organization.bad_response", { status: response.status });
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return extractOrganizationName(await response.json());
|
|
95
|
+
} catch (err) {
|
|
96
|
+
claudeLog("organization.lookup_failed", { error: err instanceof Error ? err.message : String(err) });
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
var lookupEnabled = false;
|
|
101
|
+
function enableOrganizationLookup() {
|
|
102
|
+
lookupEnabled = true;
|
|
103
|
+
}
|
|
104
|
+
var lastAttemptAt = new Map;
|
|
105
|
+
var inflight = new Set;
|
|
106
|
+
function refreshOrganizationNameSoon(profileId, opts = {}) {
|
|
107
|
+
if (!lookupEnabled && !opts.store)
|
|
108
|
+
return;
|
|
109
|
+
if (inflight.has(profileId))
|
|
110
|
+
return;
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const last = lastAttemptAt.get(profileId);
|
|
113
|
+
if (last !== undefined && now - last < (opts.retryAfterMs ?? ORGANIZATION_RETRY_AFTER_MS))
|
|
114
|
+
return;
|
|
115
|
+
lastAttemptAt.set(profileId, now);
|
|
116
|
+
inflight.add(profileId);
|
|
117
|
+
(async () => {
|
|
118
|
+
try {
|
|
119
|
+
const store = opts.store ?? createPlatformCredentialStore({ claudeConfigDir: opts.claudeConfigDir });
|
|
120
|
+
const name = await fetchOrganizationName(store, opts.fetchImpl);
|
|
121
|
+
if (name) {
|
|
122
|
+
rememberOrganizationName(profileId, name);
|
|
123
|
+
claudeLog("organization.discovered", { profile: profileId, nameLength: name.length });
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
inflight.delete(profileId);
|
|
127
|
+
}
|
|
128
|
+
})();
|
|
129
|
+
}
|
|
130
|
+
function resetOrganizationLookupForTesting() {
|
|
131
|
+
lookupEnabled = false;
|
|
132
|
+
lastAttemptAt.clear();
|
|
133
|
+
inflight.clear();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { ORGANIZATION_REFRESH_AFTER_MS, ORGANIZATION_RETRY_AFTER_MS, readOrganizations, extractOrganizationName, organizationNeedsRefresh, organizationNames, rememberOrganizationName, fetchOrganizationName, enableOrganizationLookup, refreshOrganizationNameSoon, resetOrganizationLookupForTesting };
|