@rynfar/meridian 1.41.1 → 1.42.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 +43 -4
- package/dist/{cli-e289rj3k.js → cli-7k1fcprd.js} +64 -1
- package/dist/{cli-vdp9s10c.js → cli-cx463q74.js} +8 -0
- package/dist/{cli-51a9sav0.js → cli-kvwnarfk.js} +118 -38
- package/dist/cli.js +15 -9
- package/dist/{profileCli-5f15dx7k.js → profileCli-wpb4qbjn.js} +105 -13
- package/dist/{profiles-edzz1ffd.js → profiles-rdd84b45.js} +1 -1
- package/dist/proxy/cwd.d.ts +42 -0
- package/dist/proxy/cwd.d.ts.map +1 -0
- package/dist/proxy/errors.d.ts +14 -4
- package/dist/proxy/errors.d.ts.map +1 -1
- package/dist/proxy/models.d.ts +38 -4
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/oauthUsage.d.ts +17 -7
- package/dist/proxy/oauthUsage.d.ts.map +1 -1
- package/dist/proxy/plugins/loader.d.ts.map +1 -1
- package/dist/proxy/profiles.d.ts +12 -4
- package/dist/proxy/profiles.d.ts.map +1 -1
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/tokenRefresh.d.ts +41 -0
- package/dist/proxy/tokenRefresh.d.ts.map +1 -1
- package/dist/server.js +3 -3
- package/dist/{tokenRefresh-psq94r54.js → tokenRefresh-swetnf89.js} +10 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -239,6 +239,20 @@ meridian profile add work
|
|
|
239
239
|
|
|
240
240
|
> **⚠ Important:** Claude's OAuth reuses your browser session. Before adding a second account, sign out of claude.ai and sign into the other account first.
|
|
241
241
|
|
|
242
|
+
#### Headless / CI: register an OAuth token
|
|
243
|
+
|
|
244
|
+
When a browser isn't available (containers, CI runners, remote shells), generate a long-lived OAuth token with `claude setup-token` and register it as a profile:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Prompt for the token (input is hidden — paste the value from `claude setup-token`)
|
|
248
|
+
meridian profile add ci --oauth-token
|
|
249
|
+
|
|
250
|
+
# Or pass it inline
|
|
251
|
+
meridian profile add ci --oauth-token sk-ant-oat01-...
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
OAuth-token profiles store the token in `profiles.json` and feed it to the SDK via `CLAUDE_CODE_OAUTH_TOKEN` — no Keychain entry, no browser handshake. To prevent the SDK's 401-recovery from silently falling back to the host's `~/.claude` credentials, OAuth-token profiles also pin `CLAUDE_CONFIG_DIR` to an isolated per-profile directory under `~/.config/meridian/profiles/<name>/`. That directory holds only SDK state (sessions, settings) — never `.credentials.json`, since the token is delivered through the env.
|
|
255
|
+
|
|
242
256
|
### Switching profiles
|
|
243
257
|
|
|
244
258
|
```bash
|
|
@@ -256,14 +270,15 @@ You can also switch profiles from the web UI at `http://127.0.0.1:3456/profiles`
|
|
|
256
270
|
| Command | Description |
|
|
257
271
|
|---------|-------------|
|
|
258
272
|
| `meridian profile add <name>` | Add a profile and authenticate via browser |
|
|
273
|
+
| `meridian profile add <name> --oauth-token [TOKEN]` | Add a headless profile from a `claude setup-token` value (prompts when `TOKEN` is omitted) |
|
|
259
274
|
| `meridian profile list` | List profiles and auth status |
|
|
260
275
|
| `meridian profile switch <name>` | Switch the active profile (requires running proxy) |
|
|
261
|
-
| `meridian profile login <name>` | Re-authenticate an expired profile |
|
|
276
|
+
| `meridian profile login <name>` | Re-authenticate an expired profile (browser-login profiles only) |
|
|
262
277
|
| `meridian profile remove <name>` | Remove a profile and its credentials |
|
|
263
278
|
|
|
264
279
|
### How it works
|
|
265
280
|
|
|
266
|
-
Each profile stores its credentials in an isolated `CLAUDE_CONFIG_DIR` under `~/.config/meridian/profiles/<name>/`. When a request arrives, Meridian resolves the profile in priority order:
|
|
281
|
+
Each profile stores its credentials in an isolated `CLAUDE_CONFIG_DIR` under `~/.config/meridian/profiles/<name>/`. OAuth-token profiles use the same isolated directory layout — but the token itself lives in `~/.config/meridian/profiles.json` and is fed to the SDK via `CLAUDE_CODE_OAUTH_TOKEN`, so the per-profile dir holds only SDK state (sessions, settings) and never the credential. When a request arrives, Meridian resolves the profile in priority order:
|
|
267
282
|
|
|
268
283
|
1. `x-meridian-profile` request header (per-request override)
|
|
269
284
|
2. Active profile (set via `meridian profile switch` or the web UI)
|
|
@@ -276,11 +291,21 @@ Session state is scoped per profile — switching accounts won't cross-contamina
|
|
|
276
291
|
For advanced setups (CI, Docker), profiles can also be provided via environment variable:
|
|
277
292
|
|
|
278
293
|
```bash
|
|
279
|
-
export MERIDIAN_PROFILES='[
|
|
294
|
+
export MERIDIAN_PROFILES='[
|
|
295
|
+
{"id":"personal","claudeConfigDir":"/path/to/config1"},
|
|
296
|
+
{"id":"work","claudeConfigDir":"/path/to/config2"},
|
|
297
|
+
{"id":"ci","oauthToken":"sk-ant-oat01-..."}
|
|
298
|
+
]'
|
|
280
299
|
export MERIDIAN_DEFAULT_PROFILE=personal
|
|
281
300
|
meridian
|
|
282
301
|
```
|
|
283
302
|
|
|
303
|
+
Profile shapes:
|
|
304
|
+
|
|
305
|
+
- `claudeConfigDir` — points at a `~/.claude`-style directory; uses Claude Max OAuth from that dir
|
|
306
|
+
- `apiKey` (with optional `baseUrl`) — direct Anthropic API access; sets `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`
|
|
307
|
+
- `oauthToken` — long-lived token from `claude setup-token`; sets `CLAUDE_CODE_OAUTH_TOKEN`, no config dir needed
|
|
308
|
+
|
|
284
309
|
When `MERIDIAN_PROFILES` is set, it takes precedence over disk-configured profiles. When unset, Meridian auto-discovers profiles from `~/.config/meridian/profiles.json` on each request.
|
|
285
310
|
|
|
286
311
|
## Agent Setup
|
|
@@ -710,9 +735,10 @@ export default {
|
|
|
710
735
|
| `meridian` | Start the proxy server |
|
|
711
736
|
| `meridian setup` | Configure the OpenCode plugin in `~/.config/opencode/opencode.json` |
|
|
712
737
|
| `meridian profile add <name>` | Add a profile and authenticate via browser |
|
|
738
|
+
| `meridian profile add <name> --oauth-token [TOKEN]` | Add a headless profile from a `claude setup-token` value (prompts when `TOKEN` is omitted) |
|
|
713
739
|
| `meridian profile list` | List all profiles and their auth status |
|
|
714
740
|
| `meridian profile switch <name>` | Switch the active profile (requires running proxy) |
|
|
715
|
-
| `meridian profile login <name>` | Re-authenticate an expired profile |
|
|
741
|
+
| `meridian profile login <name>` | Re-authenticate an expired profile (browser-login profiles only) |
|
|
716
742
|
| `meridian profile remove <name>` | Remove a profile and its credentials |
|
|
717
743
|
| `meridian refresh-token` | Manually refresh the Claude OAuth token (exits 0/1) |
|
|
718
744
|
|
|
@@ -767,6 +793,19 @@ docker run \
|
|
|
767
793
|
|
|
768
794
|
Switch profiles at runtime via the `x-meridian-profile` header or `meridian profile switch` (see [Multi-Profile Support](#multi-profile-support)).
|
|
769
795
|
|
|
796
|
+
### OAuth-token profiles in Docker (no volume mount)
|
|
797
|
+
|
|
798
|
+
If you'd rather not mount a credential directory, generate a long-lived OAuth token on the host with `claude setup-token` and pass it as a profile. There's nothing to mount — the token alone is the credential:
|
|
799
|
+
|
|
800
|
+
```bash
|
|
801
|
+
docker run \
|
|
802
|
+
-e 'MERIDIAN_PROFILES=[{"id":"ci","oauthToken":"sk-ant-oat01-..."}]' \
|
|
803
|
+
-e MERIDIAN_DEFAULT_PROFILE=ci \
|
|
804
|
+
-p 3456:3456 meridian
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
This is the recommended path for CI runners, ephemeral containers, and cross-host deployments where browser-based login isn't reachable. Treat the token like any other secret — inject it via your platform's secret store rather than committing it to your image or compose file.
|
|
808
|
+
|
|
770
809
|
## Testing
|
|
771
810
|
|
|
772
811
|
```bash
|
|
@@ -229,8 +229,71 @@ async function doRefresh(store) {
|
|
|
229
229
|
claudeLog("token_refresh.success", { expiresAt });
|
|
230
230
|
return true;
|
|
231
231
|
}
|
|
232
|
+
async function ensureFreshToken(store, bufferMs = 5 * 60 * 1000) {
|
|
233
|
+
const s = store ?? createPlatformCredentialStore();
|
|
234
|
+
const credentials = await s.read();
|
|
235
|
+
const expiresAt = credentials?.claudeAiOauth?.expiresAt;
|
|
236
|
+
if (!expiresAt)
|
|
237
|
+
return false;
|
|
238
|
+
if (expiresAt - Date.now() > bufferMs)
|
|
239
|
+
return true;
|
|
240
|
+
return refreshOAuthToken(s);
|
|
241
|
+
}
|
|
242
|
+
var scheduledRefreshTimer = null;
|
|
243
|
+
var scheduledRefreshActive = false;
|
|
244
|
+
var scheduledRefreshGeneration = 0;
|
|
245
|
+
function startBackgroundRefresh(store, bufferMs = 5 * 60 * 1000, failureRetryMs = 5 * 60 * 1000) {
|
|
246
|
+
if (scheduledRefreshActive)
|
|
247
|
+
return;
|
|
248
|
+
scheduledRefreshActive = true;
|
|
249
|
+
const gen = ++scheduledRefreshGeneration;
|
|
250
|
+
scheduleNext(store ?? createPlatformCredentialStore(), bufferMs, failureRetryMs, gen);
|
|
251
|
+
}
|
|
252
|
+
function stopBackgroundRefresh() {
|
|
253
|
+
scheduledRefreshActive = false;
|
|
254
|
+
scheduledRefreshGeneration++;
|
|
255
|
+
if (scheduledRefreshTimer)
|
|
256
|
+
clearTimeout(scheduledRefreshTimer);
|
|
257
|
+
scheduledRefreshTimer = null;
|
|
258
|
+
}
|
|
259
|
+
async function scheduleNext(store, bufferMs, failureRetryMs, gen) {
|
|
260
|
+
if (!scheduledRefreshActive || gen !== scheduledRefreshGeneration)
|
|
261
|
+
return;
|
|
262
|
+
const credentials = await store.read().catch(() => null);
|
|
263
|
+
if (!scheduledRefreshActive || gen !== scheduledRefreshGeneration)
|
|
264
|
+
return;
|
|
265
|
+
const expiresAt = credentials?.claudeAiOauth?.expiresAt;
|
|
266
|
+
if (!expiresAt) {
|
|
267
|
+
armTimer(failureRetryMs, store, bufferMs, failureRetryMs, gen);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const dueIn = expiresAt - Date.now() - bufferMs;
|
|
271
|
+
if (dueIn <= 0) {
|
|
272
|
+
const ok = await refreshOAuthToken(store);
|
|
273
|
+
if (!scheduledRefreshActive || gen !== scheduledRefreshGeneration)
|
|
274
|
+
return;
|
|
275
|
+
claudeLog("token_refresh.scheduled", { ok, immediate: true });
|
|
276
|
+
console.error(`[token_refresh] scheduled refresh (immediate) ok=${ok}`);
|
|
277
|
+
armTimer(ok ? 0 : failureRetryMs, store, bufferMs, failureRetryMs, gen);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
armTimer(dueIn, store, bufferMs, failureRetryMs, gen);
|
|
281
|
+
}
|
|
282
|
+
function armTimer(delayMs, store, bufferMs, failureRetryMs, gen) {
|
|
283
|
+
scheduledRefreshTimer = setTimeout(async () => {
|
|
284
|
+
if (!scheduledRefreshActive || gen !== scheduledRefreshGeneration)
|
|
285
|
+
return;
|
|
286
|
+
scheduleNext(store, bufferMs, failureRetryMs, gen);
|
|
287
|
+
}, delayMs);
|
|
288
|
+
if (scheduledRefreshTimer && scheduledRefreshTimer.unref) {
|
|
289
|
+
scheduledRefreshTimer.unref();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function isBackgroundRefreshActive() {
|
|
293
|
+
return scheduledRefreshActive;
|
|
294
|
+
}
|
|
232
295
|
function resetInflightRefresh() {
|
|
233
296
|
inflightRefresh = null;
|
|
234
297
|
}
|
|
235
298
|
|
|
236
|
-
export { withClaudeLogContext, claudeLog, configDirToKeychainService, configDirToCredentialsFile, serializeCredentials, createPlatformCredentialStore, credentialsFilePathForProfile, refreshOAuthToken, resetInflightRefresh };
|
|
299
|
+
export { withClaudeLogContext, claudeLog, configDirToKeychainService, configDirToCredentialsFile, serializeCredentials, createPlatformCredentialStore, credentialsFilePathForProfile, refreshOAuthToken, ensureFreshToken, startBackgroundRefresh, stopBackgroundRefresh, isBackgroundRefreshActive, resetInflightRefresh };
|
|
@@ -86,6 +86,14 @@ function resolveProfile(profiles, defaultProfile, requestedId) {
|
|
|
86
86
|
return buildResolvedProfile(profile);
|
|
87
87
|
}
|
|
88
88
|
function buildResolvedProfile(profile) {
|
|
89
|
+
if (profile.oauthToken || profile.type === "oauth-token") {
|
|
90
|
+
const env2 = {};
|
|
91
|
+
if (profile.oauthToken) {
|
|
92
|
+
env2.CLAUDE_CODE_OAUTH_TOKEN = profile.oauthToken;
|
|
93
|
+
env2.CLAUDE_CONFIG_DIR = join(homedir(), ".config", "meridian", "profiles", profile.id);
|
|
94
|
+
}
|
|
95
|
+
return { id: profile.id, type: "oauth-token", env: env2 };
|
|
96
|
+
}
|
|
89
97
|
const type = profile.type ?? "claude-max";
|
|
90
98
|
if (type === "api") {
|
|
91
99
|
const env2 = {};
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveProfile,
|
|
6
6
|
restoreActiveProfile,
|
|
7
7
|
setActiveProfile
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-cx463q74.js";
|
|
9
9
|
import {
|
|
10
10
|
isTrackedPlugin,
|
|
11
11
|
recordError,
|
|
@@ -26,9 +26,12 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
claudeLog,
|
|
28
28
|
createPlatformCredentialStore,
|
|
29
|
+
ensureFreshToken,
|
|
29
30
|
refreshOAuthToken,
|
|
31
|
+
startBackgroundRefresh,
|
|
32
|
+
stopBackgroundRefresh,
|
|
30
33
|
withClaudeLogContext
|
|
31
|
-
} from "./cli-
|
|
34
|
+
} from "./cli-7k1fcprd.js";
|
|
32
35
|
import {
|
|
33
36
|
__commonJS,
|
|
34
37
|
__esm,
|
|
@@ -1178,12 +1181,12 @@ __export(exports_sdkFeatures, {
|
|
|
1178
1181
|
getFeaturesForAdapter: () => getFeaturesForAdapter,
|
|
1179
1182
|
getAllFeatureConfigs: () => getAllFeatureConfigs
|
|
1180
1183
|
});
|
|
1181
|
-
import { existsSync as
|
|
1184
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, renameSync as renameSync2 } from "node:fs";
|
|
1182
1185
|
import { join as join5 } from "node:path";
|
|
1183
1186
|
import { homedir as homedir3 } from "node:os";
|
|
1184
1187
|
function getConfigPath() {
|
|
1185
1188
|
const dir = join5(homedir3(), ".config", "meridian");
|
|
1186
|
-
if (!
|
|
1189
|
+
if (!existsSync6(dir))
|
|
1187
1190
|
mkdirSync2(dir, { recursive: true });
|
|
1188
1191
|
return join5(dir, "sdk-features.json");
|
|
1189
1192
|
}
|
|
@@ -1193,7 +1196,7 @@ function readConfig() {
|
|
|
1193
1196
|
return cachedConfig;
|
|
1194
1197
|
const path3 = getConfigPath();
|
|
1195
1198
|
try {
|
|
1196
|
-
if (
|
|
1199
|
+
if (existsSync6(path3)) {
|
|
1197
1200
|
cachedConfig = JSON.parse(readFileSync4(path3, "utf-8"));
|
|
1198
1201
|
} else {
|
|
1199
1202
|
cachedConfig = {};
|
|
@@ -3788,8 +3791,8 @@ async function readAccessToken(store) {
|
|
|
3788
3791
|
const creds = await store.read();
|
|
3789
3792
|
return creds?.claudeAiOauth?.accessToken ?? null;
|
|
3790
3793
|
}
|
|
3791
|
-
async function callAnthropic(token, signal) {
|
|
3792
|
-
const res = await
|
|
3794
|
+
async function callAnthropic(token, fetchImpl, signal) {
|
|
3795
|
+
const res = await fetchImpl(OAUTH_USAGE_URL, {
|
|
3793
3796
|
headers: {
|
|
3794
3797
|
Authorization: `Bearer ${token}`,
|
|
3795
3798
|
"anthropic-beta": OAUTH_BETA_HEADER,
|
|
@@ -3801,9 +3804,17 @@ async function callAnthropic(token, signal) {
|
|
|
3801
3804
|
return { __status: res.status };
|
|
3802
3805
|
return await res.json();
|
|
3803
3806
|
}
|
|
3807
|
+
var _testOverride = null;
|
|
3804
3808
|
async function fetchOAuthUsage(opts) {
|
|
3809
|
+
if (_testOverride && !opts?.fetchImpl && !opts?.store) {
|
|
3810
|
+
return _testOverride(opts);
|
|
3811
|
+
}
|
|
3812
|
+
return fetchOAuthUsageImpl(opts);
|
|
3813
|
+
}
|
|
3814
|
+
async function fetchOAuthUsageImpl(opts) {
|
|
3805
3815
|
const ttl = opts?.ttlMs ?? CACHE_TTL_MS_DEFAULT;
|
|
3806
3816
|
const cacheKey2 = opts?.profileId ?? DEFAULT_KEY;
|
|
3817
|
+
const fetchImpl = opts?.fetchImpl ?? globalThis.fetch;
|
|
3807
3818
|
if (!opts?.force) {
|
|
3808
3819
|
const cached = cacheByProfile.get(cacheKey2);
|
|
3809
3820
|
if (cached && Date.now() - cached.fetchedAt < ttl)
|
|
@@ -3818,7 +3829,7 @@ async function fetchOAuthUsage(opts) {
|
|
|
3818
3829
|
const token = await readAccessToken(store);
|
|
3819
3830
|
if (!token)
|
|
3820
3831
|
return null;
|
|
3821
|
-
let result = await callAnthropic(token);
|
|
3832
|
+
let result = await callAnthropic(token, fetchImpl);
|
|
3822
3833
|
if ("__status" in result && result.__status === 401) {
|
|
3823
3834
|
claudeLog("oauth_usage.token_refresh_attempt", { profile: cacheKey2 });
|
|
3824
3835
|
const refreshed = await refreshOAuthToken(store);
|
|
@@ -3829,7 +3840,7 @@ async function fetchOAuthUsage(opts) {
|
|
|
3829
3840
|
const newToken = await readAccessToken(store);
|
|
3830
3841
|
if (!newToken)
|
|
3831
3842
|
return null;
|
|
3832
|
-
result = await callAnthropic(newToken);
|
|
3843
|
+
result = await callAnthropic(newToken, fetchImpl);
|
|
3833
3844
|
}
|
|
3834
3845
|
if ("__status" in result) {
|
|
3835
3846
|
claudeLog("oauth_usage.upstream_error", { profile: cacheKey2, status: result.__status });
|
|
@@ -3849,6 +3860,17 @@ async function fetchOAuthUsage(opts) {
|
|
|
3849
3860
|
return promise;
|
|
3850
3861
|
}
|
|
3851
3862
|
|
|
3863
|
+
// src/proxy/cwd.ts
|
|
3864
|
+
import { existsSync } from "node:fs";
|
|
3865
|
+
function resolveSdkWorkingDirectory(opts) {
|
|
3866
|
+
const exists = opts.exists ?? existsSync;
|
|
3867
|
+
const claimed = opts.envOverride || opts.adapterCwd || opts.fallback;
|
|
3868
|
+
if (exists(claimed)) {
|
|
3869
|
+
return { workingDirectory: claimed, claimedWorkingDirectory: claimed, fellBack: false };
|
|
3870
|
+
}
|
|
3871
|
+
return { workingDirectory: opts.fallback, claimedWorkingDirectory: claimed, fellBack: true };
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3852
3874
|
// src/proxy/types.ts
|
|
3853
3875
|
var DEFAULT_PROXY_CONFIG = {
|
|
3854
3876
|
port: 3456,
|
|
@@ -8447,7 +8469,7 @@ class MemoryDiagnosticLogStore {
|
|
|
8447
8469
|
}
|
|
8448
8470
|
var diagnosticLog = new MemoryDiagnosticLogStore;
|
|
8449
8471
|
// src/telemetry/routes.ts
|
|
8450
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
8472
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
8451
8473
|
import { resolve, dirname } from "node:path";
|
|
8452
8474
|
import { fileURLToPath } from "node:url";
|
|
8453
8475
|
|
|
@@ -8801,7 +8823,7 @@ timer = setInterval(refresh, 5000);
|
|
|
8801
8823
|
|
|
8802
8824
|
// src/telemetry/routes.ts
|
|
8803
8825
|
var _iconPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "icon.svg");
|
|
8804
|
-
var _iconSvg =
|
|
8826
|
+
var _iconSvg = existsSync2(_iconPath) ? readFileSync(_iconPath, "utf-8") : null;
|
|
8805
8827
|
function createTelemetryRoutes() {
|
|
8806
8828
|
const routes = new Hono2;
|
|
8807
8829
|
routes.get("/", (c) => {
|
|
@@ -9139,7 +9161,13 @@ function classifyError(errMsg) {
|
|
|
9139
9161
|
}
|
|
9140
9162
|
function isExpiredTokenError(errMsg) {
|
|
9141
9163
|
const lower = errMsg.toLowerCase();
|
|
9142
|
-
|
|
9164
|
+
if (lower.includes("oauth token has expired") || lower.includes("not logged in"))
|
|
9165
|
+
return true;
|
|
9166
|
+
if (lower.includes("invalid_token") || lower.includes("token_expired"))
|
|
9167
|
+
return true;
|
|
9168
|
+
if (lower.includes("401") && (lower.includes("authentication") || lower.includes("unauthorized") || lower.includes("invalid")))
|
|
9169
|
+
return true;
|
|
9170
|
+
return false;
|
|
9143
9171
|
}
|
|
9144
9172
|
function isStaleSessionError(error) {
|
|
9145
9173
|
if (!(error instanceof Error))
|
|
@@ -9234,7 +9262,7 @@ function formatSdkTermination(t, ctx) {
|
|
|
9234
9262
|
|
|
9235
9263
|
// src/proxy/models.ts
|
|
9236
9264
|
import { exec as execCallback } from "child_process";
|
|
9237
|
-
import { existsSync as
|
|
9265
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
9238
9266
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9239
9267
|
import { join as join2, dirname as dirname2 } from "path";
|
|
9240
9268
|
import { promisify } from "util";
|
|
@@ -9243,11 +9271,11 @@ var STUB_SIZE_THRESHOLD = 4096;
|
|
|
9243
9271
|
var CANONICAL_OPUS_MODEL = "claude-opus-4-7";
|
|
9244
9272
|
var CANONICAL_SONNET_MODEL = "claude-sonnet-4-6";
|
|
9245
9273
|
var CANONICAL_HAIKU_MODEL = "claude-haiku-4-5";
|
|
9246
|
-
function resolveSdkModelDefaults() {
|
|
9274
|
+
function resolveSdkModelDefaults(env2 = process.env) {
|
|
9247
9275
|
return {
|
|
9248
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL:
|
|
9249
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL:
|
|
9250
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL:
|
|
9276
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: env2.MERIDIAN_DEFAULT_OPUS_MODEL ?? CANONICAL_OPUS_MODEL,
|
|
9277
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: env2.MERIDIAN_DEFAULT_SONNET_MODEL ?? CANONICAL_SONNET_MODEL,
|
|
9278
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: env2.MERIDIAN_DEFAULT_HAIKU_MODEL ?? CANONICAL_HAIKU_MODEL
|
|
9251
9279
|
};
|
|
9252
9280
|
}
|
|
9253
9281
|
var AUTH_STATUS_CACHE_TTL_MS = 60000;
|
|
@@ -9377,10 +9405,10 @@ async function getClaudeAuthStatusAsync(profileId, envOverrides) {
|
|
|
9377
9405
|
cachedAuthStatusPromise = null;
|
|
9378
9406
|
}
|
|
9379
9407
|
}
|
|
9380
|
-
var
|
|
9408
|
+
var cachedClaudeInfo = null;
|
|
9381
9409
|
var cachedClaudePathPromise = null;
|
|
9382
9410
|
var DEFAULT_DEPS = {
|
|
9383
|
-
existsSync:
|
|
9411
|
+
existsSync: existsSync3,
|
|
9384
9412
|
statSync: (p) => statSync(p),
|
|
9385
9413
|
exec,
|
|
9386
9414
|
resolvePackage: (specifier) => fileURLToPath2(import.meta.resolve(specifier)),
|
|
@@ -9450,19 +9478,37 @@ function tryLegacySdkCliJs(deps) {
|
|
|
9450
9478
|
return null;
|
|
9451
9479
|
}
|
|
9452
9480
|
}
|
|
9453
|
-
async function
|
|
9454
|
-
|
|
9481
|
+
async function resolveClaudeExecutableWithSource(deps = DEFAULT_DEPS) {
|
|
9482
|
+
const env2 = tryEnvOverride(deps);
|
|
9483
|
+
if (env2)
|
|
9484
|
+
return { path: env2, source: "env" };
|
|
9485
|
+
const bundled = tryBundledBinary(deps);
|
|
9486
|
+
if (bundled)
|
|
9487
|
+
return { path: bundled, source: "bundled" };
|
|
9488
|
+
const platformPkg = tryPlatformPackage(deps);
|
|
9489
|
+
if (platformPkg)
|
|
9490
|
+
return { path: platformPkg, source: "platform-package" };
|
|
9491
|
+
const pathLookup = await tryPathLookup(deps);
|
|
9492
|
+
if (pathLookup)
|
|
9493
|
+
return { path: pathLookup, source: "path-lookup" };
|
|
9494
|
+
const legacy = tryLegacySdkCliJs(deps);
|
|
9495
|
+
if (legacy)
|
|
9496
|
+
return { path: legacy, source: "legacy-cli-js" };
|
|
9497
|
+
return null;
|
|
9498
|
+
}
|
|
9499
|
+
function getResolvedClaudeExecutableInfo() {
|
|
9500
|
+
return cachedClaudeInfo;
|
|
9455
9501
|
}
|
|
9456
9502
|
async function resolveClaudeExecutableAsync() {
|
|
9457
|
-
if (
|
|
9458
|
-
return
|
|
9503
|
+
if (cachedClaudeInfo)
|
|
9504
|
+
return cachedClaudeInfo.path;
|
|
9459
9505
|
if (cachedClaudePathPromise)
|
|
9460
9506
|
return cachedClaudePathPromise;
|
|
9461
9507
|
cachedClaudePathPromise = (async () => {
|
|
9462
|
-
const resolved = await
|
|
9508
|
+
const resolved = await resolveClaudeExecutableWithSource();
|
|
9463
9509
|
if (resolved) {
|
|
9464
|
-
|
|
9465
|
-
return resolved;
|
|
9510
|
+
cachedClaudeInfo = resolved;
|
|
9511
|
+
return resolved.path;
|
|
9466
9512
|
}
|
|
9467
9513
|
throw new Error("Could not find Claude Code executable. Install via: npm install -g @anthropic-ai/claude-code, " + "or set MERIDIAN_CLAUDE_PATH=/path/to/claude to point at an existing binary.");
|
|
9468
9514
|
})();
|
|
@@ -16686,6 +16732,8 @@ function createOpencodeMcpServer() {
|
|
|
16686
16732
|
function stripConfigDir(env2) {
|
|
16687
16733
|
if (!("CLAUDE_CONFIG_DIR" in env2))
|
|
16688
16734
|
return env2;
|
|
16735
|
+
if (env2.CLAUDE_CODE_OAUTH_TOKEN)
|
|
16736
|
+
return env2;
|
|
16689
16737
|
const out = { ...env2 };
|
|
16690
16738
|
delete out.CLAUDE_CONFIG_DIR;
|
|
16691
16739
|
return out;
|
|
@@ -16830,8 +16878,9 @@ function getAdapterTransforms(adapterName) {
|
|
|
16830
16878
|
}
|
|
16831
16879
|
|
|
16832
16880
|
// src/proxy/plugins/loader.ts
|
|
16833
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as
|
|
16881
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
|
|
16834
16882
|
import { join as join3, isAbsolute as isAbsolute2, extname } from "path";
|
|
16883
|
+
import { pathToFileURL } from "url";
|
|
16835
16884
|
|
|
16836
16885
|
// src/proxy/plugins/validation.ts
|
|
16837
16886
|
var KNOWN_ADAPTERS = ["opencode", "crush", "droid", "pi", "forgecode", "passthrough"];
|
|
@@ -16871,7 +16920,7 @@ function validateTransform(exported) {
|
|
|
16871
16920
|
// src/proxy/plugins/loader.ts
|
|
16872
16921
|
var loadCounter = 0;
|
|
16873
16922
|
function parsePluginConfig(configPath) {
|
|
16874
|
-
if (!
|
|
16923
|
+
if (!existsSync4(configPath))
|
|
16875
16924
|
return [];
|
|
16876
16925
|
try {
|
|
16877
16926
|
const raw2 = readFileSync2(configPath, "utf-8");
|
|
@@ -16884,7 +16933,7 @@ function parsePluginConfig(configPath) {
|
|
|
16884
16933
|
async function loadPlugins(pluginDir, configPath) {
|
|
16885
16934
|
resetAllPluginStats();
|
|
16886
16935
|
const config = configPath ? parsePluginConfig(configPath) : [];
|
|
16887
|
-
const pluginDirExists =
|
|
16936
|
+
const pluginDirExists = existsSync4(pluginDir);
|
|
16888
16937
|
let filenames = [];
|
|
16889
16938
|
if (pluginDirExists) {
|
|
16890
16939
|
try {
|
|
@@ -16928,7 +16977,8 @@ async function loadPlugins(pluginDir, configPath) {
|
|
|
16928
16977
|
}
|
|
16929
16978
|
try {
|
|
16930
16979
|
const cacheBuster = `?t=${Date.now()}-${++loadCounter}`;
|
|
16931
|
-
const
|
|
16980
|
+
const specifier = process.platform === "win32" ? pathToFileURL(filePath).href + cacheBuster : filePath + cacheBuster;
|
|
16981
|
+
const mod = await import(specifier);
|
|
16932
16982
|
const exported = mod.default ?? mod;
|
|
16933
16983
|
const transforms = Array.isArray(exported) ? exported : [exported];
|
|
16934
16984
|
for (const item of transforms) {
|
|
@@ -17286,7 +17336,7 @@ function verifyLineage(cached, messages, cacheKey2, cache) {
|
|
|
17286
17336
|
// src/proxy/sessionStore.ts
|
|
17287
17337
|
import {
|
|
17288
17338
|
closeSync,
|
|
17289
|
-
existsSync as
|
|
17339
|
+
existsSync as existsSync5,
|
|
17290
17340
|
mkdirSync,
|
|
17291
17341
|
openSync,
|
|
17292
17342
|
readFileSync as readFileSync3,
|
|
@@ -17344,7 +17394,7 @@ var sessionDirOverride = null;
|
|
|
17344
17394
|
var skipLocking = false;
|
|
17345
17395
|
function getStorePath() {
|
|
17346
17396
|
const dir = sessionDirOverride || process.env.MERIDIAN_SESSION_DIR || process.env.CLAUDE_PROXY_SESSION_DIR || getDefaultCacheDir();
|
|
17347
|
-
if (!
|
|
17397
|
+
if (!existsSync5(dir)) {
|
|
17348
17398
|
mkdirSync(dir, { recursive: true });
|
|
17349
17399
|
}
|
|
17350
17400
|
return join4(dir, "sessions.json");
|
|
@@ -17352,9 +17402,9 @@ function getStorePath() {
|
|
|
17352
17402
|
function getDefaultCacheDir() {
|
|
17353
17403
|
const newDir = join4(homedir2(), ".cache", "meridian");
|
|
17354
17404
|
const oldDir = join4(homedir2(), ".cache", "opencode-claude-max-proxy");
|
|
17355
|
-
if (
|
|
17405
|
+
if (existsSync5(newDir))
|
|
17356
17406
|
return newDir;
|
|
17357
|
-
if (
|
|
17407
|
+
if (existsSync5(oldDir)) {
|
|
17358
17408
|
try {
|
|
17359
17409
|
const { symlinkSync } = __require("fs");
|
|
17360
17410
|
symlinkSync(oldDir, newDir);
|
|
@@ -17367,7 +17417,7 @@ function getDefaultCacheDir() {
|
|
|
17367
17417
|
}
|
|
17368
17418
|
function readStore() {
|
|
17369
17419
|
const path3 = getStorePath();
|
|
17370
|
-
if (!
|
|
17420
|
+
if (!existsSync5(path3))
|
|
17371
17421
|
return {};
|
|
17372
17422
|
try {
|
|
17373
17423
|
const data = readFileSync3(path3, "utf-8");
|
|
@@ -17884,6 +17934,8 @@ function createProxyServer(config = {}) {
|
|
|
17884
17934
|
app.use("/profiles", requireAuth);
|
|
17885
17935
|
app.use("/plugins/*", requireAuth);
|
|
17886
17936
|
app.use("/plugins", requireAuth);
|
|
17937
|
+
app.use("/settings/*", requireAuth);
|
|
17938
|
+
app.use("/settings", requireAuth);
|
|
17887
17939
|
app.use("/auth/*", requireAuth);
|
|
17888
17940
|
app.get("/", (c) => {
|
|
17889
17941
|
const accept = c.req.header("accept") || "";
|
|
@@ -17944,8 +17996,19 @@ function createProxyServer(config = {}) {
|
|
|
17944
17996
|
const agentMode = c.req.header("x-opencode-agent-mode") ?? null;
|
|
17945
17997
|
const requestSource = c.req.header("x-meridian-source")?.slice(0, 64) || undefined;
|
|
17946
17998
|
let model = mapModelToClaudeModel(body.model || "sonnet", authStatus?.subscriptionType, agentMode);
|
|
17947
|
-
const
|
|
17948
|
-
|
|
17999
|
+
const cwdResolution = resolveSdkWorkingDirectory({
|
|
18000
|
+
envOverride: process.env.MERIDIAN_WORKDIR ?? process.env.CLAUDE_PROXY_WORKDIR,
|
|
18001
|
+
adapterCwd: adapter.extractWorkingDirectory(body),
|
|
18002
|
+
fallback: process.cwd()
|
|
18003
|
+
});
|
|
18004
|
+
const workingDirectory = cwdResolution.workingDirectory;
|
|
18005
|
+
if (cwdResolution.fellBack) {
|
|
18006
|
+
claudeLog("cwd_fallback", {
|
|
18007
|
+
claimed: cwdResolution.claimedWorkingDirectory,
|
|
18008
|
+
usedInstead: workingDirectory
|
|
18009
|
+
});
|
|
18010
|
+
}
|
|
18011
|
+
const clientWorkingDirectory = adapter.extractClientWorkingDirectory?.(body) || cwdResolution.claimedWorkingDirectory;
|
|
17949
18012
|
const {
|
|
17950
18013
|
ANTHROPIC_API_KEY: _dropApiKey,
|
|
17951
18014
|
ANTHROPIC_BASE_URL: _dropBaseUrl,
|
|
@@ -18224,6 +18287,7 @@ function createProxyServer(config = {}) {
|
|
|
18224
18287
|
const RATE_LIMIT_BASE_DELAY_MS = 1000;
|
|
18225
18288
|
const response = async function* () {
|
|
18226
18289
|
let rateLimitRetries = 0;
|
|
18290
|
+
await ensureFreshToken().catch(() => {});
|
|
18227
18291
|
let tokenRefreshed = false;
|
|
18228
18292
|
while (true) {
|
|
18229
18293
|
let didYieldContent = false;
|
|
@@ -18625,6 +18689,7 @@ Subprocess stderr: ${stderrOutput}`;
|
|
|
18625
18689
|
const RATE_LIMIT_BASE_DELAY_MS = 1000;
|
|
18626
18690
|
const response = async function* () {
|
|
18627
18691
|
let rateLimitRetries = 0;
|
|
18692
|
+
await ensureFreshToken().catch(() => {});
|
|
18628
18693
|
let tokenRefreshed = false;
|
|
18629
18694
|
while (true) {
|
|
18630
18695
|
let didYieldClientEvent = false;
|
|
@@ -19442,6 +19507,7 @@ data: ${JSON.stringify({
|
|
|
19442
19507
|
auth: { loggedIn: false }
|
|
19443
19508
|
}, 503);
|
|
19444
19509
|
}
|
|
19510
|
+
const claudeExecutableInfo = getResolvedClaudeExecutableInfo();
|
|
19445
19511
|
return c.json({
|
|
19446
19512
|
status: "healthy",
|
|
19447
19513
|
version: serverVersion,
|
|
@@ -19451,6 +19517,7 @@ data: ${JSON.stringify({
|
|
|
19451
19517
|
subscriptionType: auth.subscriptionType
|
|
19452
19518
|
},
|
|
19453
19519
|
mode: envBool("PASSTHROUGH") ? "passthrough" : "internal",
|
|
19520
|
+
...claudeExecutableInfo ? { claudeExecutable: claudeExecutableInfo } : {},
|
|
19454
19521
|
plugin: { opencode: checkPluginConfigured() ? "configured" : "not-configured" }
|
|
19455
19522
|
});
|
|
19456
19523
|
} catch {
|
|
@@ -19563,9 +19630,16 @@ data: ${JSON.stringify({
|
|
|
19563
19630
|
if (!anthropicBody) {
|
|
19564
19631
|
return c.json({ type: "error", error: { type: "invalid_request_error", message: "messages: Field required" } }, 400);
|
|
19565
19632
|
}
|
|
19633
|
+
const internalHeaders = { "Content-Type": "application/json" };
|
|
19634
|
+
const xApiKey = c.req.header("x-api-key");
|
|
19635
|
+
if (xApiKey)
|
|
19636
|
+
internalHeaders["x-api-key"] = xApiKey;
|
|
19637
|
+
const authz = c.req.header("authorization");
|
|
19638
|
+
if (authz)
|
|
19639
|
+
internalHeaders["authorization"] = authz;
|
|
19566
19640
|
const internalReq = new Request("http://internal/v1/messages", {
|
|
19567
19641
|
method: "POST",
|
|
19568
|
-
headers:
|
|
19642
|
+
headers: internalHeaders,
|
|
19569
19643
|
body: JSON.stringify(anthropicBody)
|
|
19570
19644
|
});
|
|
19571
19645
|
const internalRes = await app.fetch(internalReq);
|
|
@@ -19844,6 +19918,10 @@ async function startProxyServer(config = {}) {
|
|
|
19844
19918
|
console.log(`Telemetry dashboard: http://${finalConfig.host}:${info.port}/telemetry`);
|
|
19845
19919
|
const pins = resolveSdkModelDefaults();
|
|
19846
19920
|
console.log(`Model pins: opus=${pins.ANTHROPIC_DEFAULT_OPUS_MODEL} sonnet=${pins.ANTHROPIC_DEFAULT_SONNET_MODEL} haiku=${pins.ANTHROPIC_DEFAULT_HAIKU_MODEL}`);
|
|
19921
|
+
const claudeInfo = getResolvedClaudeExecutableInfo();
|
|
19922
|
+
if (claudeInfo) {
|
|
19923
|
+
console.log(`Claude executable: ${claudeInfo.path} (resolved via ${claudeInfo.source})`);
|
|
19924
|
+
}
|
|
19847
19925
|
console.log(`
|
|
19848
19926
|
Point any Anthropic-compatible tool at this endpoint:`);
|
|
19849
19927
|
console.log(` ANTHROPIC_API_KEY=x ANTHROPIC_BASE_URL=http://${finalConfig.host}:${info.port}`);
|
|
@@ -19865,6 +19943,7 @@ Or use a different port:`);
|
|
|
19865
19943
|
console.error(` MERIDIAN_PORT=4567 meridian`);
|
|
19866
19944
|
}
|
|
19867
19945
|
});
|
|
19946
|
+
startBackgroundRefresh();
|
|
19868
19947
|
let authKeepaliveInterval;
|
|
19869
19948
|
const effectiveProfiles = getEffectiveProfiles(finalConfig.profiles);
|
|
19870
19949
|
if (effectiveProfiles.length > 0) {
|
|
@@ -19888,6 +19967,7 @@ Or use a different port:`);
|
|
|
19888
19967
|
async close() {
|
|
19889
19968
|
if (authKeepaliveInterval)
|
|
19890
19969
|
clearInterval(authKeepaliveInterval);
|
|
19970
|
+
stopBackgroundRefresh();
|
|
19891
19971
|
await new Promise((resolve3, reject) => {
|
|
19892
19972
|
server.close((err) => err ? reject(err) : resolve3());
|
|
19893
19973
|
});
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startProxyServer
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-kvwnarfk.js";
|
|
5
|
+
import"./cli-cx463q74.js";
|
|
6
6
|
import"./cli-sry5aqdj.js";
|
|
7
7
|
import"./cli-4rqtm83g.js";
|
|
8
8
|
import"./cli-340h1chz.js";
|
|
9
9
|
import"./cli-rtab0qa6.js";
|
|
10
|
-
import"./cli-
|
|
10
|
+
import"./cli-7k1fcprd.js";
|
|
11
11
|
import {
|
|
12
12
|
__require
|
|
13
13
|
} from "./cli-p9swy5t3.js";
|
|
@@ -50,12 +50,18 @@ See https://github.com/rynfar/meridian for full documentation.`);
|
|
|
50
50
|
process.exit(0);
|
|
51
51
|
}
|
|
52
52
|
if (args[0] === "profile") {
|
|
53
|
-
const { profileAdd, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-
|
|
53
|
+
const { profileAdd, profileAddOauthToken, profileList, profileRemove, profileSwitch, profileLogin, profileHelp } = await import("./profileCli-wpb4qbjn.js");
|
|
54
54
|
const subcommand = args[1];
|
|
55
55
|
const profileId = args[2];
|
|
56
|
-
if (subcommand === "add" && profileId)
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (subcommand === "add" && profileId) {
|
|
57
|
+
const oauthFlagIdx = args.indexOf("--oauth-token", 3);
|
|
58
|
+
if (oauthFlagIdx >= 0) {
|
|
59
|
+
const tokenArg = args[oauthFlagIdx + 1];
|
|
60
|
+
await profileAddOauthToken(profileId, tokenArg);
|
|
61
|
+
} else {
|
|
62
|
+
profileAdd(profileId);
|
|
63
|
+
}
|
|
64
|
+
} else if (subcommand === "list" || subcommand === "ls")
|
|
59
65
|
profileList();
|
|
60
66
|
else if (subcommand === "remove" && profileId)
|
|
61
67
|
profileRemove(profileId);
|
|
@@ -89,7 +95,7 @@ Restart OpenCode for the plugin to take effect.`);
|
|
|
89
95
|
process.exit(0);
|
|
90
96
|
}
|
|
91
97
|
if (args[0] === "refresh-token") {
|
|
92
|
-
const { refreshOAuthToken } = await import("./tokenRefresh-
|
|
98
|
+
const { refreshOAuthToken } = await import("./tokenRefresh-swetnf89.js");
|
|
93
99
|
const success = await refreshOAuthToken();
|
|
94
100
|
if (success) {
|
|
95
101
|
console.log("Token refreshed successfully");
|
|
@@ -147,7 +153,7 @@ async function runCli(start = startProxyServer, runExec = exec) {
|
|
|
147
153
|
console.error("\x1B[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1B[0m");
|
|
148
154
|
}
|
|
149
155
|
if (!profiles) {
|
|
150
|
-
const { enableDiskProfileDiscovery } = await import("./profiles-
|
|
156
|
+
const { enableDiskProfileDiscovery } = await import("./profiles-rdd84b45.js");
|
|
151
157
|
enableDiskProfileDiscovery();
|
|
152
158
|
}
|
|
153
159
|
const proxy = await start({ port, host, idleTimeoutSeconds, profiles, defaultProfile, version });
|
|
@@ -116,6 +116,33 @@ function profileAdd(id) {
|
|
|
116
116
|
saveProfileConfig(profiles);
|
|
117
117
|
printEnvHint(profiles);
|
|
118
118
|
}
|
|
119
|
+
async function profileAddOauthToken(id, tokenArg) {
|
|
120
|
+
if (!id || /[^a-zA-Z0-9_-]/.test(id)) {
|
|
121
|
+
console.error("\x1B[31m✗ Invalid profile ID.\x1B[0m Use only letters, numbers, hyphens, underscores.");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const profiles = loadProfileConfig();
|
|
125
|
+
if (profiles.find((p) => p.id === id)) {
|
|
126
|
+
console.error(`\x1B[31m✗ Profile "${id}" already exists.\x1B[0m`);
|
|
127
|
+
console.error(` Run: meridian profile list`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
let token = tokenArg?.trim() ?? "";
|
|
131
|
+
if (!token) {
|
|
132
|
+
console.log(`\x1B[36mAdding profile: ${id} (OAuth token)\x1B[0m`);
|
|
133
|
+
console.log(` Generate a token with: \x1B[1mclaude setup-token\x1B[0m`);
|
|
134
|
+
console.log();
|
|
135
|
+
token = promptToken(`Paste OAuth token for "${id}" (input hidden):`);
|
|
136
|
+
}
|
|
137
|
+
if (!token) {
|
|
138
|
+
console.error("\x1B[31m✗ Empty token. Aborted.\x1B[0m");
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
profiles.push({ id, type: "oauth-token", oauthToken: token });
|
|
142
|
+
saveProfileConfig(profiles);
|
|
143
|
+
console.log(`\x1B[32m✓ Profile "${id}" added (OAuth token).\x1B[0m`);
|
|
144
|
+
printEnvHint(profiles);
|
|
145
|
+
}
|
|
119
146
|
function profileList() {
|
|
120
147
|
const profiles = loadProfileConfig();
|
|
121
148
|
if (profiles.length === 0) {
|
|
@@ -126,6 +153,10 @@ function profileList() {
|
|
|
126
153
|
console.log(`Profiles:
|
|
127
154
|
`);
|
|
128
155
|
for (const p of profiles) {
|
|
156
|
+
if (p.oauthToken || p.type === "oauth-token") {
|
|
157
|
+
console.log(` ${p.id.padEnd(20)} \x1B[32m✓ OAuth token\x1B[0m`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
129
160
|
const auth = getAuthStatus(p.claudeConfigDir ?? "");
|
|
130
161
|
const status = auth.loggedIn ? `\x1B[32m✓ ${auth.email} (${auth.subscriptionType || "unknown"})\x1B[0m` : "\x1B[31m✗ not logged in\x1B[0m";
|
|
131
162
|
console.log(` ${p.id.padEnd(20)} ${status}`);
|
|
@@ -133,6 +164,18 @@ function profileList() {
|
|
|
133
164
|
console.log();
|
|
134
165
|
printEnvHint(profiles);
|
|
135
166
|
}
|
|
167
|
+
function dirsToRemoveOnProfileRemove(profile, profilesDir) {
|
|
168
|
+
const dirs = [];
|
|
169
|
+
if (profile.claudeConfigDir && profile.claudeConfigDir.startsWith(profilesDir)) {
|
|
170
|
+
dirs.push(profile.claudeConfigDir);
|
|
171
|
+
}
|
|
172
|
+
if (profile.oauthToken || profile.type === "oauth-token") {
|
|
173
|
+
const isolationDir = join(profilesDir, profile.id);
|
|
174
|
+
if (!dirs.includes(isolationDir))
|
|
175
|
+
dirs.push(isolationDir);
|
|
176
|
+
}
|
|
177
|
+
return dirs;
|
|
178
|
+
}
|
|
136
179
|
function profileRemove(id) {
|
|
137
180
|
const profiles = loadProfileConfig();
|
|
138
181
|
const idx = profiles.findIndex((p) => p.id === id);
|
|
@@ -140,11 +183,13 @@ function profileRemove(id) {
|
|
|
140
183
|
console.error(`\x1B[31m✗ Profile "${id}" not found.\x1B[0m`);
|
|
141
184
|
process.exit(1);
|
|
142
185
|
}
|
|
143
|
-
const
|
|
186
|
+
const removed = profiles[idx];
|
|
187
|
+
const dirsToRemove = dirsToRemoveOnProfileRemove(removed, PROFILES_DIR);
|
|
144
188
|
profiles.splice(idx, 1);
|
|
145
189
|
saveProfileConfig(profiles);
|
|
146
|
-
|
|
147
|
-
|
|
190
|
+
for (const dir of dirsToRemove) {
|
|
191
|
+
if (existsSync(dir))
|
|
192
|
+
rmSync(dir, { recursive: true, force: true });
|
|
148
193
|
}
|
|
149
194
|
console.log(`\x1B[32m✓ Profile "${id}" removed.\x1B[0m`);
|
|
150
195
|
if (profiles.length > 0) {
|
|
@@ -181,6 +226,11 @@ function profileLogin(id) {
|
|
|
181
226
|
console.error(`\x1B[31m✗ Profile "${id}" not found.\x1B[0m Run: meridian profile add ${id}`);
|
|
182
227
|
process.exit(1);
|
|
183
228
|
}
|
|
229
|
+
if (profile.oauthToken || profile.type === "oauth-token") {
|
|
230
|
+
console.error(`\x1B[31m✗ Profile "${id}" uses an OAuth token; \`claude auth login\` does not apply.\x1B[0m`);
|
|
231
|
+
console.error(` To replace the token: meridian profile remove ${id} && meridian profile add ${id} --oauth-token`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
184
234
|
console.log(`\x1B[36mRe-authenticating profile: ${id}\x1B[0m`);
|
|
185
235
|
console.log();
|
|
186
236
|
console.log("\x1B[33m⚠ Make sure you're signed into the correct Claude account in your browser.\x1B[0m");
|
|
@@ -209,6 +259,41 @@ function promptYesNo(question) {
|
|
|
209
259
|
const answer = (result.stdout?.toString().trim() ?? "").toLowerCase();
|
|
210
260
|
return answer !== "n" && answer !== "no";
|
|
211
261
|
}
|
|
262
|
+
function promptToken(question) {
|
|
263
|
+
process.stderr.write(`${question}
|
|
264
|
+
> `);
|
|
265
|
+
const script = [
|
|
266
|
+
`const stdin = process.stdin;`,
|
|
267
|
+
`if (!stdin.isTTY) {`,
|
|
268
|
+
` let buf = "";`,
|
|
269
|
+
` stdin.setEncoding("utf8");`,
|
|
270
|
+
` stdin.on("data", (c) => { buf += c; });`,
|
|
271
|
+
` stdin.on("end", () => { process.stdout.write(buf.split(/\\r?\\n/)[0] || ""); process.exit(0); });`,
|
|
272
|
+
`} else {`,
|
|
273
|
+
` stdin.setRawMode(true); stdin.resume(); stdin.setEncoding("utf8");`,
|
|
274
|
+
` let input = "";`,
|
|
275
|
+
` stdin.on("data", (key) => {`,
|
|
276
|
+
` if (key === "\\u0003") { process.stderr.write("\\n"); process.exit(1); }`,
|
|
277
|
+
` else if (key === "\\r" || key === "\\n") {`,
|
|
278
|
+
` stdin.setRawMode(false); process.stderr.write("\\n");`,
|
|
279
|
+
` process.stdout.write(input); process.exit(0);`,
|
|
280
|
+
` }`,
|
|
281
|
+
` else if (key === "\\u007f" || key === "\\b") {`,
|
|
282
|
+
` if (input.length > 0) input = input.slice(0, -1);`,
|
|
283
|
+
` }`,
|
|
284
|
+
` else { input += key; }`,
|
|
285
|
+
` });`,
|
|
286
|
+
`}`
|
|
287
|
+
].join(`
|
|
288
|
+
`);
|
|
289
|
+
const result = spawnSync("node", ["-e", script], { stdio: ["inherit", "pipe", "inherit"] });
|
|
290
|
+
if (result.status !== 0) {
|
|
291
|
+
process.stderr.write(`
|
|
292
|
+
`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
return (result.stdout?.toString() ?? "").trim();
|
|
296
|
+
}
|
|
212
297
|
function printEnvHint(_profiles) {
|
|
213
298
|
console.log(`\x1B[90mConfig: ${CONFIG_FILE}\x1B[0m`);
|
|
214
299
|
console.log("\x1B[90mProfiles are picked up automatically — no restart needed.\x1B[0m");
|
|
@@ -217,17 +302,22 @@ function profileHelp() {
|
|
|
217
302
|
console.log(`meridian profile — manage Claude account profiles
|
|
218
303
|
|
|
219
304
|
Commands:
|
|
220
|
-
meridian profile add <name>
|
|
221
|
-
meridian profile
|
|
222
|
-
|
|
223
|
-
meridian profile
|
|
224
|
-
meridian profile
|
|
305
|
+
meridian profile add <name> Add a profile via browser login
|
|
306
|
+
meridian profile add <name> --oauth-token [TOKEN] Add a profile from a \`claude setup-token\` value
|
|
307
|
+
(if TOKEN is omitted, you will be prompted; input is hidden)
|
|
308
|
+
meridian profile list List profiles and auth status
|
|
309
|
+
meridian profile remove <name> Remove a profile
|
|
310
|
+
meridian profile switch <name> Switch the active profile (requires running proxy)
|
|
311
|
+
meridian profile login <name> Re-authenticate an existing profile (claude-max only)
|
|
225
312
|
|
|
226
313
|
Examples:
|
|
227
|
-
meridian profile add personal
|
|
228
|
-
meridian profile add work
|
|
229
|
-
meridian profile
|
|
230
|
-
meridian profile
|
|
314
|
+
meridian profile add personal # Add personal account (browser login)
|
|
315
|
+
meridian profile add work # Add work account
|
|
316
|
+
meridian profile add ci --oauth-token # Add headless CI profile (prompted, no echo)
|
|
317
|
+
meridian profile add ci --oauth-token sk-ant-oat01-...
|
|
318
|
+
# Add headless CI profile (token from CLI argument)
|
|
319
|
+
meridian profile switch work # Switch to work account
|
|
320
|
+
meridian profile list # Show all profiles`);
|
|
231
321
|
}
|
|
232
322
|
export {
|
|
233
323
|
profileSwitch,
|
|
@@ -235,5 +325,7 @@ export {
|
|
|
235
325
|
profileLogin,
|
|
236
326
|
profileList,
|
|
237
327
|
profileHelp,
|
|
238
|
-
|
|
328
|
+
profileAddOauthToken,
|
|
329
|
+
profileAdd,
|
|
330
|
+
dirsToRemoveOnProfileRemove
|
|
239
331
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the SDK subprocess `cwd:` option, falling back to a known-valid
|
|
3
|
+
* path on the proxy host when the resolved working directory doesn't exist.
|
|
4
|
+
*
|
|
5
|
+
* Background — issue #381:
|
|
6
|
+
* When meridian runs on a remote machine (e.g. accessed over Tailscale)
|
|
7
|
+
* and the client (OpenCode/Crush/etc.) runs on a different machine, the
|
|
8
|
+
* adapter extracts the client's reported working directory and passes it
|
|
9
|
+
* to the SDK as `cwd:`. That path doesn't exist on the proxy host, so
|
|
10
|
+
* `child_process.spawn(claude, { cwd })` fails with ENOENT — which the
|
|
11
|
+
* SDK then reports as the misleading "Claude Code native binary not
|
|
12
|
+
* found at ..." error.
|
|
13
|
+
*
|
|
14
|
+
* Falling back to the proxy's own `process.cwd()` lets the SDK spawn
|
|
15
|
+
* succeed; `clientWorkingDirectory` is tracked separately (and emitted
|
|
16
|
+
* into the model's context via buildCwdNote) so the model still hears
|
|
17
|
+
* about the user's real working directory.
|
|
18
|
+
*/
|
|
19
|
+
export interface CwdResolution {
|
|
20
|
+
/** Path passed to the SDK as `cwd:`. Always exists on the proxy host. */
|
|
21
|
+
workingDirectory: string;
|
|
22
|
+
/**
|
|
23
|
+
* The originally-resolved path before existence validation. May not
|
|
24
|
+
* exist on the proxy host. Used as `clientWorkingDirectory` for
|
|
25
|
+
* fingerprint bucketing and the system-prompt cwdNote.
|
|
26
|
+
*/
|
|
27
|
+
claimedWorkingDirectory: string;
|
|
28
|
+
/** True if `workingDirectory` differs from `claimedWorkingDirectory`. */
|
|
29
|
+
fellBack: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface ResolveCwdOpts {
|
|
32
|
+
/** MERIDIAN_WORKDIR / CLAUDE_PROXY_WORKDIR (highest precedence). */
|
|
33
|
+
envOverride: string | undefined;
|
|
34
|
+
/** Adapter's extracted client working directory. */
|
|
35
|
+
adapterCwd: string | undefined;
|
|
36
|
+
/** Last-resort fallback. Must exist; typically `process.cwd()`. */
|
|
37
|
+
fallback: string;
|
|
38
|
+
/** Injection point for tests. Defaults to `node:fs`'s existsSync. */
|
|
39
|
+
exists?: (path: string) => boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function resolveSdkWorkingDirectory(opts: ResolveCwdOpts): CwdResolution;
|
|
42
|
+
//# sourceMappingURL=cwd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd.d.ts","sourceRoot":"","sources":["../../src/proxy/cwd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,uBAAuB,EAAE,MAAM,CAAA;IAC/B,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CACnC;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,aAAa,CAO9E"}
|
package/dist/proxy/errors.d.ts
CHANGED
|
@@ -15,10 +15,20 @@ export declare function classifyError(errMsg: string): ClassifiedError;
|
|
|
15
15
|
* Detect errors caused by an expired or missing OAuth access token.
|
|
16
16
|
* Triggers an inline token refresh + retry in server.ts.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* - "OAuth token has expired"
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* Patterns, in order of specificity:
|
|
19
|
+
* - "OAuth token has expired" / "Not logged in" — CLI-emitted (subprocess
|
|
20
|
+
* either got 401 with this wording from Anthropic or detected expiry
|
|
21
|
+
* locally before sending).
|
|
22
|
+
* - "invalid_token" / "token_expired" — RFC 6750 resource-server errors that
|
|
23
|
+
* can appear in the API response body.
|
|
24
|
+
* - "401" + ("authentication" | "unauthorized" | "invalid") — generic 401
|
|
25
|
+
* wrapping. Anthropic's API does not always echo the CLI-specific wording,
|
|
26
|
+
* so without this branch a stale access token returns a generic 401 to the
|
|
27
|
+
* proxy and refresh-and-retry never fires (caller sees the 401).
|
|
28
|
+
*
|
|
29
|
+
* False positives only cost one OAuth round-trip — the refresh is single-shot
|
|
30
|
+
* per request (gated by `tokenRefreshed` in server.ts) and surfaces the
|
|
31
|
+
* original error if it doesn't help.
|
|
22
32
|
*/
|
|
23
33
|
export declare function isExpiredTokenError(errMsg: string): boolean;
|
|
24
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CA+G7D;AAED
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CA+G7D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAA;IAC5D,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;wCAEoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAuBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAuCpE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GACA,MAAM,CAYR"}
|
package/dist/proxy/models.d.ts
CHANGED
|
@@ -25,8 +25,12 @@ export declare const CANONICAL_HAIKU_MODEL = "claude-haiku-4-5";
|
|
|
25
25
|
* Build the ANTHROPIC_DEFAULT_{TYPE}_MODEL env record to apply before the
|
|
26
26
|
* inherited process env, so user-set shell values still win but unset
|
|
27
27
|
* variables get Meridian's canonical pins.
|
|
28
|
+
*
|
|
29
|
+
* Accepts an optional `env` arg so unit tests can pass a synthetic env
|
|
30
|
+
* map instead of mutating process.env (which leaks between parallel
|
|
31
|
+
* test files).
|
|
28
32
|
*/
|
|
29
|
-
export declare function resolveSdkModelDefaults(): Record<string, string>;
|
|
33
|
+
export declare function resolveSdkModelDefaults(env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
30
34
|
export interface ClaudeAuthStatus {
|
|
31
35
|
loggedIn?: boolean;
|
|
32
36
|
subscriptionType?: string;
|
|
@@ -71,6 +75,18 @@ export declare function getAuthCacheInfo(profileId?: string): {
|
|
|
71
75
|
* @param envOverrides - Optional env vars for per-profile auth (e.g. CLAUDE_CONFIG_DIR).
|
|
72
76
|
*/
|
|
73
77
|
export declare function getClaudeAuthStatusAsync(profileId?: string, envOverrides?: Record<string, string>): Promise<ClaudeAuthStatus | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Tag identifying which resolver step produced the path. Surfaced at startup
|
|
80
|
+
* and in `/health` so users can self-diagnose "wrong claude got picked"
|
|
81
|
+
* without having to inspect their PATH manually (closes the diagnostic gap
|
|
82
|
+
* from #478, where a Bun-shimmed `claude` on PATH led to silent failures
|
|
83
|
+
* that looked indistinguishable from any other SDK error).
|
|
84
|
+
*/
|
|
85
|
+
export type ClaudeExecutableSource = "env" | "bundled" | "platform-package" | "path-lookup" | "legacy-cli-js";
|
|
86
|
+
export interface ClaudeExecutableInfo {
|
|
87
|
+
path: string;
|
|
88
|
+
source: ClaudeExecutableSource;
|
|
89
|
+
}
|
|
74
90
|
/**
|
|
75
91
|
* Resolve the Claude executable path asynchronously (non-blocking).
|
|
76
92
|
*
|
|
@@ -102,11 +118,29 @@ type ResolverDeps = {
|
|
|
102
118
|
isBun: boolean;
|
|
103
119
|
};
|
|
104
120
|
/**
|
|
105
|
-
* Pure resolver — runs each step and returns the
|
|
106
|
-
*
|
|
107
|
-
*
|
|
121
|
+
* Pure resolver, source-aware variant — runs each step and returns the
|
|
122
|
+
* first hit (path + source tag), or null when all steps miss.
|
|
123
|
+
*
|
|
124
|
+
* Order matters: `env` wins unconditionally (operator escape hatch), then
|
|
125
|
+
* `bundled` (the path the SDK expects), then `platform-package` (postinstall
|
|
126
|
+
* fallback), then `path-lookup` (system PATH — most likely to surface
|
|
127
|
+
* unintended shims, see #478), then `legacy-cli-js` (only matters on stale
|
|
128
|
+
* Bun installs of SDK < 0.2.98).
|
|
129
|
+
*/
|
|
130
|
+
export declare function resolveClaudeExecutableWithSource(deps?: ResolverDeps): Promise<ClaudeExecutableInfo | null>;
|
|
131
|
+
/**
|
|
132
|
+
* Pure resolver — returns the path string only. Kept for callers that
|
|
133
|
+
* don't need the source tag (existing behavior; preserves the existing
|
|
134
|
+
* test surface in claude-executable-resolver.test.ts).
|
|
108
135
|
*/
|
|
109
136
|
export declare function resolveClaudeExecutable(deps?: ResolverDeps): Promise<string | null>;
|
|
137
|
+
/**
|
|
138
|
+
* Returns the cached resolved-executable info — `null` if
|
|
139
|
+
* `resolveClaudeExecutableAsync` hasn't run yet. Used by `/health` and the
|
|
140
|
+
* startup log so the resolver only runs once and both surfaces see the
|
|
141
|
+
* same answer.
|
|
142
|
+
*/
|
|
143
|
+
export declare function getResolvedClaudeExecutableInfo(): ClaudeExecutableInfo | null;
|
|
110
144
|
export declare function resolveClaudeExecutableAsync(): Promise<string>;
|
|
111
145
|
/** Reset cached path — for testing only */
|
|
112
146
|
export declare function resetCachedClaudePath(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"AAAA;;GAEG;AAoBH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AAEjF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,oBAAoB,oBAAoB,CAAA;AACrD,eAAO,MAAM,sBAAsB,sBAAsB,CAAA;AACzD,eAAO,MAAM,qBAAqB,qBAAqB,CAAA;AAEvD
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/proxy/models.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,6 +37,19 @@ export interface OAuthUsageSnapshot {
|
|
|
37
37
|
extraUsage: OAuthExtraUsageInfo | null;
|
|
38
38
|
fetchedAt: number;
|
|
39
39
|
}
|
|
40
|
+
/** Minimal fetch shape used by callAnthropic. Avoids `typeof fetch`'s
|
|
41
|
+
* `preconnect` property, which makes test casts unwieldy. */
|
|
42
|
+
type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
43
|
+
export interface FetchOAuthUsageOpts {
|
|
44
|
+
ttlMs?: number;
|
|
45
|
+
force?: boolean;
|
|
46
|
+
store?: CredentialStore;
|
|
47
|
+
profileId?: string | null;
|
|
48
|
+
claudeConfigDir?: string;
|
|
49
|
+
fetchImpl?: FetchLike;
|
|
50
|
+
}
|
|
51
|
+
/** Test-only setter. Pass `null` to clear. */
|
|
52
|
+
export declare function __setFetchOAuthUsageOverride(fn: ((opts?: FetchOAuthUsageOpts) => Promise<OAuthUsageSnapshot | null>) | null): void;
|
|
40
53
|
/**
|
|
41
54
|
* Fetch latest OAuth usage for a specific profile (or the default OAuth
|
|
42
55
|
* account if none specified). Returns null if no OAuth token is available
|
|
@@ -54,14 +67,11 @@ export interface OAuthUsageSnapshot {
|
|
|
54
67
|
* @param claudeConfigDir When provided, reads credentials from this dir's
|
|
55
68
|
* keychain entry (macOS) or `.credentials.json`
|
|
56
69
|
* (Linux) instead of the platform default.
|
|
70
|
+
* @param fetchImpl Override the fetch implementation (for testing).
|
|
71
|
+
* Defaults to globalThis.fetch.
|
|
57
72
|
*/
|
|
58
|
-
export declare function fetchOAuthUsage(opts?:
|
|
59
|
-
ttlMs?: number;
|
|
60
|
-
force?: boolean;
|
|
61
|
-
store?: CredentialStore;
|
|
62
|
-
profileId?: string | null;
|
|
63
|
-
claudeConfigDir?: string;
|
|
64
|
-
}): Promise<OAuthUsageSnapshot | null>;
|
|
73
|
+
export declare function fetchOAuthUsage(opts?: FetchOAuthUsageOpts): Promise<OAuthUsageSnapshot | null>;
|
|
65
74
|
/** Test-only / shutdown helper — clears all cached snapshots and pending fetches. */
|
|
66
75
|
export declare function resetOAuthUsageCache(): void;
|
|
76
|
+
export {};
|
|
67
77
|
//# sourceMappingURL=oauthUsage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthUsage.d.ts","sourceRoot":"","sources":["../../src/proxy/oauthUsage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAoD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAgCvG,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB;
|
|
1
|
+
{"version":3,"file":"oauthUsage.d.ts","sourceRoot":"","sources":["../../src/proxy/oauthUsage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAoD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAgCvG,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;CAClB;AA6DD;8DAC8D;AAC9D,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAmBzE,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;AAcD,8CAA8C;AAC9C,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,GAC9E,IAAI,CAEN;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,eAAe,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAOpG;AAqDD,qFAAqF;AACrF,wBAAgB,oBAAoB,IAAI,IAAI,CAG3C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/proxy/plugins/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/proxy/plugins/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAgB,YAAY,EAAE,MAAM,SAAS,CAAA;AAStE,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CASnE;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CA+IzB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,SAAS,EAAE,CAIxE"}
|
package/dist/proxy/profiles.d.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Multi-profile support.
|
|
3
3
|
*
|
|
4
4
|
* Allows a single Meridian instance to route requests to different Claude
|
|
5
|
-
* accounts. Each profile is a named auth context
|
|
6
|
-
* Max subscriptions,
|
|
5
|
+
* accounts. Each profile is a named auth context — a CLAUDE_CONFIG_DIR for
|
|
6
|
+
* Max subscriptions, an Anthropic API key for direct API access, or a
|
|
7
|
+
* long-lived OAuth token minted by `claude setup-token`.
|
|
7
8
|
*
|
|
8
9
|
* Profile selection priority:
|
|
9
10
|
* 1. x-meridian-profile request header (per-request override)
|
|
@@ -18,11 +19,16 @@
|
|
|
18
19
|
* while avoiding synchronous disk I/O on every request.
|
|
19
20
|
*/
|
|
20
21
|
export declare function loadProfilesFromDisk(): ProfileConfig[];
|
|
21
|
-
export type ProfileType = "claude-max" | "api";
|
|
22
|
+
export type ProfileType = "claude-max" | "api" | "oauth-token";
|
|
22
23
|
export interface ProfileConfig {
|
|
23
24
|
/** Unique profile identifier (e.g. "personal", "work") */
|
|
24
25
|
id: string;
|
|
25
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Auth type. Inferred from the populated credential field when omitted:
|
|
28
|
+
* - `oauthToken` → "oauth-token" (CLAUDE_CODE_OAUTH_TOKEN)
|
|
29
|
+
* - `apiKey`/`baseUrl` → must be combined with explicit `type: "api"`
|
|
30
|
+
* - `claudeConfigDir` → "claude-max" (CLAUDE_CONFIG_DIR)
|
|
31
|
+
*/
|
|
26
32
|
type?: ProfileType;
|
|
27
33
|
/** Path to .claude config directory (claude-max profiles) */
|
|
28
34
|
claudeConfigDir?: string;
|
|
@@ -30,6 +36,8 @@ export interface ProfileConfig {
|
|
|
30
36
|
apiKey?: string;
|
|
31
37
|
/** Anthropic base URL override (api profiles) */
|
|
32
38
|
baseUrl?: string;
|
|
39
|
+
/** Long-lived OAuth token from `claude setup-token` (oauth-token profiles) */
|
|
40
|
+
oauthToken?: string;
|
|
33
41
|
}
|
|
34
42
|
export interface ResolvedProfile {
|
|
35
43
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/proxy/profiles.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../src/proxy/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAcH;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,aAAa,EAAE,CAkBtD;AAED,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,KAAK,GAAG,aAAa,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAA;IACV;;;;;OAKG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,WAAW,CAAA;IACjB,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC5B;AAOD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAEvD;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAY3E;AAUD;;kEAEkE;AAClE,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,aAAa,EAAE,CAOjG;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,cAAc,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,CAAC,EAAE,MAAM,GACnB,eAAe,CAkBjB;AAkCD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,EACrC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,CAU7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/proxy/query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAW,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAErF,OAAO,EAAE,0BAA0B,EAAwB,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/proxy/query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAW,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAErF,OAAO,EAAE,0BAA0B,EAAwB,MAAM,oBAAoB,CAAA;AAsBrF,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;IACnC,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,gBAAgB,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAA;IACrB,gCAAgC;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,0CAA0C;IAC1C,WAAW,EAAE,OAAO,CAAA;IACpB,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAA;IACf,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC9B,mEAAmE;IACnE,cAAc,CAAC,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAA;IAC9D,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC5C,yDAAyD;IACzD,gBAAgB,EAAE,OAAO,CAAA;IACzB,0DAA0D;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAA;IACf,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,iDAAiD;IACjD,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,+CAA+C;IAC/C,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAA;IACrB,wCAAwC;IACxC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,mEAAmE;IACnE,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAA;IAC1C,0EAA0E;IAC1E,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAA;IACnG,8EAA8E;IAC9E,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9B,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,yEAAyE;IACzE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;IAChC,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAA;IAChC,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAC9B,OAAO,EAAE,OAAO,CAAA;CACjB;AA+BD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBvE;AAyBD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAuFrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAgCnG,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EAEpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAA+B,iBAAiB,EAAE,mBAAmB,EAAsC,MAAM,iBAAiB,CAAA;AAGzI,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AA+N7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CA6jFhF;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmFhG"}
|
|
@@ -77,6 +77,47 @@ export declare function credentialsFilePathForProfile(claudeConfigDir?: string):
|
|
|
77
77
|
* @param store Override the credential store (for testing).
|
|
78
78
|
*/
|
|
79
79
|
export declare function refreshOAuthToken(store?: CredentialStore): Promise<boolean>;
|
|
80
|
+
/**
|
|
81
|
+
* Refresh the access token if it is within `bufferMs` of expiry.
|
|
82
|
+
*
|
|
83
|
+
* Cheap to call before every SDK request: when the token isn't due yet this
|
|
84
|
+
* is just one credential-store read. When it is due, the underlying
|
|
85
|
+
* `refreshOAuthToken()` call is in-flight-deduplicated so concurrent callers
|
|
86
|
+
* share one network round-trip.
|
|
87
|
+
*
|
|
88
|
+
* Returns true when the token is fresh after the call (already valid OR
|
|
89
|
+
* successfully refreshed), false on any failure (no credentials, no
|
|
90
|
+
* expiresAt, refresh request failed). False is non-fatal — the caller
|
|
91
|
+
* proceeds with whatever token is on disk and falls back to the reactive
|
|
92
|
+
* refresh-on-401 path if Anthropic rejects it.
|
|
93
|
+
*/
|
|
94
|
+
export declare function ensureFreshToken(store?: CredentialStore, bufferMs?: number): Promise<boolean>;
|
|
95
|
+
/**
|
|
96
|
+
* Start a self-rescheduling timer that refreshes the access token shortly
|
|
97
|
+
* before each expiry — regardless of incoming traffic.
|
|
98
|
+
*
|
|
99
|
+
* Idempotent: a second call while one is already running is a no-op. Safe to
|
|
100
|
+
* call from any code path; returns synchronously and schedules in the
|
|
101
|
+
* background.
|
|
102
|
+
*
|
|
103
|
+
* Why traffic-independent matters: without this, an idle proxy never fires
|
|
104
|
+
* either the proactive (`ensureFreshToken`) or reactive (401-retry) refresh
|
|
105
|
+
* path. Anthropic's OAuth refresh tokens appear to be invalidated server-side
|
|
106
|
+
* after sitting unused for an extended period (observed 2026-05-03: two NAS
|
|
107
|
+
* instances idle past expiry both got `400 invalid_grant` on a manual refresh
|
|
108
|
+
* attempt; only fix was OAuth-flow re-login). Running a refresh every ~8h
|
|
109
|
+
* keeps the refresh chain warm.
|
|
110
|
+
*
|
|
111
|
+
* On `refreshOAuthToken()` failure (network, transient API error, refresh
|
|
112
|
+
* token rejected) we retry every `failureRetryMs` — gives operators a window
|
|
113
|
+
* to `claude login` and have the new tokens picked up automatically on the
|
|
114
|
+
* next tick.
|
|
115
|
+
*/
|
|
116
|
+
export declare function startBackgroundRefresh(store?: CredentialStore, bufferMs?: number, failureRetryMs?: number): void;
|
|
117
|
+
/** Stop the background scheduler. Idempotent. */
|
|
118
|
+
export declare function stopBackgroundRefresh(): void;
|
|
119
|
+
/** For testing only. */
|
|
120
|
+
export declare function isBackgroundRefreshActive(): boolean;
|
|
80
121
|
/** Reset in-flight state — for testing only. */
|
|
81
122
|
export declare function resetInflightRefresh(): void;
|
|
82
123
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../../src/proxy/tokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkBH;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED,gEAAgE;AAChE,wBAAgB,0BAA0B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,UAAU,eAAe;IACvB,aAAa,EAAE,gBAAgB,CAAA;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IACvC,KAAK,CAAC,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,eAAe,GAAG,MAAM,CAEzE;AAuGD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,CAQlG;AAED,uGAAuG;AACvG,wBAAgB,6BAA6B,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9E;AASD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAQjF;AAiED,gDAAgD;AAChD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
1
|
+
{"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../../src/proxy/tokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkBH;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED,gEAAgE;AAChE,wBAAgB,0BAA0B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,UAAU,eAAe;IACvB,aAAa,EAAE,gBAAgB,CAAA;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IACvC,KAAK,CAAC,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,eAAe,GAAG,MAAM,CAEzE;AAuGD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,CAQlG;AAED,uGAAuG;AACvG,wBAAgB,6BAA6B,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9E;AASD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAQjF;AAiED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,CAAC,EAAE,eAAe,EACvB,QAAQ,SAAgB,GACvB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAiBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,CAAC,EAAE,eAAe,EACvB,QAAQ,SAAgB,EACxB,cAAc,SAAgB,GAC7B,IAAI,CAKN;AAED,iDAAiD;AACjD,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C;AA0DD,wBAAwB;AACxB,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED,gDAAgD;AAChD,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
package/dist/server.js
CHANGED
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
runObserveHook,
|
|
11
11
|
runTransformHook,
|
|
12
12
|
startProxyServer
|
|
13
|
-
} from "./cli-
|
|
14
|
-
import"./cli-
|
|
13
|
+
} from "./cli-kvwnarfk.js";
|
|
14
|
+
import"./cli-cx463q74.js";
|
|
15
15
|
import"./cli-sry5aqdj.js";
|
|
16
16
|
import"./cli-4rqtm83g.js";
|
|
17
17
|
import"./cli-340h1chz.js";
|
|
18
18
|
import"./cli-rtab0qa6.js";
|
|
19
|
-
import"./cli-
|
|
19
|
+
import"./cli-7k1fcprd.js";
|
|
20
20
|
import"./cli-p9swy5t3.js";
|
|
21
21
|
export {
|
|
22
22
|
startProxyServer,
|
|
@@ -3,15 +3,23 @@ import {
|
|
|
3
3
|
configDirToKeychainService,
|
|
4
4
|
createPlatformCredentialStore,
|
|
5
5
|
credentialsFilePathForProfile,
|
|
6
|
+
ensureFreshToken,
|
|
7
|
+
isBackgroundRefreshActive,
|
|
6
8
|
refreshOAuthToken,
|
|
7
9
|
resetInflightRefresh,
|
|
8
|
-
serializeCredentials
|
|
9
|
-
|
|
10
|
+
serializeCredentials,
|
|
11
|
+
startBackgroundRefresh,
|
|
12
|
+
stopBackgroundRefresh
|
|
13
|
+
} from "./cli-7k1fcprd.js";
|
|
10
14
|
import"./cli-p9swy5t3.js";
|
|
11
15
|
export {
|
|
16
|
+
stopBackgroundRefresh,
|
|
17
|
+
startBackgroundRefresh,
|
|
12
18
|
serializeCredentials,
|
|
13
19
|
resetInflightRefresh,
|
|
14
20
|
refreshOAuthToken,
|
|
21
|
+
isBackgroundRefreshActive,
|
|
22
|
+
ensureFreshToken,
|
|
15
23
|
credentialsFilePathForProfile,
|
|
16
24
|
createPlatformCredentialStore,
|
|
17
25
|
configDirToKeychainService,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynfar/meridian",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.0",
|
|
4
4
|
"description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/server.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"postbuild": "node scripts/fix-bun-exports.mjs && node --check dist/cli.js && node --check dist/server.js && test -f dist/proxy/server.d.ts",
|
|
26
26
|
"postinstall": "node ./node_modules/@anthropic-ai/claude-code/install.cjs 2>/dev/null || true",
|
|
27
27
|
"prepublishOnly": "bun run build",
|
|
28
|
-
"test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*
|
|
28
|
+
"test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' --path-ignore-patterns '**/*session-recovery*' --path-ignore-patterns '**/*models.test*' && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts && bun test src/__tests__/proxy-passthrough-thinking.test.ts && bun test src/__tests__/proxy-session-recovery.test.ts && bun test src/__tests__/models.test.ts",
|
|
29
29
|
"nix:lock": "bun2nix -o bun.nix",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
31
|
"proxy:direct": "bun run ./bin/cli.ts"
|