@plexor-dev/claude-code-plugin 0.1.0-beta.32 → 0.1.0-beta.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -106,7 +106,7 @@ rm -rf ~/.plexor
106
106
  ## Support
107
107
 
108
108
  - **Documentation**: https://plexor.dev/docs
109
- - **Issues**: https://github.com/plexor-ai/claude-code-plugin/issues
109
+ - **Issues**: https://github.com/micoverde/plex-vc-fund-platform/issues
110
110
  - **Email**: hello@plexor.dev
111
111
 
112
112
  ## License
@@ -94,6 +94,30 @@ function isValidApiKeyFormat(key) {
94
94
  return key && typeof key === 'string' && key.startsWith('plx_') && key.length >= 20;
95
95
  }
96
96
 
97
+ /**
98
+ * Check for state mismatch between config.json enabled flag and settings.json routing
99
+ * @param {boolean} configEnabled - enabled flag from config.json
100
+ * @param {boolean} routingActive - whether settings.json has Plexor routing configured
101
+ * @returns {Object|null} mismatch details or null if states are consistent
102
+ */
103
+ function checkStateMismatch(configEnabled, routingActive) {
104
+ if (configEnabled && !routingActive) {
105
+ return {
106
+ type: 'config-enabled-routing-inactive',
107
+ message: 'Config shows enabled but Claude routing is not configured',
108
+ suggestion: 'Run /plexor-enabled true to sync and configure routing'
109
+ };
110
+ }
111
+ if (!configEnabled && routingActive) {
112
+ return {
113
+ type: 'config-disabled-routing-active',
114
+ message: 'Config shows disabled but Claude routing is active',
115
+ suggestion: 'Run /plexor-enabled false to sync and disable routing'
116
+ };
117
+ }
118
+ return null;
119
+ }
120
+
97
121
  function main() {
98
122
  const args = process.argv.slice(2);
99
123
  const config = loadConfig();
@@ -107,6 +131,10 @@ function main() {
107
131
  if (args.length === 0) {
108
132
  const status = currentEnabled ? '● Enabled' : '○ Disabled';
109
133
  const routingStr = routingStatus.enabled ? '● Active' : '○ Inactive';
134
+
135
+ // Check for state mismatch between config enabled flag and routing status
136
+ const stateMismatch = checkStateMismatch(currentEnabled, routingStatus.enabled);
137
+
110
138
  console.log(`┌─────────────────────────────────────────────┐`);
111
139
  console.log(`│ Plexor Proxy Status │`);
112
140
  console.log(`├─────────────────────────────────────────────┤`);
@@ -115,6 +143,12 @@ function main() {
115
143
  if (routingStatus.enabled) {
116
144
  console.log(`│ Endpoint: ${(routingStatus.isStaging ? 'Staging' : 'Production').padEnd(32)}│`);
117
145
  }
146
+ if (stateMismatch) {
147
+ console.log(`├─────────────────────────────────────────────┤`);
148
+ console.log(`│ ⚠ State mismatch detected: │`);
149
+ console.log(`│ ${stateMismatch.message.padEnd(42)}│`);
150
+ console.log(`│ ${stateMismatch.suggestion.padEnd(42)}│`);
151
+ }
118
152
  console.log(`├─────────────────────────────────────────────┤`);
119
153
  console.log(`│ Usage: │`);
120
154
  console.log(`│ /plexor-enabled true - Enable proxy │`);
@@ -161,6 +195,19 @@ function main() {
161
195
  const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
162
196
  const useStaging = apiUrl.includes('staging');
163
197
  routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
198
+
199
+ // Check if settings.json update failed
200
+ if (!routingUpdated) {
201
+ console.log(`┌─────────────────────────────────────────────┐`);
202
+ console.log(`│ ✗ Failed to Enable Plexor Routing │`);
203
+ console.log(`├─────────────────────────────────────────────┤`);
204
+ console.log(`│ Could not update ~/.claude/settings.json │`);
205
+ console.log(`│ Config.json was updated but routing failed.│`);
206
+ console.log(`├─────────────────────────────────────────────┤`);
207
+ console.log(`│ Check file permissions and try again. │`);
208
+ console.log(`└─────────────────────────────────────────────┘`);
209
+ process.exit(1);
210
+ }
164
211
  }
165
212
  } else {
166
213
  // Update Plexor plugin config
@@ -172,6 +219,19 @@ function main() {
172
219
 
173
220
  // Disable routing - remove env vars from settings.json
174
221
  routingUpdated = settingsManager.disablePlexorRouting();
222
+
223
+ // Check if settings.json update failed
224
+ if (!routingUpdated) {
225
+ console.log(`┌─────────────────────────────────────────────┐`);
226
+ console.log(`│ ✗ Failed to Disable Plexor Routing │`);
227
+ console.log(`├─────────────────────────────────────────────┤`);
228
+ console.log(`│ Could not update ~/.claude/settings.json │`);
229
+ console.log(`│ Config.json was updated but routing failed.│`);
230
+ console.log(`├─────────────────────────────────────────────┤`);
231
+ console.log(`│ Check file permissions and try again. │`);
232
+ console.log(`└─────────────────────────────────────────────┘`);
233
+ process.exit(1);
234
+ }
175
235
  }
176
236
 
177
237
  // Show error if no API key when enabling
@@ -2,27 +2,8 @@
2
2
  description: Enable or disable Plexor proxy (routes all traffic through Plexor API) (user)
3
3
  ---
4
4
 
5
- # Plexor Enabled
6
-
7
- Run this command to view or toggle the Plexor proxy:
8
-
9
- ```bash
10
- node ~/.claude/plugins/plexor/commands/plexor-enabled.js
11
- ```
12
-
13
- To enable or disable, pass an argument:
5
+ **RULE: Execute the bash command below EXACTLY ONCE. After the Bash tool returns output, your ONLY action is to present that output to the user. DO NOT re-execute the command. DO NOT call any other tools.**
14
6
 
15
7
  ```bash
16
- node ~/.claude/plugins/plexor/commands/plexor-enabled.js true
17
- node ~/.claude/plugins/plexor/commands/plexor-enabled.js false
8
+ node ~/.claude/plugins/plexor/commands/plexor-enabled.js $ARGUMENTS
18
9
  ```
19
-
20
- Use the Bash tool to execute this command.
21
-
22
- **IMPORTANT**: After running this command and displaying the output, STOP. Do not:
23
- - Read any files
24
- - Explore the codebase
25
- - Run additional commands
26
- - Ask follow-up questions
27
-
28
- The command output is the complete response. Simply show the output and wait for the user's next input.
@@ -76,6 +76,18 @@ function saveConfig(config) {
76
76
  }
77
77
  }
78
78
 
79
+ function isPlexorApiKey(token) {
80
+ return typeof token === 'string' && token.startsWith('plx_') && token.length >= 20;
81
+ }
82
+
83
+ function isJwtToken(token) {
84
+ if (typeof token !== 'string' || !token.startsWith('eyJ')) {
85
+ return false;
86
+ }
87
+ const parts = token.split('.');
88
+ return parts.length === 3 && parts.every(part => part.length > 0);
89
+ }
90
+
79
91
  function validateApiKey(apiUrl, apiKey) {
80
92
  return new Promise((resolve, reject) => {
81
93
  const url = new URL(`${apiUrl}/v1/user`);
@@ -119,6 +131,56 @@ function validateApiKey(apiUrl, apiKey) {
119
131
  });
120
132
  }
121
133
 
134
+ function validateJwtToken(apiUrl, jwtToken) {
135
+ return new Promise((resolve, reject) => {
136
+ const url = new URL(`${apiUrl}/v1/auth/me`);
137
+ const isHttps = url.protocol === 'https:';
138
+ const lib = isHttps ? https : http;
139
+
140
+ const options = {
141
+ hostname: url.hostname,
142
+ port: url.port || (isHttps ? 443 : 80),
143
+ path: url.pathname,
144
+ method: 'GET',
145
+ headers: {
146
+ 'Authorization': `Bearer ${jwtToken}`
147
+ }
148
+ };
149
+
150
+ const req = lib.request(options, (res) => {
151
+ let data = '';
152
+ res.on('data', chunk => data += chunk);
153
+ res.on('end', () => {
154
+ if (res.statusCode === 200) {
155
+ try {
156
+ resolve(JSON.parse(data));
157
+ } catch {
158
+ reject(new Error('Invalid response from server'));
159
+ }
160
+ } else if (res.statusCode === 401) {
161
+ reject(new Error('Invalid login token'));
162
+ } else {
163
+ reject(new Error(`Server error: ${res.statusCode}`));
164
+ }
165
+ });
166
+ });
167
+
168
+ req.on('error', (err) => reject(new Error(`Connection failed: ${err.message}`)));
169
+ req.setTimeout(10000, () => {
170
+ req.destroy();
171
+ reject(new Error('Connection timeout'));
172
+ });
173
+ req.end();
174
+ });
175
+ }
176
+
177
+ function validateCredential(apiUrl, credential) {
178
+ if (isJwtToken(credential)) {
179
+ return validateJwtToken(apiUrl, credential);
180
+ }
181
+ return validateApiKey(apiUrl, credential);
182
+ }
183
+
122
184
  /**
123
185
  * Read API key from stdin (for piped input)
124
186
  * @returns {Promise<string>} The API key read from stdin
@@ -220,10 +282,10 @@ async function main() {
220
282
  console.log('');
221
283
  }
222
284
 
223
- // Validate key format
224
- if (!apiKey.startsWith('plx_') || apiKey.length < 20) {
225
- console.error(`Error: Invalid API key format`);
226
- console.error(`API keys start with "plx_" and are at least 20 characters`);
285
+ // Validate credential format (Plexor API key OR login JWT)
286
+ if (!isPlexorApiKey(apiKey) && !isJwtToken(apiKey)) {
287
+ console.error(`Error: Invalid credential format`);
288
+ console.error(`Expected Plexor API key ("plx_...") or login token ("eyJ...")`);
227
289
  process.exit(1);
228
290
  }
229
291
 
@@ -232,7 +294,7 @@ async function main() {
232
294
  console.log('Validating API key...');
233
295
 
234
296
  try {
235
- const user = await validateApiKey(apiUrl, apiKey);
297
+ const user = await validateCredential(apiUrl, apiKey);
236
298
 
237
299
  config.auth = config.auth || {};
238
300
  config.auth.api_key = apiKey;
@@ -2,26 +2,10 @@
2
2
  description: Authenticate with Plexor to enable optimization (user)
3
3
  ---
4
4
 
5
- # Plexor Login
5
+ **RULE: Execute the bash command below EXACTLY ONCE. After the Bash tool returns output, your ONLY action is to present that output to the user. DO NOT re-execute the command. DO NOT call any other tools.**
6
6
 
7
- Run this command to authenticate with Plexor:
7
+ Pass the API key as argument (format: `plx_your_api_key_here`).
8
8
 
9
9
  ```bash
10
- node ~/.claude/plugins/plexor/commands/plexor-login.js
10
+ node ~/.claude/plugins/plexor/commands/plexor-login.js $ARGUMENTS
11
11
  ```
12
-
13
- To login with an API key:
14
-
15
- ```bash
16
- node ~/.claude/plugins/plexor/commands/plexor-login.js plx_your_api_key_here
17
- ```
18
-
19
- Use the Bash tool to execute this command.
20
-
21
- **IMPORTANT**: After running this command and displaying the output, STOP. Do not:
22
- - Read any files
23
- - Explore the codebase
24
- - Run additional commands
25
- - Ask follow-up questions
26
-
27
- The command output is the complete response. Simply show the output and wait for the user's next input.
@@ -2,26 +2,8 @@
2
2
  description: Log out from Plexor and clear credentials (user)
3
3
  ---
4
4
 
5
- # Plexor Logout
6
-
7
- Run this command to log out and clear credentials:
8
-
9
- ```bash
10
- node ~/.claude/plugins/plexor/commands/plexor-logout.js
11
- ```
12
-
13
- To also clear the cache:
5
+ **RULE: Execute the bash command below EXACTLY ONCE. After the Bash tool returns output, your ONLY action is to present that output to the user. DO NOT re-execute the command. DO NOT call any other tools.**
14
6
 
15
7
  ```bash
16
- node ~/.claude/plugins/plexor/commands/plexor-logout.js --clear-cache
8
+ node ~/.claude/plugins/plexor/commands/plexor-logout.js $ARGUMENTS
17
9
  ```
18
-
19
- Use the Bash tool to execute this command.
20
-
21
- **IMPORTANT**: After running this command and displaying the output, STOP. Do not:
22
- - Read any files
23
- - Explore the codebase
24
- - Run additional commands
25
- - Ask follow-up questions
26
-
27
- The command output is the complete response. Simply show the output and wait for the user's next input.
@@ -2,6 +2,8 @@
2
2
  description: First-time setup wizard for Plexor with Claude Code (user)
3
3
  ---
4
4
 
5
+ **RULE: Execute this workflow EXACTLY ONCE. After completing all steps, STOP. DO NOT restart the workflow. DO NOT re-execute any commands. DO NOT call any other tools.**
6
+
5
7
  # Plexor Setup Wizard
6
8
 
7
9
  Guide users through first-time Plexor setup. **No manual environment variable configuration required!**
@@ -165,8 +167,9 @@ Commands:
165
167
  Changes take effect immediately in all Claude Code sessions!
166
168
  ```
167
169
 
168
- **IMPORTANT NOTES**:
170
+ **NOTES**:
169
171
  - The `~/.claude/settings.json` env block is the KEY mechanism that routes Claude Code through Plexor
170
172
  - ANTHROPIC_AUTH_TOKEN takes precedence over ANTHROPIC_API_KEY (use AUTH_TOKEN for the Plexor key)
171
173
  - Changes take effect immediately - no shell restart needed
172
- - After completing setup, STOP. Do not run additional commands.
174
+
175
+ After completing all steps, STOP. DO NOT restart the workflow. DO NOT re-execute any commands.
@@ -34,6 +34,31 @@ function getRoutingStatus() {
34
34
  }
35
35
  }
36
36
 
37
+ /**
38
+ * Detect partial routing state where URL points to Plexor but auth is missing/invalid
39
+ * This can cause confusing auth errors for users
40
+ * @returns {Object} { partial: boolean, issue: string|null }
41
+ */
42
+ function detectPartialState() {
43
+ try {
44
+ const data = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
45
+ const settings = JSON.parse(data);
46
+ const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
47
+ const authToken = settings.env?.ANTHROPIC_AUTH_TOKEN || '';
48
+ const isPlexorUrl = baseUrl.includes('plexor') || baseUrl.includes('staging.api');
49
+
50
+ if (isPlexorUrl && !authToken) {
51
+ return { partial: true, issue: 'Plexor URL set but no auth token' };
52
+ }
53
+ if (isPlexorUrl && !authToken.startsWith('plx_')) {
54
+ return { partial: true, issue: 'Plexor URL set but auth token is not a Plexor key' };
55
+ }
56
+ return { partial: false, issue: null };
57
+ } catch {
58
+ return { partial: false, issue: null };
59
+ }
60
+ }
61
+
37
62
  function loadSessionStats() {
38
63
  try {
39
64
  const data = fs.readFileSync(SESSION_PATH, 'utf8');
@@ -101,6 +126,30 @@ function checkEnvironmentMismatch(configApiUrl, routingBaseUrl) {
101
126
  return null;
102
127
  }
103
128
 
129
+ /**
130
+ * Check for state mismatch between config.json enabled flag and settings.json routing
131
+ * @param {boolean} configEnabled - enabled flag from config.json
132
+ * @param {boolean} routingActive - whether settings.json has Plexor routing configured
133
+ * @returns {Object|null} mismatch details or null if states are consistent
134
+ */
135
+ function checkStateMismatch(configEnabled, routingActive) {
136
+ if (configEnabled && !routingActive) {
137
+ return {
138
+ type: 'config-enabled-routing-inactive',
139
+ message: 'Config shows enabled but Claude routing is not configured',
140
+ suggestion: 'Run /plexor-enabled true to sync and configure routing'
141
+ };
142
+ }
143
+ if (!configEnabled && routingActive) {
144
+ return {
145
+ type: 'config-disabled-routing-active',
146
+ message: 'Config shows disabled but Claude routing is active',
147
+ suggestion: 'Run /plexor-enabled false to sync and disable routing'
148
+ };
149
+ }
150
+ return null;
151
+ }
152
+
104
153
  async function main() {
105
154
  // Read config with integrity checking
106
155
  const config = loadConfig();
@@ -221,14 +270,22 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
221
270
  const routingIndicator = routing.active ? '🟢 PLEXOR MODE: ON' : '🔴 PLEXOR MODE: OFF';
222
271
  const envLabel = routing.isStaging ? '(staging)' : '(production)';
223
272
 
224
- // Check for environment mismatch
225
- const envMismatch = checkEnvironmentMismatch(apiUrl, routing.baseUrl);
226
- const mismatchWarning = envMismatch
227
- ? ` ⚠ Warning: Config uses ${envMismatch.config} but routing is ${envMismatch.routing}\n`
228
- : '';
273
+ // Note: Environment mismatch warning removed - it caused false positives during
274
+ // concurrent operations and transient states. The partial state and config/routing
275
+ // mismatch warnings below provide more actionable feedback.
276
+
277
+ // Check for partial routing state (Plexor URL without valid auth)
278
+ const partialState = detectPartialState();
279
+ if (partialState.partial) {
280
+ console.log(` ⚠ PARTIAL STATE DETECTED: ${partialState.issue}`);
281
+ console.log(` Run /plexor-login to fix, or /plexor-logout to disable routing\n`);
282
+ }
229
283
 
230
- if (mismatchWarning) {
231
- console.log(mismatchWarning);
284
+ // Check for state mismatch between config enabled flag and routing status
285
+ const stateMismatch = checkStateMismatch(enabled, routing.active);
286
+ if (stateMismatch) {
287
+ console.log(` ⚠ State mismatch: ${stateMismatch.message}`);
288
+ console.log(` └─ ${stateMismatch.suggestion}\n`);
232
289
  }
233
290
 
234
291
  console.log(` ┌─────────────────────────────────────────────┐
@@ -2,20 +2,8 @@
2
2
  description: Show Plexor optimization statistics and savings (user)
3
3
  ---
4
4
 
5
- # Plexor Status
6
-
7
- Run this command to display Plexor statistics:
5
+ **RULE: Execute the bash command below EXACTLY ONCE. After the Bash tool returns output, your ONLY action is to present that output to the user. DO NOT re-execute the command. DO NOT call any other tools.**
8
6
 
9
7
  ```bash
10
8
  node ~/.claude/plugins/plexor/commands/plexor-status.js
11
9
  ```
12
-
13
- Use the Bash tool to execute this single command.
14
-
15
- **IMPORTANT**: After running this command and displaying the output, STOP. Do not:
16
- - Read any files
17
- - Explore the codebase
18
- - Run additional commands
19
- - Ask follow-up questions
20
-
21
- The command output is the complete response. Simply show the output and wait for the user's next input.
@@ -0,0 +1,293 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Plexor Uninstall Command
5
+ *
6
+ * Comprehensive cleanup before npm uninstall.
7
+ *
8
+ * CRITICAL: npm's preuninstall hook does NOT run for global package uninstalls.
9
+ * Users MUST run this command BEFORE running npm uninstall -g.
10
+ *
11
+ * This command:
12
+ * 1. Removes Plexor routing from ~/.claude/settings.json
13
+ * 2. Removes slash command .md files from ~/.claude/commands/
14
+ * 3. Removes plugin directory ~/.claude/plugins/plexor/
15
+ * 4. Optionally removes ~/.plexor/ config directory
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const os = require('os');
21
+
22
+ // Get home directory, handling sudo case
23
+ function getHomeDir() {
24
+ if (process.env.SUDO_USER) {
25
+ const platform = os.platform();
26
+ if (platform === 'darwin') {
27
+ return path.join('/Users', process.env.SUDO_USER);
28
+ } else if (platform === 'linux') {
29
+ return path.join('/home', process.env.SUDO_USER);
30
+ }
31
+ }
32
+ return process.env.HOME || process.env.USERPROFILE || os.homedir();
33
+ }
34
+
35
+ const HOME_DIR = getHomeDir();
36
+ const CLAUDE_DIR = path.join(HOME_DIR, '.claude');
37
+ const CLAUDE_COMMANDS_DIR = path.join(CLAUDE_DIR, 'commands');
38
+ const CLAUDE_PLUGINS_DIR = path.join(CLAUDE_DIR, 'plugins', 'plexor');
39
+ const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
40
+
41
+ // All Plexor slash command files
42
+ const PLEXOR_COMMANDS = [
43
+ 'plexor-enabled.md',
44
+ 'plexor-login.md',
45
+ 'plexor-logout.md',
46
+ 'plexor-setup.md',
47
+ 'plexor-status.md',
48
+ 'plexor-uninstall.md',
49
+ 'plexor-mode.md',
50
+ 'plexor-provider.md',
51
+ 'plexor-settings.md',
52
+ 'plexor-config.md'
53
+ ];
54
+
55
+ /**
56
+ * Load settings manager if available
57
+ */
58
+ function loadSettingsManager() {
59
+ // Try plugin dir first (installed location)
60
+ try {
61
+ const pluginLib = path.join(CLAUDE_PLUGINS_DIR, 'lib', 'settings-manager.js');
62
+ if (fs.existsSync(pluginLib)) {
63
+ const lib = require(pluginLib);
64
+ return lib.settingsManager;
65
+ }
66
+ } catch {
67
+ // Continue to fallback
68
+ }
69
+
70
+ // Try package lib (during npm lifecycle)
71
+ try {
72
+ const lib = require('../lib/settings-manager');
73
+ return lib.settingsManager;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Disable Plexor routing in Claude settings manually
81
+ * Fallback when settings-manager is not available
82
+ */
83
+ function disableRoutingManually() {
84
+ const settingsPath = path.join(CLAUDE_DIR, 'settings.json');
85
+
86
+ try {
87
+ if (!fs.existsSync(settingsPath)) {
88
+ return { success: true, message: 'Settings file does not exist' };
89
+ }
90
+
91
+ const data = fs.readFileSync(settingsPath, 'utf8');
92
+ if (!data || data.trim() === '') {
93
+ return { success: true, message: 'Settings file is empty' };
94
+ }
95
+
96
+ const settings = JSON.parse(data);
97
+
98
+ if (!settings.env) {
99
+ return { success: true, message: 'No env block in settings' };
100
+ }
101
+
102
+ // Check if Plexor routing is active
103
+ const baseUrl = settings.env.ANTHROPIC_BASE_URL || '';
104
+ const isPlexorRouting = baseUrl.includes('plexor') || baseUrl.includes('staging.api');
105
+
106
+ if (!isPlexorRouting) {
107
+ return { success: true, message: 'Plexor routing not active' };
108
+ }
109
+
110
+ // Remove Plexor env vars
111
+ delete settings.env.ANTHROPIC_BASE_URL;
112
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
113
+
114
+ // Clean up empty env block
115
+ if (Object.keys(settings.env).length === 0) {
116
+ delete settings.env;
117
+ }
118
+
119
+ // Atomic write
120
+ const crypto = require('crypto');
121
+ const tempId = crypto.randomBytes(8).toString('hex');
122
+ const tempPath = path.join(CLAUDE_DIR, `.settings.${tempId}.tmp`);
123
+
124
+ fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
125
+ fs.renameSync(tempPath, settingsPath);
126
+
127
+ return { success: true, message: 'Routing disabled' };
128
+ } catch (err) {
129
+ return { success: false, message: err.message };
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Remove slash command files
135
+ */
136
+ function removeSlashCommands() {
137
+ let removed = 0;
138
+ let restored = 0;
139
+
140
+ for (const cmd of PLEXOR_COMMANDS) {
141
+ const cmdPath = path.join(CLAUDE_COMMANDS_DIR, cmd);
142
+ const backupPath = cmdPath + '.backup';
143
+
144
+ try {
145
+ if (fs.existsSync(cmdPath)) {
146
+ fs.unlinkSync(cmdPath);
147
+ removed++;
148
+
149
+ // Restore backup if exists
150
+ if (fs.existsSync(backupPath)) {
151
+ fs.renameSync(backupPath, cmdPath);
152
+ restored++;
153
+ }
154
+ }
155
+ } catch {
156
+ // Continue with other files
157
+ }
158
+ }
159
+
160
+ return { removed, restored };
161
+ }
162
+
163
+ /**
164
+ * Remove plugin directory
165
+ */
166
+ function removePluginDirectory() {
167
+ try {
168
+ if (fs.existsSync(CLAUDE_PLUGINS_DIR)) {
169
+ fs.rmSync(CLAUDE_PLUGINS_DIR, { recursive: true, force: true });
170
+ return true;
171
+ }
172
+ } catch {
173
+ return false;
174
+ }
175
+ return false;
176
+ }
177
+
178
+ /**
179
+ * Remove config directory
180
+ */
181
+ function removeConfigDirectory() {
182
+ try {
183
+ if (fs.existsSync(PLEXOR_CONFIG_DIR)) {
184
+ fs.rmSync(PLEXOR_CONFIG_DIR, { recursive: true, force: true });
185
+ return true;
186
+ }
187
+ } catch {
188
+ return false;
189
+ }
190
+ return false;
191
+ }
192
+
193
+ function main() {
194
+ const args = process.argv.slice(2);
195
+
196
+ // Handle help flag
197
+ if (args.includes('--help') || args.includes('-h')) {
198
+ console.log('');
199
+ console.log(' Usage: plexor-uninstall [options]');
200
+ console.log('');
201
+ console.log(' Cleans up Plexor integration before npm uninstall.');
202
+ console.log('');
203
+ console.log(' Options:');
204
+ console.log(' --remove-config, -c Also remove ~/.plexor/ config directory');
205
+ console.log(' --quiet, -q Suppress output messages');
206
+ console.log(' --help, -h Show this help message');
207
+ console.log('');
208
+ console.log(' After running this command:');
209
+ console.log(' npm uninstall -g @plexor-dev/claude-code-plugin');
210
+ console.log('');
211
+ process.exit(0);
212
+ }
213
+
214
+ const removeConfig = args.includes('--remove-config') || args.includes('-c');
215
+ const quiet = args.includes('--quiet') || args.includes('-q');
216
+
217
+ if (!quiet) {
218
+ console.log('');
219
+ console.log(' Plexor Uninstall - Cleaning up...');
220
+ console.log('');
221
+ }
222
+
223
+ // 1. Remove routing from ~/.claude/settings.json
224
+ let routingResult;
225
+ const settingsManager = loadSettingsManager();
226
+
227
+ if (settingsManager) {
228
+ try {
229
+ const success = settingsManager.disablePlexorRouting();
230
+ routingResult = { success, message: success ? 'Routing disabled via manager' : 'Already clean' };
231
+ } catch (err) {
232
+ routingResult = disableRoutingManually();
233
+ }
234
+ } else {
235
+ routingResult = disableRoutingManually();
236
+ }
237
+
238
+ if (!quiet) {
239
+ console.log(routingResult.success
240
+ ? ' ✓ Removed Plexor routing from Claude settings'
241
+ : ` ✗ Failed to remove routing: ${routingResult.message}`);
242
+ }
243
+
244
+ // 2. Remove slash command .md files
245
+ const cmdResult = removeSlashCommands();
246
+ if (!quiet) {
247
+ console.log(` ✓ Removed ${cmdResult.removed} slash command files`);
248
+ if (cmdResult.restored > 0) {
249
+ console.log(` ✓ Restored ${cmdResult.restored} backed-up files`);
250
+ }
251
+ }
252
+
253
+ // 3. Remove plugin directory
254
+ const pluginRemoved = removePluginDirectory();
255
+ if (!quiet) {
256
+ console.log(pluginRemoved
257
+ ? ' ✓ Removed plugin directory'
258
+ : ' ○ Plugin directory not found (already clean)');
259
+ }
260
+
261
+ // 4. Optionally remove config directory
262
+ if (removeConfig) {
263
+ const configRemoved = removeConfigDirectory();
264
+ if (!quiet) {
265
+ console.log(configRemoved
266
+ ? ' ✓ Removed ~/.plexor config directory'
267
+ : ' ○ Config directory not found');
268
+ }
269
+ }
270
+
271
+ // Show next steps
272
+ if (!quiet) {
273
+ console.log('');
274
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
275
+ console.log(' │ Cleanup complete! Now run: │');
276
+ console.log(' │ │');
277
+ console.log(' │ npm uninstall -g @plexor-dev/claude-code-plugin │');
278
+ console.log(' │ │');
279
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
280
+ console.log('');
281
+ console.log(' Your Claude Code is ready to work normally again.');
282
+ console.log('');
283
+
284
+ if (!removeConfig) {
285
+ console.log(' Note: ~/.plexor/ config directory was preserved.');
286
+ console.log(' To also remove it: plexor-uninstall --remove-config');
287
+ console.log(' Or manually: rm -rf ~/.plexor');
288
+ console.log('');
289
+ }
290
+ }
291
+ }
292
+
293
+ main();
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: plexor-uninstall
3
+ description: Clean up Plexor integration before uninstalling
4
+ ---
5
+
6
+ **RULE: Execute the bash command below EXACTLY ONCE. After the Bash tool returns output, your ONLY action is to present that output to the user. DO NOT re-execute the command. DO NOT call any other tools.**
7
+
8
+ **IMPORTANT**: Run this BEFORE `npm uninstall -g @plexor-dev/claude-code-plugin`. Pass `--remove-config` to also remove ~/.plexor/ config directory.
9
+
10
+ ```bash
11
+ node ~/.claude/plugins/plexor/commands/plexor-uninstall.js $ARGUMENTS 2>&1 || plexor-uninstall $ARGUMENTS 2>&1
12
+ ```
@@ -106,9 +106,9 @@ try {
106
106
  const saveSession = (s) => {
107
107
  try {
108
108
  const dir = path.dirname(SESSION_PATH);
109
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
109
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
110
110
  s.last_activity = Date.now();
111
- fs.writeFileSync(SESSION_PATH, JSON.stringify(s, null, 2));
111
+ fs.writeFileSync(SESSION_PATH, JSON.stringify(s, null, 2), { mode: 0o600 });
112
112
  } catch {}
113
113
  };
114
114
 
@@ -179,6 +179,17 @@ async function main() {
179
179
  input = await readStdin();
180
180
  request = JSON.parse(input);
181
181
 
182
+ // Issue #2042: Check for slash commands FIRST, before ANY other processing
183
+ // Slash commands must pass through completely clean — no metadata injection
184
+ // Adding _plexor_client or _plexor to slash command requests adds context noise
185
+ // that causes the model to re-execute commands in a loop
186
+ // Note: session.recordPassthrough() intentionally omitted — slash commands are
187
+ // not API requests and should not pollute session analytics
188
+ if (isSlashCommand(request)) {
189
+ logger.debug('Slash command detected, clean passthrough (no metadata)');
190
+ return output(request); // Completely clean — no metadata added
191
+ }
192
+
182
193
  // Phase 3 Hypervisor Mode Detection
183
194
  // When ANTHROPIC_BASE_URL points to Plexor, all intelligence is server-side
184
195
  // The plugin just passes through - server handles optimization, routing, quality
@@ -202,25 +213,6 @@ async function main() {
202
213
  });
203
214
  }
204
215
 
205
- // CRITICAL: Check for slash commands FIRST (before agentic check)
206
- // Slash commands like /plexor-status should pass through unchanged
207
- // Must check before isAgenticRequest since all Claude Code requests have tools
208
- if (isSlashCommand(request)) {
209
- logger.debug('Slash command detected, passing through unchanged');
210
- session.recordPassthrough();
211
- return output({
212
- ...request,
213
- plexor_cwd: process.cwd(),
214
- _plexor: {
215
- request_id: generateRequestId('slash'), // Issue #701: Add request_id for tracking
216
- source: 'passthrough_slash_command',
217
- reason: 'slash_command_detected',
218
- cwd: process.cwd(),
219
- latency_ms: Date.now() - startTime
220
- }
221
- });
222
- }
223
-
224
216
  // CRITICAL: Skip optimization for CLI commands requiring tool execution
225
217
  // Azure CLI, AWS CLI, kubectl, etc. need tools to be preserved
226
218
  if (requiresToolExecution(request)) {
@@ -578,12 +570,16 @@ function isSlashCommand(request) {
578
570
  }
579
571
 
580
572
  // Check for system messages with skill instructions
573
+ // Issue #2042: Updated to match new RULE-based .md format (old H1 headers removed)
581
574
  for (const msg of messages) {
582
575
  if (msg.role === 'system') {
583
576
  const content = typeof msg.content === 'string' ? msg.content : '';
584
577
  if (/# Plexor (?:Status|Login|Logout|Mode|Provider|Enabled|Settings)/i.test(content)) {
585
578
  return true;
586
579
  }
580
+ if (/plexor\/commands\/plexor-/i.test(content)) {
581
+ return true;
582
+ }
587
583
  }
588
584
  }
589
585
 
@@ -85,10 +85,10 @@ try {
85
85
  save(session) {
86
86
  try {
87
87
  if (!fs.existsSync(PLEXOR_DIR)) {
88
- fs.mkdirSync(PLEXOR_DIR, { recursive: true });
88
+ fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
89
89
  }
90
90
  session.last_activity = Date.now();
91
- fs.writeFileSync(this.sessionPath, JSON.stringify(session, null, 2));
91
+ fs.writeFileSync(this.sessionPath, JSON.stringify(session, null, 2), { mode: 0o600 });
92
92
  } catch {}
93
93
  }
94
94
 
package/lib/config.js CHANGED
@@ -56,7 +56,7 @@ class ConfigManager {
56
56
  }
57
57
  };
58
58
 
59
- fs.writeFileSync(this.configPath, JSON.stringify(updated, null, 2));
59
+ fs.writeFileSync(this.configPath, JSON.stringify(updated, null, 2), { mode: 0o600 });
60
60
  return true;
61
61
  } catch {
62
62
  return false;
@@ -49,8 +49,14 @@ class ClaudeSettingsManager {
49
49
 
50
50
  // Basic schema validation - must be an object
51
51
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
52
- console.warn('Warning: Claude settings file has invalid format, using defaults');
53
- this._backupCorruptedFile();
52
+ const backupPath = this._backupCorruptedFile();
53
+ console.warn('');
54
+ console.warn('WARNING: Claude settings file has invalid format!');
55
+ if (backupPath) {
56
+ console.warn(` Corrupted file backed up to: ${backupPath}`);
57
+ }
58
+ console.warn(' Using default settings. Your previous settings may need manual recovery.');
59
+ console.warn('');
54
60
  return {};
55
61
  }
56
62
 
@@ -61,9 +67,14 @@ class ClaudeSettingsManager {
61
67
  }
62
68
  // JSON parse error or corrupted file
63
69
  if (err instanceof SyntaxError) {
64
- console.warn('Warning: Claude settings file is corrupted, using defaults');
65
- console.warn(' A backup has been saved to settings.json.corrupted');
66
- this._backupCorruptedFile();
70
+ const backupPath = this._backupCorruptedFile();
71
+ console.warn('');
72
+ console.warn('WARNING: Claude settings file is corrupted (invalid JSON)!');
73
+ if (backupPath) {
74
+ console.warn(` Corrupted file backed up to: ${backupPath}`);
75
+ }
76
+ console.warn(' Using default settings. Your previous settings may need manual recovery.');
77
+ console.warn('');
67
78
  return {};
68
79
  }
69
80
  // Permission error
@@ -77,17 +88,30 @@ class ClaudeSettingsManager {
77
88
  }
78
89
 
79
90
  /**
80
- * Backup a corrupted settings file for debugging
91
+ * Backup a corrupted settings file with numbered suffix for debugging
92
+ * Creates settings.json.corrupted.1, .corrupted.2, etc. to preserve history
93
+ * @returns {string|null} path to backup file, or null if backup failed
81
94
  */
82
95
  _backupCorruptedFile() {
83
96
  try {
84
97
  if (fs.existsSync(this.settingsPath)) {
85
- const backupPath = this.settingsPath + '.corrupted';
98
+ // Find next available numbered backup
99
+ let backupNum = 1;
100
+ let backupPath;
101
+ while (true) {
102
+ backupPath = `${this.settingsPath}.corrupted.${backupNum}`;
103
+ if (!fs.existsSync(backupPath)) {
104
+ break;
105
+ }
106
+ backupNum++;
107
+ }
86
108
  fs.copyFileSync(this.settingsPath, backupPath);
109
+ return backupPath;
87
110
  }
88
111
  } catch {
89
- // Ignore backup errors
112
+ // Ignore backup errors silently
90
113
  }
114
+ return null;
91
115
  }
92
116
 
93
117
  /**
@@ -242,6 +266,30 @@ class ClaudeSettingsManager {
242
266
  }
243
267
  }
244
268
 
269
+ /**
270
+ * Detect partial routing state where URL points to Plexor but auth is missing/invalid
271
+ * This can cause confusing auth errors for users
272
+ * @returns {Object} { partial: boolean, issue: string|null }
273
+ */
274
+ detectPartialState() {
275
+ try {
276
+ const settings = this.load();
277
+ const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
278
+ const authToken = settings.env?.ANTHROPIC_AUTH_TOKEN || '';
279
+ const isPlexorUrl = baseUrl.includes('plexor') || baseUrl.includes('staging.api');
280
+
281
+ if (isPlexorUrl && !authToken) {
282
+ return { partial: true, issue: 'Plexor URL set but no auth token' };
283
+ }
284
+ if (isPlexorUrl && !authToken.startsWith('plx_')) {
285
+ return { partial: true, issue: 'Plexor URL set but auth token is not a Plexor key' };
286
+ }
287
+ return { partial: false, issue: null };
288
+ } catch {
289
+ return { partial: false, issue: null };
290
+ }
291
+ }
292
+
245
293
  /**
246
294
  * Update just the API key without changing other settings
247
295
  * @param {string} apiKey - new Plexor API key
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@plexor-dev/claude-code-plugin",
3
- "version": "0.1.0-beta.32",
3
+ "version": "0.1.0-beta.34",
4
4
  "description": "LLM cost optimization plugin for Claude Code - Save up to 90% on AI costs",
5
5
  "main": "lib/constants.js",
6
6
  "bin": {
7
7
  "plexor-status": "./commands/plexor-status.js",
8
8
  "plexor-enabled": "./commands/plexor-enabled.js",
9
9
  "plexor-login": "./commands/plexor-login.js",
10
- "plexor-logout": "./commands/plexor-logout.js"
10
+ "plexor-logout": "./commands/plexor-logout.js",
11
+ "plexor-uninstall": "./commands/plexor-uninstall.js"
11
12
  },
12
13
  "scripts": {
13
14
  "postinstall": "node scripts/postinstall.js",
@@ -77,6 +77,51 @@ const PLEXOR_LIB_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'lib'
77
77
  const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
78
78
  const PLEXOR_CONFIG_FILE = path.join(PLEXOR_CONFIG_DIR, 'config.json');
79
79
 
80
+ /**
81
+ * Check for orphaned Plexor routing in settings.json without valid config.
82
+ * This can happen if a previous uninstall was incomplete.
83
+ */
84
+ function checkOrphanedRouting() {
85
+ const home = process.env.HOME || process.env.USERPROFILE;
86
+ if (!home) return;
87
+
88
+ const settingsPath = path.join(home, '.claude', 'settings.json');
89
+ const configPath = path.join(home, '.plexor', 'config.json');
90
+
91
+ try {
92
+ if (!fs.existsSync(settingsPath)) return;
93
+
94
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
95
+ const env = settings.env || {};
96
+
97
+ const hasPlexorUrl = env.ANTHROPIC_BASE_URL &&
98
+ env.ANTHROPIC_BASE_URL.includes('plexor');
99
+
100
+ if (hasPlexorUrl) {
101
+ // Check if there's a valid Plexor config
102
+ let hasValidConfig = false;
103
+ try {
104
+ if (fs.existsSync(configPath)) {
105
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
106
+ hasValidConfig = config.apiKey && config.apiKey.startsWith('plx_');
107
+ }
108
+ } catch (e) {}
109
+
110
+ if (!hasValidConfig) {
111
+ console.log('\n Warning: Detected orphaned Plexor routing in Claude settings');
112
+ console.log(' This may be from a previous installation.\n');
113
+ console.log(' Run /plexor-login to reconfigure, or');
114
+ console.log(' Run /plexor-uninstall to clean up\n');
115
+ } else {
116
+ console.log('\n Existing Plexor configuration detected');
117
+ console.log(' Your previous settings have been preserved.\n');
118
+ }
119
+ }
120
+ } catch (e) {
121
+ // Ignore errors in detection - don't break install
122
+ }
123
+ }
124
+
80
125
  // Default configuration for new installs
81
126
  // PRODUCTION PACKAGE - uses production API
82
127
  const DEFAULT_CONFIG = {
@@ -94,6 +139,9 @@ const DEFAULT_CONFIG = {
94
139
  };
95
140
 
96
141
  function main() {
142
+ // Check for orphaned routing at start of postinstall
143
+ checkOrphanedRouting();
144
+
97
145
  try {
98
146
  // Get target user info for chown (if running with sudo)
99
147
  const targetUser = getTargetUserIds();
@@ -1,97 +1,154 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Plexor Claude Code Plugin - Uninstall Script
4
+ * Plexor Claude Code Plugin - Comprehensive Uninstall Script
5
5
  *
6
- * Removes slash commands from ~/.claude/commands/
7
- * Optionally restores backups if they exist.
6
+ * Runs on npm uninstall (when npm actually calls it).
7
+ * Also callable directly: node scripts/uninstall.js
8
+ *
9
+ * Performs complete cleanup:
10
+ * 1. Removes Plexor routing from ~/.claude/settings.json
11
+ * 2. Removes slash command files from ~/.claude/commands/
12
+ * 3. Removes plugin directory from ~/.claude/plugins/plexor/
13
+ * 4. Restores any backups if they exist
8
14
  */
9
15
 
10
16
  const fs = require('fs');
11
17
  const path = require('path');
12
- const os = require('os');
13
18
 
14
- // Import settings manager for Claude Code routing cleanup
15
- let settingsManager;
16
- try {
17
- const lib = require('../lib/settings-manager');
18
- settingsManager = lib.settingsManager;
19
- } catch (err) {
20
- // If settings manager can't be loaded during uninstall, continue anyway
21
- settingsManager = null;
19
+ // Get home directory - support both Unix and Windows
20
+ const home = process.env.HOME || process.env.USERPROFILE;
21
+ if (!home) {
22
+ console.log('Warning: HOME not set, skipping cleanup');
23
+ process.exit(0);
22
24
  }
23
25
 
24
- const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
25
- const CLAUDE_COMMANDS_DIR = path.join(os.homedir(), '.claude', 'commands');
26
-
27
- function main() {
28
- try {
29
- // CRITICAL: Disable Claude Code routing before removing commands
30
- // This ensures users don't get stuck with Plexor routing after uninstall
31
- let routingDisabled = false;
32
- if (settingsManager) {
33
- try {
34
- routingDisabled = settingsManager.disablePlexorRouting();
35
- } catch (e) {
36
- // Continue with uninstall even if routing cleanup fails
26
+ console.log('');
27
+ console.log(' Plexor plugin cleanup...');
28
+ console.log('');
29
+
30
+ const results = {
31
+ routing: false,
32
+ commands: [],
33
+ restored: [],
34
+ pluginDir: false
35
+ };
36
+
37
+ // 1. Remove routing from settings.json
38
+ // This is CRITICAL - do NOT depend on settings-manager module since it may not load during uninstall
39
+ try {
40
+ const settingsPath = path.join(home, '.claude', 'settings.json');
41
+ if (fs.existsSync(settingsPath)) {
42
+ const data = fs.readFileSync(settingsPath, 'utf8');
43
+ if (data && data.trim()) {
44
+ const settings = JSON.parse(data);
45
+ if (settings.env) {
46
+ const hadBaseUrl = !!settings.env.ANTHROPIC_BASE_URL;
47
+ const hadAuthToken = !!settings.env.ANTHROPIC_AUTH_TOKEN;
48
+
49
+ delete settings.env.ANTHROPIC_BASE_URL;
50
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
51
+
52
+ // Clean up empty env block
53
+ if (Object.keys(settings.env).length === 0) {
54
+ delete settings.env;
55
+ }
56
+
57
+ if (hadBaseUrl || hadAuthToken) {
58
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
59
+ results.routing = true;
60
+ }
37
61
  }
38
62
  }
63
+ }
64
+ } catch (e) {
65
+ console.log(` Warning: Could not clean settings.json: ${e.message}`);
66
+ }
39
67
 
40
- // Get list of our command files
41
- const files = fs.readdirSync(COMMANDS_SOURCE)
42
- .filter(f => f.endsWith('.md'));
43
-
44
- const removed = [];
45
- const restored = [];
68
+ // 2. Remove slash command files
69
+ // These are the Plexor-specific command files that get installed to ~/.claude/commands/
70
+ const plexorCommands = [
71
+ 'plexor-config.md',
72
+ 'plexor-enabled.md',
73
+ 'plexor-login.md',
74
+ 'plexor-logout.md',
75
+ 'plexor-mode.md',
76
+ 'plexor-provider.md',
77
+ 'plexor-settings.md',
78
+ 'plexor-setup.md',
79
+ 'plexor-status.md',
80
+ 'plexor-uninstall.md'
81
+ ];
46
82
 
47
- for (const file of files) {
48
- const dest = path.join(CLAUDE_COMMANDS_DIR, file);
49
- const backupPath = dest + '.backup';
83
+ try {
84
+ const commandsDir = path.join(home, '.claude', 'commands');
85
+ if (fs.existsSync(commandsDir)) {
86
+ for (const cmd of plexorCommands) {
87
+ const cmdPath = path.join(commandsDir, cmd);
88
+ const backupPath = cmdPath + '.backup';
50
89
 
51
- if (fs.existsSync(dest)) {
52
- fs.unlinkSync(dest);
53
- removed.push(file.replace('.md', ''));
90
+ if (fs.existsSync(cmdPath)) {
91
+ fs.unlinkSync(cmdPath);
92
+ results.commands.push(cmd.replace('.md', ''));
54
93
 
55
94
  // Restore backup if it exists
56
95
  if (fs.existsSync(backupPath)) {
57
- fs.renameSync(backupPath, dest);
58
- restored.push(file);
96
+ fs.renameSync(backupPath, cmdPath);
97
+ results.restored.push(cmd);
59
98
  }
60
99
  }
61
100
  }
101
+ }
102
+ } catch (e) {
103
+ console.log(` Warning: Could not clean commands: ${e.message}`);
104
+ }
62
105
 
63
- if (removed.length > 0 || routingDisabled) {
64
- console.log('');
65
- console.log(' Plexor plugin uninstalled');
66
- console.log('');
106
+ // 3. Remove plugin directory
107
+ try {
108
+ const pluginDir = path.join(home, '.claude', 'plugins', 'plexor');
109
+ if (fs.existsSync(pluginDir)) {
110
+ fs.rmSync(pluginDir, { recursive: true, force: true });
111
+ results.pluginDir = true;
112
+ }
113
+ } catch (e) {
114
+ console.log(` Warning: Could not remove plugin directory: ${e.message}`);
115
+ }
67
116
 
68
- if (routingDisabled) {
69
- console.log(' ✓ Claude Code routing disabled');
70
- console.log(' (Claude Code now connects directly to Anthropic)');
71
- console.log('');
72
- }
117
+ // Output results
118
+ if (results.routing || results.commands.length > 0 || results.pluginDir) {
119
+ console.log(' Plexor plugin uninstalled');
120
+ console.log('');
73
121
 
74
- if (removed.length > 0) {
75
- console.log(' Removed commands:');
76
- removed.forEach(cmd => console.log(` /${cmd}`));
77
- }
122
+ if (results.routing) {
123
+ console.log(' Removed Plexor routing from Claude settings');
124
+ console.log(' (Claude Code now connects directly to Anthropic)');
125
+ console.log('');
126
+ }
78
127
 
79
- if (restored.length > 0) {
80
- console.log('');
81
- console.log(' Restored from backup:');
82
- restored.forEach(f => console.log(` ${f}`));
83
- }
128
+ if (results.commands.length > 0) {
129
+ console.log(' Removed commands:');
130
+ results.commands.forEach(cmd => console.log(` /${cmd}`));
131
+ console.log('');
132
+ }
84
133
 
85
- console.log('');
86
- console.log(' Note: ~/.plexor/ config directory was preserved.');
87
- console.log(' To remove it: rm -rf ~/.plexor');
88
- console.log('');
89
- }
134
+ if (results.restored.length > 0) {
135
+ console.log(' Restored from backup:');
136
+ results.restored.forEach(f => console.log(` ${f}`));
137
+ console.log('');
138
+ }
90
139
 
91
- } catch (error) {
92
- // Don't fail uninstall on errors - just warn
93
- console.warn(` Warning: Could not fully uninstall: ${error.message}`);
140
+ if (results.pluginDir) {
141
+ console.log(' Removed plugin directory');
142
+ console.log('');
94
143
  }
144
+
145
+ console.log(' Note: ~/.plexor/ config directory was preserved.');
146
+ console.log(' To remove it: rm -rf ~/.plexor');
147
+ console.log('');
148
+ } else {
149
+ console.log(' No Plexor components found to clean up.');
150
+ console.log('');
95
151
  }
96
152
 
97
- main();
153
+ console.log(' Cleanup complete');
154
+ console.log('');