@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.
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
@@ -14,6 +14,9 @@
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
16
 
17
+ // Import centralized constants with HOME directory validation
18
+ const { PLEXOR_DIR, CONFIG_PATH } = require('../lib/constants');
19
+
17
20
  // Import settings manager with error handling for missing lib
18
21
  let settingsManager, PLEXOR_STAGING_URL, PLEXOR_PROD_URL;
19
22
  try {
@@ -30,9 +33,6 @@ try {
30
33
  throw err;
31
34
  }
32
35
 
33
- const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
34
- const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
35
-
36
36
  function loadConfig() {
37
37
  try {
38
38
  if (!fs.existsSync(CONFIG_PATH)) {
@@ -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.
@@ -13,6 +13,9 @@ const path = require('path');
13
13
  const https = require('https');
14
14
  const http = require('http');
15
15
 
16
+ // Import centralized constants with HOME directory validation
17
+ const { PLEXOR_DIR, CONFIG_PATH, DEFAULT_API_URL } = require('../lib/constants');
18
+
16
19
  // Import settings manager with error handling for missing lib
17
20
  let settingsManager;
18
21
  try {
@@ -26,11 +29,6 @@ try {
26
29
  throw err;
27
30
  }
28
31
 
29
- const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
30
- const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
31
- // PRODUCTION PACKAGE - uses production API
32
- const DEFAULT_API_URL = 'https://api.plexor.dev';
33
-
34
32
  function loadConfig() {
35
33
  try {
36
34
  if (!fs.existsSync(CONFIG_PATH)) {
@@ -78,6 +76,18 @@ function saveConfig(config) {
78
76
  }
79
77
  }
80
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
+
81
91
  function validateApiKey(apiUrl, apiKey) {
82
92
  return new Promise((resolve, reject) => {
83
93
  const url = new URL(`${apiUrl}/v1/user`);
@@ -121,9 +131,112 @@ function validateApiKey(apiUrl, apiKey) {
121
131
  });
122
132
  }
123
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
+
184
+ /**
185
+ * Read API key from stdin (for piped input)
186
+ * @returns {Promise<string>} The API key read from stdin
187
+ */
188
+ function readFromStdin() {
189
+ return new Promise((resolve, reject) => {
190
+ let data = '';
191
+ const timeout = setTimeout(() => {
192
+ reject(new Error('Timeout reading from stdin'));
193
+ }, 5000);
194
+
195
+ process.stdin.setEncoding('utf8');
196
+ process.stdin.on('data', (chunk) => {
197
+ data += chunk;
198
+ });
199
+ process.stdin.on('end', () => {
200
+ clearTimeout(timeout);
201
+ resolve(data.trim());
202
+ });
203
+ process.stdin.on('error', (err) => {
204
+ clearTimeout(timeout);
205
+ reject(err);
206
+ });
207
+ process.stdin.resume();
208
+ });
209
+ }
210
+
124
211
  async function main() {
125
212
  const args = process.argv.slice(2);
126
- let apiKey = args[0];
213
+ let apiKey = null;
214
+ let keySource = null;
215
+
216
+ // SECURITY FIX: Check for API key in order of preference
217
+ // 1. Environment variable (most secure for scripts/CI)
218
+ // 2. Stdin pipe (secure for interactive use)
219
+ // 3. Command line argument (warns about security risk)
220
+
221
+ // Check environment variable first (most secure)
222
+ if (process.env.PLEXOR_API_KEY) {
223
+ apiKey = process.env.PLEXOR_API_KEY;
224
+ keySource = 'environment';
225
+ }
226
+ // Check if stdin has data (piped input)
227
+ else if (!process.stdin.isTTY && args.length === 0) {
228
+ try {
229
+ apiKey = await readFromStdin();
230
+ keySource = 'stdin';
231
+ } catch {
232
+ // Stdin read failed, continue to check args
233
+ }
234
+ }
235
+ // Check command line argument (least secure - shows in ps)
236
+ else if (args[0]) {
237
+ apiKey = args[0];
238
+ keySource = 'argument';
239
+ }
127
240
 
128
241
  // Check for existing login
129
242
  const config = loadConfig();
@@ -135,14 +248,14 @@ async function main() {
135
248
  console.log(`├─────────────────────────────────────────────┤`);
136
249
  console.log(`│ API Key: ${(existingKey.substring(0, 8) + '...').padEnd(33)}│`);
137
250
  console.log(`│ To re-login, provide a new key: │`);
138
- console.log(`│ /plexor-login <api-key> │`);
251
+ console.log(`│ echo $PLEXOR_API_KEY | /plexor-login │`);
139
252
  console.log(`│ To logout: │`);
140
253
  console.log(`│ /plexor-logout │`);
141
254
  console.log(`└─────────────────────────────────────────────┘`);
142
255
  return;
143
256
  }
144
257
 
145
- // If no key provided, prompt for it
258
+ // If no key provided, show secure usage options
146
259
  if (!apiKey) {
147
260
  console.log(`┌─────────────────────────────────────────────┐`);
148
261
  console.log(`│ Plexor Login │`);
@@ -150,15 +263,29 @@ async function main() {
150
263
  console.log(`│ Get your API key at: │`);
151
264
  console.log(`│ https://plexor.dev/dashboard/api-keys │`);
152
265
  console.log(`├─────────────────────────────────────────────┤`);
153
- console.log(`│ Usage: /plexor-login <api-key> │`);
266
+ console.log(`│ Secure usage (recommended): │`);
267
+ console.log(`│ echo "plx_..." | /plexor-login │`);
268
+ console.log(`│ PLEXOR_API_KEY=plx_... /plexor-login │`);
269
+ console.log(`├─────────────────────────────────────────────┤`);
270
+ console.log(`│ Direct usage (visible in ps): │`);
271
+ console.log(`│ /plexor-login <api-key> │`);
154
272
  console.log(`└─────────────────────────────────────────────┘`);
155
273
  return;
156
274
  }
157
275
 
158
- // Validate key format
159
- if (!apiKey.startsWith('plx_') || apiKey.length < 20) {
160
- console.error(`Error: Invalid API key format`);
161
- console.error(`API keys start with "plx_" and are at least 20 characters`);
276
+ // Warn if API key was passed as command line argument
277
+ if (keySource === 'argument') {
278
+ console.log(`⚠ Security note: API key passed as argument is visible in`);
279
+ console.log(` process list (ps aux). For better security, use:`);
280
+ console.log(` echo "plx_..." | /plexor-login`);
281
+ console.log(` PLEXOR_API_KEY=plx_... /plexor-login`);
282
+ console.log('');
283
+ }
284
+
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...")`);
162
289
  process.exit(1);
163
290
  }
164
291
 
@@ -167,7 +294,7 @@ async function main() {
167
294
  console.log('Validating API key...');
168
295
 
169
296
  try {
170
- const user = await validateApiKey(apiUrl, apiKey);
297
+ const user = await validateCredential(apiUrl, apiKey);
171
298
 
172
299
  config.auth = config.auth || {};
173
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.
@@ -8,10 +8,22 @@
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
 
11
- const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
12
- const CONFIG_PATH = path.join(PLEXOR_DIR, 'config.json');
13
- const SESSION_PATH = path.join(PLEXOR_DIR, 'session.json');
14
- const CACHE_PATH = path.join(PLEXOR_DIR, 'cache.json');
11
+ // Import centralized constants with HOME directory validation
12
+ const { PLEXOR_DIR, CONFIG_PATH, SESSION_PATH, CACHE_PATH } = require('../lib/constants');
13
+
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 (err.code === 'MODULE_NOT_FOUND') {
21
+ console.error('Error: Plexor plugin files are missing or corrupted.');
22
+ console.error(' Please reinstall: npm install @plexor-dev/claude-code-plugin');
23
+ process.exit(1);
24
+ }
25
+ throw err;
26
+ }
15
27
 
16
28
  function loadConfig() {
17
29
  try {
@@ -96,6 +108,11 @@ function main() {
96
108
  process.exit(1);
97
109
  }
98
110
 
111
+ // CRITICAL FIX: Disable Claude Code routing in ~/.claude/settings.json
112
+ // This removes ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN so Claude Code
113
+ // no longer tries to route through Plexor with removed credentials
114
+ const routingDisabled = settingsManager.disablePlexorRouting();
115
+
99
116
  // Clear session
100
117
  deleteFile(SESSION_PATH);
101
118
 
@@ -109,12 +126,13 @@ function main() {
109
126
  console.log(`│ ✓ Logged Out │`);
110
127
  console.log(`├─────────────────────────────────────────────┤`);
111
128
  console.log(`│ ✓ API key removed │`);
112
- console.log(`│ ✓ Plexor proxy disabled │`);
129
+ console.log(`│ ${routingDisabled ? '' : '○'} Claude Code routing disabled │`);
113
130
  console.log(`│ ✓ Session cleared │`);
114
131
  if (clearCache) {
115
132
  console.log(`│ ${cacheCleared ? '✓' : '○'} Cache cleared │`);
116
133
  }
117
134
  console.log(`├─────────────────────────────────────────────┤`);
135
+ console.log(`│ Claude Code now connects directly. │`);
118
136
  console.log(`│ Run /plexor-login to re-authenticate. │`);
119
137
  if (!clearCache) {
120
138
  console.log(`│ Use --clear-cache to also clear cache. │`);
@@ -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.
@@ -9,10 +9,9 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const https = require('https');
11
11
 
12
- const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
13
- const SESSION_PATH = path.join(process.env.HOME, '.plexor', 'session.json');
14
- const CLAUDE_SETTINGS_PATH = path.join(process.env.HOME, '.claude', 'settings.json');
15
- const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
12
+ // Import centralized constants with HOME directory validation
13
+ const { HOME_DIR, CONFIG_PATH, SESSION_PATH, SESSION_TIMEOUT_MS } = require('../lib/constants');
14
+ const CLAUDE_SETTINGS_PATH = path.join(HOME_DIR, '.claude', 'settings.json');
16
15
 
17
16
  /**
18
17
  * Check if Claude Code is actually routing through Plexor
@@ -35,6 +34,31 @@ function getRoutingStatus() {
35
34
  }
36
35
  }
37
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
+
38
62
  function loadSessionStats() {
39
63
  try {
40
64
  const data = fs.readFileSync(SESSION_PATH, 'utf8');
@@ -102,6 +126,30 @@ function checkEnvironmentMismatch(configApiUrl, routingBaseUrl) {
102
126
  return null;
103
127
  }
104
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
+
105
153
  async function main() {
106
154
  // Read config with integrity checking
107
155
  const config = loadConfig();
@@ -222,14 +270,22 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
222
270
  const routingIndicator = routing.active ? '🟢 PLEXOR MODE: ON' : '🔴 PLEXOR MODE: OFF';
223
271
  const envLabel = routing.isStaging ? '(staging)' : '(production)';
224
272
 
225
- // Check for environment mismatch
226
- const envMismatch = checkEnvironmentMismatch(apiUrl, routing.baseUrl);
227
- const mismatchWarning = envMismatch
228
- ? ` ⚠ Warning: Config uses ${envMismatch.config} but routing is ${envMismatch.routing}\n`
229
- : '';
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
+ }
230
283
 
231
- if (mismatchWarning) {
232
- 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`);
233
289
  }
234
290
 
235
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.