@plexor-dev/claude-code-plugin-staging 0.1.0-beta.18 → 0.1.0-beta.19
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/commands/plexor-enabled.js +2 -2
- package/commands/plexor-setup.md +9 -7
- package/commands/plexor-status.js +34 -14
- package/commands/plexor-uninstall.js +34 -8
- package/lib/config-utils.js +191 -1
- package/lib/settings-manager.js +98 -16
- package/package.json +1 -1
- package/scripts/postinstall.js +42 -33
- package/scripts/uninstall.js +49 -6
|
@@ -240,7 +240,7 @@ function main() {
|
|
|
240
240
|
console.log(`│ ✗ Cannot Enable Plexor │`);
|
|
241
241
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
242
242
|
console.log(`│ No API key configured. │`);
|
|
243
|
-
console.log(`│ Run /plexor-
|
|
243
|
+
console.log(`│ Run /plexor-setup first. │`);
|
|
244
244
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
245
245
|
console.log(`│ Get your API key at: │`);
|
|
246
246
|
console.log(`│ https://plexor.dev/dashboard/api-keys │`);
|
|
@@ -256,7 +256,7 @@ function main() {
|
|
|
256
256
|
console.log(`│ Invalid API key format in config. │`);
|
|
257
257
|
console.log(`│ Keys must start with "plx_" (20+ chars). │`);
|
|
258
258
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
259
|
-
console.log(`│ Run /plexor-
|
|
259
|
+
console.log(`│ Run /plexor-setup to fix it. │`);
|
|
260
260
|
console.log(`└─────────────────────────────────────────────┘`);
|
|
261
261
|
process.exit(1);
|
|
262
262
|
}
|
package/commands/plexor-setup.md
CHANGED
|
@@ -45,7 +45,7 @@ Options:
|
|
|
45
45
|
|
|
46
46
|
**Step 3A: Claude MAX User Setup**
|
|
47
47
|
|
|
48
|
-
If user selected "
|
|
48
|
+
If user selected the "Claude MAX subscription (Pro/Team/Enterprise)" option:
|
|
49
49
|
|
|
50
50
|
1. Ask for their Plexor API key:
|
|
51
51
|
"Please provide your Plexor API key (starts with 'plx_')."
|
|
@@ -75,11 +75,12 @@ If user selected "Yes, I have Claude MAX":
|
|
|
75
75
|
{
|
|
76
76
|
"env": {
|
|
77
77
|
"ANTHROPIC_BASE_URL": "https://staging.api.plexor.dev/gateway/anthropic",
|
|
78
|
-
"ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]"
|
|
78
|
+
"ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]",
|
|
79
|
+
"ANTHROPIC_API_KEY": "[user's Plexor key]"
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
```
|
|
82
|
-
Note: Preserve
|
|
83
|
+
Note: Preserve unrelated settings, but replace `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, and `ANTHROPIC_API_KEY` in the env block. This is required when the user previously configured Claude with direct API auth.
|
|
83
84
|
|
|
84
85
|
4. Show the user:
|
|
85
86
|
```
|
|
@@ -107,7 +108,7 @@ Changes take effect immediately in all Claude Code sessions!
|
|
|
107
108
|
|
|
108
109
|
**Step 3B: API Key User Setup**
|
|
109
110
|
|
|
110
|
-
If user selected "
|
|
111
|
+
If user selected the "Pay-per-use via Plexor" option:
|
|
111
112
|
|
|
112
113
|
1. Ask for their Plexor API key:
|
|
113
114
|
"Please provide your Plexor API key (starts with 'plx_')."
|
|
@@ -137,11 +138,12 @@ If user selected "No, I'll use a Plexor API key":
|
|
|
137
138
|
{
|
|
138
139
|
"env": {
|
|
139
140
|
"ANTHROPIC_BASE_URL": "https://staging.api.plexor.dev/gateway/anthropic",
|
|
140
|
-
"ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]"
|
|
141
|
+
"ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]",
|
|
142
|
+
"ANTHROPIC_API_KEY": "[user's Plexor key]"
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
```
|
|
144
|
-
Note: Preserve
|
|
146
|
+
Note: Preserve unrelated settings, but replace `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, and `ANTHROPIC_API_KEY` in the env block. This is required when the user previously configured Claude with direct API auth.
|
|
145
147
|
|
|
146
148
|
4. Show the user:
|
|
147
149
|
```
|
|
@@ -169,7 +171,7 @@ Changes take effect immediately in all Claude Code sessions!
|
|
|
169
171
|
|
|
170
172
|
**NOTES**:
|
|
171
173
|
- The `~/.claude/settings.json` env block is the KEY mechanism that routes Claude Code through Plexor
|
|
172
|
-
-
|
|
174
|
+
- Set both `ANTHROPIC_AUTH_TOKEN` and `ANTHROPIC_API_KEY` to the Plexor key so Claude cannot bypass the Plexor gateway with a previously saved direct API key
|
|
173
175
|
- Changes take effect immediately - no shell restart needed
|
|
174
176
|
|
|
175
177
|
After completing all steps, STOP. DO NOT restart the workflow. DO NOT re-execute any commands.
|
|
@@ -13,6 +13,26 @@ const https = require('https');
|
|
|
13
13
|
const { HOME_DIR, CONFIG_PATH, SESSION_PATH, SESSION_TIMEOUT_MS } = require('../lib/constants');
|
|
14
14
|
const CLAUDE_SETTINGS_PATH = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
15
15
|
|
|
16
|
+
function isManagedGatewayUrl(baseUrl = '') {
|
|
17
|
+
return (
|
|
18
|
+
baseUrl.includes('plexor') ||
|
|
19
|
+
baseUrl.includes('staging.api') ||
|
|
20
|
+
baseUrl.includes('ngrok') ||
|
|
21
|
+
baseUrl.includes('localtunnel') ||
|
|
22
|
+
baseUrl.includes('localhost') ||
|
|
23
|
+
baseUrl.includes('127.0.0.1')
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getPlexorAuthKey(env = {}) {
|
|
28
|
+
const apiKey = env.ANTHROPIC_API_KEY || '';
|
|
29
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
30
|
+
|
|
31
|
+
if (apiKey.startsWith('plx_')) return apiKey;
|
|
32
|
+
if (authToken.startsWith('plx_')) return authToken;
|
|
33
|
+
return apiKey || authToken || '';
|
|
34
|
+
}
|
|
35
|
+
|
|
16
36
|
/**
|
|
17
37
|
* Check if Claude Code is actually routing through Plexor
|
|
18
38
|
* by reading ~/.claude/settings.json
|
|
@@ -22,10 +42,10 @@ function getRoutingStatus() {
|
|
|
22
42
|
const data = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
|
|
23
43
|
const settings = JSON.parse(data);
|
|
24
44
|
const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
25
|
-
const
|
|
26
|
-
const isPlexorRouting =
|
|
45
|
+
const authKey = getPlexorAuthKey(settings.env);
|
|
46
|
+
const isPlexorRouting = isManagedGatewayUrl(baseUrl);
|
|
27
47
|
return {
|
|
28
|
-
active: isPlexorRouting &&
|
|
48
|
+
active: isPlexorRouting && !!authKey,
|
|
29
49
|
baseUrl,
|
|
30
50
|
isStaging: baseUrl.includes('staging')
|
|
31
51
|
};
|
|
@@ -44,14 +64,14 @@ function detectPartialState() {
|
|
|
44
64
|
const data = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
|
|
45
65
|
const settings = JSON.parse(data);
|
|
46
66
|
const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
47
|
-
const
|
|
48
|
-
const isPlexorUrl =
|
|
67
|
+
const authKey = getPlexorAuthKey(settings.env);
|
|
68
|
+
const isPlexorUrl = isManagedGatewayUrl(baseUrl);
|
|
49
69
|
|
|
50
|
-
if (isPlexorUrl && !
|
|
51
|
-
return { partial: true, issue: 'Plexor URL set but no auth
|
|
70
|
+
if (isPlexorUrl && !authKey) {
|
|
71
|
+
return { partial: true, issue: 'Plexor URL set but no Plexor auth key' };
|
|
52
72
|
}
|
|
53
|
-
if (isPlexorUrl && !
|
|
54
|
-
return { partial: true, issue: 'Plexor URL set but auth
|
|
73
|
+
if (isPlexorUrl && !authKey.startsWith('plx_')) {
|
|
74
|
+
return { partial: true, issue: 'Plexor URL set but auth key is not a Plexor key' };
|
|
55
75
|
}
|
|
56
76
|
return { partial: false, issue: null };
|
|
57
77
|
} catch {
|
|
@@ -102,7 +122,7 @@ function loadConfig() {
|
|
|
102
122
|
return config;
|
|
103
123
|
} catch (err) {
|
|
104
124
|
if (err instanceof SyntaxError) {
|
|
105
|
-
console.log('Config file is corrupted. Run /plexor-
|
|
125
|
+
console.log('Config file is corrupted. Run /plexor-setup to reconfigure.');
|
|
106
126
|
}
|
|
107
127
|
return null;
|
|
108
128
|
}
|
|
@@ -154,7 +174,7 @@ async function main() {
|
|
|
154
174
|
// Read config with integrity checking
|
|
155
175
|
const config = loadConfig();
|
|
156
176
|
if (!config) {
|
|
157
|
-
console.log('Not configured. Run /plexor-
|
|
177
|
+
console.log('Not configured. Run /plexor-setup.');
|
|
158
178
|
process.exit(1);
|
|
159
179
|
}
|
|
160
180
|
|
|
@@ -166,14 +186,14 @@ async function main() {
|
|
|
166
186
|
const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
|
|
167
187
|
|
|
168
188
|
if (!apiKey) {
|
|
169
|
-
console.log('Not authenticated. Run /plexor-
|
|
189
|
+
console.log('Not authenticated. Run /plexor-setup.');
|
|
170
190
|
process.exit(1);
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
// Validate API key format
|
|
174
194
|
if (!isValidApiKeyFormat(apiKey)) {
|
|
175
195
|
console.log('Invalid API key format. Keys must start with "plx_" and be at least 20 characters.');
|
|
176
|
-
console.log('Run /plexor-
|
|
196
|
+
console.log('Run /plexor-setup with a valid Plexor API key.');
|
|
177
197
|
process.exit(1);
|
|
178
198
|
}
|
|
179
199
|
|
|
@@ -278,7 +298,7 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
|
|
|
278
298
|
const partialState = detectPartialState();
|
|
279
299
|
if (partialState.partial) {
|
|
280
300
|
console.log(` ⚠ PARTIAL STATE DETECTED: ${partialState.issue}`);
|
|
281
|
-
console.log(` Run /plexor-
|
|
301
|
+
console.log(` Run /plexor-setup to repair, or /plexor-logout to disable routing\n`);
|
|
282
302
|
}
|
|
283
303
|
|
|
284
304
|
// Check for state mismatch between config enabled flag and routing status
|
|
@@ -99,17 +99,43 @@ function disableRoutingManually() {
|
|
|
99
99
|
return { success: true, message: 'No env block in settings' };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
const isManagedGatewayUrl = (baseUrl = '') =>
|
|
103
|
+
baseUrl.includes('plexor') ||
|
|
104
|
+
baseUrl.includes('staging.api') ||
|
|
105
|
+
baseUrl.includes('localhost') ||
|
|
106
|
+
baseUrl.includes('127.0.0.1') ||
|
|
107
|
+
baseUrl.includes('ngrok') ||
|
|
108
|
+
baseUrl.includes('localtunnel');
|
|
109
|
+
const isPlexorApiKey = (value = '') =>
|
|
110
|
+
typeof value === 'string' && value.startsWith('plx_');
|
|
111
|
+
|
|
112
|
+
const hasManagedBaseUrl = isManagedGatewayUrl(settings.env.ANTHROPIC_BASE_URL || '');
|
|
113
|
+
const hasPlexorAuthToken = isPlexorApiKey(settings.env.ANTHROPIC_AUTH_TOKEN || '');
|
|
114
|
+
const hasPlexorApiKey = isPlexorApiKey(settings.env.ANTHROPIC_API_KEY || '');
|
|
115
|
+
|
|
116
|
+
if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey) {
|
|
107
117
|
return { success: true, message: 'Plexor routing not active' };
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
if (hasManagedBaseUrl) {
|
|
121
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
122
|
+
}
|
|
123
|
+
if (hasPlexorAuthToken) {
|
|
124
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
125
|
+
}
|
|
126
|
+
if (hasPlexorApiKey) {
|
|
127
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!settings.env.ANTHROPIC_API_KEY && settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY) {
|
|
131
|
+
settings.env.ANTHROPIC_API_KEY = settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
|
|
132
|
+
}
|
|
133
|
+
if (!settings.env.ANTHROPIC_AUTH_TOKEN && settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN) {
|
|
134
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
delete settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
|
|
138
|
+
delete settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
|
|
113
139
|
|
|
114
140
|
// Clean up empty env block
|
|
115
141
|
if (Object.keys(settings.env).length === 0) {
|
package/lib/config-utils.js
CHANGED
|
@@ -5,9 +5,23 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
const { PLEXOR_DIR, CONFIG_PATH } = require('./constants');
|
|
9
10
|
|
|
10
11
|
const DISABLED_HINT_VALUES = new Set(['', 'auto', 'none', 'off']);
|
|
12
|
+
const VALID_ORCHESTRATION_MODES = new Set(['supervised', 'autonomous', 'danger-full-auto']);
|
|
13
|
+
const VALID_ROUTING_MODES = new Set(['eco', 'balanced', 'quality', 'passthrough', 'cost']);
|
|
14
|
+
const MANAGED_HEADER_KEYS = new Set([
|
|
15
|
+
'x-force-provider',
|
|
16
|
+
'x-force-model',
|
|
17
|
+
'x-allow-providers',
|
|
18
|
+
'x-deny-providers',
|
|
19
|
+
'x-allow-models',
|
|
20
|
+
'x-deny-models',
|
|
21
|
+
'x-plexor-mode',
|
|
22
|
+
'x-plexor-orchestration-mode'
|
|
23
|
+
]);
|
|
24
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
11
25
|
|
|
12
26
|
function normalizeForcedProvider(value) {
|
|
13
27
|
if (typeof value !== 'string') {
|
|
@@ -31,6 +45,171 @@ function normalizeForcedModel(value) {
|
|
|
31
45
|
return normalized;
|
|
32
46
|
}
|
|
33
47
|
|
|
48
|
+
function normalizeCsv(value) {
|
|
49
|
+
if (typeof value !== 'string') {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const tokens = value
|
|
53
|
+
.split(',')
|
|
54
|
+
.map((token) => token.trim())
|
|
55
|
+
.filter(Boolean);
|
|
56
|
+
if (!tokens.length) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return tokens.join(',');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseCustomHeaders(raw) {
|
|
63
|
+
if (typeof raw !== 'string' || !raw.trim()) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
const trimmedRaw = raw.trim();
|
|
67
|
+
|
|
68
|
+
// Backward compatibility: older plugin versions persisted ANTHROPIC_CUSTOM_HEADERS
|
|
69
|
+
// as a JSON object string. Parse that first so managed key replacement works.
|
|
70
|
+
if (trimmedRaw.startsWith('{')) {
|
|
71
|
+
try {
|
|
72
|
+
const legacy = JSON.parse(trimmedRaw);
|
|
73
|
+
if (legacy && typeof legacy === 'object' && !Array.isArray(legacy)) {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [key, value] of Object.entries(legacy)) {
|
|
76
|
+
const normalizedKey = String(key || '').trim().toLowerCase();
|
|
77
|
+
const normalizedValue = String(value ?? '').trim();
|
|
78
|
+
if (!normalizedKey || !normalizedValue) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
out[normalizedKey] = normalizedValue;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Fall through to line-based parser.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parsed = {};
|
|
91
|
+
for (const line of raw.split('\n')) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const idx = trimmed.indexOf(':');
|
|
97
|
+
if (idx <= 0) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const key = trimmed.slice(0, idx).trim().toLowerCase();
|
|
101
|
+
const value = trimmed.slice(idx + 1).trim();
|
|
102
|
+
if (!key || !value) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
parsed[key] = value;
|
|
106
|
+
}
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function serializeCustomHeaders(headers) {
|
|
111
|
+
return Object.entries(headers)
|
|
112
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
113
|
+
.join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildManagedAnthropicHeaders(config) {
|
|
117
|
+
const settings = config?.settings || {};
|
|
118
|
+
const headers = {};
|
|
119
|
+
|
|
120
|
+
const modeRaw = String(settings.mode || '')
|
|
121
|
+
.trim()
|
|
122
|
+
.toLowerCase();
|
|
123
|
+
if (VALID_ROUTING_MODES.has(modeRaw)) {
|
|
124
|
+
headers['x-plexor-mode'] = modeRaw === 'cost' ? 'eco' : modeRaw;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const orchestrationRaw = String(
|
|
128
|
+
settings.orchestrationMode || settings.orchestration_mode || ''
|
|
129
|
+
)
|
|
130
|
+
.trim()
|
|
131
|
+
.toLowerCase();
|
|
132
|
+
if (VALID_ORCHESTRATION_MODES.has(orchestrationRaw)) {
|
|
133
|
+
headers['x-plexor-orchestration-mode'] = orchestrationRaw;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const forceProvider = normalizeForcedProvider(
|
|
137
|
+
settings.preferred_provider ?? settings.preferredProvider ?? 'auto'
|
|
138
|
+
);
|
|
139
|
+
if (forceProvider && forceProvider !== 'auto') {
|
|
140
|
+
headers['x-force-provider'] = forceProvider;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const forceModel = normalizeForcedModel(settings.preferred_model ?? settings.preferredModel);
|
|
144
|
+
if (forceModel) {
|
|
145
|
+
headers['x-force-model'] = forceModel;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const allowProviders = normalizeCsv(
|
|
149
|
+
settings.provider_allowlist ?? settings.providerAllowlist ?? ''
|
|
150
|
+
);
|
|
151
|
+
if (allowProviders) {
|
|
152
|
+
headers['x-allow-providers'] = allowProviders;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const denyProviders = normalizeCsv(settings.provider_denylist ?? settings.providerDenylist ?? '');
|
|
156
|
+
if (denyProviders) {
|
|
157
|
+
headers['x-deny-providers'] = denyProviders;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const allowModels = normalizeCsv(settings.model_allowlist ?? settings.modelAllowlist ?? '');
|
|
161
|
+
if (allowModels) {
|
|
162
|
+
headers['x-allow-models'] = allowModels;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const denyModels = normalizeCsv(settings.model_denylist ?? settings.modelDenylist ?? '');
|
|
166
|
+
if (denyModels) {
|
|
167
|
+
headers['x-deny-models'] = denyModels;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return headers;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function syncClaudeCustomHeaders(config) {
|
|
174
|
+
try {
|
|
175
|
+
let settings = {};
|
|
176
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
177
|
+
const raw = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
|
|
178
|
+
settings = raw.trim() ? JSON.parse(raw) : {};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
|
|
182
|
+
settings = {};
|
|
183
|
+
}
|
|
184
|
+
settings.env = settings.env && typeof settings.env === 'object' ? settings.env : {};
|
|
185
|
+
|
|
186
|
+
const existing = parseCustomHeaders(settings.env.ANTHROPIC_CUSTOM_HEADERS);
|
|
187
|
+
for (const key of MANAGED_HEADER_KEYS) {
|
|
188
|
+
delete existing[key];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const managed = buildManagedAnthropicHeaders(config);
|
|
192
|
+
const merged = { ...existing, ...managed };
|
|
193
|
+
|
|
194
|
+
if (Object.keys(merged).length) {
|
|
195
|
+
settings.env.ANTHROPIC_CUSTOM_HEADERS = serializeCustomHeaders(merged);
|
|
196
|
+
} else {
|
|
197
|
+
delete settings.env.ANTHROPIC_CUSTOM_HEADERS;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const claudeDir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
201
|
+
if (!fs.existsSync(claudeDir)) {
|
|
202
|
+
fs.mkdirSync(claudeDir, { recursive: true, mode: 0o700 });
|
|
203
|
+
}
|
|
204
|
+
const tempPath = path.join(claudeDir, `.settings.${crypto.randomBytes(8).toString('hex')}.tmp`);
|
|
205
|
+
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
206
|
+
fs.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
|
|
207
|
+
return true;
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
34
213
|
function hasForcedHintConflict(config) {
|
|
35
214
|
const settings = config?.settings || {};
|
|
36
215
|
const provider = normalizeForcedProvider(
|
|
@@ -88,6 +267,8 @@ function saveConfig(config) {
|
|
|
88
267
|
const tempPath = path.join(PLEXOR_DIR, `.config.${tempId}.tmp`);
|
|
89
268
|
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
90
269
|
fs.renameSync(tempPath, CONFIG_PATH);
|
|
270
|
+
// Best-effort sync to Claude client headers so force hints are respected.
|
|
271
|
+
syncClaudeCustomHeaders(config);
|
|
91
272
|
return true;
|
|
92
273
|
} catch (err) {
|
|
93
274
|
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
@@ -121,4 +302,13 @@ function readSetting(config, configKey, configKeyAlt, envVar, validValues, defau
|
|
|
121
302
|
return { value: defaultValue, source: 'default' };
|
|
122
303
|
}
|
|
123
304
|
|
|
124
|
-
module.exports = {
|
|
305
|
+
module.exports = {
|
|
306
|
+
loadConfig,
|
|
307
|
+
saveConfig,
|
|
308
|
+
readSetting,
|
|
309
|
+
hasForcedHintConflict,
|
|
310
|
+
validateForcedHintConfig,
|
|
311
|
+
parseCustomHeaders,
|
|
312
|
+
serializeCustomHeaders,
|
|
313
|
+
buildManagedAnthropicHeaders
|
|
314
|
+
};
|
package/lib/settings-manager.js
CHANGED
|
@@ -22,6 +22,8 @@ const LOCK_TIMEOUT_MS = 5000; // 5 second lock timeout
|
|
|
22
22
|
// Plexor gateway endpoints
|
|
23
23
|
const PLEXOR_STAGING_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
|
|
24
24
|
const PLEXOR_PROD_URL = 'https://api.plexor.dev/gateway/anthropic';
|
|
25
|
+
const PREVIOUS_API_KEY_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_API_KEY';
|
|
26
|
+
const PREVIOUS_AUTH_TOKEN_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Check if a base URL is a Plexor-managed gateway URL.
|
|
@@ -39,6 +41,83 @@ function isManagedGatewayUrl(baseUrl) {
|
|
|
39
41
|
);
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
function isPlexorApiKey(value = '') {
|
|
45
|
+
return typeof value === 'string' && value.startsWith('plx_');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getPlexorAuthKey(env = {}) {
|
|
49
|
+
const apiKey = env.ANTHROPIC_API_KEY || '';
|
|
50
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
51
|
+
|
|
52
|
+
if (isPlexorApiKey(apiKey)) return apiKey;
|
|
53
|
+
if (isPlexorApiKey(authToken)) return authToken;
|
|
54
|
+
return apiKey || authToken || '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasPlexorManagedAuth(env = {}) {
|
|
58
|
+
return (
|
|
59
|
+
isManagedGatewayUrl(env.ANTHROPIC_BASE_URL || '') ||
|
|
60
|
+
isPlexorApiKey(env.ANTHROPIC_API_KEY || '') ||
|
|
61
|
+
isPlexorApiKey(env.ANTHROPIC_AUTH_TOKEN || '')
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function stashDirectAuthEnv(env, plexorApiKey) {
|
|
66
|
+
const alreadyManaged = hasPlexorManagedAuth(env);
|
|
67
|
+
const currentApiKey = env.ANTHROPIC_API_KEY || '';
|
|
68
|
+
const currentAuthToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
69
|
+
|
|
70
|
+
if (currentApiKey && !isPlexorApiKey(currentApiKey) && currentApiKey !== plexorApiKey) {
|
|
71
|
+
env[PREVIOUS_API_KEY_ENV] = currentApiKey;
|
|
72
|
+
} else if (!alreadyManaged) {
|
|
73
|
+
delete env[PREVIOUS_API_KEY_ENV];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (currentAuthToken && !isPlexorApiKey(currentAuthToken) && currentAuthToken !== plexorApiKey) {
|
|
77
|
+
env[PREVIOUS_AUTH_TOKEN_ENV] = currentAuthToken;
|
|
78
|
+
} else if (!alreadyManaged) {
|
|
79
|
+
delete env[PREVIOUS_AUTH_TOKEN_ENV];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function setPlexorAuthKey(env, apiKey) {
|
|
84
|
+
stashDirectAuthEnv(env, apiKey);
|
|
85
|
+
env.ANTHROPIC_API_KEY = apiKey;
|
|
86
|
+
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function clearPlexorRoutingEnv(env = {}) {
|
|
90
|
+
const hasManagedBaseUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL || '');
|
|
91
|
+
const hasPlexorApiKey = isPlexorApiKey(env.ANTHROPIC_API_KEY || '');
|
|
92
|
+
const hasPlexorAuthToken = isPlexorApiKey(env.ANTHROPIC_AUTH_TOKEN || '');
|
|
93
|
+
|
|
94
|
+
if (!hasManagedBaseUrl && !hasPlexorApiKey && !hasPlexorAuthToken) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (hasManagedBaseUrl) {
|
|
99
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
100
|
+
}
|
|
101
|
+
if (hasPlexorAuthToken) {
|
|
102
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
103
|
+
}
|
|
104
|
+
if (hasPlexorApiKey) {
|
|
105
|
+
delete env.ANTHROPIC_API_KEY;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!env.ANTHROPIC_API_KEY && env[PREVIOUS_API_KEY_ENV]) {
|
|
109
|
+
env.ANTHROPIC_API_KEY = env[PREVIOUS_API_KEY_ENV];
|
|
110
|
+
}
|
|
111
|
+
if (!env.ANTHROPIC_AUTH_TOKEN && env[PREVIOUS_AUTH_TOKEN_ENV]) {
|
|
112
|
+
env.ANTHROPIC_AUTH_TOKEN = env[PREVIOUS_AUTH_TOKEN_ENV];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
delete env[PREVIOUS_API_KEY_ENV];
|
|
116
|
+
delete env[PREVIOUS_AUTH_TOKEN_ENV];
|
|
117
|
+
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
42
121
|
class ClaudeSettingsManager {
|
|
43
122
|
constructor() {
|
|
44
123
|
this.settingsPath = SETTINGS_PATH;
|
|
@@ -171,8 +250,9 @@ class ClaudeSettingsManager {
|
|
|
171
250
|
/**
|
|
172
251
|
* Enable Plexor routing by setting env vars in settings.json
|
|
173
252
|
*
|
|
174
|
-
* This is the KEY mechanism: setting ANTHROPIC_BASE_URL
|
|
175
|
-
*
|
|
253
|
+
* This is the KEY mechanism: setting ANTHROPIC_BASE_URL plus both Claude auth
|
|
254
|
+
* env vars in settings.json redirects ALL Claude Code sessions to Plexor
|
|
255
|
+
* automatically, even when the user previously configured direct API auth.
|
|
176
256
|
*
|
|
177
257
|
* @param {string} apiKey - Plexor API key (plx_*)
|
|
178
258
|
* @param {Object} options - { useStaging: boolean }
|
|
@@ -191,10 +271,10 @@ class ClaudeSettingsManager {
|
|
|
191
271
|
settings.env = {};
|
|
192
272
|
}
|
|
193
273
|
|
|
194
|
-
//
|
|
195
|
-
//
|
|
274
|
+
// Mirror the Plexor key into both Claude auth env vars so every Claude
|
|
275
|
+
// runtime path uses the gateway instead of any previously saved direct key.
|
|
196
276
|
settings.env.ANTHROPIC_BASE_URL = apiUrl;
|
|
197
|
-
settings.env
|
|
277
|
+
setPlexorAuthKey(settings.env, apiKey);
|
|
198
278
|
|
|
199
279
|
const success = this.save(settings);
|
|
200
280
|
|
|
@@ -229,9 +309,10 @@ class ClaudeSettingsManager {
|
|
|
229
309
|
return true;
|
|
230
310
|
}
|
|
231
311
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
312
|
+
if (!clearPlexorRoutingEnv(settings.env)) {
|
|
313
|
+
console.log('Plexor routing is not currently enabled.');
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
235
316
|
|
|
236
317
|
// Clean up empty env block
|
|
237
318
|
if (Object.keys(settings.env).length === 0) {
|
|
@@ -262,7 +343,8 @@ class ClaudeSettingsManager {
|
|
|
262
343
|
const settings = this.load();
|
|
263
344
|
|
|
264
345
|
const baseUrl = settings.env?.ANTHROPIC_BASE_URL || null;
|
|
265
|
-
const
|
|
346
|
+
const authKey = getPlexorAuthKey(settings.env);
|
|
347
|
+
const hasToken = !!authKey;
|
|
266
348
|
|
|
267
349
|
// Check if routing to Plexor (any variant: prod, staging, localhost, tunnel)
|
|
268
350
|
const isPlexorRouting = isManagedGatewayUrl(baseUrl);
|
|
@@ -272,7 +354,7 @@ class ClaudeSettingsManager {
|
|
|
272
354
|
baseUrl,
|
|
273
355
|
hasToken,
|
|
274
356
|
isStaging: baseUrl?.includes('staging') || false,
|
|
275
|
-
tokenPreview: hasToken ?
|
|
357
|
+
tokenPreview: hasToken ? authKey.substring(0, 12) + '...' : null
|
|
276
358
|
};
|
|
277
359
|
} catch {
|
|
278
360
|
return { enabled: false, baseUrl: null, hasToken: false };
|
|
@@ -288,14 +370,14 @@ class ClaudeSettingsManager {
|
|
|
288
370
|
try {
|
|
289
371
|
const settings = this.load();
|
|
290
372
|
const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
291
|
-
const
|
|
373
|
+
const authKey = getPlexorAuthKey(settings.env);
|
|
292
374
|
const isPlexorUrl = isManagedGatewayUrl(baseUrl);
|
|
293
375
|
|
|
294
|
-
if (isPlexorUrl && !
|
|
295
|
-
return { partial: true, issue: 'Plexor URL set but no auth
|
|
376
|
+
if (isPlexorUrl && !authKey) {
|
|
377
|
+
return { partial: true, issue: 'Plexor URL set but no Plexor auth key' };
|
|
296
378
|
}
|
|
297
|
-
if (isPlexorUrl && !
|
|
298
|
-
return { partial: true, issue: 'Plexor URL set but auth
|
|
379
|
+
if (isPlexorUrl && !isPlexorApiKey(authKey)) {
|
|
380
|
+
return { partial: true, issue: 'Plexor URL set but auth key is not a Plexor key' };
|
|
299
381
|
}
|
|
300
382
|
return { partial: false, issue: null };
|
|
301
383
|
} catch {
|
|
@@ -316,7 +398,7 @@ class ClaudeSettingsManager {
|
|
|
316
398
|
settings.env = {};
|
|
317
399
|
}
|
|
318
400
|
|
|
319
|
-
settings.env
|
|
401
|
+
setPlexorAuthKey(settings.env, apiKey);
|
|
320
402
|
return this.save(settings);
|
|
321
403
|
} catch (err) {
|
|
322
404
|
console.error('Failed to update API key:', err.message);
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -165,13 +165,39 @@ function isManagedGatewayUrl(baseUrl) {
|
|
|
165
165
|
* Used to detect when a different variant was previously installed.
|
|
166
166
|
*/
|
|
167
167
|
const THIS_VARIANT_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
|
|
168
|
+
const PREVIOUS_API_KEY_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_API_KEY';
|
|
169
|
+
const PREVIOUS_AUTH_TOKEN_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN';
|
|
168
170
|
|
|
169
171
|
/**
|
|
170
172
|
* Check for orphaned Plexor routing in settings.json without valid config.
|
|
171
173
|
* Also detects variant mismatch (e.g., localhost plugin was installed, now
|
|
172
|
-
* installing staging plugin) and migrates ANTHROPIC_BASE_URL +
|
|
173
|
-
*
|
|
174
|
+
* installing staging plugin) and migrates ANTHROPIC_BASE_URL + syncs
|
|
175
|
+
* Claude auth env vars for Plexor-managed gateways.
|
|
174
176
|
*/
|
|
177
|
+
function selectManagedAuthKey(env = {}) {
|
|
178
|
+
const apiKey = env.ANTHROPIC_API_KEY || '';
|
|
179
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
180
|
+
|
|
181
|
+
if (apiKey.startsWith('plx_')) return apiKey;
|
|
182
|
+
if (authToken.startsWith('plx_')) return authToken;
|
|
183
|
+
return apiKey || authToken || '';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function syncManagedAuthEnv(env, managedAuthKey) {
|
|
187
|
+
const currentApiKey = env.ANTHROPIC_API_KEY || '';
|
|
188
|
+
const currentAuthToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
189
|
+
|
|
190
|
+
if (currentApiKey && !currentApiKey.startsWith('plx_') && currentApiKey !== managedAuthKey) {
|
|
191
|
+
env[PREVIOUS_API_KEY_ENV] = currentApiKey;
|
|
192
|
+
}
|
|
193
|
+
if (currentAuthToken && !currentAuthToken.startsWith('plx_') && currentAuthToken !== managedAuthKey) {
|
|
194
|
+
env[PREVIOUS_AUTH_TOKEN_ENV] = currentAuthToken;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
env.ANTHROPIC_API_KEY = managedAuthKey;
|
|
198
|
+
env.ANTHROPIC_AUTH_TOKEN = managedAuthKey;
|
|
199
|
+
}
|
|
200
|
+
|
|
175
201
|
function checkOrphanedRouting() {
|
|
176
202
|
// Use the resolved HOME_DIR (not process.env.HOME which may be wrong under sudo -u)
|
|
177
203
|
const settingsPath = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
@@ -187,21 +213,15 @@ function checkOrphanedRouting() {
|
|
|
187
213
|
const hasPlexorUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL);
|
|
188
214
|
|
|
189
215
|
if (hasPlexorUrl) {
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
settings.env = env;
|
|
197
|
-
settingsChanged = true;
|
|
198
|
-
console.log('\n Migrated ANTHROPIC_API_KEY → ANTHROPIC_AUTH_TOKEN (fix #2174)');
|
|
199
|
-
} else if (env.ANTHROPIC_API_KEY && env.ANTHROPIC_AUTH_TOKEN) {
|
|
200
|
-
// Both exist — remove the lower-precedence one to avoid confusion
|
|
201
|
-
delete env.ANTHROPIC_API_KEY;
|
|
216
|
+
// Keep both Claude auth env vars aligned to the Plexor key so Claude API
|
|
217
|
+
// auth cannot override the gateway after plugin setup.
|
|
218
|
+
const managedAuthKey = selectManagedAuthKey(env);
|
|
219
|
+
if (managedAuthKey &&
|
|
220
|
+
(env.ANTHROPIC_API_KEY !== managedAuthKey || env.ANTHROPIC_AUTH_TOKEN !== managedAuthKey)) {
|
|
221
|
+
syncManagedAuthEnv(env, managedAuthKey);
|
|
202
222
|
settings.env = env;
|
|
203
223
|
settingsChanged = true;
|
|
204
|
-
console.log('\n
|
|
224
|
+
console.log('\n Synced Plexor auth into ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN');
|
|
205
225
|
}
|
|
206
226
|
// Check if there's a valid Plexor config
|
|
207
227
|
let hasValidConfig = false;
|
|
@@ -385,11 +405,6 @@ function main() {
|
|
|
385
405
|
chownRecursive(PLEXOR_CONFIG_DIR, uid, gid);
|
|
386
406
|
}
|
|
387
407
|
|
|
388
|
-
// Detect shell type
|
|
389
|
-
const shell = process.env.SHELL || '';
|
|
390
|
-
const isZsh = shell.includes('zsh');
|
|
391
|
-
const shellRc = isZsh ? '~/.zshrc' : '~/.bashrc';
|
|
392
|
-
|
|
393
408
|
// Print success message with clear onboarding steps
|
|
394
409
|
console.log('');
|
|
395
410
|
console.log(' ╔═══════════════════════════════════════════════════════════════════╗');
|
|
@@ -414,24 +429,18 @@ function main() {
|
|
|
414
429
|
}
|
|
415
430
|
console.log('');
|
|
416
431
|
|
|
417
|
-
// CRITICAL: Make the required step VERY obvious
|
|
418
432
|
console.log(' ┌─────────────────────────────────────────────────────────────────┐');
|
|
419
|
-
console.log(' │
|
|
433
|
+
console.log(' │ NEXT: Start Claude Code and run /plexor-setup │');
|
|
420
434
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
421
435
|
console.log('');
|
|
422
|
-
console.log('
|
|
423
|
-
console.log('');
|
|
424
|
-
console.log(
|
|
425
|
-
console.log(
|
|
426
|
-
console.log('');
|
|
427
|
-
console.log(' For API key users (get key at https://plexor.dev/dashboard):');
|
|
428
|
-
console.log('');
|
|
429
|
-
console.log(` echo 'export ANTHROPIC_BASE_URL="https://staging.api.plexor.dev/gateway/anthropic"' >> ${shellRc}`);
|
|
430
|
-
console.log(` echo 'export ANTHROPIC_AUTH_TOKEN="plx_your_key_here"' >> ${shellRc}`);
|
|
431
|
-
console.log(` source ${shellRc}`);
|
|
436
|
+
console.log(' /plexor-setup will:');
|
|
437
|
+
console.log(' 1. Ask for your Plexor API key');
|
|
438
|
+
console.log(' 2. Write ~/.plexor/config.json');
|
|
439
|
+
console.log(' 3. Point Claude at the Plexor staging gateway');
|
|
440
|
+
console.log(' 4. Replace any existing Claude API env auth while Plexor is active');
|
|
432
441
|
console.log('');
|
|
433
442
|
console.log(' ┌─────────────────────────────────────────────────────────────────┐');
|
|
434
|
-
console.log(' │
|
|
443
|
+
console.log(' │ No shell edits or Claude restart required after setup │');
|
|
435
444
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
436
445
|
console.log('');
|
|
437
446
|
console.log(' Available commands:');
|
package/scripts/uninstall.js
CHANGED
|
@@ -57,6 +57,53 @@ const results = {
|
|
|
57
57
|
pluginDir: false
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
function isManagedGatewayUrl(baseUrl = '') {
|
|
61
|
+
return (
|
|
62
|
+
baseUrl.includes('plexor') ||
|
|
63
|
+
baseUrl.includes('staging.api') ||
|
|
64
|
+
baseUrl.includes('localhost') ||
|
|
65
|
+
baseUrl.includes('127.0.0.1') ||
|
|
66
|
+
baseUrl.includes('ngrok') ||
|
|
67
|
+
baseUrl.includes('localtunnel')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPlexorApiKey(value = '') {
|
|
72
|
+
return typeof value === 'string' && value.startsWith('plx_');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function clearPlexorRoutingEnv(env = {}) {
|
|
76
|
+
const hasManagedBaseUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL || '');
|
|
77
|
+
const hasPlexorAuthToken = isPlexorApiKey(env.ANTHROPIC_AUTH_TOKEN || '');
|
|
78
|
+
const hasPlexorApiKey = isPlexorApiKey(env.ANTHROPIC_API_KEY || '');
|
|
79
|
+
|
|
80
|
+
if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasManagedBaseUrl) {
|
|
85
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
86
|
+
}
|
|
87
|
+
if (hasPlexorAuthToken) {
|
|
88
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
89
|
+
}
|
|
90
|
+
if (hasPlexorApiKey) {
|
|
91
|
+
delete env.ANTHROPIC_API_KEY;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!env.ANTHROPIC_API_KEY && env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY) {
|
|
95
|
+
env.ANTHROPIC_API_KEY = env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
|
|
96
|
+
}
|
|
97
|
+
if (!env.ANTHROPIC_AUTH_TOKEN && env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN) {
|
|
98
|
+
env.ANTHROPIC_AUTH_TOKEN = env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
delete env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
|
|
102
|
+
delete env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
60
107
|
// 1. Remove routing from settings.json
|
|
61
108
|
// This is CRITICAL - do NOT depend on settings-manager module since it may not load during uninstall
|
|
62
109
|
try {
|
|
@@ -66,18 +113,14 @@ try {
|
|
|
66
113
|
if (data && data.trim()) {
|
|
67
114
|
const settings = JSON.parse(data);
|
|
68
115
|
if (settings.env) {
|
|
69
|
-
const
|
|
70
|
-
const hadAuthToken = !!settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
71
|
-
|
|
72
|
-
delete settings.env.ANTHROPIC_BASE_URL;
|
|
73
|
-
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
116
|
+
const routingChanged = clearPlexorRoutingEnv(settings.env);
|
|
74
117
|
|
|
75
118
|
// Clean up empty env block
|
|
76
119
|
if (Object.keys(settings.env).length === 0) {
|
|
77
120
|
delete settings.env;
|
|
78
121
|
}
|
|
79
122
|
|
|
80
|
-
if (
|
|
123
|
+
if (routingChanged) {
|
|
81
124
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
82
125
|
results.routing = true;
|
|
83
126
|
}
|