@rynfar/meridian 1.53.0 → 1.54.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 -0
- package/dist/{profileCli-s1h4vh8w.js → cli-jhm27q0x.js} +346 -31
- package/dist/{cli-027as0fx.js → cli-mqht6rs9.js} +450 -57
- package/dist/cli.js +3 -3
- package/dist/grepTool.d.ts +23 -0
- package/dist/grepTool.d.ts.map +1 -0
- package/dist/mcpTools.d.ts.map +1 -1
- package/dist/profileCli-d9tcp916.js +35 -0
- package/dist/proxy/design.d.ts +82 -0
- package/dist/proxy/design.d.ts.map +1 -0
- package/dist/proxy/profileCli.d.ts +48 -0
- package/dist/proxy/profileCli.d.ts.map +1 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +1 -1
- package/dist/cli-vjeftz4z.js +0 -329
package/README.md
CHANGED
|
@@ -667,6 +667,47 @@ Run several configurations of the same adapter side by side — e.g. a passthrou
|
|
|
667
667
|
|
|
668
668
|
Built-in adapter names are reserved and can't be shadowed. With no instances configured, detection is exactly the built-in chain. Config file changes apply within ~5s, no restart needed.
|
|
669
669
|
|
|
670
|
+
### Claude Design MCP
|
|
671
|
+
|
|
672
|
+
Meridian proxies the Claude Design MCP API (`api.anthropic.com/v1/design/*`), so MCP clients can use Claude Design tools through your local endpoint.
|
|
673
|
+
|
|
674
|
+
**1. Add the MCP server.** For Claude Code:
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
claude mcp add -s user --transport http claude-design http://127.0.0.1:3456/v1/design/mcp
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
Any other MCP client: point it at `http://127.0.0.1:3456/v1/design/mcp` (streamable HTTP).
|
|
681
|
+
|
|
682
|
+
**2. Grant Claude Design consent (one time, per Anthropic account).** Tool calls return a `needs_consent` error until you enable it: open [claude.ai/design/settings](https://claude.ai/design/settings), find **"Claude product access"** ("Let other Claude products, like Claude Code, read and edit your Design projects"), and switch it **On**. This is a setting on the Anthropic account itself — with multiple Meridian profiles, the account behind the *profile handling the request* is the one that needs the toggle.
|
|
683
|
+
|
|
684
|
+
That's it — your existing Claude Max login covers auth (`initialize`, `tools/list`, and tool calls are all verified working with a plain Max token). Verify with a quick handshake:
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
curl -s -X POST http://127.0.0.1:3456/v1/design/mcp -H 'content-type: application/json' \
|
|
688
|
+
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**Multiple profiles:** design requests use the active profile by default. To pin design traffic to a specific profile regardless of which is active, register the server with a profile header:
|
|
692
|
+
|
|
693
|
+
```bash
|
|
694
|
+
claude mcp add -s user --transport http --header "x-meridian-profile: personal" \
|
|
695
|
+
claude-design http://127.0.0.1:3456/v1/design/mcp
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
**Fallback OAuth flow:** if the upstream ever rejects your token with an `auth_error` (scope enforcement has varied over time), `/design-login` obtains a dedicated token with the `user:design:read`/`user:design:write` scopes:
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
curl http://127.0.0.1:3456/design-login # returns an authorize URL — open it in your browser
|
|
702
|
+
curl -X POST http://127.0.0.1:3456/design-login \
|
|
703
|
+
-H 'content-type: application/json' \
|
|
704
|
+
-d '{"code": "<code-from-browser>"}' # paste the code you were shown
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
The design token is stored at `~/.config/meridian/design-token.json` (mode `0600`, global across profiles) and refreshed automatically when it expires.
|
|
708
|
+
|
|
709
|
+
> Contributed by [@sittitep](https://github.com/sittitep) (#543).
|
|
710
|
+
|
|
670
711
|
### Any Anthropic-compatible tool
|
|
671
712
|
|
|
672
713
|
```bash
|
|
@@ -834,6 +875,8 @@ ANTHROPIC_API_KEY=your-secret-key ANTHROPIC_BASE_URL=http://meridian-host:3456 o
|
|
|
834
875
|
| `POST /v1/chat/completions` | OpenAI-compatible chat completions |
|
|
835
876
|
| `POST /v1/responses` | OpenAI Responses API (Codex CLI ≥ 0.96) |
|
|
836
877
|
| `GET /v1/models` | OpenAI-compatible model list |
|
|
878
|
+
| `GET/POST /v1/design/*` | Claude Design MCP proxy (see [Claude Design MCP](#claude-design-mcp)) |
|
|
879
|
+
| `GET/POST /design-login` | OAuth flow for the design scopes |
|
|
837
880
|
| `GET /health` | Auth status, mode, plugin status |
|
|
838
881
|
| `POST /auth/refresh` | Manually refresh the OAuth token |
|
|
839
882
|
| `GET /telemetry` | Performance dashboard |
|
|
@@ -1,22 +1,347 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveClaudeExecutableSync
|
|
3
|
-
} from "./cli-vjeftz4z.js";
|
|
4
1
|
import {
|
|
5
2
|
setSetting
|
|
6
3
|
} from "./cli-340h1chz.js";
|
|
7
4
|
import {
|
|
8
5
|
createPlatformCredentialStore
|
|
9
6
|
} from "./cli-aq5zz92m.js";
|
|
10
|
-
import
|
|
7
|
+
import {
|
|
8
|
+
__esm
|
|
9
|
+
} from "./cli-p9swy5t3.js";
|
|
10
|
+
|
|
11
|
+
// src/env.ts
|
|
12
|
+
function env(suffix) {
|
|
13
|
+
return process.env[`MERIDIAN_${suffix}`] ?? process.env[`CLAUDE_PROXY_${suffix}`];
|
|
14
|
+
}
|
|
15
|
+
function envBool(suffix) {
|
|
16
|
+
const val = env(suffix);
|
|
17
|
+
return val === "1" || val === "true" || val === "yes";
|
|
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 = () => {};
|
|
11
35
|
|
|
12
36
|
// src/proxy/profileCli.ts
|
|
13
37
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
14
38
|
import { createHash, randomBytes } from "node:crypto";
|
|
15
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
39
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync, existsSync as existsSync2 } from "node:fs";
|
|
16
40
|
import { homedir } from "node:os";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
import { join as join2 } from "node:path";
|
|
42
|
+
|
|
43
|
+
// src/proxy/models.ts
|
|
44
|
+
init_env();
|
|
45
|
+
import { exec as execCallback, execFile as execFileCallback } from "child_process";
|
|
46
|
+
import { existsSync, statSync } from "fs";
|
|
47
|
+
import { fileURLToPath } from "url";
|
|
48
|
+
import { join, dirname } from "path";
|
|
49
|
+
import { promisify } from "util";
|
|
50
|
+
var exec = promisify(execCallback);
|
|
51
|
+
var execFile = promisify(execFileCallback);
|
|
52
|
+
var STUB_SIZE_THRESHOLD = 4096;
|
|
53
|
+
var CANONICAL_FABLE_MODEL = "claude-fable-5";
|
|
54
|
+
var CANONICAL_OPUS_MODEL = "claude-opus-4-8";
|
|
55
|
+
var CANONICAL_SONNET_MODEL = "claude-sonnet-5";
|
|
56
|
+
var CANONICAL_HAIKU_MODEL = "claude-haiku-4-5";
|
|
57
|
+
function resolveSdkModelDefaults(env2 = process.env) {
|
|
58
|
+
return {
|
|
59
|
+
ANTHROPIC_DEFAULT_FABLE_MODEL: env2.MERIDIAN_DEFAULT_FABLE_MODEL ?? CANONICAL_FABLE_MODEL,
|
|
60
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: env2.MERIDIAN_DEFAULT_OPUS_MODEL ?? CANONICAL_OPUS_MODEL,
|
|
61
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: env2.MERIDIAN_DEFAULT_SONNET_MODEL ?? CANONICAL_SONNET_MODEL,
|
|
62
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: env2.MERIDIAN_DEFAULT_HAIKU_MODEL ?? CANONICAL_HAIKU_MODEL
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function explicitModelPin(requestedModel) {
|
|
66
|
+
const base = requestedModel.trim().toLowerCase().replace(/\[1m\]$/, "");
|
|
67
|
+
const match = /^claude-(sonnet|opus|haiku|fable|mythos)-\d[\w.-]*$/.exec(base);
|
|
68
|
+
if (!match)
|
|
69
|
+
return;
|
|
70
|
+
const tier = match[1] === "mythos" ? "FABLE" : match[1].toUpperCase();
|
|
71
|
+
return { [`ANTHROPIC_DEFAULT_${tier}_MODEL`]: base };
|
|
72
|
+
}
|
|
73
|
+
var AUTH_STATUS_CACHE_TTL_MS = 60000;
|
|
74
|
+
var AUTH_STATUS_FAILURE_TTL_MS = 5000;
|
|
75
|
+
var cachedAuthStatus = null;
|
|
76
|
+
var lastKnownGoodAuthStatus = null;
|
|
77
|
+
var cachedAuthStatusAt = 0;
|
|
78
|
+
var cachedAuthStatusIsFailure = false;
|
|
79
|
+
var cachedAuthStatusPromise = null;
|
|
80
|
+
function supports1mContext(model) {
|
|
81
|
+
const override = env("1M_CONTEXT_SUPPORT");
|
|
82
|
+
if (override === "0" || override === "false" || override === "no")
|
|
83
|
+
return false;
|
|
84
|
+
if (model.includes("4-5") || model.includes("4.5"))
|
|
85
|
+
return false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
function mapModelToClaudeModel(model, subscriptionType, agentMode) {
|
|
89
|
+
if (model.includes("haiku"))
|
|
90
|
+
return "haiku";
|
|
91
|
+
const use1m = supports1mContext(model);
|
|
92
|
+
const isSubagent = agentMode === "subagent";
|
|
93
|
+
if (model.includes("fable") || model.includes("mythos")) {
|
|
94
|
+
if (use1m && !isSubagent && !isExtendedContextKnownUnavailable())
|
|
95
|
+
return "fable[1m]";
|
|
96
|
+
return "fable";
|
|
97
|
+
}
|
|
98
|
+
if (model.includes("opus")) {
|
|
99
|
+
if (use1m && !isSubagent && !isExtendedContextKnownUnavailable())
|
|
100
|
+
return "opus[1m]";
|
|
101
|
+
return "opus";
|
|
102
|
+
}
|
|
103
|
+
const sonnetOverride = process.env.MERIDIAN_SONNET_MODEL ?? process.env.CLAUDE_PROXY_SONNET_MODEL;
|
|
104
|
+
if (sonnetOverride === "sonnet[1m]") {
|
|
105
|
+
if (!use1m || isSubagent || isExtendedContextKnownUnavailable())
|
|
106
|
+
return "sonnet";
|
|
107
|
+
return "sonnet[1m]";
|
|
108
|
+
}
|
|
109
|
+
return "sonnet";
|
|
110
|
+
}
|
|
111
|
+
var EXTRA_USAGE_RETRY_MS = 60 * 60 * 1000;
|
|
112
|
+
var extraUsageUnavailableAt = 0;
|
|
113
|
+
function recordExtendedContextUnavailable() {
|
|
114
|
+
extraUsageUnavailableAt = Date.now();
|
|
115
|
+
}
|
|
116
|
+
function isExtendedContextKnownUnavailable() {
|
|
117
|
+
return extraUsageUnavailableAt > 0 && Date.now() - extraUsageUnavailableAt < EXTRA_USAGE_RETRY_MS;
|
|
118
|
+
}
|
|
119
|
+
function stripExtendedContext(model) {
|
|
120
|
+
if (model === "opus[1m]")
|
|
121
|
+
return "opus";
|
|
122
|
+
if (model === "sonnet[1m]")
|
|
123
|
+
return "sonnet";
|
|
124
|
+
if (model === "fable[1m]")
|
|
125
|
+
return "fable";
|
|
126
|
+
return model;
|
|
127
|
+
}
|
|
128
|
+
function hasExtendedContext(model) {
|
|
129
|
+
return model.endsWith("[1m]");
|
|
130
|
+
}
|
|
131
|
+
var profileAuthCaches = new Map;
|
|
132
|
+
function getAuthCacheInfo(profileId) {
|
|
133
|
+
if (!profileId) {
|
|
134
|
+
return { lastCheckedAt: cachedAuthStatusAt, lastSuccessAt: cachedAuthStatusIsFailure ? 0 : cachedAuthStatusAt, isFailure: cachedAuthStatusIsFailure };
|
|
135
|
+
}
|
|
136
|
+
const cache = profileAuthCaches.get(profileId);
|
|
137
|
+
if (!cache)
|
|
138
|
+
return { lastCheckedAt: 0, lastSuccessAt: 0, isFailure: false };
|
|
139
|
+
return { lastCheckedAt: cache.at, lastSuccessAt: cache.lastSuccessAt, isFailure: cache.isFailure };
|
|
140
|
+
}
|
|
141
|
+
function getAuthCache(key) {
|
|
142
|
+
let cache = profileAuthCaches.get(key);
|
|
143
|
+
if (!cache) {
|
|
144
|
+
cache = { status: null, lastKnownGood: null, at: 0, isFailure: false, promise: null, lastSuccessAt: 0 };
|
|
145
|
+
profileAuthCaches.set(key, cache);
|
|
146
|
+
}
|
|
147
|
+
return cache;
|
|
148
|
+
}
|
|
149
|
+
async function getClaudeAuthStatusAsync(profileId, envOverrides) {
|
|
150
|
+
const isDefault = !profileId;
|
|
151
|
+
const cache = isDefault ? null : getAuthCache(profileId);
|
|
152
|
+
const c_status = cache ? cache.status : cachedAuthStatus;
|
|
153
|
+
const c_lastKnownGood = cache ? cache.lastKnownGood : lastKnownGoodAuthStatus;
|
|
154
|
+
const c_at = cache ? cache.at : cachedAuthStatusAt;
|
|
155
|
+
const c_isFailure = cache ? cache.isFailure : cachedAuthStatusIsFailure;
|
|
156
|
+
let c_promise = cache ? cache.promise : cachedAuthStatusPromise;
|
|
157
|
+
const ttl = c_isFailure ? AUTH_STATUS_FAILURE_TTL_MS : AUTH_STATUS_CACHE_TTL_MS;
|
|
158
|
+
if (c_at > 0 && Date.now() - c_at < ttl) {
|
|
159
|
+
return c_status ?? c_lastKnownGood;
|
|
160
|
+
}
|
|
161
|
+
if (c_promise)
|
|
162
|
+
return c_promise;
|
|
163
|
+
c_promise = (async () => {
|
|
164
|
+
try {
|
|
165
|
+
const claudePath = await resolveClaudeExecutableAsync();
|
|
166
|
+
const { stdout } = await execFile(claudePath, ["auth", "status"], {
|
|
167
|
+
timeout: 5000,
|
|
168
|
+
...envOverrides ? { env: { ...process.env, ...envOverrides } } : {}
|
|
169
|
+
});
|
|
170
|
+
const parsed = JSON.parse(stdout);
|
|
171
|
+
if (cache) {
|
|
172
|
+
cache.status = parsed;
|
|
173
|
+
cache.lastKnownGood = parsed;
|
|
174
|
+
cache.at = Date.now();
|
|
175
|
+
cache.isFailure = false;
|
|
176
|
+
cache.lastSuccessAt = Date.now();
|
|
177
|
+
} else {
|
|
178
|
+
cachedAuthStatus = parsed;
|
|
179
|
+
lastKnownGoodAuthStatus = parsed;
|
|
180
|
+
cachedAuthStatusAt = Date.now();
|
|
181
|
+
cachedAuthStatusIsFailure = false;
|
|
182
|
+
}
|
|
183
|
+
return parsed;
|
|
184
|
+
} catch {
|
|
185
|
+
if (cache) {
|
|
186
|
+
cache.isFailure = true;
|
|
187
|
+
cache.at = Date.now();
|
|
188
|
+
cache.status = null;
|
|
189
|
+
return cache.lastKnownGood;
|
|
190
|
+
} else {
|
|
191
|
+
cachedAuthStatusIsFailure = true;
|
|
192
|
+
cachedAuthStatusAt = Date.now();
|
|
193
|
+
cachedAuthStatus = null;
|
|
194
|
+
return lastKnownGoodAuthStatus;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
if (cache)
|
|
199
|
+
cache.promise = c_promise;
|
|
200
|
+
else
|
|
201
|
+
cachedAuthStatusPromise = c_promise;
|
|
202
|
+
try {
|
|
203
|
+
return await c_promise;
|
|
204
|
+
} finally {
|
|
205
|
+
if (cache)
|
|
206
|
+
cache.promise = null;
|
|
207
|
+
else
|
|
208
|
+
cachedAuthStatusPromise = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
var cachedClaudeInfo = null;
|
|
212
|
+
var cachedClaudePathPromise = null;
|
|
213
|
+
var DEFAULT_DEPS = {
|
|
214
|
+
existsSync,
|
|
215
|
+
statSync: (p) => statSync(p),
|
|
216
|
+
exec,
|
|
217
|
+
resolvePackage: (specifier) => fileURLToPath(import.meta.resolve(specifier)),
|
|
218
|
+
envGet: (name) => process.env[name],
|
|
219
|
+
platform: process.platform,
|
|
220
|
+
arch: process.arch,
|
|
221
|
+
isBun: typeof process.versions.bun !== "undefined"
|
|
222
|
+
};
|
|
223
|
+
function tryEnvOverride(deps) {
|
|
224
|
+
const explicit = deps.envGet("MERIDIAN_CLAUDE_PATH");
|
|
225
|
+
if (!explicit)
|
|
226
|
+
return null;
|
|
227
|
+
return deps.existsSync(explicit) ? explicit : null;
|
|
228
|
+
}
|
|
229
|
+
function tryBundledBinary(deps) {
|
|
230
|
+
try {
|
|
231
|
+
const pkgPath = deps.resolvePackage("@anthropic-ai/claude-code/package.json");
|
|
232
|
+
const bundled = join(dirname(pkgPath), "bin", "claude.exe");
|
|
233
|
+
if (!deps.existsSync(bundled))
|
|
234
|
+
return null;
|
|
235
|
+
const size = deps.statSync(bundled).size;
|
|
236
|
+
if (size <= STUB_SIZE_THRESHOLD)
|
|
237
|
+
return null;
|
|
238
|
+
return bundled;
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function tryPlatformPackage(deps) {
|
|
244
|
+
const binName = deps.platform === "win32" ? "claude.exe" : "claude";
|
|
245
|
+
const candidates = [`@anthropic-ai/claude-code-${deps.platform}-${deps.arch}`];
|
|
246
|
+
if (deps.platform === "linux") {
|
|
247
|
+
candidates.push(`@anthropic-ai/claude-code-${deps.platform}-${deps.arch}-musl`);
|
|
248
|
+
}
|
|
249
|
+
for (const pkg of candidates) {
|
|
250
|
+
try {
|
|
251
|
+
const pkgJson = deps.resolvePackage(`${pkg}/package.json`);
|
|
252
|
+
const candidate = join(dirname(pkgJson), binName);
|
|
253
|
+
if (deps.existsSync(candidate))
|
|
254
|
+
return candidate;
|
|
255
|
+
} catch {}
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
async function tryPathLookup(deps) {
|
|
260
|
+
const cmd = deps.platform === "win32" ? "where claude" : "which claude";
|
|
261
|
+
try {
|
|
262
|
+
const { stdout } = await deps.exec(cmd);
|
|
263
|
+
const candidates = stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
264
|
+
for (const candidate of candidates) {
|
|
265
|
+
if (deps.platform === "win32" && candidate.startsWith("/"))
|
|
266
|
+
continue;
|
|
267
|
+
if (deps.existsSync(candidate))
|
|
268
|
+
return candidate;
|
|
269
|
+
}
|
|
270
|
+
} catch {}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
function tryLegacySdkCliJs(deps) {
|
|
274
|
+
if (!deps.isBun)
|
|
275
|
+
return null;
|
|
276
|
+
try {
|
|
277
|
+
const sdkPath = deps.resolvePackage("@anthropic-ai/claude-agent-sdk");
|
|
278
|
+
const cliJs = join(dirname(sdkPath), "cli.js");
|
|
279
|
+
return deps.existsSync(cliJs) ? cliJs : null;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function resolveClaudeExecutableWithSource(deps = DEFAULT_DEPS) {
|
|
285
|
+
const env2 = tryEnvOverride(deps);
|
|
286
|
+
if (env2)
|
|
287
|
+
return { path: env2, source: "env" };
|
|
288
|
+
const bundled = tryBundledBinary(deps);
|
|
289
|
+
if (bundled)
|
|
290
|
+
return { path: bundled, source: "bundled" };
|
|
291
|
+
const platformPkg = tryPlatformPackage(deps);
|
|
292
|
+
if (platformPkg)
|
|
293
|
+
return { path: platformPkg, source: "platform-package" };
|
|
294
|
+
const pathLookup = await tryPathLookup(deps);
|
|
295
|
+
if (pathLookup)
|
|
296
|
+
return { path: pathLookup, source: "path-lookup" };
|
|
297
|
+
const legacy = tryLegacySdkCliJs(deps);
|
|
298
|
+
if (legacy)
|
|
299
|
+
return { path: legacy, source: "legacy-cli-js" };
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
function resolveClaudeExecutableSync(deps = DEFAULT_DEPS) {
|
|
303
|
+
const env2 = tryEnvOverride(deps);
|
|
304
|
+
if (env2)
|
|
305
|
+
return { path: env2, source: "env" };
|
|
306
|
+
const bundled = tryBundledBinary(deps);
|
|
307
|
+
if (bundled)
|
|
308
|
+
return { path: bundled, source: "bundled" };
|
|
309
|
+
const platformPkg = tryPlatformPackage(deps);
|
|
310
|
+
if (platformPkg)
|
|
311
|
+
return { path: platformPkg, source: "platform-package" };
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function getResolvedClaudeExecutableInfo() {
|
|
315
|
+
return cachedClaudeInfo;
|
|
316
|
+
}
|
|
317
|
+
async function resolveClaudeExecutableAsync() {
|
|
318
|
+
if (cachedClaudeInfo)
|
|
319
|
+
return cachedClaudeInfo.path;
|
|
320
|
+
if (cachedClaudePathPromise)
|
|
321
|
+
return cachedClaudePathPromise;
|
|
322
|
+
cachedClaudePathPromise = (async () => {
|
|
323
|
+
const resolved = await resolveClaudeExecutableWithSource();
|
|
324
|
+
if (resolved) {
|
|
325
|
+
cachedClaudeInfo = resolved;
|
|
326
|
+
return resolved.path;
|
|
327
|
+
}
|
|
328
|
+
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.");
|
|
329
|
+
})();
|
|
330
|
+
try {
|
|
331
|
+
return await cachedClaudePathPromise;
|
|
332
|
+
} finally {
|
|
333
|
+
cachedClaudePathPromise = null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function isClosedControllerError(error) {
|
|
337
|
+
if (!(error instanceof Error))
|
|
338
|
+
return false;
|
|
339
|
+
return error.message.includes("Controller is already closed");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/proxy/profileCli.ts
|
|
343
|
+
var PROFILES_DIR = join2(homedir(), ".config", "meridian", "profiles");
|
|
344
|
+
var CONFIG_FILE = join2(homedir(), ".config", "meridian", "profiles.json");
|
|
20
345
|
var OAUTH_AUTHORIZE_URL = "https://claude.com/cai/oauth/authorize";
|
|
21
346
|
var OAUTH_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
22
347
|
var OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
@@ -33,18 +358,19 @@ function ensureProfilesDir() {
|
|
|
33
358
|
mkdirSync(PROFILES_DIR, { recursive: true });
|
|
34
359
|
}
|
|
35
360
|
function getProfileDir(id) {
|
|
36
|
-
return
|
|
361
|
+
return join2(PROFILES_DIR, id);
|
|
37
362
|
}
|
|
38
363
|
function buildAuthLoginEnv(configDir, _options = {}, baseEnv = process.env) {
|
|
39
|
-
const
|
|
364
|
+
const env2 = { ...baseEnv };
|
|
40
365
|
if (configDir)
|
|
41
|
-
|
|
42
|
-
return
|
|
366
|
+
env2.CLAUDE_CONFIG_DIR = configDir;
|
|
367
|
+
return env2;
|
|
43
368
|
}
|
|
44
369
|
function base64Url(bytes) {
|
|
45
370
|
return bytes.toString("base64url");
|
|
46
371
|
}
|
|
47
|
-
function createManualOAuthSession() {
|
|
372
|
+
function createManualOAuthSession(scopes) {
|
|
373
|
+
const scopeList = scopes ?? OAUTH_SCOPES;
|
|
48
374
|
const codeVerifier = base64Url(randomBytes(32));
|
|
49
375
|
const state = base64Url(randomBytes(32));
|
|
50
376
|
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
|
|
@@ -53,7 +379,7 @@ function createManualOAuthSession() {
|
|
|
53
379
|
url.searchParams.set("client_id", OAUTH_CLIENT_ID);
|
|
54
380
|
url.searchParams.set("response_type", "code");
|
|
55
381
|
url.searchParams.set("redirect_uri", OAUTH_REDIRECT_URI);
|
|
56
|
-
url.searchParams.set("scope",
|
|
382
|
+
url.searchParams.set("scope", scopeList.join(" "));
|
|
57
383
|
url.searchParams.set("code_challenge", codeChallenge);
|
|
58
384
|
url.searchParams.set("code_challenge_method", "S256");
|
|
59
385
|
url.searchParams.set("state", state);
|
|
@@ -78,7 +404,7 @@ function parseAuthorizationCodeInput(input) {
|
|
|
78
404
|
return code ? { code, state } : null;
|
|
79
405
|
}
|
|
80
406
|
function loadProfileConfig() {
|
|
81
|
-
if (!
|
|
407
|
+
if (!existsSync2(CONFIG_FILE))
|
|
82
408
|
return [];
|
|
83
409
|
try {
|
|
84
410
|
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
@@ -186,7 +512,7 @@ async function profileAdd(id, options = {}) {
|
|
|
186
512
|
console.error(` Run: meridian profile list`);
|
|
187
513
|
process.exit(1);
|
|
188
514
|
}
|
|
189
|
-
const defaultClaudeDir =
|
|
515
|
+
const defaultClaudeDir = join2(homedir(), ".claude");
|
|
190
516
|
const alreadyImported = profiles.some((p) => p.claudeConfigDir === defaultClaudeDir);
|
|
191
517
|
if (!alreadyImported) {
|
|
192
518
|
const defaultAuth = getAuthStatus(defaultClaudeDir);
|
|
@@ -317,7 +643,7 @@ function dirsToRemoveOnProfileRemove(profile, profilesDir) {
|
|
|
317
643
|
dirs.push(profile.claudeConfigDir);
|
|
318
644
|
}
|
|
319
645
|
if (profile.oauthToken || profile.type === "oauth-token") {
|
|
320
|
-
const isolationDir =
|
|
646
|
+
const isolationDir = join2(profilesDir, profile.id);
|
|
321
647
|
if (!dirs.includes(isolationDir))
|
|
322
648
|
dirs.push(isolationDir);
|
|
323
649
|
}
|
|
@@ -339,7 +665,7 @@ function profileRemove(id) {
|
|
|
339
665
|
profiles.splice(idx, 1);
|
|
340
666
|
saveProfileConfig(profiles);
|
|
341
667
|
for (const dir of dirsToRemove) {
|
|
342
|
-
if (
|
|
668
|
+
if (existsSync2(dir))
|
|
343
669
|
rmSync(dir, { recursive: true, force: true });
|
|
344
670
|
}
|
|
345
671
|
console.log(`\x1B[32m✓ Profile "${id}" removed.\x1B[0m`);
|
|
@@ -498,16 +824,5 @@ Examples:
|
|
|
498
824
|
meridian profile switch work # Switch to work account
|
|
499
825
|
meridian profile list # Show all profiles`);
|
|
500
826
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
profileRemove,
|
|
504
|
-
profileLogin,
|
|
505
|
-
profileList,
|
|
506
|
-
profileHelp,
|
|
507
|
-
profileAddOauthToken,
|
|
508
|
-
profileAdd,
|
|
509
|
-
parseAuthorizationCodeInput,
|
|
510
|
-
dirsToRemoveOnProfileRemove,
|
|
511
|
-
createManualOAuthSession,
|
|
512
|
-
buildAuthLoginEnv
|
|
513
|
-
};
|
|
827
|
+
|
|
828
|
+
export { env, envBool, resolvePassthrough, envInt, init_env, CANONICAL_SONNET_MODEL, resolveSdkModelDefaults, explicitModelPin, mapModelToClaudeModel, recordExtendedContextUnavailable, stripExtendedContext, hasExtendedContext, getAuthCacheInfo, getClaudeAuthStatusAsync, getResolvedClaudeExecutableInfo, resolveClaudeExecutableAsync, isClosedControllerError, OAUTH_TOKEN_URL, OAUTH_CLIENT_ID, OAUTH_REDIRECT_URI, buildAuthLoginEnv, createManualOAuthSession, parseAuthorizationCodeInput, profileAdd, profileAddOauthToken, profileList, dirsToRemoveOnProfileRemove, profileRemove, profileSwitch, profileLogin, profileHelp };
|