@rynfar/meridian 1.27.5 → 1.28.1
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 +78 -0
- package/dist/cli-340h1chz.js +33 -0
- package/dist/cli-g9ypdz51.js +113 -0
- package/dist/cli-vdp9s10c.js +115 -0
- package/dist/{cli-td95gtqj.js → cli-zcxn6xmn.js} +188 -35
- package/dist/cli.js +39 -2
- package/dist/profileCli-pdqrpw0m.js +239 -0
- package/dist/profilePage-9nkbct3w.js +275 -0
- package/dist/profiles-ntgacztq.js +26 -0
- package/dist/proxy/models.d.ts +13 -1
- package/dist/proxy/models.d.ts.map +1 -1
- package/dist/proxy/profiles.d.ts +81 -0
- package/dist/proxy/profiles.d.ts.map +1 -0
- package/dist/proxy/query.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/settings.d.ts +22 -0
- package/dist/proxy/settings.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +5 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/server.js +4 -1
- package/dist/telemetry/dashboard.d.ts +1 -1
- package/dist/telemetry/dashboard.d.ts.map +1 -1
- package/dist/telemetry/landing.d.ts +1 -1
- package/dist/telemetry/landing.d.ts.map +1 -1
- package/dist/telemetry/profileBar.d.ts +11 -0
- package/dist/telemetry/profileBar.d.ts.map +1 -0
- package/dist/telemetry/profilePage.d.ts +6 -0
- package/dist/telemetry/profilePage.d.ts.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,8 +61,70 @@ Meridian bridges that gap. It runs locally, accepts standard Anthropic API reque
|
|
|
61
61
|
- **Auto token refresh** — expired OAuth tokens are refreshed automatically; requests continue without interruption
|
|
62
62
|
- **Passthrough mode** — forward tool calls to the client instead of executing internally
|
|
63
63
|
- **Multimodal** — images, documents, and file attachments pass through to Claude
|
|
64
|
+
- **Multi-profile** — switch between Claude accounts instantly, no restart needed
|
|
64
65
|
- **Telemetry dashboard** — real-time performance metrics at `/telemetry`
|
|
65
66
|
|
|
67
|
+
## Multi-Profile Support
|
|
68
|
+
|
|
69
|
+
Meridian can route requests to different Claude accounts. Each **profile** is a named auth context — a separate Claude login with its own OAuth tokens. Switch between personal and work accounts, or share a single Meridian instance across teams.
|
|
70
|
+
|
|
71
|
+
### Adding profiles
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Add your personal account
|
|
75
|
+
meridian profile add personal
|
|
76
|
+
# → Opens browser for Claude login
|
|
77
|
+
|
|
78
|
+
# Add your work account (sign out of claude.ai first, then sign into the work account)
|
|
79
|
+
meridian profile add work
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> **⚠ 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.
|
|
83
|
+
|
|
84
|
+
### Switching profiles
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# CLI (while proxy is running)
|
|
88
|
+
meridian profile switch work
|
|
89
|
+
|
|
90
|
+
# Per-request header (any agent)
|
|
91
|
+
curl -H "x-meridian-profile: work" ...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
You can also switch profiles from the web UI at `http://127.0.0.1:3456/profiles` — a dropdown appears in the nav bar on all pages when profiles are configured.
|
|
95
|
+
|
|
96
|
+
### Profile commands
|
|
97
|
+
|
|
98
|
+
| Command | Description |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| `meridian profile add <name>` | Add a profile and authenticate via browser |
|
|
101
|
+
| `meridian profile list` | List profiles and auth status |
|
|
102
|
+
| `meridian profile switch <name>` | Switch the active profile (requires running proxy) |
|
|
103
|
+
| `meridian profile login <name>` | Re-authenticate an expired profile |
|
|
104
|
+
| `meridian profile remove <name>` | Remove a profile and its credentials |
|
|
105
|
+
|
|
106
|
+
### How it works
|
|
107
|
+
|
|
108
|
+
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:
|
|
109
|
+
|
|
110
|
+
1. `x-meridian-profile` request header (per-request override)
|
|
111
|
+
2. Active profile (set via `meridian profile switch` or the web UI)
|
|
112
|
+
3. First configured profile
|
|
113
|
+
|
|
114
|
+
Session state is scoped per profile — switching accounts won't cross-contaminate conversation history.
|
|
115
|
+
|
|
116
|
+
### Environment variable configuration
|
|
117
|
+
|
|
118
|
+
For advanced setups (CI, Docker), profiles can also be provided via environment variable:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
export MERIDIAN_PROFILES='[{"id":"personal","claudeConfigDir":"/path/to/config1"},{"id":"work","claudeConfigDir":"/path/to/config2"}]'
|
|
122
|
+
export MERIDIAN_DEFAULT_PROFILE=personal
|
|
123
|
+
meridian
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
66
128
|
## Agent Setup
|
|
67
129
|
|
|
68
130
|
### OpenCode
|
|
@@ -282,8 +344,14 @@ src/proxy/
|
|
|
282
344
|
│ ├── lineage.ts ← Per-message hashing, mutation classification (pure)
|
|
283
345
|
│ ├── fingerprint.ts ← Conversation fingerprinting
|
|
284
346
|
│ └── cache.ts ← LRU session caches
|
|
347
|
+
├── profiles.ts ← Multi-profile: resolve, list, switch auth contexts
|
|
348
|
+
├── profileCli.ts ← CLI commands for profile management
|
|
285
349
|
├── sessionStore.ts ← Cross-proxy file-based session persistence
|
|
286
350
|
└── passthroughTools.ts ← Tool forwarding mode
|
|
351
|
+
telemetry/
|
|
352
|
+
├── ...
|
|
353
|
+
├── profileBar.ts ← Shared profile switcher bar
|
|
354
|
+
└── profilePage.ts ← Profile management page
|
|
287
355
|
plugin/
|
|
288
356
|
└── meridian.ts ← OpenCode plugin (session headers + agent mode)
|
|
289
357
|
```
|
|
@@ -333,6 +401,8 @@ Implement the `AgentAdapter` interface in `src/proxy/adapters/`. See [`adapters/
|
|
|
333
401
|
| `MERIDIAN_NO_FILE_CHANGES` | `CLAUDE_PROXY_NO_FILE_CHANGES` | unset | Disable "Files changed" summary in responses |
|
|
334
402
|
| `MERIDIAN_SONNET_MODEL` | `CLAUDE_PROXY_SONNET_MODEL` | `sonnet` | Sonnet context tier: `sonnet` (200k, default) or `sonnet[1m]` (1M, requires Extra Usage†) |
|
|
335
403
|
| `MERIDIAN_DEFAULT_AGENT` | — | `opencode` | Default adapter for unrecognized agents: `opencode`, `pi`, `crush`, `droid`, `passthrough`. Requires restart. |
|
|
404
|
+
| `MERIDIAN_PROFILES` | — | unset | JSON array of profile configs (overrides disk discovery). See [Multi-Profile Support](#multi-profile-support). |
|
|
405
|
+
| `MERIDIAN_DEFAULT_PROFILE` | — | *(first profile)* | Default profile ID when no header is sent |
|
|
336
406
|
|
|
337
407
|
†Sonnet 1M requires Extra Usage on all plans including Max ([docs](https://code.claude.com/docs/en/model-config#extended-context)). Opus 1M is included with Max/Team/Enterprise at no extra cost.
|
|
338
408
|
|
|
@@ -351,6 +421,9 @@ Implement the `AgentAdapter` interface in `src/proxy/adapters/`. See [`adapters/
|
|
|
351
421
|
| `GET /telemetry/requests` | Recent request metrics (JSON) |
|
|
352
422
|
| `GET /telemetry/summary` | Aggregate statistics (JSON) |
|
|
353
423
|
| `GET /telemetry/logs` | Diagnostic logs (JSON) |
|
|
424
|
+
| `GET /profiles` | Profile management page |
|
|
425
|
+
| `GET /profiles/list` | List profiles with auth status (JSON) |
|
|
426
|
+
| `POST /profiles/active` | Switch the active profile |
|
|
354
427
|
|
|
355
428
|
Health response example:
|
|
356
429
|
|
|
@@ -371,6 +444,11 @@ Health response example:
|
|
|
371
444
|
|---------|-------------|
|
|
372
445
|
| `meridian` | Start the proxy server |
|
|
373
446
|
| `meridian setup` | Configure the OpenCode plugin in `~/.config/opencode/opencode.json` |
|
|
447
|
+
| `meridian profile add <name>` | Add a profile and authenticate via browser |
|
|
448
|
+
| `meridian profile list` | List all profiles and their auth status |
|
|
449
|
+
| `meridian profile switch <name>` | Switch the active profile (requires running proxy) |
|
|
450
|
+
| `meridian profile login <name>` | Re-authenticate an expired profile |
|
|
451
|
+
| `meridian profile remove <name>` | Remove a profile and its credentials |
|
|
374
452
|
| `meridian refresh-token` | Manually refresh the Claude OAuth token (exits 0/1) |
|
|
375
453
|
|
|
376
454
|
## Programmatic API
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/proxy/settings.ts
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
var SETTINGS_FILE = join(homedir(), ".config", "meridian", "settings.json");
|
|
6
|
+
function loadSettings() {
|
|
7
|
+
try {
|
|
8
|
+
if (!existsSync(SETTINGS_FILE))
|
|
9
|
+
return {};
|
|
10
|
+
return JSON.parse(readFileSync(SETTINGS_FILE, "utf-8"));
|
|
11
|
+
} catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function saveSettings(updates) {
|
|
16
|
+
const current = loadSettings();
|
|
17
|
+
const merged = { ...current, ...updates };
|
|
18
|
+
try {
|
|
19
|
+
mkdirSync(dirname(SETTINGS_FILE), { recursive: true });
|
|
20
|
+
writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2) + `
|
|
21
|
+
`, { mode: 384 });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.warn(`[meridian] Failed to write ${SETTINGS_FILE}: ${err instanceof Error ? err.message : err}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getSetting(key) {
|
|
27
|
+
return loadSettings()[key];
|
|
28
|
+
}
|
|
29
|
+
function setSetting(key, value) {
|
|
30
|
+
saveSettings({ [key]: value });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { getSetting, setSetting };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/telemetry/profileBar.ts
|
|
2
|
+
var profileBarCss = `
|
|
3
|
+
.meridian-profile-bar {
|
|
4
|
+
position: sticky; top: 0; z-index: 100;
|
|
5
|
+
display: none; align-items: center; gap: 12px;
|
|
6
|
+
padding: 8px 24px;
|
|
7
|
+
background: rgba(13, 17, 23, 0.92);
|
|
8
|
+
backdrop-filter: blur(12px);
|
|
9
|
+
border-bottom: 1px solid var(--border, #30363d);
|
|
10
|
+
font-size: 12px;
|
|
11
|
+
color: var(--muted, #8b949e);
|
|
12
|
+
}
|
|
13
|
+
.meridian-profile-bar.visible { display: flex; }
|
|
14
|
+
.meridian-profile-bar .profile-label {
|
|
15
|
+
font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;
|
|
16
|
+
font-size: 10px; color: var(--muted, #8b949e);
|
|
17
|
+
}
|
|
18
|
+
.meridian-profile-bar select {
|
|
19
|
+
background: var(--surface, #161b22); color: var(--text, #e6edf3);
|
|
20
|
+
border: 1px solid var(--border, #30363d); border-radius: 6px;
|
|
21
|
+
padding: 4px 24px 4px 10px; font-size: 12px; cursor: pointer;
|
|
22
|
+
appearance: none;
|
|
23
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 5l3 3 3-3' fill='none' stroke='%238b949e' stroke-width='1.5'/%3E%3C/svg%3E");
|
|
24
|
+
background-repeat: no-repeat; background-position: right 6px center;
|
|
25
|
+
}
|
|
26
|
+
.meridian-profile-bar select:hover { border-color: var(--accent, #58a6ff); }
|
|
27
|
+
.meridian-profile-bar select:focus { outline: none; border-color: var(--accent, #58a6ff); box-shadow: 0 0 0 1px var(--accent, #58a6ff); }
|
|
28
|
+
.meridian-profile-bar .profile-status {
|
|
29
|
+
font-size: 11px; color: var(--green, #3fb950); opacity: 0;
|
|
30
|
+
transition: opacity 0.3s;
|
|
31
|
+
}
|
|
32
|
+
.meridian-profile-bar .profile-status.show { opacity: 1; }
|
|
33
|
+
.meridian-profile-bar .profile-type {
|
|
34
|
+
font-size: 10px; padding: 2px 8px; border-radius: 4px;
|
|
35
|
+
background: var(--surface, #161b22); border: 1px solid var(--border, #30363d);
|
|
36
|
+
}
|
|
37
|
+
.meridian-profile-bar .spacer { flex: 1; }
|
|
38
|
+
.meridian-profile-bar .profile-nav a {
|
|
39
|
+
color: var(--muted, #8b949e); text-decoration: none; font-size: 11px;
|
|
40
|
+
padding: 4px 8px; border-radius: 4px; transition: color 0.15s;
|
|
41
|
+
}
|
|
42
|
+
.meridian-profile-bar .profile-nav a:hover { color: var(--text, #e6edf3); }
|
|
43
|
+
.meridian-profile-bar .profile-nav a.active { color: var(--accent, #58a6ff); }
|
|
44
|
+
`;
|
|
45
|
+
var profileBarHtml = `
|
|
46
|
+
<div class="meridian-profile-bar" id="meridianProfileBar">
|
|
47
|
+
<span class="profile-label">Profile</span>
|
|
48
|
+
<select id="meridianProfileSelect"></select>
|
|
49
|
+
<span class="profile-type" id="meridianProfileType"></span>
|
|
50
|
+
<span class="profile-status" id="meridianProfileStatus">✓ Switched</span>
|
|
51
|
+
<div class="spacer"></div>
|
|
52
|
+
<div class="profile-nav">
|
|
53
|
+
<a href="/" id="nav-home">Home</a>
|
|
54
|
+
<a href="/profiles" id="nav-profiles">Profiles</a>
|
|
55
|
+
<a href="/telemetry" id="nav-telemetry">Telemetry</a>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
`;
|
|
59
|
+
var profileBarJs = `
|
|
60
|
+
(function() {
|
|
61
|
+
var profileBar = document.getElementById('meridianProfileBar');
|
|
62
|
+
var profileSelect = document.getElementById('meridianProfileSelect');
|
|
63
|
+
var profileType = document.getElementById('meridianProfileType');
|
|
64
|
+
var profileStatus = document.getElementById('meridianProfileStatus');
|
|
65
|
+
var statusTimeout;
|
|
66
|
+
|
|
67
|
+
// Highlight active nav link
|
|
68
|
+
var path = location.pathname;
|
|
69
|
+
var navLinks = document.querySelectorAll('.profile-nav a');
|
|
70
|
+
navLinks.forEach(function(a) {
|
|
71
|
+
if (a.getAttribute('href') === path || (path === '/telemetry' && a.id === 'nav-telemetry') || (path === '/' && a.id === 'nav-home')) {
|
|
72
|
+
a.classList.add('active');
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
77
|
+
|
|
78
|
+
function loadProfiles() {
|
|
79
|
+
fetch('/profiles/list').then(function(r) { return r.json(); }).then(function(data) {
|
|
80
|
+
if (!data.profiles || data.profiles.length === 0) {
|
|
81
|
+
profileBar.classList.remove('visible');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
profileBar.classList.add('visible');
|
|
85
|
+
var current = data.profiles.find(function(p) { return p.isActive; });
|
|
86
|
+
profileSelect.innerHTML = data.profiles.map(function(p) {
|
|
87
|
+
return '<option value="' + esc(p.id) + '"' + (p.isActive ? ' selected' : '') + '>' + esc(p.id) + '</option>';
|
|
88
|
+
}).join('');
|
|
89
|
+
if (current) profileType.textContent = current.type;
|
|
90
|
+
}).catch(function() {});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
profileSelect.onchange = function() {
|
|
94
|
+
fetch('/profiles/active', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
body: JSON.stringify({ profile: profileSelect.value })
|
|
98
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
99
|
+
if (data.success) {
|
|
100
|
+
profileStatus.classList.add('show');
|
|
101
|
+
clearTimeout(statusTimeout);
|
|
102
|
+
statusTimeout = setTimeout(function() { profileStatus.classList.remove('show'); }, 2000);
|
|
103
|
+
loadProfiles();
|
|
104
|
+
}
|
|
105
|
+
}).catch(function() {});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
loadProfiles();
|
|
109
|
+
setInterval(loadProfiles, 10000);
|
|
110
|
+
})();
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
export { profileBarCss, profileBarHtml, profileBarJs };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSetting,
|
|
3
|
+
setSetting
|
|
4
|
+
} from "./cli-340h1chz.js";
|
|
5
|
+
|
|
6
|
+
// src/proxy/profiles.ts
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
var CONFIG_FILE = join(homedir(), ".config", "meridian", "profiles.json");
|
|
11
|
+
var DISK_CACHE_TTL_MS = 5000;
|
|
12
|
+
var diskProfilesCache = [];
|
|
13
|
+
var diskProfilesCacheAt = 0;
|
|
14
|
+
function loadProfilesFromDisk() {
|
|
15
|
+
if (diskProfilesCacheAt > 0 && Date.now() - diskProfilesCacheAt < DISK_CACHE_TTL_MS) {
|
|
16
|
+
return diskProfilesCache;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
20
|
+
diskProfilesCache = [];
|
|
21
|
+
} else {
|
|
22
|
+
diskProfilesCache = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
23
|
+
}
|
|
24
|
+
diskProfilesCacheAt = Date.now();
|
|
25
|
+
return diskProfilesCache;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.warn(`[meridian] Failed to read ${CONFIG_FILE}: ${err instanceof Error ? err.message : err}`);
|
|
28
|
+
diskProfilesCacheAt = Date.now();
|
|
29
|
+
diskProfilesCache = [];
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
var DEFAULT_PROFILE_ID = "default";
|
|
34
|
+
var activeProfileId;
|
|
35
|
+
function setActiveProfile(profileId) {
|
|
36
|
+
activeProfileId = profileId;
|
|
37
|
+
setSetting("activeProfile", profileId);
|
|
38
|
+
}
|
|
39
|
+
function getActiveProfileId() {
|
|
40
|
+
return activeProfileId;
|
|
41
|
+
}
|
|
42
|
+
function resetActiveProfile() {
|
|
43
|
+
activeProfileId = undefined;
|
|
44
|
+
}
|
|
45
|
+
function restoreActiveProfile(configProfiles) {
|
|
46
|
+
if (activeProfileId)
|
|
47
|
+
return;
|
|
48
|
+
if (!diskDiscoveryEnabled)
|
|
49
|
+
return;
|
|
50
|
+
const saved = getSetting("activeProfile");
|
|
51
|
+
if (!saved)
|
|
52
|
+
return;
|
|
53
|
+
const effective = getEffectiveProfiles(configProfiles);
|
|
54
|
+
if (effective.length === 0 || effective.some((p) => p.id === saved)) {
|
|
55
|
+
activeProfileId = saved;
|
|
56
|
+
} else {
|
|
57
|
+
console.warn(`[meridian] Saved active profile "${saved}" not found. Using default.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
var diskDiscoveryEnabled = false;
|
|
61
|
+
function enableDiskProfileDiscovery() {
|
|
62
|
+
diskDiscoveryEnabled = true;
|
|
63
|
+
}
|
|
64
|
+
function getEffectiveProfiles(configProfiles) {
|
|
65
|
+
const fromConfig = configProfiles ?? [];
|
|
66
|
+
if (!diskDiscoveryEnabled)
|
|
67
|
+
return fromConfig;
|
|
68
|
+
const fromDisk = loadProfilesFromDisk();
|
|
69
|
+
const configIds = new Set(fromConfig.map((p) => p.id));
|
|
70
|
+
return [...fromConfig, ...fromDisk.filter((p) => !configIds.has(p.id))];
|
|
71
|
+
}
|
|
72
|
+
function hasProfiles(configProfiles) {
|
|
73
|
+
return getEffectiveProfiles(configProfiles).length > 0;
|
|
74
|
+
}
|
|
75
|
+
function resolveProfile(profiles, defaultProfile, requestedId) {
|
|
76
|
+
const effective = getEffectiveProfiles(profiles);
|
|
77
|
+
if (effective.length === 0) {
|
|
78
|
+
return { id: DEFAULT_PROFILE_ID, type: "claude-max", env: {} };
|
|
79
|
+
}
|
|
80
|
+
const resolvedId = requestedId || activeProfileId || defaultProfile || effective[0].id;
|
|
81
|
+
const profile = effective.find((p) => p.id === resolvedId);
|
|
82
|
+
if (!profile) {
|
|
83
|
+
console.warn(`[meridian] Unknown profile "${resolvedId}". Using first configured profile.`);
|
|
84
|
+
return buildResolvedProfile(effective[0]);
|
|
85
|
+
}
|
|
86
|
+
return buildResolvedProfile(profile);
|
|
87
|
+
}
|
|
88
|
+
function buildResolvedProfile(profile) {
|
|
89
|
+
const type = profile.type ?? "claude-max";
|
|
90
|
+
if (type === "api") {
|
|
91
|
+
const env2 = {};
|
|
92
|
+
if (profile.apiKey)
|
|
93
|
+
env2.ANTHROPIC_API_KEY = profile.apiKey;
|
|
94
|
+
if (profile.baseUrl)
|
|
95
|
+
env2.ANTHROPIC_BASE_URL = profile.baseUrl;
|
|
96
|
+
return { id: profile.id, type, env: env2 };
|
|
97
|
+
}
|
|
98
|
+
const env = {};
|
|
99
|
+
if (profile.claudeConfigDir)
|
|
100
|
+
env.CLAUDE_CONFIG_DIR = profile.claudeConfigDir;
|
|
101
|
+
return { id: profile.id, type, env };
|
|
102
|
+
}
|
|
103
|
+
function listProfiles(profiles, defaultProfile) {
|
|
104
|
+
const effective = getEffectiveProfiles(profiles);
|
|
105
|
+
if (effective.length === 0)
|
|
106
|
+
return [];
|
|
107
|
+
const currentActive = activeProfileId || defaultProfile || effective[0].id;
|
|
108
|
+
return effective.map((p) => ({
|
|
109
|
+
id: p.id,
|
|
110
|
+
type: p.type ?? "claude-max",
|
|
111
|
+
isActive: p.id === currentActive
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { loadProfilesFromDisk, setActiveProfile, getActiveProfileId, resetActiveProfile, restoreActiveProfile, enableDiskProfileDiscovery, getEffectiveProfiles, hasProfiles, resolveProfile, listProfiles };
|