@plexor-dev/claude-code-plugin-staging 0.1.0-beta.21 → 0.1.0-beta.23

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.
@@ -243,7 +243,8 @@ function main() {
243
243
  console.log(`│ Run /plexor-setup first. │`);
244
244
  console.log(`├─────────────────────────────────────────────┤`);
245
245
  console.log(`│ Get your API key at: │`);
246
- console.log(`│ https://staging.plexorlabs.com │`);
246
+ console.log(`│ https://staging.plexorlabs.com/ │`);
247
+ console.log(`│ api-keys.html │`);
247
248
  console.log(`└─────────────────────────────────────────────┘`);
248
249
  process.exit(1);
249
250
  }
@@ -262,7 +262,8 @@ async function main() {
262
262
  console.log(`│ Human first-run: /plexor-setup │`);
263
263
  console.log(`├─────────────────────────────────────────────┤`);
264
264
  console.log(`│ Get your API key at: │`);
265
- console.log(`│ https://staging.plexorlabs.com │`);
265
+ console.log(`│ https://staging.plexorlabs.com/ │`);
266
+ console.log(`│ api-keys.html │`);
266
267
  console.log(`├─────────────────────────────────────────────┤`);
267
268
  console.log(`│ Secure usage (recommended): │`);
268
269
  console.log(`│ echo "plx_..." | /plexor-login │`);
@@ -340,7 +341,7 @@ async function main() {
340
341
  }
341
342
  console.log(`├─────────────────────────────────────────────┤`);
342
343
  console.log(`│ Human first-run: /plexor-setup │`);
343
- console.log(`│ Automation complete. Status: /plexor-status│`);
344
+ console.log(`│ Restart Claude before testing prompts │`);
344
345
  console.log(`└─────────────────────────────────────────────┘`);
345
346
  } catch (err) {
346
347
  console.error(`┌─────────────────────────────────────────────┐`);
@@ -102,7 +102,8 @@ function main() {
102
102
  const currentSettings = settingsManager.load();
103
103
  const hadPriorAuth = Boolean(
104
104
  currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY ||
105
- currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN
105
+ currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN ||
106
+ currentSettings.env?.PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY
106
107
  );
107
108
 
108
109
  // Clear credentials
@@ -128,9 +128,9 @@ function printReceipt({ user, gateway, previousAuthPreserved, verifyResult }) {
128
128
  console.log(line(`Previous Claude auth: ${previousAuthPreserved ? 'Saved' : 'None found'}`));
129
129
  console.log('├─────────────────────────────────────────────┤');
130
130
  if (verifyResult.ok) {
131
- console.log(line('Claude is now routed through Plexor'));
131
+ console.log(line('Plexor routing is configured'));
132
+ console.log(line('Restart Claude, then send a prompt'));
132
133
  console.log(line('Logout/uninstall restores prior auth'));
133
- console.log(line('Next: Run /plexor-status for health + stats'));
134
134
  } else {
135
135
  console.log(line('Claude routing was configured'));
136
136
  console.log(line('Verification did not pass'));
@@ -217,7 +217,8 @@ async function main() {
217
217
  const currentSettings = settingsManager.load();
218
218
  const previousAuthPreserved = Boolean(
219
219
  currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY ||
220
- currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN
220
+ currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN ||
221
+ currentSettings.env?.PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY
221
222
  );
222
223
 
223
224
  const verifyResult = skipVerify
@@ -13,7 +13,7 @@ node ~/.claude/plugins/plexor/commands/plexor-setup.js $ARGUMENTS
13
13
 
14
14
  If the user did not provide a key yet, ask them:
15
15
 
16
- `Please paste your Plexor API key (starts with plx_). You can get one at https://staging.plexorlabs.com.`
16
+ `Please paste your Plexor API key (starts with plx_). You can get one at https://staging.plexorlabs.com/api-keys.html.`
17
17
 
18
18
  After the user replies with the key, run:
19
19
 
@@ -12,6 +12,7 @@ const https = require('https');
12
12
  // Import centralized constants with HOME directory validation
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
+ const CLAUDE_STATE_PATH = path.join(HOME_DIR, '.claude.json');
15
16
 
16
17
  function isManagedGatewayUrl(baseUrl = '') {
17
18
  return (
@@ -33,6 +34,49 @@ function getPlexorAuthKey(env = {}) {
33
34
  return apiKey || authToken || '';
34
35
  }
35
36
 
37
+ function getDirectClaudeAuthState() {
38
+ try {
39
+ const data = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
40
+ const settings = JSON.parse(data);
41
+ const env = settings.env || {};
42
+ const baseUrl = env.ANTHROPIC_BASE_URL || '';
43
+ const apiKey = env.ANTHROPIC_API_KEY || '';
44
+ const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
45
+
46
+ if (!isManagedGatewayUrl(baseUrl)) {
47
+ if (apiKey && !apiKey.startsWith('plx_')) {
48
+ return { present: true, source: 'Claude settings' };
49
+ }
50
+ if (authToken && !authToken.startsWith('plx_')) {
51
+ return { present: true, source: 'Claude settings' };
52
+ }
53
+ }
54
+ } catch {
55
+ // Ignore missing or unreadable Claude settings.
56
+ }
57
+
58
+ try {
59
+ const data = fs.readFileSync(CLAUDE_STATE_PATH, 'utf8');
60
+ const state = JSON.parse(data);
61
+ if (state.primaryApiKey) {
62
+ return { present: true, source: '/login managed key' };
63
+ }
64
+ } catch {
65
+ // Ignore missing or unreadable Claude state.
66
+ }
67
+
68
+ const envApiKey = process.env.ANTHROPIC_API_KEY || '';
69
+ const envAuthToken = process.env.ANTHROPIC_AUTH_TOKEN || '';
70
+ if (envApiKey && !envApiKey.startsWith('plx_')) {
71
+ return { present: true, source: 'shell env' };
72
+ }
73
+ if (envAuthToken && !envAuthToken.startsWith('plx_')) {
74
+ return { present: true, source: 'shell env' };
75
+ }
76
+
77
+ return { present: false, source: null };
78
+ }
79
+
36
80
  /**
37
81
  * Check if Claude Code is actually routing through Plexor
38
82
  * by reading ~/.claude/settings.json
@@ -204,8 +248,24 @@ async function main() {
204
248
  const localCache = config.settings?.localCacheEnabled ?? false;
205
249
  const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
206
250
  const verification = config.health || {};
251
+ const routing = getRoutingStatus();
252
+ const partialState = detectPartialState();
253
+ const directClaudeAuth = getDirectClaudeAuthState();
207
254
 
208
255
  if (!apiKey) {
256
+ if (!enabled && !routing.active && !partialState.partial && directClaudeAuth.present) {
257
+ printHealthSummary({
258
+ installed: 'OK',
259
+ connected: 'Direct Claude auth',
260
+ routing: 'Inactive',
261
+ verified: 'Restored',
262
+ nextAction: 'Run /plexor-setup'
263
+ });
264
+ console.log('');
265
+ console.log(` Claude is using ${directClaudeAuth.source}. Plexor is inactive.`);
266
+ process.exit(0);
267
+ }
268
+
209
269
  printHealthSummary({
210
270
  installed: 'OK',
211
271
  connected: 'Missing key',
@@ -277,7 +337,7 @@ async function main() {
277
337
  let dashboardUrl = 'https://plexor.dev/dashboard';
278
338
  try {
279
339
  const url = new URL(apiUrl);
280
- if (url.hostname.includes('staging.api.plexor.dev')) {
340
+ if (url.hostname === 'staging.api.plexor.dev') {
281
341
  dashboardUrl = 'https://staging.plexorlabs.com/dashboard';
282
342
  } else {
283
343
  const host = url.hostname.replace(/^api\./, '').replace(/\.api\./, '.');
@@ -321,10 +381,8 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
321
381
  ` : '';
322
382
 
323
383
  // Get routing status from Claude settings.json
324
- const routing = getRoutingStatus();
325
384
  const routingIndicator = routing.active ? '🟢 PLEXOR MODE: ON' : '🔴 PLEXOR MODE: OFF';
326
385
  const envLabel = routing.isStaging ? '(staging)' : '(production)';
327
- const partialState = detectPartialState();
328
386
  const stateMismatch = checkStateMismatch(enabled, routing.active);
329
387
  const connectedState = userFetchWorked ? 'OK' : 'Unknown';
330
388
  const verifiedState = verification.verified
@@ -82,6 +82,9 @@ function loadSettingsManager() {
82
82
  */
83
83
  function disableRoutingManually() {
84
84
  const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
85
+ const statePath = path.join(HOME_DIR, '.claude.json');
86
+ const previousPrimaryApiKeyEnv = 'PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY';
87
+ const crypto = require('crypto');
85
88
 
86
89
  try {
87
90
  if (!fs.existsSync(settingsPath)) {
@@ -113,7 +116,9 @@ function disableRoutingManually() {
113
116
  const hasPlexorAuthToken = isPlexorApiKey(settings.env.ANTHROPIC_AUTH_TOKEN || '');
114
117
  const hasPlexorApiKey = isPlexorApiKey(settings.env.ANTHROPIC_API_KEY || '');
115
118
 
116
- if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey) {
119
+ const hasPreviousPrimaryApiKey = Boolean(settings.env[previousPrimaryApiKeyEnv]);
120
+
121
+ if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey && !hasPreviousPrimaryApiKey) {
117
122
  return { success: true, message: 'Plexor routing not active' };
118
123
  }
119
124
 
@@ -136,6 +141,30 @@ function disableRoutingManually() {
136
141
 
137
142
  delete settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
138
143
  delete settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
144
+ const previousPrimaryApiKey = settings.env[previousPrimaryApiKeyEnv] || '';
145
+ if (previousPrimaryApiKey) {
146
+ let claudeState = {};
147
+ try {
148
+ if (fs.existsSync(statePath)) {
149
+ const stateData = fs.readFileSync(statePath, 'utf8');
150
+ if (stateData && stateData.trim()) {
151
+ claudeState = JSON.parse(stateData);
152
+ }
153
+ }
154
+ } catch {
155
+ claudeState = {};
156
+ }
157
+
158
+ if (!claudeState.primaryApiKey) {
159
+ claudeState.primaryApiKey = previousPrimaryApiKey;
160
+ }
161
+ delete settings.env[previousPrimaryApiKeyEnv];
162
+
163
+ const stateTempId = crypto.randomBytes(8).toString('hex');
164
+ const stateTempPath = path.join(HOME_DIR, `.claude.${stateTempId}.tmp`);
165
+ fs.writeFileSync(stateTempPath, JSON.stringify(claudeState, null, 2), { mode: 0o600 });
166
+ fs.renameSync(stateTempPath, statePath);
167
+ }
139
168
 
140
169
  // Clean up empty env block
141
170
  if (Object.keys(settings.env).length === 0) {
@@ -143,7 +172,6 @@ function disableRoutingManually() {
143
172
  }
144
173
 
145
174
  // Atomic write
146
- const crypto = require('crypto');
147
175
  const tempId = crypto.randomBytes(8).toString('hex');
148
176
  const tempPath = path.join(CLAUDE_DIR, `.settings.${tempId}.tmp`);
149
177
 
@@ -31,7 +31,7 @@ class PlexorClient {
31
31
  'Content-Type': 'application/json',
32
32
  'X-API-Key': this.apiKey,
33
33
  'X-Plexor-Key': this.apiKey,
34
- 'User-Agent': 'plexor-claude-code-plugin/0.1.0-beta.22'
34
+ 'User-Agent': 'plexor-claude-code-plugin/0.1.0-beta.23'
35
35
  },
36
36
  timeout: this.timeout
37
37
  };
@@ -16,6 +16,7 @@ const os = require('os');
16
16
  const crypto = require('crypto');
17
17
 
18
18
  const CLAUDE_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude');
19
+ const CLAUDE_STATE_PATH = path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude.json');
19
20
  const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
20
21
  const LOCK_TIMEOUT_MS = 5000; // 5 second lock timeout
21
22
 
@@ -24,6 +25,7 @@ const PLEXOR_STAGING_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
24
25
  const PLEXOR_PROD_URL = 'https://api.plexor.dev/gateway/anthropic';
25
26
  const PREVIOUS_API_KEY_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_API_KEY';
26
27
  const PREVIOUS_AUTH_TOKEN_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN';
28
+ const PREVIOUS_PRIMARY_API_KEY_ENV = 'PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY';
27
29
 
28
30
  /**
29
31
  * Check if a base URL is a Plexor-managed gateway URL.
@@ -118,10 +120,39 @@ function clearPlexorRoutingEnv(env = {}) {
118
120
  return true;
119
121
  }
120
122
 
123
+ function stashDirectPrimaryApiKey(env = {}, claudeState = {}, plexorApiKey = '') {
124
+ const currentPrimaryApiKey = claudeState.primaryApiKey || '';
125
+ if (!currentPrimaryApiKey || isPlexorApiKey(currentPrimaryApiKey) || currentPrimaryApiKey === plexorApiKey) {
126
+ return false;
127
+ }
128
+
129
+ env[PREVIOUS_PRIMARY_API_KEY_ENV] = currentPrimaryApiKey;
130
+ delete claudeState.primaryApiKey;
131
+ return true;
132
+ }
133
+
134
+ function restoreDirectPrimaryApiKey(env = {}, claudeState = {}, options = {}) {
135
+ const { consumeBackup = true } = options;
136
+ const previousPrimaryApiKey = env[PREVIOUS_PRIMARY_API_KEY_ENV] || '';
137
+ if (!previousPrimaryApiKey) {
138
+ return false;
139
+ }
140
+
141
+ if (!claudeState.primaryApiKey) {
142
+ claudeState.primaryApiKey = previousPrimaryApiKey;
143
+ }
144
+
145
+ if (consumeBackup) {
146
+ delete env[PREVIOUS_PRIMARY_API_KEY_ENV];
147
+ }
148
+ return true;
149
+ }
150
+
121
151
  class ClaudeSettingsManager {
122
152
  constructor() {
123
153
  this.settingsPath = SETTINGS_PATH;
124
154
  this.claudeDir = CLAUDE_DIR;
155
+ this.claudeStatePath = CLAUDE_STATE_PATH;
125
156
  }
126
157
 
127
158
  /**
@@ -209,6 +240,56 @@ class ClaudeSettingsManager {
209
240
  return null;
210
241
  }
211
242
 
243
+ loadClaudeState() {
244
+ try {
245
+ if (!fs.existsSync(this.claudeStatePath)) {
246
+ return {};
247
+ }
248
+
249
+ const data = fs.readFileSync(this.claudeStatePath, 'utf8');
250
+ if (!data || data.trim() === '') {
251
+ return {};
252
+ }
253
+
254
+ const parsed = JSON.parse(data);
255
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
256
+ return {};
257
+ }
258
+
259
+ return parsed;
260
+ } catch (err) {
261
+ if (err.code === 'ENOENT' || err instanceof SyntaxError) {
262
+ return {};
263
+ }
264
+ console.warn('Warning: Failed to load Claude auth state:', err.message);
265
+ return {};
266
+ }
267
+ }
268
+
269
+ saveClaudeState(state) {
270
+ try {
271
+ const stateDir = path.dirname(this.claudeStatePath);
272
+ if (!fs.existsSync(stateDir)) {
273
+ fs.mkdirSync(stateDir, { recursive: true });
274
+ }
275
+
276
+ const tempId = crypto.randomBytes(8).toString('hex');
277
+ const tempPath = path.join(stateDir, `.claude.${tempId}.tmp`);
278
+ const content = JSON.stringify(state, null, 2);
279
+ fs.writeFileSync(tempPath, content, { mode: 0o600 });
280
+ fs.renameSync(tempPath, this.claudeStatePath);
281
+ return true;
282
+ } catch (err) {
283
+ if (err.code === 'EACCES' || err.code === 'EPERM') {
284
+ console.error(`Error: Cannot write to ${this.claudeStatePath}`);
285
+ console.error(' Check file permissions or run with appropriate access.');
286
+ } else {
287
+ console.error('Failed to save Claude auth state:', err.message);
288
+ }
289
+ return false;
290
+ }
291
+ }
292
+
212
293
  /**
213
294
  * Save Claude settings using atomic write pattern
214
295
  * Prevents race conditions by writing to temp file then renaming
@@ -265,6 +346,7 @@ class ClaudeSettingsManager {
265
346
 
266
347
  try {
267
348
  const settings = this.load();
349
+ const claudeState = this.loadClaudeState();
268
350
 
269
351
  // Initialize env block if doesn't exist
270
352
  if (!settings.env) {
@@ -275,15 +357,23 @@ class ClaudeSettingsManager {
275
357
  // runtime path uses the gateway instead of any previously saved direct key.
276
358
  settings.env.ANTHROPIC_BASE_URL = apiUrl;
277
359
  setPlexorAuthKey(settings.env, apiKey);
360
+ const claudeStateChanged = stashDirectPrimaryApiKey(settings.env, claudeState, apiKey);
278
361
 
279
362
  const success = this.save(settings);
363
+ if (!success) {
364
+ return false;
365
+ }
366
+
367
+ if (claudeStateChanged && !this.saveClaudeState(claudeState)) {
368
+ return false;
369
+ }
280
370
 
281
371
  if (success) {
282
372
  console.log(`✓ Plexor routing enabled`);
283
373
  console.log(` Base URL: ${apiUrl}`);
284
374
  console.log(` API Key: ${apiKey.substring(0, 12)}...`);
285
- console.log(`\n All Claude Code sessions will now route through Plexor.`);
286
- console.log(` Changes take effect immediately (no restart needed).`);
375
+ console.log(`\n New Claude launches will route through Plexor.`);
376
+ console.log(` Restart Claude before testing routed prompts.`);
287
377
  }
288
378
 
289
379
  return success;
@@ -303,31 +393,50 @@ class ClaudeSettingsManager {
303
393
  disablePlexorRouting() {
304
394
  try {
305
395
  const settings = this.load();
396
+ const claudeState = this.loadClaudeState();
397
+ const originalClaudeState = JSON.parse(JSON.stringify(claudeState));
306
398
 
307
399
  if (!settings.env) {
308
400
  console.log('Plexor routing is not currently enabled.');
309
401
  return true;
310
402
  }
311
403
 
312
- if (!clearPlexorRoutingEnv(settings.env)) {
404
+ const routingChanged = clearPlexorRoutingEnv(settings.env);
405
+ const claudeStateChanged = restoreDirectPrimaryApiKey(settings.env, claudeState, {
406
+ consumeBackup: false
407
+ });
408
+
409
+ if (!routingChanged && !claudeStateChanged) {
313
410
  console.log('Plexor routing is not currently enabled.');
314
411
  return true;
315
412
  }
316
413
 
414
+ if (claudeStateChanged && !this.saveClaudeState(claudeState)) {
415
+ return false;
416
+ }
417
+
418
+ if (claudeStateChanged) {
419
+ delete settings.env[PREVIOUS_PRIMARY_API_KEY_ENV];
420
+ }
421
+
317
422
  // Clean up empty env block
318
423
  if (Object.keys(settings.env).length === 0) {
319
424
  delete settings.env;
320
425
  }
321
426
 
322
427
  const success = this.save(settings);
323
-
324
- if (success) {
325
- console.log('✓ Plexor routing disabled');
326
- console.log(' Claude Code will now connect directly to Anthropic.');
327
- console.log(' Changes take effect immediately (no restart needed).');
428
+ if (!success) {
429
+ if (claudeStateChanged) {
430
+ this.saveClaudeState(originalClaudeState);
431
+ }
432
+ return false;
328
433
  }
329
434
 
330
- return success;
435
+ console.log('✓ Plexor routing disabled');
436
+ console.log(' New Claude launches will connect directly to Anthropic.');
437
+ console.log(' Restart Claude if it is still open.');
438
+
439
+ return true;
331
440
  } catch (err) {
332
441
  console.error('Failed to disable Plexor routing:', err.message);
333
442
  return false;
@@ -369,6 +478,7 @@ class ClaudeSettingsManager {
369
478
  detectPartialState() {
370
479
  try {
371
480
  const settings = this.load();
481
+ const claudeState = this.loadClaudeState();
372
482
  const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
373
483
  const authKey = getPlexorAuthKey(settings.env);
374
484
  const isPlexorUrl = isManagedGatewayUrl(baseUrl);
@@ -379,6 +489,9 @@ class ClaudeSettingsManager {
379
489
  if (isPlexorUrl && !isPlexorApiKey(authKey)) {
380
490
  return { partial: true, issue: 'Plexor URL set but auth key is not a Plexor key' };
381
491
  }
492
+ if (isPlexorUrl && claudeState.primaryApiKey && !isPlexorApiKey(claudeState.primaryApiKey)) {
493
+ return { partial: true, issue: 'Plexor URL set but Claude managed API key is still active' };
494
+ }
382
495
  return { partial: false, issue: null };
383
496
  } catch {
384
497
  return { partial: false, issue: null };
@@ -442,6 +555,7 @@ module.exports = {
442
555
  settingsManager,
443
556
  isManagedGatewayUrl,
444
557
  CLAUDE_DIR,
558
+ CLAUDE_STATE_PATH,
445
559
  SETTINGS_PATH,
446
560
  PLEXOR_STAGING_URL,
447
561
  PLEXOR_PROD_URL
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexor-dev/claude-code-plugin-staging",
3
- "version": "0.1.0-beta.21",
3
+ "version": "0.1.0-beta.23",
4
4
  "description": "STAGING - LLM cost optimization plugin for Claude Code (internal testing)",
5
5
  "main": "lib/constants.js",
6
6
  "bin": {
@@ -167,6 +167,7 @@ function isManagedGatewayUrl(baseUrl) {
167
167
  const THIS_VARIANT_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
168
168
  const PREVIOUS_API_KEY_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_API_KEY';
169
169
  const PREVIOUS_AUTH_TOKEN_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN';
170
+ const PREVIOUS_PRIMARY_API_KEY_ENV = 'PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY';
170
171
 
171
172
  /**
172
173
  * Check for orphaned Plexor routing in settings.json without valid config.
@@ -198,6 +199,36 @@ function syncManagedAuthEnv(env, managedAuthKey) {
198
199
  env.ANTHROPIC_AUTH_TOKEN = managedAuthKey;
199
200
  }
200
201
 
202
+ function syncManagedPrimaryApiKey(env, managedAuthKey) {
203
+ const statePath = path.join(HOME_DIR, '.claude.json');
204
+ try {
205
+ if (!fs.existsSync(statePath)) {
206
+ return false;
207
+ }
208
+
209
+ const data = fs.readFileSync(statePath, 'utf8');
210
+ if (!data || !data.trim()) {
211
+ return false;
212
+ }
213
+
214
+ const claudeState = JSON.parse(data);
215
+ const primaryApiKey = claudeState.primaryApiKey || '';
216
+ if (!primaryApiKey || primaryApiKey.startsWith('plx_') || primaryApiKey === managedAuthKey) {
217
+ return false;
218
+ }
219
+
220
+ env[PREVIOUS_PRIMARY_API_KEY_ENV] = primaryApiKey;
221
+ delete claudeState.primaryApiKey;
222
+
223
+ const tempPath = `${statePath}.tmp.${Date.now()}`;
224
+ fs.writeFileSync(tempPath, JSON.stringify(claudeState, null, 2), { mode: 0o600 });
225
+ fs.renameSync(tempPath, statePath);
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+
201
232
  function checkOrphanedRouting() {
202
233
  // Use the resolved HOME_DIR (not process.env.HOME which may be wrong under sudo -u)
203
234
  const settingsPath = path.join(HOME_DIR, '.claude', 'settings.json');
@@ -223,6 +254,11 @@ function checkOrphanedRouting() {
223
254
  settingsChanged = true;
224
255
  console.log('\n Synced Plexor auth into ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN');
225
256
  }
257
+ if (managedAuthKey && syncManagedPrimaryApiKey(env, managedAuthKey)) {
258
+ settings.env = env;
259
+ settingsChanged = true;
260
+ console.log('\n Suspended Claude managed API key while Plexor is active');
261
+ }
226
262
  // Check if there's a valid Plexor config
227
263
  let hasValidConfig = false;
228
264
  try {
@@ -56,6 +56,7 @@ const results = {
56
56
  restored: [],
57
57
  pluginDir: false
58
58
  };
59
+ const PREVIOUS_PRIMARY_API_KEY_ENV = 'PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY';
59
60
 
60
61
  function isManagedGatewayUrl(baseUrl = '') {
61
62
  return (
@@ -104,6 +105,36 @@ function clearPlexorRoutingEnv(env = {}) {
104
105
  return true;
105
106
  }
106
107
 
108
+ function restoreClaudePrimaryApiKey(env = {}) {
109
+ const previousPrimaryApiKey = env[PREVIOUS_PRIMARY_API_KEY_ENV] || '';
110
+ if (!previousPrimaryApiKey) {
111
+ return false;
112
+ }
113
+
114
+ const statePath = path.join(home, '.claude.json');
115
+ let claudeState = {};
116
+ try {
117
+ if (fs.existsSync(statePath)) {
118
+ const data = fs.readFileSync(statePath, 'utf8');
119
+ if (data && data.trim()) {
120
+ claudeState = JSON.parse(data);
121
+ }
122
+ }
123
+ } catch {
124
+ claudeState = {};
125
+ }
126
+
127
+ if (!claudeState.primaryApiKey) {
128
+ claudeState.primaryApiKey = previousPrimaryApiKey;
129
+ }
130
+ delete env[PREVIOUS_PRIMARY_API_KEY_ENV];
131
+
132
+ const tempPath = `${statePath}.tmp.${Date.now()}`;
133
+ fs.writeFileSync(tempPath, JSON.stringify(claudeState, null, 2), { mode: 0o600 });
134
+ fs.renameSync(tempPath, statePath);
135
+ return true;
136
+ }
137
+
107
138
  // 1. Remove routing from settings.json
108
139
  // This is CRITICAL - do NOT depend on settings-manager module since it may not load during uninstall
109
140
  try {
@@ -114,13 +145,14 @@ try {
114
145
  const settings = JSON.parse(data);
115
146
  if (settings.env) {
116
147
  const routingChanged = clearPlexorRoutingEnv(settings.env);
148
+ const primaryApiKeyRestored = restoreClaudePrimaryApiKey(settings.env);
117
149
 
118
150
  // Clean up empty env block
119
151
  if (Object.keys(settings.env).length === 0) {
120
152
  delete settings.env;
121
153
  }
122
154
 
123
- if (routingChanged) {
155
+ if (routingChanged || primaryApiKeyRestored) {
124
156
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
125
157
  results.routing = true;
126
158
  }