@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.
- package/commands/plexor-login.js +27 -14
- package/commands/plexor-login.md +1 -2
- package/commands/plexor-logout.js +10 -3
- package/commands/plexor-setup.js +253 -0
- package/commands/plexor-setup.md +16 -165
- package/commands/plexor-status.js +54 -7
- package/lib/settings-manager.js +1 -1
- package/lib/verify-route.js +77 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +3 -2
package/commands/plexor-login.js
CHANGED
|
@@ -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(`│
|
|
251
|
-
console.log(`│
|
|
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
|
-
//
|
|
308
|
-
//
|
|
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(`│ ✓
|
|
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
|
|
324
|
-
console.log(`│
|
|
335
|
+
console.log(`│ ✓ Claude routing: ACTIVE │`);
|
|
336
|
+
console.log(`│ ○ Claude verify: NOT RUN │`);
|
|
325
337
|
} else {
|
|
326
|
-
console.log(`│ ⚠ Claude
|
|
327
|
-
console.log(`│
|
|
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(`│
|
|
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(`┌─────────────────────────────────────────────┐`);
|
package/commands/plexor-login.md
CHANGED
|
@@ -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
|
|
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-
|
|
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-
|
|
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
|
+
});
|
package/commands/plexor-setup.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
6
|
+
Plexor setup is the primary human setup flow.
|
|
16
7
|
|
|
17
|
-
|
|
18
|
-
Also check `~/.claude/settings.json` for routing status.
|
|
8
|
+
If `$ARGUMENTS` already contains a Plexor API key (`plx_...`), run:
|
|
19
9
|
|
|
20
|
-
|
|
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
|
-
|
|
14
|
+
If the user did not provide a key yet, ask them:
|
|
39
15
|
|
|
40
|
-
|
|
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
|
-
|
|
18
|
+
After the user replies with the key, run:
|
|
47
19
|
|
|
48
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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
|
-
|
|
298
|
-
|
|
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`);
|
package/lib/settings-manager.js
CHANGED
|
@@ -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-
|
|
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.
|
|
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",
|
package/scripts/postinstall.js
CHANGED
|
@@ -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.
|
|
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 -
|
|
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('');
|