@rynfar/meridian 1.43.0 → 1.44.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 +40 -7
- package/dist/{cli-7k1fcprd.js → cli-1kbcm3yn.js} +37 -20
- package/dist/{cli-1x5gqsqc.js → cli-fc6mt326.js} +121 -46
- package/dist/{cli-rtab0qa6.js → cli-je60fevk.js} +38 -20
- package/dist/cli.js +25 -18
- package/dist/{profileCli-0h4nc2h8.js → profileCli-xcmmr5w4.js} +208 -46
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/adapters/openai.d.ts +23 -0
- package/dist/proxy/adapters/openai.d.ts.map +1 -0
- package/dist/proxy/effort.d.ts +21 -0
- package/dist/proxy/effort.d.ts.map +1 -0
- package/dist/proxy/openai.d.ts +12 -0
- package/dist/proxy/openai.d.ts.map +1 -1
- package/dist/proxy/query.d.ts +3 -2
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/sdkFeatures.d.ts.map +1 -1
- package/dist/proxy/server.d.ts +1 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/setup.d.ts +9 -0
- package/dist/proxy/setup.d.ts.map +1 -1
- package/dist/proxy/tokenRefresh.d.ts +2 -0
- package/dist/proxy/tokenRefresh.d.ts.map +1 -1
- package/dist/proxy/transforms/registry.d.ts.map +1 -1
- package/dist/proxy/types.d.ts +7 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/server.js +5 -3
- package/dist/{setup-v5pnqe04.js → setup-6c11e8d6.js} +4 -2
- package/dist/{tokenRefresh-swetnf89.js → tokenRefresh-pvc2q8ea.js} +1 -1
- package/package.json +4 -3
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 / SSH: complete Claude OAuth with a pasted code
|
|
243
|
+
|
|
244
|
+
When you still want a normal Claude Max browser-login profile but the Meridian host cannot open a browser (SSH, WSL, containers, remote servers), use `--headless`. Meridian prints a Claude OAuth URL, prompts for the returned code, exchanges it with PKCE, and saves the resulting credentials into the profile's isolated `CLAUDE_CONFIG_DIR`:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
meridian profile add work --headless
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Open the printed URL in a browser, sign in to the target Claude account, then paste the returned code at Meridian's `Paste code:` prompt. For an existing browser-login profile:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
meridian profile login work --headless
|
|
254
|
+
```
|
|
255
|
+
|
|
242
256
|
#### Headless / CI: register an OAuth token
|
|
243
257
|
|
|
244
258
|
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:
|
|
@@ -269,11 +283,11 @@ You can also switch profiles from the web UI at `http://127.0.0.1:3456/profiles`
|
|
|
269
283
|
|
|
270
284
|
| Command | Description |
|
|
271
285
|
|---------|-------------|
|
|
272
|
-
| `meridian profile add <name
|
|
286
|
+
| `meridian profile add <name> [--headless]` | Add a profile and authenticate via Claude OAuth; `--headless` prints a URL, prompts for the returned code, and stores the exchanged credentials |
|
|
273
287
|
| `meridian profile add <name> --oauth-token [TOKEN]` | Add a headless profile from a `claude setup-token` value (prompts when `TOKEN` is omitted) |
|
|
274
288
|
| `meridian profile list` | List profiles and auth status |
|
|
275
289
|
| `meridian profile switch <name>` | Switch the active profile (requires running proxy) |
|
|
276
|
-
| `meridian profile login <name
|
|
290
|
+
| `meridian profile login <name> [--headless]` | Re-authenticate an expired profile (browser-login profiles only); `--headless` uses the URL/code flow |
|
|
277
291
|
| `meridian profile remove <name>` | Remove a profile and its credentials |
|
|
278
292
|
|
|
279
293
|
### How it works
|
|
@@ -361,9 +375,11 @@ Add a provider to `~/.config/crush/crush.json`:
|
|
|
361
375
|
"base_url": "http://127.0.0.1:3456",
|
|
362
376
|
"api_key": "dummy",
|
|
363
377
|
"models": [
|
|
364
|
-
{ "id": "claude-
|
|
365
|
-
{ "id": "claude-opus-4-
|
|
366
|
-
{ "id": "claude-
|
|
378
|
+
{ "id": "claude-opus-4-8", "name": "Claude Opus 4.8 (1M)", "context_window": 1000000, "default_max_tokens": 32768, "can_reason": true, "supports_attachments": true },
|
|
379
|
+
{ "id": "claude-opus-4-7", "name": "Claude Opus 4.7 (1M)", "context_window": 1000000, "default_max_tokens": 32768, "can_reason": true, "supports_attachments": true },
|
|
380
|
+
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6 (1M)", "context_window": 1000000, "default_max_tokens": 64000, "can_reason": true, "supports_attachments": true },
|
|
381
|
+
{ "id": "claude-opus-4-6", "name": "Claude Opus 4.6 (1M)", "context_window": 1000000, "default_max_tokens": 32768, "can_reason": true, "supports_attachments": true },
|
|
382
|
+
{ "id": "claude-haiku-4-5-20251001", "name": "Claude Haiku 4.5", "context_window": 200000, "default_max_tokens": 16384, "can_reason": true, "supports_attachments": true }
|
|
367
383
|
]
|
|
368
384
|
}
|
|
369
385
|
}
|
|
@@ -384,6 +400,8 @@ Add Meridian as a custom model provider in `~/.factory/settings.json`:
|
|
|
384
400
|
```json
|
|
385
401
|
{
|
|
386
402
|
"customModels": [
|
|
403
|
+
{ "model": "claude-opus-4-8", "name": "Opus 4.8 (Meridian)", "provider": "anthropic", "baseUrl": "http://127.0.0.1:3456", "apiKey": "x" },
|
|
404
|
+
{ "model": "claude-opus-4-7", "name": "Opus 4.7 (Meridian)", "provider": "anthropic", "baseUrl": "http://127.0.0.1:3456", "apiKey": "x" },
|
|
387
405
|
{ "model": "claude-sonnet-4-6", "name": "Sonnet 4.6 (Meridian)", "provider": "anthropic", "baseUrl": "http://127.0.0.1:3456", "apiKey": "x" },
|
|
388
406
|
{ "model": "claude-opus-4-6", "name": "Opus 4.6 (Meridian)", "provider": "anthropic", "baseUrl": "http://127.0.0.1:3456", "apiKey": "x" },
|
|
389
407
|
{ "model": "claude-haiku-4-5-20251001", "name": "Haiku 4.5 (Meridian)", "provider": "anthropic", "baseUrl": "http://127.0.0.1:3456", "apiKey": "x" }
|
|
@@ -734,11 +752,11 @@ export default {
|
|
|
734
752
|
|---------|-------------|
|
|
735
753
|
| `meridian` | Start the proxy server |
|
|
736
754
|
| `meridian setup` | Configure the OpenCode plugin in `~/.config/opencode/opencode.json` |
|
|
737
|
-
| `meridian profile add <name
|
|
755
|
+
| `meridian profile add <name> [--headless]` | Add a profile and authenticate via Claude OAuth; `--headless` prints a URL, prompts for the returned code, and stores the exchanged credentials |
|
|
738
756
|
| `meridian profile add <name> --oauth-token [TOKEN]` | Add a headless profile from a `claude setup-token` value (prompts when `TOKEN` is omitted) |
|
|
739
757
|
| `meridian profile list` | List all profiles and their auth status |
|
|
740
758
|
| `meridian profile switch <name>` | Switch the active profile (requires running proxy) |
|
|
741
|
-
| `meridian profile login <name
|
|
759
|
+
| `meridian profile login <name> [--headless]` | Re-authenticate an expired profile (browser-login profiles only); `--headless` uses the URL/code flow |
|
|
742
760
|
| `meridian profile remove <name>` | Remove a profile and its credentials |
|
|
743
761
|
| `meridian refresh-token` | Manually refresh the Claude OAuth token (exits 0/1) |
|
|
744
762
|
|
|
@@ -840,6 +858,21 @@ meridian refresh-token
|
|
|
840
858
|
curl -X POST http://127.0.0.1:3456/auth/refresh
|
|
841
859
|
```
|
|
842
860
|
|
|
861
|
+
**I'm getting `400 You're out of extra usage` only when tools are present. What do I do?**
|
|
862
|
+
First confirm the failure pattern: a tiny no-tool request succeeds, but the same client fails once it sends tool definitions. If that is the case, beta-header stripping and model fallback usually will not help because the request body still contains agentic tool context.
|
|
863
|
+
|
|
864
|
+
For the affected adapter, try disabling the connecting client's system prompt while keeping the Claude Code prompt enabled:
|
|
865
|
+
|
|
866
|
+
```bash
|
|
867
|
+
curl -X PATCH http://127.0.0.1:3456/settings/api/features/pi \
|
|
868
|
+
-H 'Content-Type: application/json' \
|
|
869
|
+
-d '{"clientSystemPrompt":false,"codeSystemPrompt":true}'
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Replace `pi` with the adapter you use (`opencode`, `crush`, `forgecode`, `droid`, `passthrough`, or `openai`). You can make the same change in the `/settings` UI under **SDK Feature Toggles**. (The `openai` adapter — used by the `/v1/chat/completions` endpoint — already defaults `codeSystemPrompt` to off, so on that one you typically only need `clientSystemPrompt:false`.)
|
|
873
|
+
|
|
874
|
+
This keeps the SDK's preset prompt and tool bridge, but removes the external client's large agent prompt from the request. That may help when the error is triggered by the combination of tool definitions plus client prompt context. The tradeoff is that the connected agent may behave more like vanilla Claude Code because its own persona and workflow instructions are no longer included. If it still fails, the remaining options are to use fewer/no tools for that client, enable Extra Usage/API billing, or switch to a local/provider-backed model for that workflow. See [#516](https://github.com/rynfar/meridian/issues/516) for the current debugging thread.
|
|
875
|
+
|
|
843
876
|
**I'm hitting rate limits on 1M context. What do I do?**
|
|
844
877
|
Meridian defaults Sonnet to 200k context because Sonnet 1M is always billed as Extra Usage on Max plans — even when regular usage isn't exhausted. This is [Anthropic's intended billing model](https://code.claude.com/docs/en/model-config#extended-context), not a bug. Set `MERIDIAN_SONNET_MODEL=sonnet[1m]` to opt in if you have Extra Usage enabled and understand the billing implications. Opus defaults to 1M context, which is included with Max/Team/Enterprise subscriptions at no extra cost. Note: there is a [known upstream bug](https://github.com/anthropics/claude-code/issues/39841) where Claude Code incorrectly gates Opus 1M behind Extra Usage on Max — this is Anthropic's to fix.
|
|
845
878
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/proxy/tokenRefresh.ts
|
|
2
|
-
import { execFile as execFileCb } from "child_process";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { promisify } from "util";
|
|
2
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir, platform, userInfo } from "node:os";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
8
|
|
|
9
9
|
// src/logger.ts
|
|
10
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -102,6 +102,7 @@ function parseKeychainValue(raw) {
|
|
|
102
102
|
var keychainWasHexByService = new Map;
|
|
103
103
|
function buildMacosStore(serviceName) {
|
|
104
104
|
return {
|
|
105
|
+
refreshKey: `keychain:${serviceName}`,
|
|
105
106
|
async read() {
|
|
106
107
|
try {
|
|
107
108
|
const { stdout } = await execFile("/usr/bin/security", ["find-generic-password", "-s", serviceName, "-a", userInfo().username, "-w"], { timeout: 5000 });
|
|
@@ -131,24 +132,26 @@ function buildMacosStore(serviceName) {
|
|
|
131
132
|
}
|
|
132
133
|
var macosStore = buildMacosStore(KEYCHAIN_SERVICE);
|
|
133
134
|
function buildFileStore(filePath) {
|
|
135
|
+
const absPath = resolve(filePath);
|
|
134
136
|
return {
|
|
137
|
+
refreshKey: `file:${absPath}`,
|
|
135
138
|
async read() {
|
|
136
139
|
try {
|
|
137
|
-
if (!existsSync(
|
|
140
|
+
if (!existsSync(absPath))
|
|
138
141
|
return null;
|
|
139
|
-
return JSON.parse(readFileSync(
|
|
142
|
+
return JSON.parse(readFileSync(absPath, "utf-8"));
|
|
140
143
|
} catch (err) {
|
|
141
|
-
claudeLog("token_refresh.file_read_failed", { path:
|
|
144
|
+
claudeLog("token_refresh.file_read_failed", { path: absPath, error: String(err) });
|
|
142
145
|
return null;
|
|
143
146
|
}
|
|
144
147
|
},
|
|
145
148
|
async write(credentials) {
|
|
146
149
|
try {
|
|
147
|
-
mkdirSync(dirname(
|
|
148
|
-
writeFileSync(
|
|
150
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
151
|
+
writeFileSync(absPath, serializeCredentials(credentials), "utf-8");
|
|
149
152
|
return true;
|
|
150
153
|
} catch (err) {
|
|
151
|
-
claudeLog("token_refresh.file_write_failed", { path:
|
|
154
|
+
claudeLog("token_refresh.file_write_failed", { path: absPath, error: String(err) });
|
|
152
155
|
return false;
|
|
153
156
|
}
|
|
154
157
|
}
|
|
@@ -167,14 +170,29 @@ function createPlatformCredentialStore(opts) {
|
|
|
167
170
|
function credentialsFilePathForProfile(claudeConfigDir) {
|
|
168
171
|
return claudeConfigDir ? configDirToCredentialsFile(claudeConfigDir) : CREDENTIALS_FILE;
|
|
169
172
|
}
|
|
170
|
-
var
|
|
173
|
+
var inflightRefreshByKey = new Map;
|
|
174
|
+
var inflightRefreshByStore = new WeakMap;
|
|
171
175
|
async function refreshOAuthToken(store) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
const s = store ?? createPlatformCredentialStore();
|
|
177
|
+
const refreshKey = s.refreshKey;
|
|
178
|
+
if (refreshKey) {
|
|
179
|
+
const inflight2 = inflightRefreshByKey.get(refreshKey);
|
|
180
|
+
if (inflight2)
|
|
181
|
+
return inflight2;
|
|
182
|
+
const refresh2 = doRefresh(s).finally(() => {
|
|
183
|
+
inflightRefreshByKey.delete(refreshKey);
|
|
184
|
+
});
|
|
185
|
+
inflightRefreshByKey.set(refreshKey, refresh2);
|
|
186
|
+
return refresh2;
|
|
187
|
+
}
|
|
188
|
+
const inflight = inflightRefreshByStore.get(s);
|
|
189
|
+
if (inflight)
|
|
190
|
+
return inflight;
|
|
191
|
+
const refresh = doRefresh(s).finally(() => {
|
|
192
|
+
inflightRefreshByStore.delete(s);
|
|
176
193
|
});
|
|
177
|
-
|
|
194
|
+
inflightRefreshByStore.set(s, refresh);
|
|
195
|
+
return refresh;
|
|
178
196
|
}
|
|
179
197
|
async function doRefresh(store) {
|
|
180
198
|
const credentials = await store.read();
|
|
@@ -273,7 +291,6 @@ async function scheduleNext(store, bufferMs, failureRetryMs, gen) {
|
|
|
273
291
|
if (!scheduledRefreshActive || gen !== scheduledRefreshGeneration)
|
|
274
292
|
return;
|
|
275
293
|
claudeLog("token_refresh.scheduled", { ok, immediate: true });
|
|
276
|
-
console.error(`[token_refresh] scheduled refresh (immediate) ok=${ok}`);
|
|
277
294
|
armTimer(ok ? 0 : failureRetryMs, store, bufferMs, failureRetryMs, gen);
|
|
278
295
|
return;
|
|
279
296
|
}
|
|
@@ -293,7 +310,7 @@ function isBackgroundRefreshActive() {
|
|
|
293
310
|
return scheduledRefreshActive;
|
|
294
311
|
}
|
|
295
312
|
function resetInflightRefresh() {
|
|
296
|
-
|
|
313
|
+
inflightRefreshByKey.clear();
|
|
297
314
|
}
|
|
298
315
|
|
|
299
316
|
export { withClaudeLogContext, claudeLog, configDirToKeychainService, configDirToCredentialsFile, serializeCredentials, createPlatformCredentialStore, credentialsFilePathForProfile, refreshOAuthToken, ensureFreshToken, startBackgroundRefresh, stopBackgroundRefresh, isBackgroundRefreshActive, resetInflightRefresh };
|