@plexor-dev/claude-code-plugin 0.1.0-beta.3 → 0.1.0-beta.31
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 +4 -7
- package/commands/plexor-enabled.js +221 -0
- package/commands/plexor-enabled.md +15 -41
- package/commands/plexor-login.js +218 -0
- package/commands/plexor-login.md +13 -62
- package/commands/plexor-logout.js +125 -0
- package/commands/plexor-logout.md +13 -28
- package/commands/plexor-setup.md +172 -0
- package/commands/plexor-status.js +344 -0
- package/commands/plexor-status.md +10 -50
- package/hooks/intercept.js +634 -0
- package/hooks/track-response.js +376 -0
- package/lib/cache.js +107 -0
- package/lib/config.js +67 -0
- package/lib/constants.js +16 -31
- package/lib/index.js +19 -0
- package/lib/logger.js +36 -0
- package/lib/plexor-client.js +122 -0
- package/lib/server-sync.js +237 -0
- package/lib/session.js +156 -0
- package/lib/settings-manager.js +304 -0
- package/package.json +8 -1
- package/scripts/plexor-cli.sh +48 -0
- package/scripts/postinstall.js +216 -24
- package/commands/plexor-config.md +0 -42
- package/commands/plexor-mode.md +0 -47
- package/commands/plexor-provider.md +0 -47
- package/commands/plexor-settings.md +0 -58
package/README.md
CHANGED
|
@@ -32,14 +32,11 @@ This installs slash commands to `~/.claude/commands/`.
|
|
|
32
32
|
|
|
33
33
|
| Command | Description |
|
|
34
34
|
|---------|-------------|
|
|
35
|
-
| `/plexor-
|
|
36
|
-
| `/plexor-
|
|
37
|
-
| `/plexor-mode` | Set optimization mode (eco/balanced/quality/passthrough) |
|
|
38
|
-
| `/plexor-provider` | Force specific provider (auto/claude/deepseek/mistral/gemini) |
|
|
39
|
-
| `/plexor-config` | Quick config (enable/disable/cache/reset) |
|
|
40
|
-
| `/plexor-settings` | Advanced settings (API URL, mode, provider) |
|
|
41
|
-
| `/plexor-enabled` | Enable/disable the proxy |
|
|
35
|
+
| `/plexor-setup` | First-time setup wizard |
|
|
36
|
+
| `/plexor-login` | Authenticate with Plexor API key |
|
|
42
37
|
| `/plexor-logout` | Sign out and clear credentials |
|
|
38
|
+
| `/plexor-status` | View usage stats and savings |
|
|
39
|
+
| `/plexor-enabled` | Enable/disable Plexor routing |
|
|
43
40
|
|
|
44
41
|
## How It Works
|
|
45
42
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Enabled Command
|
|
5
|
+
* Enable or disable Plexor optimization proxy
|
|
6
|
+
*
|
|
7
|
+
* KEY FEATURE: When toggling on/off, automatically updates ~/.claude/settings.json
|
|
8
|
+
* to route or un-route ALL Claude Code sessions through Plexor gateway.
|
|
9
|
+
*
|
|
10
|
+
* THE DREAM: User just runs "/plexor-enabled off" to disable, "/plexor-enabled on" to enable.
|
|
11
|
+
* No manual environment variables or config updates required!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// Import settings manager with error handling for missing lib
|
|
18
|
+
let settingsManager, PLEXOR_STAGING_URL, PLEXOR_PROD_URL;
|
|
19
|
+
try {
|
|
20
|
+
const lib = require('../lib/settings-manager');
|
|
21
|
+
settingsManager = lib.settingsManager;
|
|
22
|
+
PLEXOR_STAGING_URL = lib.PLEXOR_STAGING_URL;
|
|
23
|
+
PLEXOR_PROD_URL = lib.PLEXOR_PROD_URL;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (err.code === 'MODULE_NOT_FOUND') {
|
|
26
|
+
console.error('Error: Plexor plugin files are missing or corrupted.');
|
|
27
|
+
console.error(' Please reinstall: npm install @plexor-dev/claude-code-plugin');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
34
|
+
const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
|
|
35
|
+
|
|
36
|
+
function loadConfig() {
|
|
37
|
+
try {
|
|
38
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
39
|
+
return { version: 1, auth: {}, settings: {} };
|
|
40
|
+
}
|
|
41
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
42
|
+
if (!data || data.trim() === '') {
|
|
43
|
+
return { version: 1, auth: {}, settings: {} };
|
|
44
|
+
}
|
|
45
|
+
const config = JSON.parse(data);
|
|
46
|
+
if (typeof config !== 'object' || config === null) {
|
|
47
|
+
console.warn('Warning: Config file has invalid format, using defaults');
|
|
48
|
+
return { version: 1, auth: {}, settings: {} };
|
|
49
|
+
}
|
|
50
|
+
return config;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof SyntaxError) {
|
|
53
|
+
console.warn('Warning: Config file is corrupted, using defaults');
|
|
54
|
+
// Backup corrupted file
|
|
55
|
+
try {
|
|
56
|
+
fs.copyFileSync(CONFIG_PATH, CONFIG_PATH + '.corrupted');
|
|
57
|
+
} catch { /* ignore */ }
|
|
58
|
+
}
|
|
59
|
+
return { version: 1, auth: {}, settings: {} };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function saveConfig(config) {
|
|
64
|
+
try {
|
|
65
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
66
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Atomic write: write to temp file, then rename
|
|
70
|
+
const crypto = require('crypto');
|
|
71
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
72
|
+
const tempPath = path.join(PLEXOR_DIR, `.config.${tempId}.tmp`);
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
75
|
+
fs.renameSync(tempPath, CONFIG_PATH);
|
|
76
|
+
return true;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
79
|
+
console.error(`Error: Cannot write to ~/.plexor/config.json`);
|
|
80
|
+
console.error(' Check file permissions or run with appropriate access.');
|
|
81
|
+
} else {
|
|
82
|
+
console.error('Failed to save config:', err.message);
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate API key format
|
|
90
|
+
* @param {string} key - API key to validate
|
|
91
|
+
* @returns {boolean} true if valid format
|
|
92
|
+
*/
|
|
93
|
+
function isValidApiKeyFormat(key) {
|
|
94
|
+
return key && typeof key === 'string' && key.startsWith('plx_') && key.length >= 20;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function main() {
|
|
98
|
+
const args = process.argv.slice(2);
|
|
99
|
+
const config = loadConfig();
|
|
100
|
+
const currentEnabled = config.settings?.enabled ?? false;
|
|
101
|
+
const apiKey = config.auth?.api_key;
|
|
102
|
+
|
|
103
|
+
// Get current Claude settings.json routing status
|
|
104
|
+
const routingStatus = settingsManager.getRoutingStatus();
|
|
105
|
+
|
|
106
|
+
// No args - show current status
|
|
107
|
+
if (args.length === 0) {
|
|
108
|
+
const status = currentEnabled ? '● Enabled' : '○ Disabled';
|
|
109
|
+
const routingStr = routingStatus.enabled ? '● Active' : '○ Inactive';
|
|
110
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
111
|
+
console.log(`│ Plexor Proxy Status │`);
|
|
112
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
113
|
+
console.log(`│ Plugin Config: ${status.padEnd(27)}│`);
|
|
114
|
+
console.log(`│ Claude Routing: ${routingStr.padEnd(26)}│`);
|
|
115
|
+
if (routingStatus.enabled) {
|
|
116
|
+
console.log(`│ Endpoint: ${(routingStatus.isStaging ? 'Staging' : 'Production').padEnd(32)}│`);
|
|
117
|
+
}
|
|
118
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
119
|
+
console.log(`│ Usage: │`);
|
|
120
|
+
console.log(`│ /plexor-enabled true - Enable proxy │`);
|
|
121
|
+
console.log(`│ /plexor-enabled false - Disable proxy │`);
|
|
122
|
+
console.log(`│ /plexor-enabled on - Enable proxy │`);
|
|
123
|
+
console.log(`│ /plexor-enabled off - Disable proxy │`);
|
|
124
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const arg = args[0].toLowerCase();
|
|
129
|
+
let newEnabled;
|
|
130
|
+
|
|
131
|
+
if (['true', 'on', 'yes', '1', 'enable'].includes(arg)) {
|
|
132
|
+
newEnabled = true;
|
|
133
|
+
} else if (['false', 'off', 'no', '0', 'disable'].includes(arg)) {
|
|
134
|
+
newEnabled = false;
|
|
135
|
+
} else {
|
|
136
|
+
console.error(`Error: Invalid value "${args[0]}"`);
|
|
137
|
+
console.error(`Use: true/false, on/off, yes/no, enable/disable`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// THE KEY FEATURE: Update Claude Code settings.json routing
|
|
142
|
+
let routingUpdated = false;
|
|
143
|
+
let missingApiKey = false;
|
|
144
|
+
let invalidApiKey = false;
|
|
145
|
+
|
|
146
|
+
if (newEnabled) {
|
|
147
|
+
// Enable routing - need valid API key from config
|
|
148
|
+
if (!apiKey) {
|
|
149
|
+
missingApiKey = true;
|
|
150
|
+
} else if (!isValidApiKeyFormat(apiKey)) {
|
|
151
|
+
invalidApiKey = true;
|
|
152
|
+
} else {
|
|
153
|
+
// Update Plexor plugin config first
|
|
154
|
+
config.settings = config.settings || {};
|
|
155
|
+
config.settings.enabled = newEnabled;
|
|
156
|
+
if (!saveConfig(config)) {
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// PRODUCTION PACKAGE - uses production API
|
|
161
|
+
const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
|
|
162
|
+
const useStaging = apiUrl.includes('staging');
|
|
163
|
+
routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// Update Plexor plugin config
|
|
167
|
+
config.settings = config.settings || {};
|
|
168
|
+
config.settings.enabled = newEnabled;
|
|
169
|
+
if (!saveConfig(config)) {
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Disable routing - remove env vars from settings.json
|
|
174
|
+
routingUpdated = settingsManager.disablePlexorRouting();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Show error if no API key when enabling
|
|
178
|
+
if (missingApiKey) {
|
|
179
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
180
|
+
console.log(`│ ✗ Cannot Enable Plexor │`);
|
|
181
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
182
|
+
console.log(`│ No API key configured. │`);
|
|
183
|
+
console.log(`│ Run /plexor-login <api-key> first. │`);
|
|
184
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
185
|
+
console.log(`│ Get your API key at: │`);
|
|
186
|
+
console.log(`│ https://plexor.dev/dashboard/api-keys │`);
|
|
187
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Show error if API key format is invalid
|
|
192
|
+
if (invalidApiKey) {
|
|
193
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
194
|
+
console.log(`│ ✗ Cannot Enable Plexor │`);
|
|
195
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
196
|
+
console.log(`│ Invalid API key format in config. │`);
|
|
197
|
+
console.log(`│ Keys must start with "plx_" (20+ chars). │`);
|
|
198
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
199
|
+
console.log(`│ Run /plexor-login <api-key> to fix. │`);
|
|
200
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
|
|
205
|
+
const prevStatus = currentEnabled ? 'Enabled' : 'Disabled';
|
|
206
|
+
const routingMsg = routingUpdated
|
|
207
|
+
? (newEnabled ? 'Claude Code now routes through Plexor' : 'Claude Code now connects directly')
|
|
208
|
+
: 'Manual routing update may be needed';
|
|
209
|
+
|
|
210
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
211
|
+
console.log(`│ ✓ Plexor Proxy Updated │`);
|
|
212
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
213
|
+
console.log(`│ Previous: ${prevStatus.padEnd(32)}│`);
|
|
214
|
+
console.log(`│ New: ${newStatus.padEnd(37)}│`);
|
|
215
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
216
|
+
console.log(`│ ${routingMsg.padEnd(42)}│`);
|
|
217
|
+
console.log(`│ Changes take effect immediately. │`);
|
|
218
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
main();
|
|
@@ -4,51 +4,25 @@ description: Enable or disable Plexor proxy (routes all traffic through Plexor A
|
|
|
4
4
|
|
|
5
5
|
# Plexor Enabled
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Run this command to view or toggle the Plexor proxy:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**Step 1: Read current configuration**
|
|
12
|
-
|
|
13
|
-
Use the Read tool to read `~/.plexor/config.json` and check the current `enabled` status.
|
|
14
|
-
|
|
15
|
-
**Step 2: Ask user what they want to do**
|
|
16
|
-
|
|
17
|
-
Use the `AskUserQuestion` tool to present options:
|
|
18
|
-
|
|
19
|
-
Question: "Enable or disable Plexor proxy?"
|
|
20
|
-
Header: "Proxy"
|
|
21
|
-
Options:
|
|
22
|
-
1. **Enable** - Route traffic through Plexor for optimization (60-90% cost savings)
|
|
23
|
-
2. **Disable** - Direct to Anthropic (no optimization, full price)
|
|
24
|
-
|
|
25
|
-
**Step 3: Update the configuration**
|
|
26
|
-
|
|
27
|
-
Use the Read tool to get the current config, then use the Write tool to update `~/.plexor/config.json`:
|
|
28
|
-
- If **Enable**: Set `"enabled": true`
|
|
29
|
-
- If **Disable**: Set `"enabled": false`
|
|
30
|
-
|
|
31
|
-
Keep all other settings unchanged.
|
|
32
|
-
|
|
33
|
-
**Step 4: Show confirmation**
|
|
34
|
-
|
|
35
|
-
If **Enabled**:
|
|
9
|
+
```bash
|
|
10
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js
|
|
36
11
|
```
|
|
37
|
-
Plexor Proxy: ENABLED
|
|
38
12
|
|
|
39
|
-
|
|
40
|
-
- 60-90% cost reduction via intelligent provider routing
|
|
41
|
-
- Automatic selection of Mistral/DeepSeek/Gemini based on task
|
|
42
|
-
- Usage tracking and savings analytics
|
|
13
|
+
To enable or disable, pass an argument:
|
|
43
14
|
|
|
44
|
-
|
|
45
|
-
|
|
15
|
+
```bash
|
|
16
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js true
|
|
17
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js false
|
|
46
18
|
```
|
|
47
19
|
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
Plexor Proxy: DISABLED
|
|
20
|
+
Use the Bash tool to execute this command.
|
|
51
21
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Login Command
|
|
5
|
+
* Authenticate with Plexor API and auto-configure Claude Code routing
|
|
6
|
+
*
|
|
7
|
+
* KEY FEATURE: After successful login, automatically configures ~/.claude/settings.json
|
|
8
|
+
* to route ALL Claude Code sessions through Plexor gateway.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const https = require('https');
|
|
14
|
+
const http = require('http');
|
|
15
|
+
|
|
16
|
+
// Import settings manager with error handling for missing lib
|
|
17
|
+
let settingsManager;
|
|
18
|
+
try {
|
|
19
|
+
settingsManager = require('../lib/settings-manager').settingsManager;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err.code === 'MODULE_NOT_FOUND') {
|
|
22
|
+
console.error('Error: Plexor plugin files are missing or corrupted.');
|
|
23
|
+
console.error(' Please reinstall: npm install @plexor-dev/claude-code-plugin');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
|
|
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
|
+
function loadConfig() {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
37
|
+
return { version: 1, auth: {}, settings: {} };
|
|
38
|
+
}
|
|
39
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
40
|
+
if (!data || data.trim() === '') {
|
|
41
|
+
return { version: 1, auth: {}, settings: {} };
|
|
42
|
+
}
|
|
43
|
+
const config = JSON.parse(data);
|
|
44
|
+
if (typeof config !== 'object' || config === null) {
|
|
45
|
+
return { version: 1, auth: {}, settings: {} };
|
|
46
|
+
}
|
|
47
|
+
return config;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof SyntaxError) {
|
|
50
|
+
console.warn('Warning: Config file is corrupted, will be overwritten');
|
|
51
|
+
}
|
|
52
|
+
return { version: 1, auth: {}, settings: {} };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function saveConfig(config) {
|
|
57
|
+
try {
|
|
58
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
59
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Atomic write: write to temp file, then rename
|
|
63
|
+
const crypto = require('crypto');
|
|
64
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
65
|
+
const tempPath = path.join(PLEXOR_DIR, `.config.${tempId}.tmp`);
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
68
|
+
fs.renameSync(tempPath, CONFIG_PATH);
|
|
69
|
+
return true;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
72
|
+
console.error('Error: Cannot write to ~/.plexor/config.json');
|
|
73
|
+
console.error(' Check file permissions or run with appropriate access.');
|
|
74
|
+
} else {
|
|
75
|
+
console.error('Failed to save config:', err.message);
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateApiKey(apiUrl, apiKey) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const url = new URL(`${apiUrl}/v1/user`);
|
|
84
|
+
const isHttps = url.protocol === 'https:';
|
|
85
|
+
const lib = isHttps ? https : http;
|
|
86
|
+
|
|
87
|
+
const options = {
|
|
88
|
+
hostname: url.hostname,
|
|
89
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
90
|
+
path: url.pathname,
|
|
91
|
+
method: 'GET',
|
|
92
|
+
headers: {
|
|
93
|
+
'X-Plexor-Key': apiKey
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const req = lib.request(options, (res) => {
|
|
98
|
+
let data = '';
|
|
99
|
+
res.on('data', chunk => data += chunk);
|
|
100
|
+
res.on('end', () => {
|
|
101
|
+
if (res.statusCode === 200) {
|
|
102
|
+
try {
|
|
103
|
+
resolve(JSON.parse(data));
|
|
104
|
+
} catch {
|
|
105
|
+
reject(new Error('Invalid response from server'));
|
|
106
|
+
}
|
|
107
|
+
} else if (res.statusCode === 401) {
|
|
108
|
+
reject(new Error('Invalid API key'));
|
|
109
|
+
} else {
|
|
110
|
+
reject(new Error(`Server error: ${res.statusCode}`));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
req.on('error', (err) => reject(new Error(`Connection failed: ${err.message}`)));
|
|
116
|
+
req.setTimeout(10000, () => {
|
|
117
|
+
req.destroy();
|
|
118
|
+
reject(new Error('Connection timeout'));
|
|
119
|
+
});
|
|
120
|
+
req.end();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function main() {
|
|
125
|
+
const args = process.argv.slice(2);
|
|
126
|
+
let apiKey = args[0];
|
|
127
|
+
|
|
128
|
+
// Check for existing login
|
|
129
|
+
const config = loadConfig();
|
|
130
|
+
const existingKey = config.auth?.api_key;
|
|
131
|
+
|
|
132
|
+
if (existingKey && !apiKey) {
|
|
133
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
134
|
+
console.log(`│ Already Logged In │`);
|
|
135
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
136
|
+
console.log(`│ API Key: ${(existingKey.substring(0, 8) + '...').padEnd(33)}│`);
|
|
137
|
+
console.log(`│ To re-login, provide a new key: │`);
|
|
138
|
+
console.log(`│ /plexor-login <api-key> │`);
|
|
139
|
+
console.log(`│ To logout: │`);
|
|
140
|
+
console.log(`│ /plexor-logout │`);
|
|
141
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If no key provided, prompt for it
|
|
146
|
+
if (!apiKey) {
|
|
147
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
148
|
+
console.log(`│ Plexor Login │`);
|
|
149
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
150
|
+
console.log(`│ Get your API key at: │`);
|
|
151
|
+
console.log(`│ https://plexor.dev/dashboard/api-keys │`);
|
|
152
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
153
|
+
console.log(`│ Usage: /plexor-login <api-key> │`);
|
|
154
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
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`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const apiUrl = config.settings?.apiUrl || DEFAULT_API_URL;
|
|
166
|
+
|
|
167
|
+
console.log('Validating API key...');
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const user = await validateApiKey(apiUrl, apiKey);
|
|
171
|
+
|
|
172
|
+
config.auth = config.auth || {};
|
|
173
|
+
config.auth.api_key = apiKey;
|
|
174
|
+
config.settings = config.settings || {};
|
|
175
|
+
config.settings.enabled = true;
|
|
176
|
+
if (!saveConfig(config)) {
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// AUTO-CONFIGURE CLAUDE CODE ROUTING
|
|
181
|
+
// This is the key feature: automatically set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN
|
|
182
|
+
// in ~/.claude/settings.json so ALL Claude Code sessions route through Plexor
|
|
183
|
+
const useStaging = apiUrl.includes('staging');
|
|
184
|
+
const routingEnabled = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
185
|
+
|
|
186
|
+
const email = user.email || 'Unknown';
|
|
187
|
+
const tier = user.tier?.name || 'Free';
|
|
188
|
+
|
|
189
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
190
|
+
console.log(`│ ✓ Login Successful │`);
|
|
191
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
192
|
+
console.log(`│ Email: ${email.substring(0, 35).padEnd(35)}│`);
|
|
193
|
+
console.log(`│ Tier: ${tier.padEnd(36)}│`);
|
|
194
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
195
|
+
if (routingEnabled) {
|
|
196
|
+
console.log(`│ ✓ Claude Code routing: CONFIGURED │`);
|
|
197
|
+
console.log(`│ All sessions now route through Plexor │`);
|
|
198
|
+
} else {
|
|
199
|
+
console.log(`│ ⚠ Claude Code routing: MANUAL SETUP NEEDED │`);
|
|
200
|
+
console.log(`│ Set ANTHROPIC_BASE_URL in environment │`);
|
|
201
|
+
}
|
|
202
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
203
|
+
console.log(`│ Run /plexor-status to see your stats. │`);
|
|
204
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(`┌─────────────────────────────────────────────┐`);
|
|
207
|
+
console.error(`│ ✗ Login Failed │`);
|
|
208
|
+
console.error(`├─────────────────────────────────────────────┤`);
|
|
209
|
+
console.error(`│ ${err.message.padEnd(42)}│`);
|
|
210
|
+
console.error(`└─────────────────────────────────────────────┘`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().catch(err => {
|
|
216
|
+
console.error('Error:', err.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
package/commands/plexor-login.md
CHANGED
|
@@ -4,73 +4,24 @@ description: Authenticate with Plexor to enable optimization (user)
|
|
|
4
4
|
|
|
5
5
|
# Plexor Login
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Run this command to authenticate with Plexor:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**Step 1: Check existing authentication**
|
|
12
|
-
|
|
13
|
-
Use the Read tool to read `~/.plexor/config.json`.
|
|
14
|
-
|
|
15
|
-
If the file exists and has an `apiKey` field that starts with "plx_", the user is already authenticated. Show them:
|
|
16
|
-
```
|
|
17
|
-
Plexor Login
|
|
18
|
-
============
|
|
19
|
-
Already authenticated!
|
|
20
|
-
API Key: plx_****[last 4 chars]
|
|
21
|
-
API URL: [apiUrl from config]
|
|
22
|
-
Status: [Enabled/Disabled]
|
|
23
|
-
|
|
24
|
-
Run /plexor-status to see your usage statistics.
|
|
25
|
-
Run /plexor-logout to sign out.
|
|
9
|
+
```bash
|
|
10
|
+
node ~/.claude/plugins/plexor/commands/plexor-login.js
|
|
26
11
|
```
|
|
27
12
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Ask the user to provide their Plexor API key. They can get one from:
|
|
31
|
-
- https://plexor.dev/dashboard (if they have an account)
|
|
32
|
-
- https://plexor.dev/signup (to create an account)
|
|
33
|
-
|
|
34
|
-
Tell them: "Please provide your Plexor API key (starts with 'plx_'):"
|
|
35
|
-
|
|
36
|
-
**Step 3: Save the configuration**
|
|
37
|
-
|
|
38
|
-
Once the user provides an API key, use the Write tool to create/update `~/.plexor/config.json`:
|
|
13
|
+
To login with an API key:
|
|
39
14
|
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
"enabled": true,
|
|
43
|
-
"apiUrl": "https://api.plexor.dev",
|
|
44
|
-
"apiKey": "[user's API key]",
|
|
45
|
-
"mode": "balanced",
|
|
46
|
-
"preferredProvider": "auto",
|
|
47
|
-
"localCacheEnabled": true,
|
|
48
|
-
"timeout": 5000
|
|
49
|
-
}
|
|
15
|
+
```bash
|
|
16
|
+
node ~/.claude/plugins/plexor/commands/plexor-login.js plx_your_api_key_here
|
|
50
17
|
```
|
|
51
18
|
|
|
52
|
-
|
|
19
|
+
Use the Bash tool to execute this command.
|
|
53
20
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
If successful, show:
|
|
61
|
-
```
|
|
62
|
-
Plexor Login
|
|
63
|
-
============
|
|
64
|
-
Authentication successful!
|
|
65
|
-
|
|
66
|
-
Your API key has been saved to ~/.plexor/config.json
|
|
67
|
-
|
|
68
|
-
Next steps:
|
|
69
|
-
1. Run /plexor-status to see your usage
|
|
70
|
-
2. Run /plexor-enabled true to enable the proxy
|
|
71
|
-
3. Start saving on LLM costs!
|
|
72
|
-
|
|
73
|
-
Dashboard: https://plexor.dev/dashboard
|
|
74
|
-
```
|
|
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
|
|
75
26
|
|
|
76
|
-
|
|
27
|
+
The command output is the complete response. Simply show the output and wait for the user's next input.
|