@plexor-dev/claude-code-plugin-staging 0.1.0-beta.1 → 0.1.0-beta.10
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 +118 -19
- 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 +117 -14
- package/lib/constants.js +19 -1
- package/lib/settings-manager.js +72 -7
- package/package.json +2 -6
- package/scripts/postinstall.js +48 -5
- package/scripts/uninstall.js +33 -3
- 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,84 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
17
|
-
// Import
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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-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;
|
|
37
95
|
}
|
|
38
96
|
|
|
39
97
|
function main() {
|
|
@@ -80,28 +138,69 @@ function main() {
|
|
|
80
138
|
process.exit(1);
|
|
81
139
|
}
|
|
82
140
|
|
|
83
|
-
// Update Plexor plugin config
|
|
84
|
-
config.settings = config.settings || {};
|
|
85
|
-
config.settings.enabled = newEnabled;
|
|
86
|
-
saveConfig(config);
|
|
87
|
-
|
|
88
141
|
// THE KEY FEATURE: Update Claude Code settings.json routing
|
|
89
142
|
let routingUpdated = false;
|
|
143
|
+
let missingApiKey = false;
|
|
144
|
+
let invalidApiKey = false;
|
|
145
|
+
|
|
90
146
|
if (newEnabled) {
|
|
91
|
-
// Enable routing - need API key from config
|
|
92
|
-
if (apiKey) {
|
|
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
|
+
|
|
93
160
|
// STAGING PACKAGE - uses staging API
|
|
94
161
|
const apiUrl = config.settings?.apiUrl || 'https://staging.api.plexor.dev';
|
|
95
162
|
const useStaging = apiUrl.includes('staging');
|
|
96
163
|
routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
97
|
-
} else {
|
|
98
|
-
console.log('⚠ No API key found. Run /plexor-login first.');
|
|
99
164
|
}
|
|
100
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
|
+
|
|
101
173
|
// Disable routing - remove env vars from settings.json
|
|
102
174
|
routingUpdated = settingsManager.disablePlexorRouting();
|
|
103
175
|
}
|
|
104
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
|
+
|
|
105
204
|
const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
|
|
106
205
|
const prevStatus = currentEnabled ? 'Enabled' : 'Disabled';
|
|
107
206
|
const routingMsg = routingUpdated
|
|
@@ -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. │`);
|
package/commands/plexor-setup.md
CHANGED
|
@@ -61,7 +61,7 @@ If user selected "Yes, I have Claude MAX":
|
|
|
61
61
|
},
|
|
62
62
|
"settings": {
|
|
63
63
|
"enabled": true,
|
|
64
|
-
"apiUrl": "https://api.plexor.dev",
|
|
64
|
+
"apiUrl": "https://staging.api.plexor.dev",
|
|
65
65
|
"mode": "balanced",
|
|
66
66
|
"localCacheEnabled": true
|
|
67
67
|
}
|
|
@@ -72,7 +72,7 @@ If user selected "Yes, I have Claude MAX":
|
|
|
72
72
|
```json
|
|
73
73
|
{
|
|
74
74
|
"env": {
|
|
75
|
-
"ANTHROPIC_BASE_URL": "https://api.plexor.dev/gateway/anthropic",
|
|
75
|
+
"ANTHROPIC_BASE_URL": "https://staging.api.plexor.dev/gateway/anthropic",
|
|
76
76
|
"ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -122,7 +122,7 @@ If user selected "No, I'll use a Plexor API key":
|
|
|
122
122
|
},
|
|
123
123
|
"settings": {
|
|
124
124
|
"enabled": true,
|
|
125
|
-
"apiUrl": "https://api.plexor.dev",
|
|
125
|
+
"apiUrl": "https://staging.api.plexor.dev",
|
|
126
126
|
"preferred_provider": "auto",
|
|
127
127
|
"mode": "balanced",
|
|
128
128
|
"localCacheEnabled": true
|
|
@@ -134,7 +134,7 @@ If user selected "No, I'll use a Plexor API key":
|
|
|
134
134
|
```json
|
|
135
135
|
{
|
|
136
136
|
"env": {
|
|
137
|
-
"ANTHROPIC_BASE_URL": "https://api.plexor.dev/gateway/anthropic",
|
|
137
|
+
"ANTHROPIC_BASE_URL": "https://staging.api.plexor.dev/gateway/anthropic",
|
|
138
138
|
"ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]"
|
|
139
139
|
}
|
|
140
140
|
}
|