@plexor-dev/claude-code-plugin 0.1.0-beta.31 → 0.1.0-beta.33

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.
@@ -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;
package/lib/constants.js CHANGED
@@ -4,7 +4,23 @@
4
4
 
5
5
  const path = require('path');
6
6
 
7
- const PLEXOR_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.plexor');
7
+ /**
8
+ * Get the user's home directory with proper validation
9
+ * @returns {string} Home directory path
10
+ * @throws {Error} If HOME is not set or empty
11
+ */
12
+ function getHomeDir() {
13
+ const home = process.env.HOME || process.env.USERPROFILE;
14
+ if (!home || home.trim() === '') {
15
+ console.error('Error: HOME environment variable is not set.');
16
+ console.error(' Please set HOME to your user directory before running Plexor commands.');
17
+ process.exit(1);
18
+ }
19
+ return home;
20
+ }
21
+
22
+ const HOME_DIR = getHomeDir();
23
+ const PLEXOR_DIR = path.join(HOME_DIR, '.plexor');
8
24
  const CONFIG_PATH = path.join(PLEXOR_DIR, 'config.json');
9
25
  const SESSION_PATH = path.join(PLEXOR_DIR, 'session.json');
10
26
  const CACHE_PATH = path.join(PLEXOR_DIR, 'cache.json');
@@ -15,6 +31,8 @@ const DEFAULT_API_URL = 'https://api.plexor.dev';
15
31
  const DEFAULT_TIMEOUT = 5000;
16
32
 
17
33
  module.exports = {
34
+ getHomeDir,
35
+ HOME_DIR,
18
36
  PLEXOR_DIR,
19
37
  CONFIG_PATH,
20
38
  SESSION_PATH,
@@ -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.31",
3
+ "version": "0.1.0-beta.33",
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();