@plexor-dev/claude-code-plugin-localhost 0.1.0-beta.11

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 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,114 @@
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-setup` | First-time setup wizard |
36
+ | `/plexor-login` | Authenticate with Plexor API key |
37
+ | `/plexor-logout` | Sign out and clear credentials |
38
+ | `/plexor-status` | View usage stats and savings |
39
+ | `/plexor-enabled` | Enable/disable Plexor routing |
40
+
41
+ ## How It Works
42
+
43
+ Plexor acts as an intelligent gateway between Claude Code and LLM providers:
44
+
45
+ ```
46
+ Claude Code → Plexor Gateway → Best Provider (Anthropic/DeepSeek/Mistral/Gemini/OpenAI)
47
+ ```
48
+
49
+ The gateway:
50
+ - **Optimizes prompts** to reduce token usage
51
+ - **Routes requests** to the most cost-effective provider
52
+ - **Maintains quality** based on your selected mode
53
+ - **Tracks savings** so you can see your ROI
54
+
55
+ ## Optimization Modes
56
+
57
+ | Mode | Savings | Best For |
58
+ |------|---------|----------|
59
+ | **eco** | 60-90% | Development, testing, iteration |
60
+ | **balanced** | 40-60% | Most production workloads |
61
+ | **quality** | 20-40% | Critical responses, customer-facing |
62
+ | **passthrough** | 0% | Debugging, comparison |
63
+
64
+ ## Pricing
65
+
66
+ - **Beta**: $1/month (unlimited requests)
67
+ - Savings typically 10-100x the subscription cost
68
+
69
+ ## Configuration
70
+
71
+ Settings are stored in `~/.plexor/config.json`:
72
+
73
+ ```json
74
+ {
75
+ "version": 1,
76
+ "auth": {
77
+ "api_key": "plx_...",
78
+ "email": "user@example.com"
79
+ },
80
+ "settings": {
81
+ "enabled": true,
82
+ "mode": "balanced",
83
+ "preferred_provider": "auto"
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## Uninstall
89
+
90
+ ```bash
91
+ npm uninstall -g @plexor-dev/claude-code-plugin
92
+ ```
93
+
94
+ To fully remove:
95
+ ```bash
96
+ unset ANTHROPIC_BASE_URL
97
+ rm -rf ~/.plexor
98
+ ```
99
+
100
+ ## Requirements
101
+
102
+ - Node.js 16+
103
+ - Claude Code CLI
104
+ - macOS, Linux, or Windows (WSL)
105
+
106
+ ## Support
107
+
108
+ - **Documentation**: https://plexor.dev/docs
109
+ - **Issues**: https://github.com/plexor-ai/claude-code-plugin/issues
110
+ - **Email**: hello@plexor.dev
111
+
112
+ ## License
113
+
114
+ MIT
@@ -0,0 +1,281 @@
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 centralized constants with HOME directory validation
18
+ const { PLEXOR_DIR, CONFIG_PATH } = require('../lib/constants');
19
+
20
+ // Import settings manager with error handling for missing lib
21
+ let settingsManager, PLEXOR_STAGING_URL, PLEXOR_PROD_URL;
22
+ try {
23
+ const lib = require('../lib/settings-manager');
24
+ settingsManager = lib.settingsManager;
25
+ PLEXOR_STAGING_URL = lib.PLEXOR_STAGING_URL;
26
+ PLEXOR_PROD_URL = lib.PLEXOR_PROD_URL;
27
+ } catch (err) {
28
+ if (err.code === 'MODULE_NOT_FOUND') {
29
+ console.error('Error: Plexor plugin files are missing or corrupted.');
30
+ console.error(' Please reinstall: npm install @plexor-dev/claude-code-plugin-localhost');
31
+ process.exit(1);
32
+ }
33
+ throw err;
34
+ }
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
+ /**
98
+ * Check for state mismatch between config.json enabled flag and settings.json routing
99
+ * @param {boolean} configEnabled - enabled flag from config.json
100
+ * @param {boolean} routingActive - whether settings.json has Plexor routing configured
101
+ * @returns {Object|null} mismatch details or null if states are consistent
102
+ */
103
+ function checkStateMismatch(configEnabled, routingActive) {
104
+ if (configEnabled && !routingActive) {
105
+ return {
106
+ type: 'config-enabled-routing-inactive',
107
+ message: 'Config shows enabled but Claude routing is not configured',
108
+ suggestion: 'Run /plexor-enabled true to sync and configure routing'
109
+ };
110
+ }
111
+ if (!configEnabled && routingActive) {
112
+ return {
113
+ type: 'config-disabled-routing-active',
114
+ message: 'Config shows disabled but Claude routing is active',
115
+ suggestion: 'Run /plexor-enabled false to sync and disable routing'
116
+ };
117
+ }
118
+ return null;
119
+ }
120
+
121
+ function main() {
122
+ const args = process.argv.slice(2);
123
+ const config = loadConfig();
124
+ const currentEnabled = config.settings?.enabled ?? false;
125
+ const apiKey = config.auth?.api_key;
126
+
127
+ // Get current Claude settings.json routing status
128
+ const routingStatus = settingsManager.getRoutingStatus();
129
+
130
+ // No args - show current status
131
+ if (args.length === 0) {
132
+ const status = currentEnabled ? '● Enabled' : '○ Disabled';
133
+ const routingStr = routingStatus.enabled ? '● Active' : '○ Inactive';
134
+
135
+ // Check for state mismatch between config enabled flag and routing status
136
+ const stateMismatch = checkStateMismatch(currentEnabled, routingStatus.enabled);
137
+
138
+ console.log(`┌─────────────────────────────────────────────┐`);
139
+ console.log(`│ Plexor Proxy Status │`);
140
+ console.log(`├─────────────────────────────────────────────┤`);
141
+ console.log(`│ Plugin Config: ${status.padEnd(27)}│`);
142
+ console.log(`│ Claude Routing: ${routingStr.padEnd(26)}│`);
143
+ if (routingStatus.enabled) {
144
+ console.log(`│ Endpoint: ${(routingStatus.isStaging ? 'Staging' : 'Production').padEnd(32)}│`);
145
+ }
146
+ if (stateMismatch) {
147
+ console.log(`├─────────────────────────────────────────────┤`);
148
+ console.log(`│ ⚠ State mismatch detected: │`);
149
+ console.log(`│ ${stateMismatch.message.padEnd(42)}│`);
150
+ console.log(`│ ${stateMismatch.suggestion.padEnd(42)}│`);
151
+ }
152
+ console.log(`├─────────────────────────────────────────────┤`);
153
+ console.log(`│ Usage: │`);
154
+ console.log(`│ /plexor-enabled true - Enable proxy │`);
155
+ console.log(`│ /plexor-enabled false - Disable proxy │`);
156
+ console.log(`│ /plexor-enabled on - Enable proxy │`);
157
+ console.log(`│ /plexor-enabled off - Disable proxy │`);
158
+ console.log(`└─────────────────────────────────────────────┘`);
159
+ return;
160
+ }
161
+
162
+ const arg = args[0].toLowerCase();
163
+ let newEnabled;
164
+
165
+ if (['true', 'on', 'yes', '1', 'enable'].includes(arg)) {
166
+ newEnabled = true;
167
+ } else if (['false', 'off', 'no', '0', 'disable'].includes(arg)) {
168
+ newEnabled = false;
169
+ } else {
170
+ console.error(`Error: Invalid value "${args[0]}"`);
171
+ console.error(`Use: true/false, on/off, yes/no, enable/disable`);
172
+ process.exit(1);
173
+ }
174
+
175
+ // THE KEY FEATURE: Update Claude Code settings.json routing
176
+ let routingUpdated = false;
177
+ let missingApiKey = false;
178
+ let invalidApiKey = false;
179
+
180
+ if (newEnabled) {
181
+ // Enable routing - need valid API key from config
182
+ if (!apiKey) {
183
+ missingApiKey = true;
184
+ } else if (!isValidApiKeyFormat(apiKey)) {
185
+ invalidApiKey = true;
186
+ } else {
187
+ // Update Plexor plugin config first
188
+ config.settings = config.settings || {};
189
+ config.settings.enabled = newEnabled;
190
+ if (!saveConfig(config)) {
191
+ process.exit(1);
192
+ }
193
+
194
+ // LOCALHOST PACKAGE - uses tunnel URL
195
+ const apiUrl = config.settings?.apiUrl || 'LOCALHOST_TUNNEL_URL';
196
+ const useStaging = apiUrl.includes('staging');
197
+ routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
198
+
199
+ // Check if settings.json update failed
200
+ if (!routingUpdated) {
201
+ console.log(`┌─────────────────────────────────────────────┐`);
202
+ console.log(`│ ✗ Failed to Enable Plexor Routing │`);
203
+ console.log(`├─────────────────────────────────────────────┤`);
204
+ console.log(`│ Could not update ~/.claude/settings.json │`);
205
+ console.log(`│ Config.json was updated but routing failed.│`);
206
+ console.log(`├─────────────────────────────────────────────┤`);
207
+ console.log(`│ Check file permissions and try again. │`);
208
+ console.log(`└─────────────────────────────────────────────┘`);
209
+ process.exit(1);
210
+ }
211
+ }
212
+ } else {
213
+ // Update Plexor plugin config
214
+ config.settings = config.settings || {};
215
+ config.settings.enabled = newEnabled;
216
+ if (!saveConfig(config)) {
217
+ process.exit(1);
218
+ }
219
+
220
+ // Disable routing - remove env vars from settings.json
221
+ routingUpdated = settingsManager.disablePlexorRouting();
222
+
223
+ // Check if settings.json update failed
224
+ if (!routingUpdated) {
225
+ console.log(`┌─────────────────────────────────────────────┐`);
226
+ console.log(`│ ✗ Failed to Disable Plexor Routing │`);
227
+ console.log(`├─────────────────────────────────────────────┤`);
228
+ console.log(`│ Could not update ~/.claude/settings.json │`);
229
+ console.log(`│ Config.json was updated but routing failed.│`);
230
+ console.log(`├─────────────────────────────────────────────┤`);
231
+ console.log(`│ Check file permissions and try again. │`);
232
+ console.log(`└─────────────────────────────────────────────┘`);
233
+ process.exit(1);
234
+ }
235
+ }
236
+
237
+ // Show error if no API key when enabling
238
+ if (missingApiKey) {
239
+ console.log(`┌─────────────────────────────────────────────┐`);
240
+ console.log(`│ ✗ Cannot Enable Plexor │`);
241
+ console.log(`├─────────────────────────────────────────────┤`);
242
+ console.log(`│ No API key configured. │`);
243
+ console.log(`│ Run /plexor-login <api-key> first. │`);
244
+ console.log(`├─────────────────────────────────────────────┤`);
245
+ console.log(`│ Get your API key at: │`);
246
+ console.log(`│ https://plexor.dev/dashboard/api-keys │`);
247
+ console.log(`└─────────────────────────────────────────────┘`);
248
+ process.exit(1);
249
+ }
250
+
251
+ // Show error if API key format is invalid
252
+ if (invalidApiKey) {
253
+ console.log(`┌─────────────────────────────────────────────┐`);
254
+ console.log(`│ ✗ Cannot Enable Plexor │`);
255
+ console.log(`├─────────────────────────────────────────────┤`);
256
+ console.log(`│ Invalid API key format in config. │`);
257
+ console.log(`│ Keys must start with "plx_" (20+ chars). │`);
258
+ console.log(`├─────────────────────────────────────────────┤`);
259
+ console.log(`│ Run /plexor-login <api-key> to fix. │`);
260
+ console.log(`└─────────────────────────────────────────────┘`);
261
+ process.exit(1);
262
+ }
263
+
264
+ const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
265
+ const prevStatus = currentEnabled ? 'Enabled' : 'Disabled';
266
+ const routingMsg = routingUpdated
267
+ ? (newEnabled ? 'Claude Code now routes through Plexor' : 'Claude Code now connects directly')
268
+ : 'Manual routing update may be needed';
269
+
270
+ console.log(`┌─────────────────────────────────────────────┐`);
271
+ console.log(`│ ✓ Plexor Proxy Updated │`);
272
+ console.log(`├─────────────────────────────────────────────┤`);
273
+ console.log(`│ Previous: ${prevStatus.padEnd(32)}│`);
274
+ console.log(`│ New: ${newStatus.padEnd(37)}│`);
275
+ console.log(`├─────────────────────────────────────────────┤`);
276
+ console.log(`│ ${routingMsg.padEnd(42)}│`);
277
+ console.log(`│ Changes take effect immediately. │`);
278
+ console.log(`└─────────────────────────────────────────────┘`);
279
+ }
280
+
281
+ main();
@@ -0,0 +1,48 @@
1
+ ---
2
+ description: Enable or disable Plexor proxy (routes all traffic through Plexor API) (user)
3
+ ---
4
+
5
+ # Plexor Enabled
6
+
7
+ Toggle Plexor proxy on or off.
8
+
9
+ ## If argument provided (on/off/true/false)
10
+
11
+ Run the command directly with the argument:
12
+
13
+ ```bash
14
+ node ~/.claude/plugins/plexor/commands/plexor-enabled.js <argument>
15
+ ```
16
+
17
+ Then display the output and STOP.
18
+
19
+ ## If NO argument provided
20
+
21
+ **Step 1**: First get current status by running:
22
+
23
+ ```bash
24
+ node ~/.claude/plugins/plexor/commands/plexor-enabled.js
25
+ ```
26
+
27
+ **Step 2**: Based on the output, use AskUserQuestion to present options:
28
+
29
+ If currently ENABLED (shows "● Enabled" or "● Active"):
30
+ - Question: "Plexor is currently ON. What would you like to do?"
31
+ - Header: "Plexor"
32
+ - Options:
33
+ 1. **Turn OFF** - Disable Plexor routing (connect directly to Anthropic)
34
+ 2. **Keep ON** - No change
35
+
36
+ If currently DISABLED (shows "○ Disabled" or "○ Inactive"):
37
+ - Question: "Plexor is currently OFF. What would you like to do?"
38
+ - Header: "Plexor"
39
+ - Options:
40
+ 1. **Turn ON** - Enable Plexor routing
41
+ 2. **Keep OFF** - No change
42
+
43
+ **Step 3**: Based on user selection:
44
+ - If "Turn OFF" selected: Run `node ~/.claude/plugins/plexor/commands/plexor-enabled.js false`
45
+ - If "Turn ON" selected: Run `node ~/.claude/plugins/plexor/commands/plexor-enabled.js true`
46
+ - If "Keep" selected: Do nothing, just confirm current state
47
+
48
+ **IMPORTANT**: After completing, STOP. Do not read files, explore codebase, or run additional commands.