@plexor-dev/claude-code-plugin-staging 0.1.0-beta.24 → 0.1.0-beta.26
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-logout.js +3 -3
- package/commands/plexor-setup.js +14 -2
- package/commands/plexor-setup.md +4 -4
- package/commands/plexor-status.js +6 -2
- package/commands/plexor-uninstall.js +86 -28
- package/hooks/session-sync.js +194 -0
- package/hooks/statusline.js +130 -0
- package/lib/config-utils.js +83 -13
- package/lib/hooks-manager.js +209 -0
- package/lib/settings-manager.js +52 -47
- package/lib/statusline-manager.js +135 -0
- package/package.json +1 -1
- package/scripts/plexor-cli.sh +6 -1
- package/scripts/postinstall.js +132 -55
- package/scripts/uninstall.js +89 -30
package/scripts/postinstall.js
CHANGED
|
@@ -11,6 +11,12 @@ const fs = require('fs');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const os = require('os');
|
|
13
13
|
const { execSync } = require('child_process');
|
|
14
|
+
const {
|
|
15
|
+
applyManagedClaudeCustomHeadersToEnv,
|
|
16
|
+
getManagedPlexorAuthHeader
|
|
17
|
+
} = require('../lib/config-utils');
|
|
18
|
+
const { upsertManagedStatusLine } = require('../lib/statusline-manager');
|
|
19
|
+
const { upsertManagedHooks, cleanupLegacyManagedHooksFile } = require('../lib/hooks-manager');
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* Resolve the home directory for a given username by querying /etc/passwd.
|
|
@@ -137,12 +143,16 @@ function chownRecursive(dirPath, uid, gid) {
|
|
|
137
143
|
|
|
138
144
|
const HOME_DIR = getHomeDir();
|
|
139
145
|
const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
|
|
146
|
+
const HOOKS_SOURCE = path.join(__dirname, '..', 'hooks');
|
|
140
147
|
const LIB_SOURCE = path.join(__dirname, '..', 'lib');
|
|
141
148
|
const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands');
|
|
142
149
|
const PLEXOR_PLUGINS_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'commands');
|
|
150
|
+
const PLEXOR_HOOKS_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'hooks');
|
|
143
151
|
const PLEXOR_LIB_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'lib');
|
|
144
152
|
const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
|
|
145
153
|
const PLEXOR_CONFIG_FILE = path.join(PLEXOR_CONFIG_DIR, 'config.json');
|
|
154
|
+
const CLAUDE_SETTINGS_FILE = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
155
|
+
const CLAUDE_LEGACY_HOOKS_FILE = path.join(HOME_DIR, '.claude', 'hooks.json');
|
|
146
156
|
|
|
147
157
|
/**
|
|
148
158
|
* Check if a base URL is a Plexor-managed gateway URL.
|
|
@@ -172,61 +182,80 @@ const PREVIOUS_PRIMARY_API_KEY_ENV = 'PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY';
|
|
|
172
182
|
/**
|
|
173
183
|
* Check for orphaned Plexor routing in settings.json without valid config.
|
|
174
184
|
* Also detects variant mismatch (e.g., localhost plugin was installed, now
|
|
175
|
-
* installing staging plugin) and migrates
|
|
176
|
-
* Claude
|
|
185
|
+
* installing staging plugin) and migrates legacy env-based Plexor auth into
|
|
186
|
+
* managed Claude custom headers.
|
|
177
187
|
*/
|
|
178
|
-
function selectManagedAuthKey(env = {}) {
|
|
188
|
+
function selectManagedAuthKey(env = {}, config = {}) {
|
|
189
|
+
const configApiKey = config.auth?.api_key || config.auth?.apiKey || config.apiKey || '';
|
|
179
190
|
const apiKey = env.ANTHROPIC_API_KEY || '';
|
|
180
191
|
const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
192
|
+
const headerApiKey = getManagedPlexorAuthHeader(env);
|
|
181
193
|
|
|
194
|
+
if (configApiKey.startsWith('plx_')) return configApiKey;
|
|
195
|
+
if (headerApiKey.startsWith('plx_')) return headerApiKey;
|
|
182
196
|
if (apiKey.startsWith('plx_')) return apiKey;
|
|
183
197
|
if (authToken.startsWith('plx_')) return authToken;
|
|
184
|
-
return
|
|
198
|
+
return '';
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
const
|
|
201
|
+
function restoreLegacyManagedAuthEnvValue(env, field, backupField) {
|
|
202
|
+
let changed = false;
|
|
203
|
+
const currentValue = env[field] || '';
|
|
204
|
+
const backupValue = env[backupField] || '';
|
|
190
205
|
|
|
191
|
-
if (
|
|
192
|
-
|
|
206
|
+
if (currentValue.startsWith('plx_')) {
|
|
207
|
+
if (backupValue && !backupValue.startsWith('plx_')) {
|
|
208
|
+
env[field] = backupValue;
|
|
209
|
+
} else {
|
|
210
|
+
delete env[field];
|
|
211
|
+
}
|
|
212
|
+
changed = true;
|
|
193
213
|
}
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
|
|
215
|
+
if (backupValue) {
|
|
216
|
+
delete env[backupField];
|
|
217
|
+
changed = true;
|
|
196
218
|
}
|
|
197
219
|
|
|
198
|
-
|
|
199
|
-
env.ANTHROPIC_AUTH_TOKEN = managedAuthKey;
|
|
220
|
+
return changed;
|
|
200
221
|
}
|
|
201
222
|
|
|
202
|
-
function
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const data = fs.readFileSync(statePath, 'utf8');
|
|
210
|
-
if (!data || !data.trim()) {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
223
|
+
function writeJsonAtomically(filePath, value) {
|
|
224
|
+
const tempPath = `${filePath}.tmp.${Date.now()}`;
|
|
225
|
+
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
226
|
+
fs.renameSync(tempPath, filePath);
|
|
227
|
+
}
|
|
213
228
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
function migrateManagedAuthState(env, claudeState, config = {}) {
|
|
230
|
+
let settingsChanged = false;
|
|
231
|
+
let claudeStateChanged = false;
|
|
232
|
+
const managedAuthKey = selectManagedAuthKey(env, config);
|
|
233
|
+
|
|
234
|
+
if (managedAuthKey) {
|
|
235
|
+
const managedConfig = {
|
|
236
|
+
...config,
|
|
237
|
+
auth: {
|
|
238
|
+
...(config.auth || {}),
|
|
239
|
+
api_key: managedAuthKey
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
settingsChanged = applyManagedClaudeCustomHeadersToEnv(env, managedConfig) || settingsChanged;
|
|
243
|
+
}
|
|
219
244
|
|
|
220
|
-
|
|
221
|
-
|
|
245
|
+
settingsChanged = restoreLegacyManagedAuthEnvValue(env, 'ANTHROPIC_API_KEY', PREVIOUS_API_KEY_ENV) || settingsChanged;
|
|
246
|
+
settingsChanged = restoreLegacyManagedAuthEnvValue(env, 'ANTHROPIC_AUTH_TOKEN', PREVIOUS_AUTH_TOKEN_ENV) || settingsChanged;
|
|
222
247
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
248
|
+
const previousPrimaryApiKey = env[PREVIOUS_PRIMARY_API_KEY_ENV] || '';
|
|
249
|
+
if (previousPrimaryApiKey) {
|
|
250
|
+
if (!claudeState.primaryApiKey) {
|
|
251
|
+
claudeState.primaryApiKey = previousPrimaryApiKey;
|
|
252
|
+
claudeStateChanged = true;
|
|
253
|
+
}
|
|
254
|
+
delete env[PREVIOUS_PRIMARY_API_KEY_ENV];
|
|
255
|
+
settingsChanged = true;
|
|
229
256
|
}
|
|
257
|
+
|
|
258
|
+
return { settingsChanged, claudeStateChanged };
|
|
230
259
|
}
|
|
231
260
|
|
|
232
261
|
function checkOrphanedRouting() {
|
|
@@ -240,30 +269,19 @@ function checkOrphanedRouting() {
|
|
|
240
269
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
241
270
|
const env = settings.env || {};
|
|
242
271
|
let settingsChanged = false;
|
|
272
|
+
let claudeStateChanged = false;
|
|
273
|
+
let claudeState = {};
|
|
274
|
+
const statePath = path.join(HOME_DIR, '.claude.json');
|
|
243
275
|
|
|
244
276
|
const hasPlexorUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL);
|
|
245
277
|
|
|
246
278
|
if (hasPlexorUrl) {
|
|
247
|
-
// Keep both Claude auth env vars aligned to the Plexor key so Claude API
|
|
248
|
-
// auth cannot override the gateway after plugin setup.
|
|
249
|
-
const managedAuthKey = selectManagedAuthKey(env);
|
|
250
|
-
if (managedAuthKey &&
|
|
251
|
-
(env.ANTHROPIC_API_KEY !== managedAuthKey || env.ANTHROPIC_AUTH_TOKEN !== managedAuthKey)) {
|
|
252
|
-
syncManagedAuthEnv(env, managedAuthKey);
|
|
253
|
-
settings.env = env;
|
|
254
|
-
settingsChanged = true;
|
|
255
|
-
console.log('\n Synced Plexor auth into ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN');
|
|
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
|
-
}
|
|
262
279
|
// Check if there's a valid Plexor config
|
|
263
280
|
let hasValidConfig = false;
|
|
281
|
+
let config = null;
|
|
264
282
|
try {
|
|
265
283
|
if (fs.existsSync(configPath)) {
|
|
266
|
-
|
|
284
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
267
285
|
hasValidConfig = (config.auth?.api_key || config.apiKey || '').startsWith('plx_');
|
|
268
286
|
}
|
|
269
287
|
} catch (e) {}
|
|
@@ -274,6 +292,22 @@ function checkOrphanedRouting() {
|
|
|
274
292
|
console.log(' Run /plexor-login to reconfigure, or');
|
|
275
293
|
console.log(' Run /plexor-uninstall to clean up\n');
|
|
276
294
|
} else {
|
|
295
|
+
try {
|
|
296
|
+
if (fs.existsSync(statePath)) {
|
|
297
|
+
const data = fs.readFileSync(statePath, 'utf8');
|
|
298
|
+
if (data && data.trim()) {
|
|
299
|
+
claudeState = JSON.parse(data);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
claudeState = {};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const migration = migrateManagedAuthState(env, claudeState, config);
|
|
307
|
+
settings.env = env;
|
|
308
|
+
settingsChanged = migration.settingsChanged || settingsChanged;
|
|
309
|
+
claudeStateChanged = migration.claudeStateChanged || claudeStateChanged;
|
|
310
|
+
|
|
277
311
|
// Fix #2176: Detect variant mismatch and migrate URL
|
|
278
312
|
const currentUrl = env.ANTHROPIC_BASE_URL;
|
|
279
313
|
if (currentUrl !== THIS_VARIANT_URL) {
|
|
@@ -299,6 +333,16 @@ function checkOrphanedRouting() {
|
|
|
299
333
|
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
300
334
|
fs.renameSync(tempPath, settingsPath);
|
|
301
335
|
}
|
|
336
|
+
|
|
337
|
+
if (claudeStateChanged) {
|
|
338
|
+
try {
|
|
339
|
+
writeJsonAtomically(statePath, claudeState);
|
|
340
|
+
console.log('\n Restored saved Claude auth alongside Plexor header routing');
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.log('\n Warning: Migrated Plexor auth headers but could not restore saved Claude auth');
|
|
343
|
+
console.log(` ${e.message}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
302
346
|
} catch (e) {
|
|
303
347
|
// Ignore errors in detection - don't break install
|
|
304
348
|
}
|
|
@@ -334,6 +378,9 @@ function main() {
|
|
|
334
378
|
// Create ~/.claude/plugins/plexor/commands/ for JS executors
|
|
335
379
|
fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
|
|
336
380
|
|
|
381
|
+
// Create ~/.claude/plugins/plexor/hooks/ for hook scripts
|
|
382
|
+
fs.mkdirSync(PLEXOR_HOOKS_DIR, { recursive: true });
|
|
383
|
+
|
|
337
384
|
// Create ~/.claude/plugins/plexor/lib/ for shared modules
|
|
338
385
|
fs.mkdirSync(PLEXOR_LIB_DIR, { recursive: true });
|
|
339
386
|
|
|
@@ -356,6 +403,9 @@ function main() {
|
|
|
356
403
|
.filter(f => f.endsWith('.md'));
|
|
357
404
|
const jsFiles = fs.readdirSync(COMMANDS_SOURCE)
|
|
358
405
|
.filter(f => f.endsWith('.js'));
|
|
406
|
+
const hookFiles = fs.existsSync(HOOKS_SOURCE)
|
|
407
|
+
? fs.readdirSync(HOOKS_SOURCE).filter(f => f.endsWith('.js'))
|
|
408
|
+
: [];
|
|
359
409
|
|
|
360
410
|
if (mdFiles.length === 0) {
|
|
361
411
|
console.error('No command files found in package. Installation may be corrupt.');
|
|
@@ -397,6 +447,16 @@ function main() {
|
|
|
397
447
|
jsInstalled.push(file);
|
|
398
448
|
}
|
|
399
449
|
|
|
450
|
+
// Copy hook files to ~/.claude/plugins/plexor/hooks/
|
|
451
|
+
const hooksInstalled = [];
|
|
452
|
+
for (const file of hookFiles) {
|
|
453
|
+
const src = path.join(HOOKS_SOURCE, file);
|
|
454
|
+
const dest = path.join(PLEXOR_HOOKS_DIR, file);
|
|
455
|
+
fs.copyFileSync(src, dest);
|
|
456
|
+
fs.chmodSync(dest, 0o755);
|
|
457
|
+
hooksInstalled.push(file);
|
|
458
|
+
}
|
|
459
|
+
|
|
400
460
|
// Copy lib files to ~/.claude/plugins/plexor/lib/
|
|
401
461
|
// CRITICAL: These are required for commands to work
|
|
402
462
|
const libInstalled = [];
|
|
@@ -431,6 +491,10 @@ function main() {
|
|
|
431
491
|
console.error('');
|
|
432
492
|
}
|
|
433
493
|
|
|
494
|
+
const statusLineRegistration = upsertManagedStatusLine(CLAUDE_SETTINGS_FILE, HOME_DIR);
|
|
495
|
+
const hooksRegistration = upsertManagedHooks(CLAUDE_SETTINGS_FILE, HOME_DIR);
|
|
496
|
+
const legacyHooksCleanup = cleanupLegacyManagedHooksFile(CLAUDE_LEGACY_HOOKS_FILE);
|
|
497
|
+
|
|
434
498
|
// Fix file ownership when running with sudo
|
|
435
499
|
// Files are created as root but should be owned by the original user
|
|
436
500
|
if (targetUser) {
|
|
@@ -457,9 +521,21 @@ function main() {
|
|
|
457
521
|
if (jsInstalled.length > 0) {
|
|
458
522
|
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/commands/`);
|
|
459
523
|
}
|
|
524
|
+
if (hooksInstalled.length > 0) {
|
|
525
|
+
console.log(` ✓ Installed ${hooksInstalled.length} hook scripts to ~/.claude/plugins/plexor/hooks/`);
|
|
526
|
+
}
|
|
460
527
|
if (libInstalled.length > 0) {
|
|
461
528
|
console.log(` ✓ Installed ${libInstalled.length} lib modules to ~/.claude/plugins/plexor/lib/`);
|
|
462
529
|
}
|
|
530
|
+
if (statusLineRegistration.changed || statusLineRegistration.existed) {
|
|
531
|
+
console.log(' ✓ Registered Plexor status line in ~/.claude/settings.json');
|
|
532
|
+
}
|
|
533
|
+
if (hooksRegistration.changed || hooksRegistration.existed) {
|
|
534
|
+
console.log(' ✓ Registered Plexor hooks in ~/.claude/settings.json');
|
|
535
|
+
}
|
|
536
|
+
if (legacyHooksCleanup.changed) {
|
|
537
|
+
console.log(' ✓ Cleaned up legacy ~/.claude/hooks.json entries');
|
|
538
|
+
}
|
|
463
539
|
if (targetUser) {
|
|
464
540
|
console.log(` ✓ Set file ownership to ${targetUser.user}`);
|
|
465
541
|
}
|
|
@@ -474,10 +550,11 @@ function main() {
|
|
|
474
550
|
console.log(' 2. Write ~/.plexor/config.json');
|
|
475
551
|
console.log(' 3. Point Claude at the Plexor staging gateway');
|
|
476
552
|
console.log(' 4. Preserve prior Claude auth for restore on logout');
|
|
477
|
-
console.log(' 5.
|
|
553
|
+
console.log(' 5. Show Plexor state in Claude footer via status line');
|
|
554
|
+
console.log(' 6. Update session savings in real time via Claude hooks');
|
|
478
555
|
console.log('');
|
|
479
556
|
console.log(' ┌─────────────────────────────────────────────────────────────────┐');
|
|
480
|
-
console.log(' │
|
|
557
|
+
console.log(' │ After /plexor-setup, restart Claude before first prompt │');
|
|
481
558
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
482
559
|
console.log('');
|
|
483
560
|
console.log(' Available commands:');
|
package/scripts/uninstall.js
CHANGED
|
@@ -17,6 +17,9 @@ const fs = require('fs');
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const { execSync } = require('child_process');
|
|
20
|
+
const { removeManagedStatusLine } = require('../lib/statusline-manager');
|
|
21
|
+
const { removeManagedHooks, cleanupLegacyManagedHooksFile } = require('../lib/hooks-manager');
|
|
22
|
+
const { removeManagedClaudeCustomHeadersFromEnv } = require('../lib/config-utils');
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Get the correct home directory for the process's effective user.
|
|
@@ -52,11 +55,17 @@ console.log('');
|
|
|
52
55
|
|
|
53
56
|
const results = {
|
|
54
57
|
routing: false,
|
|
58
|
+
statusLine: false,
|
|
59
|
+
hooks: false,
|
|
55
60
|
commands: [],
|
|
56
61
|
restored: [],
|
|
57
|
-
pluginDir: false
|
|
62
|
+
pluginDir: false,
|
|
63
|
+
configDir: false
|
|
58
64
|
};
|
|
59
65
|
const PREVIOUS_PRIMARY_API_KEY_ENV = 'PLEXOR_PREVIOUS_CLAUDE_PRIMARY_API_KEY';
|
|
66
|
+
const CLAUDE_SETTINGS_PATH = path.join(home, '.claude', 'settings.json');
|
|
67
|
+
const CLAUDE_HOOKS_PATH = path.join(home, '.claude', 'settings.json');
|
|
68
|
+
const CLAUDE_LEGACY_HOOKS_PATH = path.join(home, '.claude', 'hooks.json');
|
|
60
69
|
|
|
61
70
|
function isManagedGatewayUrl(baseUrl = '') {
|
|
62
71
|
return (
|
|
@@ -77,8 +86,9 @@ function clearPlexorRoutingEnv(env = {}) {
|
|
|
77
86
|
const hasManagedBaseUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL || '');
|
|
78
87
|
const hasPlexorAuthToken = isPlexorApiKey(env.ANTHROPIC_AUTH_TOKEN || '');
|
|
79
88
|
const hasPlexorApiKey = isPlexorApiKey(env.ANTHROPIC_API_KEY || '');
|
|
89
|
+
const removedManagedHeaders = removeManagedClaudeCustomHeadersFromEnv(env);
|
|
80
90
|
|
|
81
|
-
if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey) {
|
|
91
|
+
if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey && !removedManagedHeaders) {
|
|
82
92
|
return false;
|
|
83
93
|
}
|
|
84
94
|
|
|
@@ -105,10 +115,16 @@ function clearPlexorRoutingEnv(env = {}) {
|
|
|
105
115
|
return true;
|
|
106
116
|
}
|
|
107
117
|
|
|
118
|
+
function writeJsonAtomically(filePath, value) {
|
|
119
|
+
const tempPath = `${filePath}.tmp.${Date.now()}`;
|
|
120
|
+
fs.writeFileSync(tempPath, JSON.stringify(value, null, 2), { mode: 0o600 });
|
|
121
|
+
fs.renameSync(tempPath, filePath);
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
function restoreClaudePrimaryApiKey(env = {}) {
|
|
109
125
|
const previousPrimaryApiKey = env[PREVIOUS_PRIMARY_API_KEY_ENV] || '';
|
|
110
126
|
if (!previousPrimaryApiKey) {
|
|
111
|
-
return false;
|
|
127
|
+
return { restored: false, warning: null };
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
const statePath = path.join(home, '.claude.json');
|
|
@@ -127,12 +143,14 @@ function restoreClaudePrimaryApiKey(env = {}) {
|
|
|
127
143
|
if (!claudeState.primaryApiKey) {
|
|
128
144
|
claudeState.primaryApiKey = previousPrimaryApiKey;
|
|
129
145
|
}
|
|
130
|
-
delete env[PREVIOUS_PRIMARY_API_KEY_ENV];
|
|
131
146
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
try {
|
|
148
|
+
writeJsonAtomically(statePath, claudeState);
|
|
149
|
+
delete env[PREVIOUS_PRIMARY_API_KEY_ENV];
|
|
150
|
+
return { restored: true, warning: null };
|
|
151
|
+
} catch (e) {
|
|
152
|
+
return { restored: false, warning: e.message };
|
|
153
|
+
}
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
// 1. Remove routing from settings.json
|
|
@@ -145,17 +163,20 @@ try {
|
|
|
145
163
|
const settings = JSON.parse(data);
|
|
146
164
|
if (settings.env) {
|
|
147
165
|
const routingChanged = clearPlexorRoutingEnv(settings.env);
|
|
148
|
-
const
|
|
166
|
+
const primaryApiKeyRestore = restoreClaudePrimaryApiKey(settings.env);
|
|
149
167
|
|
|
150
168
|
// Clean up empty env block
|
|
151
169
|
if (Object.keys(settings.env).length === 0) {
|
|
152
170
|
delete settings.env;
|
|
153
171
|
}
|
|
154
172
|
|
|
155
|
-
if (routingChanged ||
|
|
173
|
+
if (routingChanged || primaryApiKeyRestore.restored) {
|
|
156
174
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
157
175
|
results.routing = true;
|
|
158
176
|
}
|
|
177
|
+
if (primaryApiKeyRestore.warning) {
|
|
178
|
+
console.log(` Warning: Could not restore Claude managed API key: ${primaryApiKeyRestore.warning}`);
|
|
179
|
+
}
|
|
159
180
|
}
|
|
160
181
|
}
|
|
161
182
|
}
|
|
@@ -163,24 +184,34 @@ try {
|
|
|
163
184
|
console.log(` Warning: Could not clean settings.json: ${e.message}`);
|
|
164
185
|
}
|
|
165
186
|
|
|
166
|
-
// 2. Remove
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
187
|
+
// 2. Remove managed Claude status line
|
|
188
|
+
try {
|
|
189
|
+
const statusLineRemoval = removeManagedStatusLine(CLAUDE_SETTINGS_PATH, home);
|
|
190
|
+
results.statusLine = statusLineRemoval.changed;
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.log(` Warning: Could not clean Plexor status line: ${e.message}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 2b. Remove managed Claude hooks
|
|
196
|
+
try {
|
|
197
|
+
const hooksRemoval = removeManagedHooks(CLAUDE_HOOKS_PATH);
|
|
198
|
+
results.hooks = hooksRemoval.changed;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.log(` Warning: Could not clean Plexor hooks: ${e.message}`);
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const legacyHooksRemoval = cleanupLegacyManagedHooksFile(CLAUDE_LEGACY_HOOKS_PATH);
|
|
204
|
+
results.hooks = results.hooks || legacyHooksRemoval.changed;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.log(` Warning: Could not clean legacy Plexor hooks file: ${e.message}`);
|
|
207
|
+
}
|
|
180
208
|
|
|
209
|
+
// 3. Remove slash command files
|
|
210
|
+
// These are the Plexor-specific command files that get installed to ~/.claude/commands/
|
|
181
211
|
try {
|
|
182
212
|
const commandsDir = path.join(home, '.claude', 'commands');
|
|
183
213
|
if (fs.existsSync(commandsDir)) {
|
|
214
|
+
const plexorCommands = fs.readdirSync(commandsDir).filter((entry) => /^plexor-.*\.md$/i.test(entry));
|
|
184
215
|
for (const cmd of plexorCommands) {
|
|
185
216
|
const cmdPath = path.join(commandsDir, cmd);
|
|
186
217
|
const backupPath = cmdPath + '.backup';
|
|
@@ -201,7 +232,7 @@ try {
|
|
|
201
232
|
console.log(` Warning: Could not clean commands: ${e.message}`);
|
|
202
233
|
}
|
|
203
234
|
|
|
204
|
-
//
|
|
235
|
+
// 4. Remove plugin directory
|
|
205
236
|
try {
|
|
206
237
|
const pluginDir = path.join(home, '.claude', 'plugins', 'plexor');
|
|
207
238
|
if (fs.existsSync(pluginDir)) {
|
|
@@ -212,8 +243,26 @@ try {
|
|
|
212
243
|
console.log(` Warning: Could not remove plugin directory: ${e.message}`);
|
|
213
244
|
}
|
|
214
245
|
|
|
246
|
+
// 5. Remove config directory
|
|
247
|
+
try {
|
|
248
|
+
const configDir = path.join(home, '.plexor');
|
|
249
|
+
if (fs.existsSync(configDir)) {
|
|
250
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
251
|
+
results.configDir = true;
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.log(` Warning: Could not remove ~/.plexor config directory: ${e.message}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
215
257
|
// Output results
|
|
216
|
-
if (
|
|
258
|
+
if (
|
|
259
|
+
results.routing ||
|
|
260
|
+
results.statusLine ||
|
|
261
|
+
results.hooks ||
|
|
262
|
+
results.commands.length > 0 ||
|
|
263
|
+
results.pluginDir ||
|
|
264
|
+
results.configDir
|
|
265
|
+
) {
|
|
217
266
|
console.log(' Plexor plugin uninstalled');
|
|
218
267
|
console.log('');
|
|
219
268
|
|
|
@@ -223,6 +272,16 @@ if (results.routing || results.commands.length > 0 || results.pluginDir) {
|
|
|
223
272
|
console.log('');
|
|
224
273
|
}
|
|
225
274
|
|
|
275
|
+
if (results.statusLine) {
|
|
276
|
+
console.log(' Removed Plexor Claude status line');
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (results.hooks) {
|
|
281
|
+
console.log(' Removed Plexor Claude hooks');
|
|
282
|
+
console.log('');
|
|
283
|
+
}
|
|
284
|
+
|
|
226
285
|
if (results.commands.length > 0) {
|
|
227
286
|
console.log(' Removed commands:');
|
|
228
287
|
results.commands.forEach(cmd => console.log(` /${cmd}`));
|
|
@@ -239,10 +298,10 @@ if (results.routing || results.commands.length > 0 || results.pluginDir) {
|
|
|
239
298
|
console.log(' Removed plugin directory');
|
|
240
299
|
console.log('');
|
|
241
300
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
301
|
+
if (results.configDir) {
|
|
302
|
+
console.log(' Removed ~/.plexor config directory');
|
|
303
|
+
console.log('');
|
|
304
|
+
}
|
|
246
305
|
} else {
|
|
247
306
|
console.log(' No Plexor components found to clean up.');
|
|
248
307
|
console.log('');
|