@plexor-dev/claude-code-plugin-staging 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +117 -0
- package/commands/plexor-config.js +170 -0
- package/commands/plexor-config.md +28 -0
- package/commands/plexor-enabled.js +122 -0
- package/commands/plexor-enabled.md +28 -0
- package/commands/plexor-login.js +189 -0
- package/commands/plexor-login.md +27 -0
- package/commands/plexor-logout.js +92 -0
- package/commands/plexor-logout.md +27 -0
- package/commands/plexor-mode.js +107 -0
- package/commands/plexor-mode.md +27 -0
- package/commands/plexor-provider.js +110 -0
- package/commands/plexor-provider.md +27 -0
- package/commands/plexor-settings.js +155 -0
- package/commands/plexor-settings.md +28 -0
- package/commands/plexor-setup.md +172 -0
- package/commands/plexor-status.js +240 -0
- package/commands/plexor-status.md +21 -0
- 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 +26 -0
- 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 +239 -0
- package/package.json +60 -0
- package/scripts/plexor-cli.sh +48 -0
- package/scripts/postinstall.js +251 -0
- package/scripts/uninstall.js +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Plexor AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @plexor-dev/claude-code-plugin
|
|
2
|
+
|
|
3
|
+
LLM cost optimization plugin for Claude Code. Save up to 90% on AI costs through intelligent prompt optimization and multi-provider routing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @plexor-dev/claude-code-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This installs slash commands to `~/.claude/commands/`.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
1. **Install the plugin** (see above)
|
|
16
|
+
|
|
17
|
+
2. **Open Claude Code** and run:
|
|
18
|
+
```
|
|
19
|
+
/plexor-login
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
3. **Follow the prompts** to authenticate via browser
|
|
23
|
+
|
|
24
|
+
4. **Set the gateway URL** in your terminal:
|
|
25
|
+
```bash
|
|
26
|
+
export ANTHROPIC_BASE_URL="https://api.plexor.dev/v1/gateway/anthropic"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
5. **Restart Claude Code** - all prompts are now optimized!
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| `/plexor-login` | Authenticate with Plexor |
|
|
36
|
+
| `/plexor-status` | View usage stats and savings |
|
|
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 |
|
|
42
|
+
| `/plexor-logout` | Sign out and clear credentials |
|
|
43
|
+
|
|
44
|
+
## How It Works
|
|
45
|
+
|
|
46
|
+
Plexor acts as an intelligent gateway between Claude Code and LLM providers:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Claude Code → Plexor Gateway → Best Provider (Anthropic/DeepSeek/Mistral/Gemini/OpenAI)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The gateway:
|
|
53
|
+
- **Optimizes prompts** to reduce token usage
|
|
54
|
+
- **Routes requests** to the most cost-effective provider
|
|
55
|
+
- **Maintains quality** based on your selected mode
|
|
56
|
+
- **Tracks savings** so you can see your ROI
|
|
57
|
+
|
|
58
|
+
## Optimization Modes
|
|
59
|
+
|
|
60
|
+
| Mode | Savings | Best For |
|
|
61
|
+
|------|---------|----------|
|
|
62
|
+
| **eco** | 60-90% | Development, testing, iteration |
|
|
63
|
+
| **balanced** | 40-60% | Most production workloads |
|
|
64
|
+
| **quality** | 20-40% | Critical responses, customer-facing |
|
|
65
|
+
| **passthrough** | 0% | Debugging, comparison |
|
|
66
|
+
|
|
67
|
+
## Pricing
|
|
68
|
+
|
|
69
|
+
- **Beta**: $1/month (unlimited requests)
|
|
70
|
+
- Savings typically 10-100x the subscription cost
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
Settings are stored in `~/.plexor/config.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"version": 1,
|
|
79
|
+
"auth": {
|
|
80
|
+
"api_key": "plx_...",
|
|
81
|
+
"email": "user@example.com"
|
|
82
|
+
},
|
|
83
|
+
"settings": {
|
|
84
|
+
"enabled": true,
|
|
85
|
+
"mode": "balanced",
|
|
86
|
+
"preferred_provider": "auto"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Uninstall
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm uninstall -g @plexor-dev/claude-code-plugin
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
To fully remove:
|
|
98
|
+
```bash
|
|
99
|
+
unset ANTHROPIC_BASE_URL
|
|
100
|
+
rm -rf ~/.plexor
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Requirements
|
|
104
|
+
|
|
105
|
+
- Node.js 16+
|
|
106
|
+
- Claude Code CLI
|
|
107
|
+
- macOS, Linux, or Windows (WSL)
|
|
108
|
+
|
|
109
|
+
## Support
|
|
110
|
+
|
|
111
|
+
- **Documentation**: https://plexor.dev/docs
|
|
112
|
+
- **Issues**: https://github.com/plexor-ai/claude-code-plugin/issues
|
|
113
|
+
- **Email**: hello@plexor.dev
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Config Command
|
|
5
|
+
* Display raw configuration (for debugging)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
12
|
+
const SESSION_PATH = path.join(process.env.HOME, '.plexor', 'session.json');
|
|
13
|
+
const CACHE_PATH = path.join(process.env.HOME, '.plexor', 'cache.json');
|
|
14
|
+
const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
|
|
15
|
+
|
|
16
|
+
function fileExists(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.existsSync(filePath);
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getFileSize(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
const stats = fs.statSync(filePath);
|
|
27
|
+
return stats.size;
|
|
28
|
+
} catch {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatBytes(bytes) {
|
|
34
|
+
if (bytes === 0) return '0 B';
|
|
35
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
36
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
37
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function loadConfig() {
|
|
41
|
+
try {
|
|
42
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
43
|
+
return { error: 'not_found', message: 'Config file does not exist' };
|
|
44
|
+
}
|
|
45
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
46
|
+
return { config: JSON.parse(data) };
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err.code === 'ENOENT') {
|
|
49
|
+
return { error: 'not_found', message: 'Config file does not exist' };
|
|
50
|
+
}
|
|
51
|
+
if (err.code === 'EACCES') {
|
|
52
|
+
return { error: 'permission', message: 'Permission denied reading config' };
|
|
53
|
+
}
|
|
54
|
+
if (err instanceof SyntaxError) {
|
|
55
|
+
return { error: 'invalid_json', message: 'Config file contains invalid JSON' };
|
|
56
|
+
}
|
|
57
|
+
return { error: 'unknown', message: err.message };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function maskApiKey(key) {
|
|
62
|
+
if (!key) return 'not set';
|
|
63
|
+
if (key.length <= 12) return '***masked***';
|
|
64
|
+
return key.substring(0, 8) + '...' + key.substring(key.length - 4);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
const args = process.argv.slice(2);
|
|
69
|
+
const showRaw = args.includes('--raw') || args.includes('-r');
|
|
70
|
+
const showPaths = args.includes('--paths') || args.includes('-p');
|
|
71
|
+
|
|
72
|
+
// Show paths only
|
|
73
|
+
if (showPaths) {
|
|
74
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
75
|
+
console.log(`│ Plexor File Paths │`);
|
|
76
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
77
|
+
console.log(`│ Directory: ${PLEXOR_DIR.substring(0, 31).padEnd(31)}│`);
|
|
78
|
+
console.log(`│ Config: ${CONFIG_PATH.substring(0, 34).padEnd(34)}│`);
|
|
79
|
+
console.log(`│ Session: ${SESSION_PATH.substring(0, 33).padEnd(33)}│`);
|
|
80
|
+
console.log(`│ Cache: ${CACHE_PATH.substring(0, 35).padEnd(35)}│`);
|
|
81
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = loadConfig();
|
|
86
|
+
|
|
87
|
+
if (result.error) {
|
|
88
|
+
const errorMessages = {
|
|
89
|
+
not_found: 'Config file does not exist',
|
|
90
|
+
permission: 'Permission denied reading config file',
|
|
91
|
+
invalid_json: 'Config file contains invalid JSON (corrupted?)',
|
|
92
|
+
unknown: result.message || 'Unknown error'
|
|
93
|
+
};
|
|
94
|
+
const errorMsg = errorMessages[result.error] || result.message;
|
|
95
|
+
const suggestion = result.error === 'invalid_json'
|
|
96
|
+
? 'Try: rm ~/.plexor/config.json && /plexor-login'
|
|
97
|
+
: 'Run /plexor-login to create configuration.';
|
|
98
|
+
|
|
99
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
100
|
+
console.log(`│ Configuration Error │`);
|
|
101
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
102
|
+
console.log(`│ Error: ${result.error.padEnd(35)}│`);
|
|
103
|
+
console.log(`│ ${errorMsg.substring(0, 42).padEnd(42)}│`);
|
|
104
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
105
|
+
console.log(`│ ${suggestion.substring(0, 42).padEnd(42)}│`);
|
|
106
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
107
|
+
console.log(`│ Expected path: │`);
|
|
108
|
+
console.log(`│ ${CONFIG_PATH.substring(0, 42).padEnd(42)}│`);
|
|
109
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const config = result.config;
|
|
114
|
+
|
|
115
|
+
// Show raw JSON
|
|
116
|
+
if (showRaw) {
|
|
117
|
+
// Mask API key in raw output
|
|
118
|
+
const safeConfig = JSON.parse(JSON.stringify(config));
|
|
119
|
+
if (safeConfig.auth?.api_key) {
|
|
120
|
+
safeConfig.auth.api_key = maskApiKey(safeConfig.auth.api_key);
|
|
121
|
+
}
|
|
122
|
+
console.log(JSON.stringify(safeConfig, null, 2));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Show formatted config info
|
|
127
|
+
const configSize = getFileSize(CONFIG_PATH);
|
|
128
|
+
const sessionSize = getFileSize(SESSION_PATH);
|
|
129
|
+
const cacheSize = getFileSize(CACHE_PATH);
|
|
130
|
+
const hasSession = fileExists(SESSION_PATH);
|
|
131
|
+
const hasCache = fileExists(CACHE_PATH);
|
|
132
|
+
|
|
133
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
134
|
+
console.log(`│ Plexor Configuration │`);
|
|
135
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
136
|
+
console.log(`│ Version: ${(config.version || 1).toString().padEnd(33)}│`);
|
|
137
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
138
|
+
console.log(`│ Authentication │`);
|
|
139
|
+
console.log(`│ └── API Key: ${maskApiKey(config.auth?.api_key).padEnd(29)}│`);
|
|
140
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
141
|
+
console.log(`│ Settings │`);
|
|
142
|
+
|
|
143
|
+
const settings = config.settings || {};
|
|
144
|
+
const settingEntries = [
|
|
145
|
+
['enabled', settings.enabled ?? false],
|
|
146
|
+
['mode', settings.mode || 'balanced'],
|
|
147
|
+
['preferred_provider', settings.preferred_provider || 'auto'],
|
|
148
|
+
['apiUrl', settings.apiUrl || 'https://api.plexor.dev'],
|
|
149
|
+
['timeout', (settings.timeout || 5000) + 'ms'],
|
|
150
|
+
['localCacheEnabled', settings.localCacheEnabled ?? false]
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
for (const [key, value] of settingEntries) {
|
|
154
|
+
const displayValue = String(value).substring(0, 25);
|
|
155
|
+
console.log(`│ ├── ${key.padEnd(18)} ${displayValue.padEnd(18)}│`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
159
|
+
console.log(`│ Files │`);
|
|
160
|
+
console.log(`│ ├── config.json: ${formatBytes(configSize).padEnd(24)}│`);
|
|
161
|
+
console.log(`│ ├── session.json: ${(hasSession ? formatBytes(sessionSize) : 'not found').padEnd(23)}│`);
|
|
162
|
+
console.log(`│ └── cache.json: ${(hasCache ? formatBytes(cacheSize) : 'not found').padEnd(25)}│`);
|
|
163
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
164
|
+
console.log(`│ Options: │`);
|
|
165
|
+
console.log(`│ /plexor-config --raw Show raw JSON │`);
|
|
166
|
+
console.log(`│ /plexor-config --paths Show file paths │`);
|
|
167
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
main();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Configure Plexor settings (user)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Plexor Config
|
|
6
|
+
|
|
7
|
+
Run this command to view configuration details:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node ~/.claude/plugins/plexor/commands/plexor-config.js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node ~/.claude/plugins/plexor/commands/plexor-config.js --raw # Show raw JSON
|
|
17
|
+
node ~/.claude/plugins/plexor/commands/plexor-config.js --paths # Show file paths
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use the Bash tool to execute this command.
|
|
21
|
+
|
|
22
|
+
**IMPORTANT**: After running this command and displaying the output, STOP. Do not:
|
|
23
|
+
- Read any files
|
|
24
|
+
- Explore the codebase
|
|
25
|
+
- Run additional commands
|
|
26
|
+
- Ask follow-up questions
|
|
27
|
+
|
|
28
|
+
The command output is the complete response. Simply show the output and wait for the user's next input.
|
|
@@ -0,0 +1,122 @@
|
|
|
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 for automatic Claude Code configuration
|
|
18
|
+
const { settingsManager, PLEXOR_STAGING_URL, PLEXOR_PROD_URL } = require('../lib/settings-manager');
|
|
19
|
+
|
|
20
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
21
|
+
const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
|
|
22
|
+
|
|
23
|
+
function loadConfig() {
|
|
24
|
+
try {
|
|
25
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
} catch {
|
|
28
|
+
return { version: 1, auth: {}, settings: {} };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function saveConfig(config) {
|
|
33
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
34
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
35
|
+
}
|
|
36
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function main() {
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
const currentEnabled = config.settings?.enabled ?? false;
|
|
43
|
+
const apiKey = config.auth?.api_key;
|
|
44
|
+
|
|
45
|
+
// Get current Claude settings.json routing status
|
|
46
|
+
const routingStatus = settingsManager.getRoutingStatus();
|
|
47
|
+
|
|
48
|
+
// No args - show current status
|
|
49
|
+
if (args.length === 0) {
|
|
50
|
+
const status = currentEnabled ? '● Enabled' : '○ Disabled';
|
|
51
|
+
const routingStr = routingStatus.enabled ? '● Active' : '○ Inactive';
|
|
52
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
53
|
+
console.log(`│ Plexor Proxy Status │`);
|
|
54
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
55
|
+
console.log(`│ Plugin Config: ${status.padEnd(27)}│`);
|
|
56
|
+
console.log(`│ Claude Routing: ${routingStr.padEnd(26)}│`);
|
|
57
|
+
if (routingStatus.enabled) {
|
|
58
|
+
console.log(`│ Endpoint: ${(routingStatus.isStaging ? 'Staging' : 'Production').padEnd(32)}│`);
|
|
59
|
+
}
|
|
60
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
61
|
+
console.log(`│ Usage: │`);
|
|
62
|
+
console.log(`│ /plexor-enabled true - Enable proxy │`);
|
|
63
|
+
console.log(`│ /plexor-enabled false - Disable proxy │`);
|
|
64
|
+
console.log(`│ /plexor-enabled on - Enable proxy │`);
|
|
65
|
+
console.log(`│ /plexor-enabled off - Disable proxy │`);
|
|
66
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const arg = args[0].toLowerCase();
|
|
71
|
+
let newEnabled;
|
|
72
|
+
|
|
73
|
+
if (['true', 'on', 'yes', '1', 'enable'].includes(arg)) {
|
|
74
|
+
newEnabled = true;
|
|
75
|
+
} else if (['false', 'off', 'no', '0', 'disable'].includes(arg)) {
|
|
76
|
+
newEnabled = false;
|
|
77
|
+
} else {
|
|
78
|
+
console.error(`Error: Invalid value "${args[0]}"`);
|
|
79
|
+
console.error(`Use: true/false, on/off, yes/no, enable/disable`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Update Plexor plugin config
|
|
84
|
+
config.settings = config.settings || {};
|
|
85
|
+
config.settings.enabled = newEnabled;
|
|
86
|
+
saveConfig(config);
|
|
87
|
+
|
|
88
|
+
// THE KEY FEATURE: Update Claude Code settings.json routing
|
|
89
|
+
let routingUpdated = false;
|
|
90
|
+
if (newEnabled) {
|
|
91
|
+
// Enable routing - need API key from config
|
|
92
|
+
if (apiKey) {
|
|
93
|
+
// STAGING PACKAGE - uses staging API
|
|
94
|
+
const apiUrl = config.settings?.apiUrl || 'https://staging.api.plexor.dev';
|
|
95
|
+
const useStaging = apiUrl.includes('staging');
|
|
96
|
+
routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
97
|
+
} else {
|
|
98
|
+
console.log('⚠ No API key found. Run /plexor-login first.');
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Disable routing - remove env vars from settings.json
|
|
102
|
+
routingUpdated = settingsManager.disablePlexorRouting();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
|
|
106
|
+
const prevStatus = currentEnabled ? 'Enabled' : 'Disabled';
|
|
107
|
+
const routingMsg = routingUpdated
|
|
108
|
+
? (newEnabled ? 'Claude Code now routes through Plexor' : 'Claude Code now connects directly')
|
|
109
|
+
: 'Manual routing update may be needed';
|
|
110
|
+
|
|
111
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
112
|
+
console.log(`│ ✓ Plexor Proxy Updated │`);
|
|
113
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
114
|
+
console.log(`│ Previous: ${prevStatus.padEnd(32)}│`);
|
|
115
|
+
console.log(`│ New: ${newStatus.padEnd(37)}│`);
|
|
116
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
117
|
+
console.log(`│ ${routingMsg.padEnd(42)}│`);
|
|
118
|
+
console.log(`│ Changes take effect immediately. │`);
|
|
119
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enable or disable Plexor proxy (routes all traffic through Plexor API) (user)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Plexor Enabled
|
|
6
|
+
|
|
7
|
+
Run this command to view or toggle the Plexor proxy:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
To enable or disable, pass an argument:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js true
|
|
17
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js false
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use the Bash tool to execute this command.
|
|
21
|
+
|
|
22
|
+
**IMPORTANT**: After running this command and displaying the output, STOP. Do not:
|
|
23
|
+
- Read any files
|
|
24
|
+
- Explore the codebase
|
|
25
|
+
- Run additional commands
|
|
26
|
+
- Ask follow-up questions
|
|
27
|
+
|
|
28
|
+
The command output is the complete response. Simply show the output and wait for the user's next input.
|
|
@@ -0,0 +1,189 @@
|
|
|
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 for automatic Claude Code configuration
|
|
17
|
+
const { settingsManager } = require('../lib/settings-manager');
|
|
18
|
+
|
|
19
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
20
|
+
const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
|
|
21
|
+
// STAGING PACKAGE - uses staging API
|
|
22
|
+
const DEFAULT_API_URL = 'https://staging.api.plexor.dev';
|
|
23
|
+
|
|
24
|
+
function loadConfig() {
|
|
25
|
+
try {
|
|
26
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
27
|
+
return JSON.parse(data);
|
|
28
|
+
} catch {
|
|
29
|
+
return { version: 1, auth: {}, settings: {} };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function saveConfig(config) {
|
|
34
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
35
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
36
|
+
}
|
|
37
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function validateApiKey(apiUrl, apiKey) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const url = new URL(`${apiUrl}/v1/user`);
|
|
43
|
+
const isHttps = url.protocol === 'https:';
|
|
44
|
+
const lib = isHttps ? https : http;
|
|
45
|
+
|
|
46
|
+
const options = {
|
|
47
|
+
hostname: url.hostname,
|
|
48
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
49
|
+
path: url.pathname,
|
|
50
|
+
method: 'GET',
|
|
51
|
+
headers: {
|
|
52
|
+
'X-Plexor-Key': apiKey
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const req = lib.request(options, (res) => {
|
|
57
|
+
let data = '';
|
|
58
|
+
res.on('data', chunk => data += chunk);
|
|
59
|
+
res.on('end', () => {
|
|
60
|
+
if (res.statusCode === 200) {
|
|
61
|
+
try {
|
|
62
|
+
resolve(JSON.parse(data));
|
|
63
|
+
} catch {
|
|
64
|
+
reject(new Error('Invalid response from server'));
|
|
65
|
+
}
|
|
66
|
+
} else if (res.statusCode === 401) {
|
|
67
|
+
reject(new Error('Invalid API key'));
|
|
68
|
+
} else {
|
|
69
|
+
reject(new Error(`Server error: ${res.statusCode}`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
req.on('error', (err) => reject(new Error(`Connection failed: ${err.message}`)));
|
|
75
|
+
req.setTimeout(10000, () => {
|
|
76
|
+
req.destroy();
|
|
77
|
+
reject(new Error('Connection timeout'));
|
|
78
|
+
});
|
|
79
|
+
req.end();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function promptForApiKey() {
|
|
84
|
+
const rl = readline.createInterface({
|
|
85
|
+
input: process.stdin,
|
|
86
|
+
output: process.stdout
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
rl.question('Enter your Plexor API key: ', (answer) => {
|
|
91
|
+
rl.close();
|
|
92
|
+
resolve(answer.trim());
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const args = process.argv.slice(2);
|
|
99
|
+
let apiKey = args[0];
|
|
100
|
+
|
|
101
|
+
// Check for existing login
|
|
102
|
+
const config = loadConfig();
|
|
103
|
+
const existingKey = config.auth?.api_key;
|
|
104
|
+
|
|
105
|
+
if (existingKey && !apiKey) {
|
|
106
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
107
|
+
console.log(`│ Already Logged In │`);
|
|
108
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
109
|
+
console.log(`│ API Key: ${(existingKey.substring(0, 8) + '...').padEnd(33)}│`);
|
|
110
|
+
console.log(`│ To re-login, provide a new key: │`);
|
|
111
|
+
console.log(`│ /plexor-login <api-key> │`);
|
|
112
|
+
console.log(`│ To logout: │`);
|
|
113
|
+
console.log(`│ /plexor-logout │`);
|
|
114
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If no key provided, prompt for it
|
|
119
|
+
if (!apiKey) {
|
|
120
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
121
|
+
console.log(`│ Plexor Login │`);
|
|
122
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
123
|
+
console.log(`│ Get your API key at: │`);
|
|
124
|
+
console.log(`│ https://plexor.dev/dashboard/api-keys │`);
|
|
125
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
126
|
+
console.log(`│ Usage: /plexor-login <api-key> │`);
|
|
127
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Validate key format
|
|
132
|
+
if (!apiKey.startsWith('plx_') || apiKey.length < 20) {
|
|
133
|
+
console.error(`Error: Invalid API key format`);
|
|
134
|
+
console.error(`API keys start with "plx_" and are at least 20 characters`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const apiUrl = config.settings?.apiUrl || DEFAULT_API_URL;
|
|
139
|
+
|
|
140
|
+
console.log('Validating API key...');
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const user = await validateApiKey(apiUrl, apiKey);
|
|
144
|
+
|
|
145
|
+
config.auth = config.auth || {};
|
|
146
|
+
config.auth.api_key = apiKey;
|
|
147
|
+
config.settings = config.settings || {};
|
|
148
|
+
config.settings.enabled = true;
|
|
149
|
+
saveConfig(config);
|
|
150
|
+
|
|
151
|
+
// AUTO-CONFIGURE CLAUDE CODE ROUTING
|
|
152
|
+
// This is the key feature: automatically set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN
|
|
153
|
+
// in ~/.claude/settings.json so ALL Claude Code sessions route through Plexor
|
|
154
|
+
const useStaging = apiUrl.includes('staging');
|
|
155
|
+
const routingEnabled = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
156
|
+
|
|
157
|
+
const email = user.email || 'Unknown';
|
|
158
|
+
const tier = user.tier?.name || 'Free';
|
|
159
|
+
|
|
160
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
161
|
+
console.log(`│ ✓ Login Successful │`);
|
|
162
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
163
|
+
console.log(`│ Email: ${email.substring(0, 35).padEnd(35)}│`);
|
|
164
|
+
console.log(`│ Tier: ${tier.padEnd(36)}│`);
|
|
165
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
166
|
+
if (routingEnabled) {
|
|
167
|
+
console.log(`│ ✓ Claude Code routing: CONFIGURED │`);
|
|
168
|
+
console.log(`│ All sessions now route through Plexor │`);
|
|
169
|
+
} else {
|
|
170
|
+
console.log(`│ ⚠ Claude Code routing: MANUAL SETUP NEEDED │`);
|
|
171
|
+
console.log(`│ Set ANTHROPIC_BASE_URL in environment │`);
|
|
172
|
+
}
|
|
173
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
174
|
+
console.log(`│ Run /plexor-status to see your stats. │`);
|
|
175
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error(`┌─────────────────────────────────────────────┐`);
|
|
178
|
+
console.error(`│ ✗ Login Failed │`);
|
|
179
|
+
console.error(`├─────────────────────────────────────────────┤`);
|
|
180
|
+
console.error(`│ ${err.message.padEnd(42)}│`);
|
|
181
|
+
console.error(`└─────────────────────────────────────────────┘`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main().catch(err => {
|
|
187
|
+
console.error('Error:', err.message);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|