@plexor-dev/claude-code-plugin-staging 0.1.0-beta.19 → 0.1.0-beta.20

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.
@@ -247,10 +247,9 @@ async function main() {
247
247
  console.log(`│ Already Logged In │`);
248
248
  console.log(`├─────────────────────────────────────────────┤`);
249
249
  console.log(`│ API Key: ${(existingKey.substring(0, 8) + '...').padEnd(33)}│`);
250
- console.log(`│ To re-login, provide a new key: │`);
251
- console.log(`│ echo $PLEXOR_API_KEY | /plexor-login │`);
252
- console.log(`│ To logout: │`);
253
- console.log(`│ /plexor-logout │`);
250
+ console.log(`│ Human setup path: /plexor-setup │`);
251
+ console.log(`│ Automation path: /plexor-login <key> │`);
252
+ console.log(`│ To logout: /plexor-logout │`);
254
253
  console.log(`└─────────────────────────────────────────────┘`);
255
254
  return;
256
255
  }
@@ -258,7 +257,9 @@ async function main() {
258
257
  // If no key provided, show secure usage options
259
258
  if (!apiKey) {
260
259
  console.log(`┌─────────────────────────────────────────────┐`);
261
- console.log(`│ Plexor Login │`);
260
+ console.log(`│ Plexor Login (Advanced) │`);
261
+ console.log(`├─────────────────────────────────────────────┤`);
262
+ console.log(`│ Human first-run: /plexor-setup │`);
262
263
  console.log(`├─────────────────────────────────────────────┤`);
263
264
  console.log(`│ Get your API key at: │`);
264
265
  console.log(`│ https://plexor.dev/dashboard/api-keys │`);
@@ -304,30 +305,42 @@ async function main() {
304
305
  process.exit(1);
305
306
  }
306
307
 
307
- // AUTO-CONFIGURE CLAUDE CODE ROUTING
308
- // This is the key feature: automatically set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN
309
- // in ~/.claude/settings.json so ALL Claude Code sessions route through Plexor
308
+ // Automation path: save credentials and activate routing.
309
+ // Guided human setup should go through /plexor-setup, which also verifies the route.
310
310
  const useStaging = apiUrl.includes('staging');
311
311
  const routingEnabled = settingsManager.enablePlexorRouting(apiKey, { useStaging });
312
312
 
313
+ config.health = {
314
+ installed: true,
315
+ connected: true,
316
+ routing_active: routingEnabled,
317
+ verified: false,
318
+ verified_at: null,
319
+ verify_error: 'Verification not run. Use /plexor-setup for guided verification.',
320
+ gateway: useStaging ? 'staging' : 'production',
321
+ previous_auth_preserved: false
322
+ };
323
+ saveConfig(config);
324
+
313
325
  const email = user.email || 'Unknown';
314
326
  const tier = user.tier?.name || 'Free';
315
327
 
316
328
  console.log(`┌─────────────────────────────────────────────┐`);
317
- console.log(`│ ✓ Login Successful │`);
329
+ console.log(`│ ✓ Plexor Credentials Saved │`);
318
330
  console.log(`├─────────────────────────────────────────────┤`);
319
331
  console.log(`│ Email: ${email.substring(0, 35).padEnd(35)}│`);
320
332
  console.log(`│ Tier: ${tier.padEnd(36)}│`);
321
333
  console.log(`├─────────────────────────────────────────────┤`);
322
334
  if (routingEnabled) {
323
- console.log(`│ ✓ Claude Code routing: CONFIGURED │`);
324
- console.log(`│ All sessions now route through Plexor │`);
335
+ console.log(`│ ✓ Claude routing: ACTIVE │`);
336
+ console.log(`│ Claude verify: NOT RUN │`);
325
337
  } else {
326
- console.log(`│ ⚠ Claude Code routing: MANUAL SETUP NEEDED │`);
327
- console.log(`│ Set ANTHROPIC_BASE_URL in environment │`);
338
+ console.log(`│ ⚠ Claude routing: MANUAL SETUP NEEDED │`);
339
+ console.log(`│ Run /plexor-setup after fixing permissions │`);
328
340
  }
329
341
  console.log(`├─────────────────────────────────────────────┤`);
330
- console.log(`│ Run /plexor-status to see your stats. │`);
342
+ console.log(`│ Human first-run: /plexor-setup │`);
343
+ console.log(`│ Automation complete. Status: /plexor-status│`);
331
344
  console.log(`└─────────────────────────────────────────────┘`);
332
345
  } catch (err) {
333
346
  console.error(`┌─────────────────────────────────────────────┐`);
@@ -1,5 +1,4 @@
1
- ---
2
- description: Authenticate with Plexor to enable optimization (user)
1
+ description: Advanced/manual Plexor authentication for automation and repair (user)
3
2
  ---
4
3
 
5
4
  **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.**
@@ -90,20 +90,26 @@ function main() {
90
90
 
91
91
  if (!config || !config.auth?.api_key) {
92
92
  console.log(`┌─────────────────────────────────────────────┐`);
93
- console.log(`│ Not Logged In │`);
93
+ console.log(`│ Plexor Is Not Active │`);
94
94
  console.log(`├─────────────────────────────────────────────┤`);
95
95
  console.log(`│ No active Plexor session found. │`);
96
- console.log(`│ Run /plexor-login to authenticate. │`);
96
+ console.log(`│ Run /plexor-setup to connect Plexor. │`);
97
97
  console.log(`└─────────────────────────────────────────────┘`);
98
98
  return;
99
99
  }
100
100
 
101
101
  const clearCache = args.includes('--clear-cache') || args.includes('-c');
102
+ const currentSettings = settingsManager.load();
103
+ const hadPriorAuth = Boolean(
104
+ currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY ||
105
+ currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN
106
+ );
102
107
 
103
108
  // Clear credentials
104
109
  delete config.auth.api_key;
105
110
  config.settings = config.settings || {};
106
111
  config.settings.enabled = false;
112
+ delete config.health;
107
113
  if (!saveConfig(config)) {
108
114
  process.exit(1);
109
115
  }
@@ -127,13 +133,14 @@ function main() {
127
133
  console.log(`├─────────────────────────────────────────────┤`);
128
134
  console.log(`│ ✓ API key removed │`);
129
135
  console.log(`│ ${routingDisabled ? '✓' : '○'} Claude Code routing disabled │`);
136
+ console.log(`│ ${hadPriorAuth ? '✓' : '○'} Prior Claude auth restored │`);
130
137
  console.log(`│ ✓ Session cleared │`);
131
138
  if (clearCache) {
132
139
  console.log(`│ ${cacheCleared ? '✓' : '○'} Cache cleared │`);
133
140
  }
134
141
  console.log(`├─────────────────────────────────────────────┤`);
135
142
  console.log(`│ Claude Code now connects directly. │`);
136
- console.log(`│ Run /plexor-login to re-authenticate. │`);
143
+ console.log(`│ Run /plexor-setup to reconnect Plexor. │`);
137
144
  if (!clearCache) {
138
145
  console.log(`│ Use --clear-cache to also clear cache. │`);
139
146
  }
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { PLEXOR_DIR, CONFIG_PATH, DEFAULT_API_URL } = require('../lib/constants');
6
+ const PlexorClient = require('../lib/plexor-client');
7
+ const { settingsManager } = require('../lib/settings-manager');
8
+ const {
9
+ EXPECTED_VERIFY_RESPONSE,
10
+ VERIFY_PROMPT,
11
+ runClaudeRouteVerification
12
+ } = require('../lib/verify-route');
13
+
14
+ function isPlexorApiKey(value) {
15
+ return typeof value === 'string' && value.startsWith('plx_') && value.length >= 20;
16
+ }
17
+
18
+ function loadConfig() {
19
+ try {
20
+ if (!fs.existsSync(CONFIG_PATH)) {
21
+ return { version: 1, auth: {}, settings: {} };
22
+ }
23
+ const data = fs.readFileSync(CONFIG_PATH, 'utf8');
24
+ if (!data || data.trim() === '') {
25
+ return { version: 1, auth: {}, settings: {} };
26
+ }
27
+ const parsed = JSON.parse(data);
28
+ if (typeof parsed !== 'object' || parsed === null) {
29
+ return { version: 1, auth: {}, settings: {} };
30
+ }
31
+ return parsed;
32
+ } catch {
33
+ return { version: 1, auth: {}, settings: {} };
34
+ }
35
+ }
36
+
37
+ function saveConfig(config) {
38
+ try {
39
+ if (!fs.existsSync(PLEXOR_DIR)) {
40
+ fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
41
+ }
42
+ const tempPath = path.join(PLEXOR_DIR, `.config.${Date.now()}.tmp`);
43
+ fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
44
+ fs.renameSync(tempPath, CONFIG_PATH);
45
+ return true;
46
+ } catch (err) {
47
+ console.error(`Failed to save config: ${err.message}`);
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function readFromStdin() {
53
+ return new Promise((resolve, reject) => {
54
+ let data = '';
55
+ const timeout = setTimeout(() => {
56
+ reject(new Error('Timeout reading from stdin'));
57
+ }, 5000);
58
+
59
+ process.stdin.setEncoding('utf8');
60
+ process.stdin.on('data', (chunk) => {
61
+ data += chunk;
62
+ });
63
+ process.stdin.on('end', () => {
64
+ clearTimeout(timeout);
65
+ resolve(data.trim());
66
+ });
67
+ process.stdin.on('error', (err) => {
68
+ clearTimeout(timeout);
69
+ reject(err);
70
+ });
71
+ process.stdin.resume();
72
+ });
73
+ }
74
+
75
+ function getKeySource(args, existingKey) {
76
+ if (process.env.PLEXOR_API_KEY) {
77
+ return { apiKey: process.env.PLEXOR_API_KEY, source: 'environment' };
78
+ }
79
+ if (!process.stdin.isTTY && args.length === 0) {
80
+ return { apiKey: null, source: 'stdin' };
81
+ }
82
+ if (args[0] && !args[0].startsWith('--')) {
83
+ return { apiKey: args[0], source: 'argument' };
84
+ }
85
+ if (existingKey) {
86
+ return { apiKey: existingKey, source: 'saved-config' };
87
+ }
88
+ return { apiKey: null, source: null };
89
+ }
90
+
91
+ function getGatewayLabel(apiUrl) {
92
+ return apiUrl.includes('staging') ? 'staging' : 'production';
93
+ }
94
+
95
+ function updateHealth(config, state) {
96
+ config.health = {
97
+ installed: true,
98
+ connected: true,
99
+ routing_active: state.routingEnabled,
100
+ verified: state.verifyResult.ok,
101
+ verified_at: state.verifyResult.ok ? new Date().toISOString() : null,
102
+ verify_prompt: VERIFY_PROMPT,
103
+ verify_expected: EXPECTED_VERIFY_RESPONSE,
104
+ verify_error: state.verifyResult.ok ? null : state.verifyResult.reason,
105
+ gateway: state.gateway,
106
+ previous_auth_preserved: state.previousAuthPreserved
107
+ };
108
+ }
109
+
110
+ function printUsage() {
111
+ console.log('Plexor setup is the guided first-run path.');
112
+ console.log('');
113
+ console.log('Provide a Plexor API key one of these ways:');
114
+ console.log(' /plexor-setup plx_...');
115
+ console.log(' echo "plx_..." | /plexor-setup');
116
+ console.log(' PLEXOR_API_KEY=plx_... /plexor-setup');
117
+ }
118
+
119
+ function printReceipt({ user, gateway, previousAuthPreserved, verifyResult }) {
120
+ const line = (content) => `│ ${String(content).slice(0, 43).padEnd(43)}│`;
121
+
122
+ console.log('┌─────────────────────────────────────────────┐');
123
+ console.log(line('Plexor setup complete'));
124
+ console.log('├─────────────────────────────────────────────┤');
125
+ console.log(line(`Connected: OK (${user.email || 'Unknown'})`));
126
+ console.log(line(`Routing Active: OK (${gateway})`));
127
+ console.log(line(`Verified: ${verifyResult.ok ? 'OK' : 'FAILED'}`));
128
+ console.log(line(`Previous Claude auth: ${previousAuthPreserved ? 'Saved' : 'None found'}`));
129
+ console.log('├─────────────────────────────────────────────┤');
130
+ if (verifyResult.ok) {
131
+ console.log(line('Claude is now routed through Plexor'));
132
+ console.log(line('Logout/uninstall restores prior auth'));
133
+ console.log(line('Next: Run /plexor-status for health + stats'));
134
+ } else {
135
+ console.log(line('Claude routing was configured'));
136
+ console.log(line('Verification did not pass'));
137
+ console.log(line('Next: Run /plexor-setup again to repair'));
138
+ }
139
+ console.log('└─────────────────────────────────────────────┘');
140
+
141
+ if (!verifyResult.ok) {
142
+ console.log('');
143
+ console.log(`Verification failed: ${verifyResult.reason}`);
144
+ }
145
+ }
146
+
147
+ async function main() {
148
+ const args = process.argv.slice(2);
149
+ const skipVerify = args.includes('--skip-verify');
150
+ const config = loadConfig();
151
+ const existingKey = config.auth?.api_key || '';
152
+ const keyInput = getKeySource(args.filter(arg => arg !== '--skip-verify'), existingKey);
153
+ let apiKey = keyInput.apiKey;
154
+
155
+ if (!apiKey && keyInput.source === 'stdin') {
156
+ try {
157
+ apiKey = await readFromStdin();
158
+ } catch {
159
+ apiKey = null;
160
+ }
161
+ }
162
+
163
+ if (!apiKey) {
164
+ printUsage();
165
+ process.exit(1);
166
+ }
167
+
168
+ if (!isPlexorApiKey(apiKey)) {
169
+ console.error('Error: Plexor API keys must start with "plx_" and be at least 20 characters.');
170
+ process.exit(1);
171
+ }
172
+
173
+ const apiUrl = config.settings?.apiUrl || DEFAULT_API_URL;
174
+ const client = new PlexorClient({ apiKey, baseUrl: apiUrl });
175
+
176
+ let user;
177
+ try {
178
+ user = await client.getUser();
179
+ } catch (err) {
180
+ console.error(`Error: ${err.message}`);
181
+ process.exit(1);
182
+ }
183
+
184
+ const updatedConfig = {
185
+ ...config,
186
+ version: 1,
187
+ auth: {
188
+ ...(config.auth || {}),
189
+ api_key: apiKey,
190
+ mode: 'api_key',
191
+ authenticated_at: new Date().toISOString()
192
+ },
193
+ settings: {
194
+ ...(config.settings || {}),
195
+ enabled: true,
196
+ apiUrl,
197
+ preferred_provider: config.settings?.preferred_provider || 'auto',
198
+ mode: config.settings?.mode || 'balanced',
199
+ localCacheEnabled: config.settings?.localCacheEnabled ?? true
200
+ }
201
+ };
202
+
203
+ if (!saveConfig(updatedConfig)) {
204
+ process.exit(1);
205
+ }
206
+
207
+ const routingEnabled = settingsManager.enablePlexorRouting(apiKey, {
208
+ useStaging: apiUrl.includes('staging')
209
+ });
210
+
211
+ if (!routingEnabled) {
212
+ console.error('Error: Plexor saved your key but could not activate Claude routing.');
213
+ console.error('Run /plexor-setup again after fixing ~/.claude/settings.json permissions.');
214
+ process.exit(1);
215
+ }
216
+
217
+ const currentSettings = settingsManager.load();
218
+ const previousAuthPreserved = Boolean(
219
+ currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY ||
220
+ currentSettings.env?.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN
221
+ );
222
+
223
+ const verifyResult = skipVerify
224
+ ? { ok: true, reason: '', stdout: EXPECTED_VERIFY_RESPONSE, stderr: '', code: 0 }
225
+ : runClaudeRouteVerification();
226
+
227
+ updateHealth(updatedConfig, {
228
+ gateway: getGatewayLabel(apiUrl),
229
+ previousAuthPreserved,
230
+ routingEnabled,
231
+ verifyResult
232
+ });
233
+
234
+ if (!saveConfig(updatedConfig)) {
235
+ process.exit(1);
236
+ }
237
+
238
+ printReceipt({
239
+ user,
240
+ gateway: getGatewayLabel(apiUrl),
241
+ previousAuthPreserved,
242
+ verifyResult
243
+ });
244
+
245
+ if (!verifyResult.ok) {
246
+ process.exit(1);
247
+ }
248
+ }
249
+
250
+ main().catch((err) => {
251
+ console.error(`Error: ${err.message}`);
252
+ process.exit(1);
253
+ });
@@ -1,177 +1,28 @@
1
+ description: Guided first-run setup for Plexor with Claude Code (user)
1
2
  ---
2
- description: First-time setup wizard for Plexor with Claude Code (user)
3
- ---
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
-
7
- # Plexor Setup Wizard
8
-
9
- Guide users through first-time Plexor setup. **No manual environment variable configuration required!**
10
-
11
- The plugin automatically configures `~/.claude/settings.json` to route all Claude Code sessions through Plexor.
12
3
 
13
- ## Steps
4
+ **RULE: Execute this workflow EXACTLY ONCE. After the Bash tool returns output, your ONLY action is to present that output to the user. DO NOT restart the workflow.**
14
5
 
15
- **Step 1: Check if already configured**
6
+ Plexor setup is the primary human setup flow.
16
7
 
17
- Use the Read tool to check if `~/.plexor/config.json` exists and has valid configuration.
18
- Also check `~/.claude/settings.json` for routing status.
8
+ If `$ARGUMENTS` already contains a Plexor API key (`plx_...`), run:
19
9
 
20
- If configured, show:
10
+ ```bash
11
+ node ~/.claude/plugins/plexor/commands/plexor-setup.js $ARGUMENTS
21
12
  ```
22
- Plexor Setup
23
- ============
24
- Already configured!
25
-
26
- API URL: [apiUrl from config]
27
- Mode: [mode from config]
28
- Status: [Enabled/Disabled]
29
- Claude Routing: [Active/Inactive]
30
-
31
- Run /plexor-status to see your usage.
32
- Run /plexor-settings to modify configuration.
33
- Run /plexor-enabled off to disable routing.
34
- ```
35
-
36
- **Step 2: Ask about Claude subscription**
37
13
 
38
- Use the AskUserQuestion tool:
14
+ If the user did not provide a key yet, ask them:
39
15
 
40
- Question: "How do you pay for Claude usage?"
41
- Header: "Billing"
42
- Options:
43
- 1. **Claude MAX subscription (Pro/Team/Enterprise)** - I have a subscription and want Plexor for optimization & tracking (you'll still need a Plexor API key)
44
- 2. **Pay-per-use via Plexor** - I want Plexor to handle billing and route to the cheapest provider
16
+ `Please paste your Plexor API key (starts with plx_). You can get one at https://plexor.dev/dashboard.`
45
17
 
46
- **Step 3A: Claude MAX User Setup**
18
+ After the user replies with the key, run:
47
19
 
48
- If user selected the "Claude MAX subscription (Pro/Team/Enterprise)" option:
49
-
50
- 1. Ask for their Plexor API key:
51
- "Please provide your Plexor API key (starts with 'plx_')."
52
- "Get one at: https://plexor.dev/dashboard"
53
- "Your MAX subscription will be used for Claude - the Plexor key is for tracking/optimization."
54
-
55
- 2. Use the Write tool to create `~/.plexor/config.json`:
56
- ```json
57
- {
58
- "version": 1,
59
- "auth": {
60
- "api_key": "[user's Plexor key]",
61
- "mode": "oauth_passthrough",
62
- "authenticated_at": "[current ISO timestamp]"
63
- },
64
- "settings": {
65
- "enabled": true,
66
- "apiUrl": "https://staging.api.plexor.dev",
67
- "mode": "balanced",
68
- "localCacheEnabled": true
69
- }
70
- }
20
+ ```bash
21
+ node ~/.claude/plugins/plexor/commands/plexor-setup.js <user_key>
71
22
  ```
72
23
 
73
- 3. Use the Write tool to update `~/.claude/settings.json` env block:
74
- ```json
75
- {
76
- "env": {
77
- "ANTHROPIC_BASE_URL": "https://staging.api.plexor.dev/gateway/anthropic",
78
- "ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]",
79
- "ANTHROPIC_API_KEY": "[user's Plexor key]"
80
- }
81
- }
82
- ```
83
- Note: Preserve unrelated settings, but replace `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, and `ANTHROPIC_API_KEY` in the env block. This is required when the user previously configured Claude with direct API auth.
84
-
85
- 4. Show the user:
86
- ```
87
- Plexor Setup - Claude MAX User
88
- ==============================
89
- Setup Complete! No manual configuration needed.
90
-
91
- What was configured:
92
- - ~/.plexor/config.json (Plexor plugin settings)
93
- - ~/.claude/settings.json (automatic Claude Code routing)
94
-
95
- How it works:
96
- - All Claude Code sessions now route through Plexor
97
- - Your MAX subscription OAuth token is passed through
98
- - You keep your MAX benefits ($0 cost, 20x rate limits)
99
- - Plexor optimizes prompts and tracks usage
100
-
101
- Commands:
102
- - /plexor-status - See your usage stats
103
- - /plexor-enabled off - Temporarily disable Plexor
104
- - /plexor-enabled on - Re-enable Plexor
105
-
106
- Changes take effect immediately in all Claude Code sessions!
107
- ```
108
-
109
- **Step 3B: API Key User Setup**
110
-
111
- If user selected the "Pay-per-use via Plexor" option:
112
-
113
- 1. Ask for their Plexor API key:
114
- "Please provide your Plexor API key (starts with 'plx_')."
115
- "Get one at: https://plexor.dev/dashboard"
116
-
117
- 2. Once they provide the key, use the Write tool to create `~/.plexor/config.json`:
118
- ```json
119
- {
120
- "version": 1,
121
- "auth": {
122
- "api_key": "[user's API key]",
123
- "mode": "api_key",
124
- "authenticated_at": "[current ISO timestamp]"
125
- },
126
- "settings": {
127
- "enabled": true,
128
- "apiUrl": "https://staging.api.plexor.dev",
129
- "preferred_provider": "auto",
130
- "mode": "balanced",
131
- "localCacheEnabled": true
132
- }
133
- }
134
- ```
135
-
136
- 3. Use the Write tool to update `~/.claude/settings.json` env block:
137
- ```json
138
- {
139
- "env": {
140
- "ANTHROPIC_BASE_URL": "https://staging.api.plexor.dev/gateway/anthropic",
141
- "ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]",
142
- "ANTHROPIC_API_KEY": "[user's Plexor key]"
143
- }
144
- }
145
- ```
146
- Note: Preserve unrelated settings, but replace `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, and `ANTHROPIC_API_KEY` in the env block. This is required when the user previously configured Claude with direct API auth.
147
-
148
- 4. Show the user:
149
- ```
150
- Plexor Setup - API Key User
151
- ===========================
152
- Setup Complete! No manual configuration needed.
153
-
154
- What was configured:
155
- - ~/.plexor/config.json (Plexor plugin settings)
156
- - ~/.claude/settings.json (automatic Claude Code routing)
157
-
158
- How it works:
159
- - All Claude Code sessions now route through Plexor
160
- - Plexor picks the best provider (can save up to 90%)
161
- - Your usage is tracked and optimized
162
-
163
- Commands:
164
- - /plexor-status - See your usage and savings
165
- - /plexor-mode eco - Maximize savings
166
- - /plexor-mode quality - Maximize quality
167
- - /plexor-enabled off - Temporarily disable Plexor
168
-
169
- Changes take effect immediately in all Claude Code sessions!
170
- ```
171
-
172
- **NOTES**:
173
- - The `~/.claude/settings.json` env block is the KEY mechanism that routes Claude Code through Plexor
174
- - Set both `ANTHROPIC_AUTH_TOKEN` and `ANTHROPIC_API_KEY` to the Plexor key so Claude cannot bypass the Plexor gateway with a previously saved direct API key
175
- - Changes take effect immediately - no shell restart needed
176
-
177
- After completing all steps, STOP. DO NOT restart the workflow. DO NOT re-execute any commands.
24
+ This command:
25
+ - saves the Plexor key
26
+ - routes Claude through the Plexor staging gateway
27
+ - preserves prior direct Claude auth for restore on logout/uninstall
28
+ - runs a deterministic Claude verification step
@@ -170,11 +170,30 @@ function checkStateMismatch(configEnabled, routingActive) {
170
170
  return null;
171
171
  }
172
172
 
173
+ function printHealthSummary(summary) {
174
+ const line = (content) => ` │ ${String(content).slice(0, 43).padEnd(43)}│`;
175
+ console.log(` ┌─────────────────────────────────────────────┐`);
176
+ console.log(line('Plexor health'));
177
+ console.log(` ├─────────────────────────────────────────────┤`);
178
+ console.log(line(`Installed: ${summary.installed}`));
179
+ console.log(line(`Connected: ${summary.connected}`));
180
+ console.log(line(`Routing Active: ${summary.routing}`));
181
+ console.log(line(`Verified: ${summary.verified}`));
182
+ console.log(line(`Next action: ${summary.nextAction}`));
183
+ console.log(` └─────────────────────────────────────────────┘`);
184
+ }
185
+
173
186
  async function main() {
174
187
  // Read config with integrity checking
175
188
  const config = loadConfig();
176
189
  if (!config) {
177
- console.log('Not configured. Run /plexor-setup.');
190
+ printHealthSummary({
191
+ installed: 'Missing',
192
+ connected: 'Not connected',
193
+ routing: 'Inactive',
194
+ verified: 'Not run',
195
+ nextAction: 'Run /plexor-setup'
196
+ });
178
197
  process.exit(1);
179
198
  }
180
199
 
@@ -184,28 +203,42 @@ async function main() {
184
203
  const provider = config.settings?.preferred_provider || 'auto';
185
204
  const localCache = config.settings?.localCacheEnabled ?? false;
186
205
  const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
206
+ const verification = config.health || {};
187
207
 
188
208
  if (!apiKey) {
189
- console.log('Not authenticated. Run /plexor-setup.');
209
+ printHealthSummary({
210
+ installed: 'OK',
211
+ connected: 'Missing key',
212
+ routing: 'Inactive',
213
+ verified: 'Not run',
214
+ nextAction: 'Run /plexor-setup'
215
+ });
190
216
  process.exit(1);
191
217
  }
192
218
 
193
219
  // Validate API key format
194
220
  if (!isValidApiKeyFormat(apiKey)) {
195
- console.log('Invalid API key format. Keys must start with "plx_" and be at least 20 characters.');
196
- console.log('Run /plexor-setup with a valid Plexor API key.');
221
+ printHealthSummary({
222
+ installed: 'OK',
223
+ connected: 'Invalid key',
224
+ routing: 'Needs repair',
225
+ verified: 'Failed',
226
+ nextAction: 'Run /plexor-setup'
227
+ });
197
228
  process.exit(1);
198
229
  }
199
230
 
200
231
  // Fetch user info and stats
201
232
  let user = { email: 'Unknown', tier: { name: 'Free', limits: {} } };
202
233
  let stats = { period: {}, summary: {} };
234
+ let userFetchWorked = false;
203
235
 
204
236
  try {
205
237
  [user, stats] = await Promise.all([
206
238
  fetchJson(apiUrl, '/v1/user', apiKey),
207
239
  fetchJson(apiUrl, '/v1/stats', apiKey)
208
240
  ]);
241
+ userFetchWorked = true;
209
242
  } catch (err) {
210
243
  // Continue with defaults if API fails
211
244
  }
@@ -289,20 +322,34 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
289
322
  const routing = getRoutingStatus();
290
323
  const routingIndicator = routing.active ? '🟢 PLEXOR MODE: ON' : '🔴 PLEXOR MODE: OFF';
291
324
  const envLabel = routing.isStaging ? '(staging)' : '(production)';
325
+ const partialState = detectPartialState();
326
+ const stateMismatch = checkStateMismatch(enabled, routing.active);
327
+ const connectedState = userFetchWorked ? 'OK' : 'Unknown';
328
+ const verifiedState = verification.verified
329
+ ? 'OK'
330
+ : (verification.verify_error ? 'Failed' : 'Not yet');
331
+ const nextAction = !routing.active || partialState.partial || stateMismatch || !verification.verified
332
+ ? '/plexor-setup'
333
+ : '/plexor-status';
292
334
 
293
335
  // Note: Environment mismatch warning removed - it caused false positives during
294
336
  // concurrent operations and transient states. The partial state and config/routing
295
337
  // mismatch warnings below provide more actionable feedback.
296
338
 
297
- // Check for partial routing state (Plexor URL without valid auth)
298
- const partialState = detectPartialState();
339
+ printHealthSummary({
340
+ installed: 'OK',
341
+ connected: connectedState,
342
+ routing: routing.active ? `OK ${envLabel}` : 'Needs repair',
343
+ verified: verifiedState,
344
+ nextAction
345
+ });
346
+ console.log('');
299
347
  if (partialState.partial) {
300
348
  console.log(` ⚠ PARTIAL STATE DETECTED: ${partialState.issue}`);
301
349
  console.log(` Run /plexor-setup to repair, or /plexor-logout to disable routing\n`);
302
350
  }
303
351
 
304
352
  // Check for state mismatch between config enabled flag and routing status
305
- const stateMismatch = checkStateMismatch(enabled, routing.active);
306
353
  if (stateMismatch) {
307
354
  console.log(` ⚠ State mismatch: ${stateMismatch.message}`);
308
355
  console.log(` └─ ${stateMismatch.suggestion}\n`);
@@ -416,7 +416,7 @@ class ClaudeSettingsManager {
416
416
  const settings = this.load();
417
417
 
418
418
  if (!settings.env?.ANTHROPIC_BASE_URL) {
419
- console.log('Plexor routing is not enabled. Run /plexor-login first.');
419
+ console.log('Plexor routing is not enabled. Run /plexor-setup first.');
420
420
  return false;
421
421
  }
422
422
 
@@ -0,0 +1,77 @@
1
+ const { spawnSync } = require('child_process');
2
+ const { HOME_DIR } = require('./constants');
3
+
4
+ const VERIFY_PROMPT = 'Reply with exactly PONG. Do not use tools.';
5
+ const EXPECTED_VERIFY_RESPONSE = 'PONG';
6
+
7
+ function normalizeVerifyOutput(output = '') {
8
+ return String(output).trim();
9
+ }
10
+
11
+ function interpretVerifyResult(result = {}) {
12
+ const stdout = normalizeVerifyOutput(result.stdout);
13
+ const stderr = normalizeVerifyOutput(result.stderr);
14
+
15
+ if (result.error) {
16
+ return {
17
+ ok: false,
18
+ stdout,
19
+ stderr,
20
+ code: null,
21
+ reason: result.error.message || 'Claude verification failed to start'
22
+ };
23
+ }
24
+
25
+ if (result.status !== 0) {
26
+ return {
27
+ ok: false,
28
+ stdout,
29
+ stderr,
30
+ code: result.status,
31
+ reason: stderr || stdout || `Claude exited with status ${result.status}`
32
+ };
33
+ }
34
+
35
+ if (stdout !== EXPECTED_VERIFY_RESPONSE) {
36
+ return {
37
+ ok: false,
38
+ stdout,
39
+ stderr,
40
+ code: result.status,
41
+ reason: `Expected "${EXPECTED_VERIFY_RESPONSE}" but received "${stdout || '(empty)'}"`
42
+ };
43
+ }
44
+
45
+ return {
46
+ ok: true,
47
+ stdout,
48
+ stderr,
49
+ code: result.status,
50
+ reason: ''
51
+ };
52
+ }
53
+
54
+ function runClaudeRouteVerification(options = {}, runCommand = spawnSync) {
55
+ const result = runCommand(options.claudeCommand || 'claude', [
56
+ '-p',
57
+ '--tools', '',
58
+ '--output-format', 'text',
59
+ VERIFY_PROMPT
60
+ ], {
61
+ cwd: options.cwd || HOME_DIR,
62
+ env: options.env || process.env,
63
+ encoding: 'utf8',
64
+ timeout: options.timeoutMs || 30000,
65
+ maxBuffer: 1024 * 1024
66
+ });
67
+
68
+ return interpretVerifyResult(result);
69
+ }
70
+
71
+ module.exports = {
72
+ EXPECTED_VERIFY_RESPONSE,
73
+ VERIFY_PROMPT,
74
+ interpretVerifyResult,
75
+ normalizeVerifyOutput,
76
+ runClaudeRouteVerification
77
+ };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@plexor-dev/claude-code-plugin-staging",
3
- "version": "0.1.0-beta.19",
3
+ "version": "0.1.0-beta.20",
4
4
  "description": "STAGING - LLM cost optimization plugin for Claude Code (internal testing)",
5
5
  "main": "lib/constants.js",
6
6
  "bin": {
7
7
  "plexor-status": "./commands/plexor-status.js",
8
+ "plexor-setup": "./commands/plexor-setup.js",
8
9
  "plexor-enabled": "./commands/plexor-enabled.js",
9
10
  "plexor-login": "./commands/plexor-login.js",
10
11
  "plexor-logout": "./commands/plexor-logout.js",
@@ -437,7 +437,8 @@ function main() {
437
437
  console.log(' 1. Ask for your Plexor API key');
438
438
  console.log(' 2. Write ~/.plexor/config.json');
439
439
  console.log(' 3. Point Claude at the Plexor staging gateway');
440
- console.log(' 4. Replace any existing Claude API env auth while Plexor is active');
440
+ console.log(' 4. Preserve prior Claude auth for restore on logout');
441
+ console.log(' 5. Verify Claude routing with a deterministic check');
441
442
  console.log('');
442
443
  console.log(' ┌─────────────────────────────────────────────────────────────────┐');
443
444
  console.log(' │ No shell edits or Claude restart required after setup │');
@@ -445,7 +446,7 @@ function main() {
445
446
  console.log('');
446
447
  console.log(' Available commands:');
447
448
  console.log(' /plexor-setup - First-time setup wizard');
448
- console.log(' /plexor-login - Authenticate with API key');
449
+ console.log(' /plexor-login - Advanced/manual auth path');
449
450
  console.log(' /plexor-status - Check connection and see savings');
450
451
  console.log(' /plexor-enabled - Enable/disable Plexor routing');
451
452
  console.log('');