@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.
@@ -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 ANTHROPIC_BASE_URL + syncs
176
- * Claude auth env vars for Plexor-managed gateways.
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 apiKey || authToken || '';
198
+ return '';
185
199
  }
186
200
 
187
- function syncManagedAuthEnv(env, managedAuthKey) {
188
- const currentApiKey = env.ANTHROPIC_API_KEY || '';
189
- const currentAuthToken = env.ANTHROPIC_AUTH_TOKEN || '';
201
+ function restoreLegacyManagedAuthEnvValue(env, field, backupField) {
202
+ let changed = false;
203
+ const currentValue = env[field] || '';
204
+ const backupValue = env[backupField] || '';
190
205
 
191
- if (currentApiKey && !currentApiKey.startsWith('plx_') && currentApiKey !== managedAuthKey) {
192
- env[PREVIOUS_API_KEY_ENV] = currentApiKey;
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
- if (currentAuthToken && !currentAuthToken.startsWith('plx_') && currentAuthToken !== managedAuthKey) {
195
- env[PREVIOUS_AUTH_TOKEN_ENV] = currentAuthToken;
214
+
215
+ if (backupValue) {
216
+ delete env[backupField];
217
+ changed = true;
196
218
  }
197
219
 
198
- env.ANTHROPIC_API_KEY = managedAuthKey;
199
- env.ANTHROPIC_AUTH_TOKEN = managedAuthKey;
220
+ return changed;
200
221
  }
201
222
 
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
- }
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
- const claudeState = JSON.parse(data);
215
- const primaryApiKey = claudeState.primaryApiKey || '';
216
- if (!primaryApiKey || primaryApiKey.startsWith('plx_') || primaryApiKey === managedAuthKey) {
217
- return false;
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
- env[PREVIOUS_PRIMARY_API_KEY_ENV] = primaryApiKey;
221
- delete claudeState.primaryApiKey;
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
- 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;
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
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
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. Verify Claude routing with a deterministic check');
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(' │ No shell edits or Claude restart required after setup │');
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:');
@@ -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
- 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;
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 primaryApiKeyRestored = restoreClaudePrimaryApiKey(settings.env);
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 || primaryApiKeyRestored) {
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 slash command files
167
- // These are the Plexor-specific command files that get installed to ~/.claude/commands/
168
- const plexorCommands = [
169
- 'plexor-config.md',
170
- 'plexor-enabled.md',
171
- 'plexor-login.md',
172
- 'plexor-logout.md',
173
- 'plexor-mode.md',
174
- 'plexor-provider.md',
175
- 'plexor-settings.md',
176
- 'plexor-setup.md',
177
- 'plexor-status.md',
178
- 'plexor-uninstall.md'
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
- // 3. Remove plugin directory
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 (results.routing || results.commands.length > 0 || results.pluginDir) {
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
- console.log(' Note: ~/.plexor/ config directory was preserved.');
244
- console.log(' To remove it: rm -rf ~/.plexor');
245
- console.log('');
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('');