@plexor-dev/claude-code-plugin-staging 0.1.0-beta.2 → 0.1.0-beta.21

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,319 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Plexor Uninstall Command (STAGING)
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
+ const isManagedGatewayUrl = (baseUrl = '') =>
103
+ baseUrl.includes('plexor') ||
104
+ baseUrl.includes('staging.api') ||
105
+ baseUrl.includes('localhost') ||
106
+ baseUrl.includes('127.0.0.1') ||
107
+ baseUrl.includes('ngrok') ||
108
+ baseUrl.includes('localtunnel');
109
+ const isPlexorApiKey = (value = '') =>
110
+ typeof value === 'string' && value.startsWith('plx_');
111
+
112
+ const hasManagedBaseUrl = isManagedGatewayUrl(settings.env.ANTHROPIC_BASE_URL || '');
113
+ const hasPlexorAuthToken = isPlexorApiKey(settings.env.ANTHROPIC_AUTH_TOKEN || '');
114
+ const hasPlexorApiKey = isPlexorApiKey(settings.env.ANTHROPIC_API_KEY || '');
115
+
116
+ if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey) {
117
+ return { success: true, message: 'Plexor routing not active' };
118
+ }
119
+
120
+ if (hasManagedBaseUrl) {
121
+ delete settings.env.ANTHROPIC_BASE_URL;
122
+ }
123
+ if (hasPlexorAuthToken) {
124
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
125
+ }
126
+ if (hasPlexorApiKey) {
127
+ delete settings.env.ANTHROPIC_API_KEY;
128
+ }
129
+
130
+ if (!settings.env.ANTHROPIC_API_KEY && settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY) {
131
+ settings.env.ANTHROPIC_API_KEY = settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
132
+ }
133
+ if (!settings.env.ANTHROPIC_AUTH_TOKEN && settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN) {
134
+ settings.env.ANTHROPIC_AUTH_TOKEN = settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
135
+ }
136
+
137
+ delete settings.env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
138
+ delete settings.env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
139
+
140
+ // Clean up empty env block
141
+ if (Object.keys(settings.env).length === 0) {
142
+ delete settings.env;
143
+ }
144
+
145
+ // Atomic write
146
+ const crypto = require('crypto');
147
+ const tempId = crypto.randomBytes(8).toString('hex');
148
+ const tempPath = path.join(CLAUDE_DIR, `.settings.${tempId}.tmp`);
149
+
150
+ fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
151
+ fs.renameSync(tempPath, settingsPath);
152
+
153
+ return { success: true, message: 'Routing disabled' };
154
+ } catch (err) {
155
+ return { success: false, message: err.message };
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Remove slash command files
161
+ */
162
+ function removeSlashCommands() {
163
+ let removed = 0;
164
+ let restored = 0;
165
+
166
+ for (const cmd of PLEXOR_COMMANDS) {
167
+ const cmdPath = path.join(CLAUDE_COMMANDS_DIR, cmd);
168
+ const backupPath = cmdPath + '.backup';
169
+
170
+ try {
171
+ if (fs.existsSync(cmdPath)) {
172
+ fs.unlinkSync(cmdPath);
173
+ removed++;
174
+
175
+ // Restore backup if exists
176
+ if (fs.existsSync(backupPath)) {
177
+ fs.renameSync(backupPath, cmdPath);
178
+ restored++;
179
+ }
180
+ }
181
+ } catch {
182
+ // Continue with other files
183
+ }
184
+ }
185
+
186
+ return { removed, restored };
187
+ }
188
+
189
+ /**
190
+ * Remove plugin directory
191
+ */
192
+ function removePluginDirectory() {
193
+ try {
194
+ if (fs.existsSync(CLAUDE_PLUGINS_DIR)) {
195
+ fs.rmSync(CLAUDE_PLUGINS_DIR, { recursive: true, force: true });
196
+ return true;
197
+ }
198
+ } catch {
199
+ return false;
200
+ }
201
+ return false;
202
+ }
203
+
204
+ /**
205
+ * Remove config directory
206
+ */
207
+ function removeConfigDirectory() {
208
+ try {
209
+ if (fs.existsSync(PLEXOR_CONFIG_DIR)) {
210
+ fs.rmSync(PLEXOR_CONFIG_DIR, { recursive: true, force: true });
211
+ return true;
212
+ }
213
+ } catch {
214
+ return false;
215
+ }
216
+ return false;
217
+ }
218
+
219
+ function main() {
220
+ const args = process.argv.slice(2);
221
+
222
+ // Handle help flag
223
+ if (args.includes('--help') || args.includes('-h')) {
224
+ console.log('');
225
+ console.log(' Usage: plexor-uninstall [options]');
226
+ console.log('');
227
+ console.log(' Cleans up Plexor integration before npm uninstall.');
228
+ console.log('');
229
+ console.log(' Options:');
230
+ console.log(' --remove-config, -c Also remove ~/.plexor/ config directory');
231
+ console.log(' --quiet, -q Suppress output messages');
232
+ console.log(' --help, -h Show this help message');
233
+ console.log('');
234
+ console.log(' After running this command:');
235
+ console.log(' npm uninstall -g @plexor-dev/claude-code-plugin-staging');
236
+ console.log('');
237
+ process.exit(0);
238
+ }
239
+
240
+ const removeConfig = args.includes('--remove-config') || args.includes('-c');
241
+ const quiet = args.includes('--quiet') || args.includes('-q');
242
+
243
+ if (!quiet) {
244
+ console.log('');
245
+ console.log(' Plexor Uninstall (STAGING) - Cleaning up...');
246
+ console.log('');
247
+ }
248
+
249
+ // 1. Remove routing from ~/.claude/settings.json
250
+ let routingResult;
251
+ const settingsManager = loadSettingsManager();
252
+
253
+ if (settingsManager) {
254
+ try {
255
+ const success = settingsManager.disablePlexorRouting();
256
+ routingResult = { success, message: success ? 'Routing disabled via manager' : 'Already clean' };
257
+ } catch (err) {
258
+ routingResult = disableRoutingManually();
259
+ }
260
+ } else {
261
+ routingResult = disableRoutingManually();
262
+ }
263
+
264
+ if (!quiet) {
265
+ console.log(routingResult.success
266
+ ? ' ✓ Removed Plexor routing from Claude settings'
267
+ : ` ✗ Failed to remove routing: ${routingResult.message}`);
268
+ }
269
+
270
+ // 2. Remove slash command .md files
271
+ const cmdResult = removeSlashCommands();
272
+ if (!quiet) {
273
+ console.log(` ✓ Removed ${cmdResult.removed} slash command files`);
274
+ if (cmdResult.restored > 0) {
275
+ console.log(` ✓ Restored ${cmdResult.restored} backed-up files`);
276
+ }
277
+ }
278
+
279
+ // 3. Remove plugin directory
280
+ const pluginRemoved = removePluginDirectory();
281
+ if (!quiet) {
282
+ console.log(pluginRemoved
283
+ ? ' ✓ Removed plugin directory'
284
+ : ' ○ Plugin directory not found (already clean)');
285
+ }
286
+
287
+ // 4. Optionally remove config directory
288
+ if (removeConfig) {
289
+ const configRemoved = removeConfigDirectory();
290
+ if (!quiet) {
291
+ console.log(configRemoved
292
+ ? ' ✓ Removed ~/.plexor config directory'
293
+ : ' ○ Config directory not found');
294
+ }
295
+ }
296
+
297
+ // Show next steps
298
+ if (!quiet) {
299
+ console.log('');
300
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
301
+ console.log(' │ Cleanup complete! Now run: │');
302
+ console.log(' │ │');
303
+ console.log(' │ npm uninstall -g @plexor-dev/claude-code-plugin-staging │');
304
+ console.log(' │ │');
305
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
306
+ console.log('');
307
+ console.log(' Your Claude Code is ready to work normally again.');
308
+ console.log('');
309
+
310
+ if (!removeConfig) {
311
+ console.log(' Note: ~/.plexor/ config directory was preserved.');
312
+ console.log(' To also remove it: plexor-uninstall --remove-config');
313
+ console.log(' Or manually: rm -rf ~/.plexor');
314
+ console.log('');
315
+ }
316
+ }
317
+ }
318
+
319
+ 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-staging`. 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
+ ```
@@ -35,6 +35,129 @@ function generateRequestId(prefix = 'pass') {
35
35
  return `${prefix}_${timestamp}_${random}`;
36
36
  }
37
37
 
38
+ const VALID_ORCHESTRATION_MODES = new Set([
39
+ 'supervised',
40
+ 'autonomous',
41
+ 'danger-full-auto'
42
+ ]);
43
+
44
+ const VALID_GATEWAY_MODES = new Set(['eco', 'balanced', 'quality', 'passthrough', 'cost']);
45
+ const VALID_PROVIDER_HINTS = new Set([
46
+ 'auto',
47
+ 'anthropic',
48
+ 'claude',
49
+ 'openai',
50
+ 'deepseek',
51
+ 'mistral',
52
+ 'gemini',
53
+ 'grok',
54
+ 'cohere'
55
+ ]);
56
+
57
+ const DISABLED_MODEL_HINTS = new Set(['auto', 'none', 'off']);
58
+
59
+ function normalizeOrchestrationMode(mode) {
60
+ if (typeof mode !== 'string') {
61
+ return null;
62
+ }
63
+ const normalized = mode.trim().toLowerCase();
64
+ if (!VALID_ORCHESTRATION_MODES.has(normalized)) {
65
+ return null;
66
+ }
67
+ return normalized;
68
+ }
69
+
70
+ function normalizeGatewayMode(mode) {
71
+ if (typeof mode !== 'string') {
72
+ return null;
73
+ }
74
+ const normalized = mode.trim().toLowerCase();
75
+ if (!VALID_GATEWAY_MODES.has(normalized)) {
76
+ return null;
77
+ }
78
+ return normalized === 'cost' ? 'eco' : normalized;
79
+ }
80
+
81
+ function normalizePreferredProvider(provider) {
82
+ if (typeof provider !== 'string') {
83
+ return null;
84
+ }
85
+ const normalized = provider.trim().toLowerCase();
86
+ if (!VALID_PROVIDER_HINTS.has(normalized)) {
87
+ return null;
88
+ }
89
+ return normalized;
90
+ }
91
+
92
+ function resolveOrchestrationMode(settings) {
93
+ const envMode = normalizeOrchestrationMode(process.env.PLEXOR_ORCHESTRATION_MODE);
94
+ if (envMode) {
95
+ return envMode;
96
+ }
97
+ const cfgMode = normalizeOrchestrationMode(
98
+ settings?.orchestrationMode || settings?.orchestration_mode
99
+ );
100
+ return cfgMode || 'autonomous';
101
+ }
102
+
103
+ function resolveGatewayMode(settings) {
104
+ const envMode = normalizeGatewayMode(process.env.PLEXOR_MODE);
105
+ if (envMode) {
106
+ return envMode;
107
+ }
108
+ const cfgMode = normalizeGatewayMode(settings?.mode);
109
+ return cfgMode || 'balanced';
110
+ }
111
+
112
+ function resolvePreferredProvider(settings) {
113
+ const envProvider = normalizePreferredProvider(
114
+ process.env.PLEXOR_PROVIDER || process.env.PLEXOR_PREFERRED_PROVIDER
115
+ );
116
+ if (envProvider) {
117
+ return envProvider;
118
+ }
119
+ const cfgProvider = normalizePreferredProvider(
120
+ settings?.preferredProvider || settings?.preferred_provider
121
+ );
122
+ return cfgProvider || 'auto';
123
+ }
124
+
125
+ function normalizePreferredModel(model) {
126
+ if (typeof model !== 'string') {
127
+ return null;
128
+ }
129
+ const trimmed = model.trim();
130
+ if (!trimmed) {
131
+ return null;
132
+ }
133
+ if (DISABLED_MODEL_HINTS.has(trimmed.toLowerCase())) {
134
+ return null;
135
+ }
136
+ return trimmed;
137
+ }
138
+
139
+ function resolvePreferredModel(settings) {
140
+ const envModel = normalizePreferredModel(
141
+ process.env.PLEXOR_MODEL || process.env.PLEXOR_PREFERRED_MODEL
142
+ );
143
+ if (envModel) {
144
+ return envModel;
145
+ }
146
+ const cfgModel = normalizePreferredModel(settings?.preferredModel || settings?.preferred_model);
147
+ return cfgModel || null;
148
+ }
149
+
150
+ function validateForceHintSelection(preferredProvider, preferredModel) {
151
+ if (!preferredModel || preferredProvider === 'auto') {
152
+ return;
153
+ }
154
+ const error = new Error(
155
+ 'Invalid Plexor config: force only one hint. Set preferred_provider OR preferred_model, not both.'
156
+ );
157
+ error.code = 'PLEXOR_CONFIG_CONFLICT';
158
+ throw error;
159
+ }
160
+
38
161
  // Try to load lib modules, fall back to inline implementations
39
162
  let ConfigManager, SessionManager, LocalCache, Logger, PlexorClient;
40
163
  let config, session, cache, logger;
@@ -56,10 +179,25 @@ try {
56
179
  const SESSION_PATH = path.join(process.env.HOME || '', '.plexor', 'session.json');
57
180
  const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
58
181
 
182
+ const uxMessagesEnabled = !/^(0|false|no|off)$/i.test(
183
+ String(process.env.PLEXOR_UX_MESSAGES ?? process.env.PLEXOR_UX_DEBUG_MESSAGES ?? 'true')
184
+ );
185
+ const CYAN = '\x1b[36m';
186
+ const RESET = '\x1b[0m';
187
+ const formatUx = (msg) => {
188
+ const text = String(msg || '').trim();
189
+ if (!text) return '[PLEXOR: message]';
190
+ return text.startsWith('[PLEXOR:') ? text : `[PLEXOR: ${text}]`;
191
+ };
192
+
59
193
  logger = {
60
194
  debug: (msg) => process.env.PLEXOR_DEBUG && console.error(`[DEBUG] ${msg}`),
61
195
  info: (msg) => console.error(msg),
62
- error: (msg) => console.error(`[ERROR] ${msg}`)
196
+ error: (msg) => console.error(`[ERROR] ${msg}`),
197
+ ux: (msg) => {
198
+ if (!uxMessagesEnabled) return;
199
+ console.error(`${CYAN}${formatUx(msg)}${RESET}`);
200
+ }
63
201
  };
64
202
 
65
203
  config = {
@@ -73,7 +211,11 @@ try {
73
211
  apiUrl: cfg.settings?.apiUrl || 'https://api.plexor.dev',
74
212
  timeout: cfg.settings?.timeout || 5000,
75
213
  localCacheEnabled: cfg.settings?.localCacheEnabled ?? false,
76
- mode: cfg.settings?.mode || 'balanced'
214
+ mode: cfg.settings?.mode || 'balanced',
215
+ preferredProvider: cfg.settings?.preferred_provider || 'auto',
216
+ preferredModel: cfg.settings?.preferredModel || cfg.settings?.preferred_model || null,
217
+ orchestrationMode:
218
+ cfg.settings?.orchestrationMode || cfg.settings?.orchestration_mode || 'autonomous'
77
219
  };
78
220
  } catch {
79
221
  return { enabled: false };
@@ -106,9 +248,9 @@ try {
106
248
  const saveSession = (s) => {
107
249
  try {
108
250
  const dir = path.dirname(SESSION_PATH);
109
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
251
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
110
252
  s.last_activity = Date.now();
111
- fs.writeFileSync(SESSION_PATH, JSON.stringify(s, null, 2));
253
+ fs.writeFileSync(SESSION_PATH, JSON.stringify(s, null, 2), { mode: 0o600 });
112
254
  } catch {}
113
255
  };
114
256
 
@@ -179,52 +321,69 @@ async function main() {
179
321
  input = await readStdin();
180
322
  request = JSON.parse(input);
181
323
 
324
+ // Issue #2042: Check for slash commands FIRST, before ANY other processing
325
+ // Slash commands must pass through completely clean — no metadata injection
326
+ // Adding _plexor_client or _plexor to slash command requests adds context noise
327
+ // that causes the model to re-execute commands in a loop
328
+ // Note: session.recordPassthrough() intentionally omitted — slash commands are
329
+ // not API requests and should not pollute session analytics
330
+ if (isSlashCommand(request)) {
331
+ logger.debug('Slash command detected, clean passthrough (no metadata)');
332
+ logger.ux('Slash command passthrough (no optimization applied)');
333
+ return output(request); // Completely clean — no metadata added
334
+ }
335
+
336
+ const settings = await config.load();
337
+ const orchestrationMode = resolveOrchestrationMode(settings);
338
+ const gatewayMode = resolveGatewayMode(settings);
339
+ const preferredProvider = resolvePreferredProvider(settings);
340
+ const preferredModel = resolvePreferredModel(settings);
341
+ validateForceHintSelection(preferredProvider, preferredModel);
342
+
182
343
  // Phase 3 Hypervisor Mode Detection
183
344
  // When ANTHROPIC_BASE_URL points to Plexor, all intelligence is server-side
184
345
  // The plugin just passes through - server handles optimization, routing, quality
185
346
  const baseUrl = process.env.ANTHROPIC_BASE_URL || '';
186
- const isHypervisorMode = baseUrl.includes('plexor') || baseUrl.includes('staging.api');
347
+ const isHypervisorMode =
348
+ baseUrl.includes('plexor') ||
349
+ baseUrl.includes('staging.api') ||
350
+ baseUrl.includes('localhost') ||
351
+ baseUrl.includes('127.0.0.1');
187
352
 
188
353
  if (isHypervisorMode) {
189
354
  // HYPERVISOR MODE: Server handles everything
190
355
  // Just pass through with minimal metadata for session tracking
191
- logger.debug('[Plexor] Hypervisor mode active - server handles all optimization');
356
+ logger.debug('Hypervisor mode active - server handles all optimization');
357
+ logger.ux(
358
+ `Hypervisor mode active (${gatewayMode}/${orchestrationMode}); routing handled server-side`
359
+ );
192
360
 
193
361
  // Add session tracking metadata (server will use this for analytics)
194
362
  return output({
195
363
  ...request,
364
+ ...(preferredModel ? { model: preferredModel } : {}),
365
+ plexor_mode: gatewayMode,
366
+ ...(preferredProvider !== 'auto' ? { plexor_provider: preferredProvider } : {}),
367
+ plexor_orchestration_mode: orchestrationMode,
196
368
  _plexor_client: {
369
+ ...(request._plexor_client || {}),
197
370
  mode: 'hypervisor',
198
371
  plugin_version: '0.1.0-beta.18',
372
+ orchestration_mode: orchestrationMode,
373
+ plexor_mode: gatewayMode,
374
+ preferred_provider: preferredProvider,
375
+ preferred_model: preferredModel,
199
376
  cwd: process.cwd(),
200
377
  timestamp: Date.now(),
201
378
  }
202
379
  });
203
380
  }
204
381
 
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
382
  // CRITICAL: Skip optimization for CLI commands requiring tool execution
225
383
  // Azure CLI, AWS CLI, kubectl, etc. need tools to be preserved
226
384
  if (requiresToolExecution(request)) {
227
385
  logger.debug('CLI tool execution detected, passing through unchanged');
386
+ logger.ux('Tool-execution request detected; preserving tools via passthrough');
228
387
  session.recordPassthrough();
229
388
  return output({
230
389
  ...request,
@@ -243,6 +402,7 @@ async function main() {
243
402
  // Modifying messages breaks the agent loop and causes infinite loops
244
403
  if (isAgenticRequest(request)) {
245
404
  logger.debug('Agentic request detected, passing through unchanged');
405
+ logger.ux('Agentic/tool-use request detected; passthrough to avoid loop breakage');
246
406
  session.recordPassthrough();
247
407
  return output({
248
408
  ...request,
@@ -257,10 +417,9 @@ async function main() {
257
417
  });
258
418
  }
259
419
 
260
- const settings = await config.load();
261
-
262
420
  if (!settings.enabled) {
263
421
  logger.debug('Plexor disabled, passing through');
422
+ logger.ux('Plexor plugin is disabled; forwarding request unchanged');
264
423
  session.recordPassthrough();
265
424
  return output({
266
425
  ...request,
@@ -275,6 +434,7 @@ async function main() {
275
434
 
276
435
  if (!settings.apiKey) {
277
436
  logger.info('Not authenticated. Run /plexor-login to enable optimization.');
437
+ logger.ux('Not authenticated. Run /plexor-login to enable Plexor routing');
278
438
  session.recordPassthrough();
279
439
  return output({
280
440
  ...request,
@@ -301,7 +461,8 @@ async function main() {
301
461
  const cachedResponse = await cache.get(cacheKey);
302
462
 
303
463
  if (cachedResponse && settings.localCacheEnabled) {
304
- logger.info('[Plexor] Local cache hit');
464
+ logger.info('Local cache hit');
465
+ logger.ux('Local cache hit');
305
466
  session.recordCacheHit();
306
467
  return output({
307
468
  ...request,
@@ -329,10 +490,16 @@ async function main() {
329
490
 
330
491
  const savingsPercent = ((result.original_tokens - result.optimized_tokens) / result.original_tokens * 100).toFixed(1);
331
492
 
332
- logger.info(`[Plexor] Optimized: ${result.original_tokens} → ${result.optimized_tokens} tokens (${savingsPercent}% saved)`);
493
+ logger.info(`Optimized: ${result.original_tokens} → ${result.optimized_tokens} tokens (${savingsPercent}% saved)`);
494
+ logger.ux(
495
+ `Optimized ${result.original_tokens} -> ${result.optimized_tokens} tokens (${savingsPercent}% saved)`
496
+ );
333
497
 
334
498
  if (result.recommended_provider !== 'anthropic') {
335
- logger.info(`[Plexor] Recommended: ${result.recommended_provider} (~$${result.estimated_cost.toFixed(4)})`);
499
+ logger.info(`Recommended: ${result.recommended_provider} (~$${result.estimated_cost.toFixed(4)})`);
500
+ logger.ux(
501
+ `Provider recommendation: ${result.recommended_provider} (~$${result.estimated_cost.toFixed(4)})`
502
+ );
336
503
  }
337
504
 
338
505
  const optimizedRequest = {
@@ -368,7 +535,15 @@ async function main() {
368
535
  return output(optimizedRequest);
369
536
 
370
537
  } catch (error) {
371
- logger.error(`[Plexor] Error: ${error.message}`);
538
+ if (error?.code === 'PLEXOR_CONFIG_CONFLICT') {
539
+ logger.error(`Configuration error: ${error.message}`);
540
+ logger.ux(error.message);
541
+ process.stderr.write(`\n[Plexor] ${error.message}\n`);
542
+ process.exit(1);
543
+ }
544
+
545
+ logger.error(`Error: ${error.message}`);
546
+ logger.ux(`Optimization hook error: ${error.message}`);
372
547
  logger.debug(error.stack);
373
548
 
374
549
  const errorRequestId = generateRequestId('error'); // Issue #701: Add request_id for tracking
@@ -578,12 +753,16 @@ function isSlashCommand(request) {
578
753
  }
579
754
 
580
755
  // Check for system messages with skill instructions
756
+ // Issue #2042: Updated to match new RULE-based .md format (old H1 headers removed)
581
757
  for (const msg of messages) {
582
758
  if (msg.role === 'system') {
583
759
  const content = typeof msg.content === 'string' ? msg.content : '';
584
760
  if (/# Plexor (?:Status|Login|Logout|Mode|Provider|Enabled|Settings)/i.test(content)) {
585
761
  return true;
586
762
  }
763
+ if (/plexor\/commands\/plexor-/i.test(content)) {
764
+ return true;
765
+ }
587
766
  }
588
767
  }
589
768
 
@@ -629,6 +808,6 @@ function requiresToolExecution(request) {
629
808
  }
630
809
 
631
810
  main().catch((error) => {
632
- console.error(`[Plexor] Fatal error: ${error.message}`);
811
+ console.error(`\x1b[1m\x1b[37m\x1b[41m PLEXOR \x1b[0m \x1b[31mFatal: ${error.message}\x1b[0m`);
633
812
  process.exit(1);
634
813
  });