@plexor-dev/claude-code-plugin-staging 0.1.0-beta.1 → 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/README.md +4 -7
- package/commands/plexor-enabled.js +177 -18
- package/commands/plexor-enabled.md +32 -12
- package/commands/plexor-login.js +119 -25
- package/commands/plexor-logout.js +62 -11
- package/commands/plexor-setup.md +4 -4
- package/commands/plexor-status.js +180 -14
- package/commands/plexor-uninstall.js +274 -0
- package/commands/plexor-uninstall.md +30 -0
- package/hooks/intercept.js +2 -2
- package/hooks/track-response.js +2 -2
- package/lib/config.js +1 -1
- package/lib/constants.js +19 -1
- package/lib/settings-manager.js +120 -7
- package/package.json +2 -5
- package/scripts/postinstall.js +96 -5
- package/scripts/uninstall.js +129 -42
- package/commands/plexor-config.js +0 -170
- package/commands/plexor-config.md +0 -28
- package/commands/plexor-mode.js +0 -107
- package/commands/plexor-mode.md +0 -27
- package/commands/plexor-provider.js +0 -110
- package/commands/plexor-provider.md +0 -27
- package/commands/plexor-settings.js +0 -155
- package/commands/plexor-settings.md +0 -28
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
|
|
|
@@ -14,26 +14,108 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
17
|
-
// Import
|
|
18
|
-
const {
|
|
17
|
+
// Import centralized constants with HOME directory validation
|
|
18
|
+
const { PLEXOR_DIR, CONFIG_PATH } = require('../lib/constants');
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
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-staging');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
22
35
|
|
|
23
36
|
function loadConfig() {
|
|
24
37
|
try {
|
|
38
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
39
|
+
return { version: 1, auth: {}, settings: {} };
|
|
40
|
+
}
|
|
25
41
|
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
}
|
|
28
59
|
return { version: 1, auth: {}, settings: {} };
|
|
29
60
|
}
|
|
30
61
|
}
|
|
31
62
|
|
|
32
63
|
function saveConfig(config) {
|
|
33
|
-
|
|
34
|
-
fs.
|
|
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;
|
|
35
85
|
}
|
|
36
|
-
|
|
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;
|
|
37
119
|
}
|
|
38
120
|
|
|
39
121
|
function main() {
|
|
@@ -49,6 +131,10 @@ function main() {
|
|
|
49
131
|
if (args.length === 0) {
|
|
50
132
|
const status = currentEnabled ? '● Enabled' : '○ Disabled';
|
|
51
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
|
+
|
|
52
138
|
console.log(`┌─────────────────────────────────────────────┐`);
|
|
53
139
|
console.log(`│ Plexor Proxy Status │`);
|
|
54
140
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
@@ -57,6 +143,12 @@ function main() {
|
|
|
57
143
|
if (routingStatus.enabled) {
|
|
58
144
|
console.log(`│ Endpoint: ${(routingStatus.isStaging ? 'Staging' : 'Production').padEnd(32)}│`);
|
|
59
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
|
+
}
|
|
60
152
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
61
153
|
console.log(`│ Usage: │`);
|
|
62
154
|
console.log(`│ /plexor-enabled true - Enable proxy │`);
|
|
@@ -80,26 +172,93 @@ function main() {
|
|
|
80
172
|
process.exit(1);
|
|
81
173
|
}
|
|
82
174
|
|
|
83
|
-
// Update Plexor plugin config
|
|
84
|
-
config.settings = config.settings || {};
|
|
85
|
-
config.settings.enabled = newEnabled;
|
|
86
|
-
saveConfig(config);
|
|
87
|
-
|
|
88
175
|
// THE KEY FEATURE: Update Claude Code settings.json routing
|
|
89
176
|
let routingUpdated = false;
|
|
177
|
+
let missingApiKey = false;
|
|
178
|
+
let invalidApiKey = false;
|
|
179
|
+
|
|
90
180
|
if (newEnabled) {
|
|
91
|
-
// Enable routing - need API key from config
|
|
92
|
-
if (apiKey) {
|
|
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
|
+
|
|
93
194
|
// STAGING PACKAGE - uses staging API
|
|
94
195
|
const apiUrl = config.settings?.apiUrl || 'https://staging.api.plexor.dev';
|
|
95
196
|
const useStaging = apiUrl.includes('staging');
|
|
96
197
|
routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
97
|
-
|
|
98
|
-
|
|
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
|
+
}
|
|
99
211
|
}
|
|
100
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
|
+
|
|
101
220
|
// Disable routing - remove env vars from settings.json
|
|
102
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);
|
|
103
262
|
}
|
|
104
263
|
|
|
105
264
|
const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
|
|
@@ -4,25 +4,45 @@ description: Enable or disable Plexor proxy (routes all traffic through Plexor A
|
|
|
4
4
|
|
|
5
5
|
# Plexor Enabled
|
|
6
6
|
|
|
7
|
-
|
|
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:
|
|
8
12
|
|
|
9
13
|
```bash
|
|
10
|
-
node ~/.claude/plugins/plexor/commands/plexor-enabled.js
|
|
14
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js <argument>
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Then display the output and STOP.
|
|
18
|
+
|
|
19
|
+
## If NO argument provided
|
|
20
|
+
|
|
21
|
+
**Step 1**: First get current status by running:
|
|
14
22
|
|
|
15
23
|
```bash
|
|
16
|
-
node ~/.claude/plugins/plexor/commands/plexor-enabled.js
|
|
17
|
-
node ~/.claude/plugins/plexor/commands/plexor-enabled.js false
|
|
24
|
+
node ~/.claude/plugins/plexor/commands/plexor-enabled.js
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
|
|
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
|
|
21
42
|
|
|
22
|
-
**
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- Ask follow-up questions
|
|
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
|
|
27
47
|
|
|
28
|
-
|
|
48
|
+
**IMPORTANT**: After completing, STOP. Do not read files, explore codebase, or run additional commands.
|
package/commands/plexor-login.js
CHANGED
|
@@ -13,28 +13,67 @@ const path = require('path');
|
|
|
13
13
|
const https = require('https');
|
|
14
14
|
const http = require('http');
|
|
15
15
|
|
|
16
|
-
// Import
|
|
17
|
-
const {
|
|
16
|
+
// Import centralized constants with HOME directory validation
|
|
17
|
+
const { PLEXOR_DIR, CONFIG_PATH, DEFAULT_API_URL } = require('../lib/constants');
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
// Import settings manager with error handling for missing lib
|
|
20
|
+
let settingsManager;
|
|
21
|
+
try {
|
|
22
|
+
settingsManager = require('../lib/settings-manager').settingsManager;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err.code === 'MODULE_NOT_FOUND') {
|
|
25
|
+
console.error('Error: Plexor plugin files are missing or corrupted.');
|
|
26
|
+
console.error(' Please reinstall: npm install @plexor-dev/claude-code-plugin-staging');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
function loadConfig() {
|
|
25
33
|
try {
|
|
34
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
35
|
+
return { version: 1, auth: {}, settings: {} };
|
|
36
|
+
}
|
|
26
37
|
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
if (!data || data.trim() === '') {
|
|
39
|
+
return { version: 1, auth: {}, settings: {} };
|
|
40
|
+
}
|
|
41
|
+
const config = JSON.parse(data);
|
|
42
|
+
if (typeof config !== 'object' || config === null) {
|
|
43
|
+
return { version: 1, auth: {}, settings: {} };
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err instanceof SyntaxError) {
|
|
48
|
+
console.warn('Warning: Config file is corrupted, will be overwritten');
|
|
49
|
+
}
|
|
29
50
|
return { version: 1, auth: {}, settings: {} };
|
|
30
51
|
}
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
function saveConfig(config) {
|
|
34
|
-
|
|
35
|
-
fs.
|
|
55
|
+
try {
|
|
56
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
57
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Atomic write: write to temp file, then rename
|
|
61
|
+
const crypto = require('crypto');
|
|
62
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
63
|
+
const tempPath = path.join(PLEXOR_DIR, `.config.${tempId}.tmp`);
|
|
64
|
+
|
|
65
|
+
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
66
|
+
fs.renameSync(tempPath, CONFIG_PATH);
|
|
67
|
+
return true;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
70
|
+
console.error('Error: Cannot write to ~/.plexor/config.json');
|
|
71
|
+
console.error(' Check file permissions or run with appropriate access.');
|
|
72
|
+
} else {
|
|
73
|
+
console.error('Failed to save config:', err.message);
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
36
76
|
}
|
|
37
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
38
77
|
}
|
|
39
78
|
|
|
40
79
|
function validateApiKey(apiUrl, apiKey) {
|
|
@@ -80,23 +119,62 @@ function validateApiKey(apiUrl, apiKey) {
|
|
|
80
119
|
});
|
|
81
120
|
}
|
|
82
121
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Read API key from stdin (for piped input)
|
|
124
|
+
* @returns {Promise<string>} The API key read from stdin
|
|
125
|
+
*/
|
|
126
|
+
function readFromStdin() {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
let data = '';
|
|
129
|
+
const timeout = setTimeout(() => {
|
|
130
|
+
reject(new Error('Timeout reading from stdin'));
|
|
131
|
+
}, 5000);
|
|
88
132
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
133
|
+
process.stdin.setEncoding('utf8');
|
|
134
|
+
process.stdin.on('data', (chunk) => {
|
|
135
|
+
data += chunk;
|
|
136
|
+
});
|
|
137
|
+
process.stdin.on('end', () => {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
resolve(data.trim());
|
|
140
|
+
});
|
|
141
|
+
process.stdin.on('error', (err) => {
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
reject(err);
|
|
93
144
|
});
|
|
145
|
+
process.stdin.resume();
|
|
94
146
|
});
|
|
95
147
|
}
|
|
96
148
|
|
|
97
149
|
async function main() {
|
|
98
150
|
const args = process.argv.slice(2);
|
|
99
|
-
let apiKey =
|
|
151
|
+
let apiKey = null;
|
|
152
|
+
let keySource = null;
|
|
153
|
+
|
|
154
|
+
// SECURITY FIX: Check for API key in order of preference
|
|
155
|
+
// 1. Environment variable (most secure for scripts/CI)
|
|
156
|
+
// 2. Stdin pipe (secure for interactive use)
|
|
157
|
+
// 3. Command line argument (warns about security risk)
|
|
158
|
+
|
|
159
|
+
// Check environment variable first (most secure)
|
|
160
|
+
if (process.env.PLEXOR_API_KEY) {
|
|
161
|
+
apiKey = process.env.PLEXOR_API_KEY;
|
|
162
|
+
keySource = 'environment';
|
|
163
|
+
}
|
|
164
|
+
// Check if stdin has data (piped input)
|
|
165
|
+
else if (!process.stdin.isTTY && args.length === 0) {
|
|
166
|
+
try {
|
|
167
|
+
apiKey = await readFromStdin();
|
|
168
|
+
keySource = 'stdin';
|
|
169
|
+
} catch {
|
|
170
|
+
// Stdin read failed, continue to check args
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Check command line argument (least secure - shows in ps)
|
|
174
|
+
else if (args[0]) {
|
|
175
|
+
apiKey = args[0];
|
|
176
|
+
keySource = 'argument';
|
|
177
|
+
}
|
|
100
178
|
|
|
101
179
|
// Check for existing login
|
|
102
180
|
const config = loadConfig();
|
|
@@ -108,14 +186,14 @@ async function main() {
|
|
|
108
186
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
109
187
|
console.log(`│ API Key: ${(existingKey.substring(0, 8) + '...').padEnd(33)}│`);
|
|
110
188
|
console.log(`│ To re-login, provide a new key: │`);
|
|
111
|
-
console.log(`│ /plexor-login
|
|
189
|
+
console.log(`│ echo $PLEXOR_API_KEY | /plexor-login │`);
|
|
112
190
|
console.log(`│ To logout: │`);
|
|
113
191
|
console.log(`│ /plexor-logout │`);
|
|
114
192
|
console.log(`└─────────────────────────────────────────────┘`);
|
|
115
193
|
return;
|
|
116
194
|
}
|
|
117
195
|
|
|
118
|
-
// If no key provided,
|
|
196
|
+
// If no key provided, show secure usage options
|
|
119
197
|
if (!apiKey) {
|
|
120
198
|
console.log(`┌─────────────────────────────────────────────┐`);
|
|
121
199
|
console.log(`│ Plexor Login │`);
|
|
@@ -123,11 +201,25 @@ async function main() {
|
|
|
123
201
|
console.log(`│ Get your API key at: │`);
|
|
124
202
|
console.log(`│ https://plexor.dev/dashboard/api-keys │`);
|
|
125
203
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
126
|
-
console.log(`│
|
|
204
|
+
console.log(`│ Secure usage (recommended): │`);
|
|
205
|
+
console.log(`│ echo "plx_..." | /plexor-login │`);
|
|
206
|
+
console.log(`│ PLEXOR_API_KEY=plx_... /plexor-login │`);
|
|
207
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
208
|
+
console.log(`│ Direct usage (visible in ps): │`);
|
|
209
|
+
console.log(`│ /plexor-login <api-key> │`);
|
|
127
210
|
console.log(`└─────────────────────────────────────────────┘`);
|
|
128
211
|
return;
|
|
129
212
|
}
|
|
130
213
|
|
|
214
|
+
// Warn if API key was passed as command line argument
|
|
215
|
+
if (keySource === 'argument') {
|
|
216
|
+
console.log(`⚠ Security note: API key passed as argument is visible in`);
|
|
217
|
+
console.log(` process list (ps aux). For better security, use:`);
|
|
218
|
+
console.log(` echo "plx_..." | /plexor-login`);
|
|
219
|
+
console.log(` PLEXOR_API_KEY=plx_... /plexor-login`);
|
|
220
|
+
console.log('');
|
|
221
|
+
}
|
|
222
|
+
|
|
131
223
|
// Validate key format
|
|
132
224
|
if (!apiKey.startsWith('plx_') || apiKey.length < 20) {
|
|
133
225
|
console.error(`Error: Invalid API key format`);
|
|
@@ -146,7 +238,9 @@ async function main() {
|
|
|
146
238
|
config.auth.api_key = apiKey;
|
|
147
239
|
config.settings = config.settings || {};
|
|
148
240
|
config.settings.enabled = true;
|
|
149
|
-
saveConfig(config)
|
|
241
|
+
if (!saveConfig(config)) {
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
150
244
|
|
|
151
245
|
// AUTO-CONFIGURE CLAUDE CODE ROUTING
|
|
152
246
|
// This is the key feature: automatically set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN
|
|
@@ -8,25 +8,68 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const CONFIG_PATH =
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Import centralized constants with HOME directory validation
|
|
12
|
+
const { PLEXOR_DIR, CONFIG_PATH, SESSION_PATH, CACHE_PATH } = require('../lib/constants');
|
|
13
|
+
|
|
14
|
+
// Import settings manager for Claude Code routing cleanup
|
|
15
|
+
let settingsManager;
|
|
16
|
+
try {
|
|
17
|
+
const lib = require('../lib/settings-manager');
|
|
18
|
+
settingsManager = lib.settingsManager;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err.code === 'MODULE_NOT_FOUND') {
|
|
21
|
+
console.error('Error: Plexor plugin files are missing or corrupted.');
|
|
22
|
+
console.error(' Please reinstall: npm install @plexor-dev/claude-code-plugin-staging');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
15
27
|
|
|
16
28
|
function loadConfig() {
|
|
17
29
|
try {
|
|
30
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
18
33
|
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
if (!data || data.trim() === '') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const config = JSON.parse(data);
|
|
38
|
+
if (typeof config !== 'object' || config === null) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return config;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof SyntaxError) {
|
|
44
|
+
console.warn('Warning: Config file is corrupted');
|
|
45
|
+
}
|
|
21
46
|
return null;
|
|
22
47
|
}
|
|
23
48
|
}
|
|
24
49
|
|
|
25
50
|
function saveConfig(config) {
|
|
26
|
-
|
|
27
|
-
fs.
|
|
51
|
+
try {
|
|
52
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
53
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Atomic write: write to temp file, then rename
|
|
57
|
+
const crypto = require('crypto');
|
|
58
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
59
|
+
const tempPath = path.join(PLEXOR_DIR, `.config.${tempId}.tmp`);
|
|
60
|
+
|
|
61
|
+
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
62
|
+
fs.renameSync(tempPath, CONFIG_PATH);
|
|
63
|
+
return true;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
66
|
+
console.error('Error: Cannot write to ~/.plexor/config.json');
|
|
67
|
+
console.error(' Check file permissions or run with appropriate access.');
|
|
68
|
+
} else {
|
|
69
|
+
console.error('Failed to save config:', err.message);
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
28
72
|
}
|
|
29
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
30
73
|
}
|
|
31
74
|
|
|
32
75
|
function deleteFile(filePath) {
|
|
@@ -61,7 +104,14 @@ function main() {
|
|
|
61
104
|
delete config.auth.api_key;
|
|
62
105
|
config.settings = config.settings || {};
|
|
63
106
|
config.settings.enabled = false;
|
|
64
|
-
saveConfig(config)
|
|
107
|
+
if (!saveConfig(config)) {
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// CRITICAL FIX: Disable Claude Code routing in ~/.claude/settings.json
|
|
112
|
+
// This removes ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN so Claude Code
|
|
113
|
+
// no longer tries to route through Plexor with removed credentials
|
|
114
|
+
const routingDisabled = settingsManager.disablePlexorRouting();
|
|
65
115
|
|
|
66
116
|
// Clear session
|
|
67
117
|
deleteFile(SESSION_PATH);
|
|
@@ -76,12 +126,13 @@ function main() {
|
|
|
76
126
|
console.log(`│ ✓ Logged Out │`);
|
|
77
127
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
78
128
|
console.log(`│ ✓ API key removed │`);
|
|
79
|
-
console.log(`│ ✓
|
|
129
|
+
console.log(`│ ${routingDisabled ? '✓' : '○'} Claude Code routing disabled │`);
|
|
80
130
|
console.log(`│ ✓ Session cleared │`);
|
|
81
131
|
if (clearCache) {
|
|
82
132
|
console.log(`│ ${cacheCleared ? '✓' : '○'} Cache cleared │`);
|
|
83
133
|
}
|
|
84
134
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
135
|
+
console.log(`│ Claude Code now connects directly. │`);
|
|
85
136
|
console.log(`│ Run /plexor-login to re-authenticate. │`);
|
|
86
137
|
if (!clearCache) {
|
|
87
138
|
console.log(`│ Use --clear-cache to also clear cache. │`);
|