@plexor-dev/claude-code-plugin 0.1.0-beta.30 → 0.1.0-beta.32
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/commands/plexor-enabled.js +119 -19
- package/commands/plexor-login.js +124 -15
- package/commands/plexor-logout.js +62 -11
- package/commands/plexor-status.js +117 -14
- package/lib/constants.js +19 -1
- package/lib/settings-manager.js +72 -10
- package/package.json +1 -1
- package/scripts/postinstall.js +44 -1
- package/scripts/uninstall.js +33 -3
|
@@ -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');
|
|
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,27 +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
|
+
|
|
160
|
+
// PRODUCTION PACKAGE - uses production API
|
|
93
161
|
const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
|
|
94
162
|
const useStaging = apiUrl.includes('staging');
|
|
95
163
|
routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
|
|
96
|
-
} else {
|
|
97
|
-
console.log('⚠ No API key found. Run /plexor-login first.');
|
|
98
164
|
}
|
|
99
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
|
+
|
|
100
173
|
// Disable routing - remove env vars from settings.json
|
|
101
174
|
routingUpdated = settingsManager.disablePlexorRouting();
|
|
102
175
|
}
|
|
103
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
|
+
|
|
104
204
|
const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
|
|
105
205
|
const prevStatus = currentEnabled ? 'Enabled' : 'Disabled';
|
|
106
206
|
const routingMsg = routingUpdated
|
package/commands/plexor-login.js
CHANGED
|
@@ -13,27 +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
|
-
|
|
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');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
function loadConfig() {
|
|
24
33
|
try {
|
|
34
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
35
|
+
return { version: 1, auth: {}, settings: {} };
|
|
36
|
+
}
|
|
25
37
|
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
}
|
|
28
50
|
return { version: 1, auth: {}, settings: {} };
|
|
29
51
|
}
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
function saveConfig(config) {
|
|
33
|
-
|
|
34
|
-
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;
|
|
35
76
|
}
|
|
36
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
37
77
|
}
|
|
38
78
|
|
|
39
79
|
function validateApiKey(apiUrl, apiKey) {
|
|
@@ -79,9 +119,62 @@ function validateApiKey(apiUrl, apiKey) {
|
|
|
79
119
|
});
|
|
80
120
|
}
|
|
81
121
|
|
|
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);
|
|
132
|
+
|
|
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);
|
|
144
|
+
});
|
|
145
|
+
process.stdin.resume();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
82
149
|
async function main() {
|
|
83
150
|
const args = process.argv.slice(2);
|
|
84
|
-
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
|
+
}
|
|
85
178
|
|
|
86
179
|
// Check for existing login
|
|
87
180
|
const config = loadConfig();
|
|
@@ -93,14 +186,14 @@ async function main() {
|
|
|
93
186
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
94
187
|
console.log(`│ API Key: ${(existingKey.substring(0, 8) + '...').padEnd(33)}│`);
|
|
95
188
|
console.log(`│ To re-login, provide a new key: │`);
|
|
96
|
-
console.log(`│ /plexor-login
|
|
189
|
+
console.log(`│ echo $PLEXOR_API_KEY | /plexor-login │`);
|
|
97
190
|
console.log(`│ To logout: │`);
|
|
98
191
|
console.log(`│ /plexor-logout │`);
|
|
99
192
|
console.log(`└─────────────────────────────────────────────┘`);
|
|
100
193
|
return;
|
|
101
194
|
}
|
|
102
195
|
|
|
103
|
-
// If no key provided,
|
|
196
|
+
// If no key provided, show secure usage options
|
|
104
197
|
if (!apiKey) {
|
|
105
198
|
console.log(`┌─────────────────────────────────────────────┐`);
|
|
106
199
|
console.log(`│ Plexor Login │`);
|
|
@@ -108,11 +201,25 @@ async function main() {
|
|
|
108
201
|
console.log(`│ Get your API key at: │`);
|
|
109
202
|
console.log(`│ https://plexor.dev/dashboard/api-keys │`);
|
|
110
203
|
console.log(`├─────────────────────────────────────────────┤`);
|
|
111
|
-
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> │`);
|
|
112
210
|
console.log(`└─────────────────────────────────────────────┘`);
|
|
113
211
|
return;
|
|
114
212
|
}
|
|
115
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
|
+
|
|
116
223
|
// Validate key format
|
|
117
224
|
if (!apiKey.startsWith('plx_') || apiKey.length < 20) {
|
|
118
225
|
console.error(`Error: Invalid API key format`);
|
|
@@ -131,7 +238,9 @@ async function main() {
|
|
|
131
238
|
config.auth.api_key = apiKey;
|
|
132
239
|
config.settings = config.settings || {};
|
|
133
240
|
config.settings.enabled = true;
|
|
134
|
-
saveConfig(config)
|
|
241
|
+
if (!saveConfig(config)) {
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
135
244
|
|
|
136
245
|
// AUTO-CONFIGURE CLAUDE CODE ROUTING
|
|
137
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');
|
|
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. │`);
|
|
@@ -9,10 +9,9 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const https = require('https');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const CLAUDE_SETTINGS_PATH = path.join(
|
|
15
|
-
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
12
|
+
// Import centralized constants with HOME directory validation
|
|
13
|
+
const { HOME_DIR, CONFIG_PATH, SESSION_PATH, SESSION_TIMEOUT_MS } = require('../lib/constants');
|
|
14
|
+
const CLAUDE_SETTINGS_PATH = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* Check if Claude Code is actually routing through Plexor
|
|
@@ -49,13 +48,63 @@ function loadSessionStats() {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Validate API key format
|
|
53
|
+
* @param {string} key - API key to validate
|
|
54
|
+
* @returns {boolean} true if valid format
|
|
55
|
+
*/
|
|
56
|
+
function isValidApiKeyFormat(key) {
|
|
57
|
+
return key && typeof key === 'string' && key.startsWith('plx_') && key.length >= 20;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Load config file with integrity checking
|
|
62
|
+
* @returns {Object|null} config object or null if invalid
|
|
63
|
+
*/
|
|
64
|
+
function loadConfig() {
|
|
55
65
|
try {
|
|
66
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
56
69
|
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
57
|
-
|
|
70
|
+
if (!data || data.trim() === '') {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const config = JSON.parse(data);
|
|
74
|
+
if (typeof config !== 'object' || config === null) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return config;
|
|
58
78
|
} catch (err) {
|
|
79
|
+
if (err instanceof SyntaxError) {
|
|
80
|
+
console.log('Config file is corrupted. Run /plexor-login to reconfigure.');
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check for environment mismatch between config and routing
|
|
88
|
+
*/
|
|
89
|
+
function checkEnvironmentMismatch(configApiUrl, routingBaseUrl) {
|
|
90
|
+
if (!configApiUrl || !routingBaseUrl) return null;
|
|
91
|
+
|
|
92
|
+
const configIsStaging = configApiUrl.includes('staging');
|
|
93
|
+
const routingIsStaging = routingBaseUrl.includes('staging');
|
|
94
|
+
|
|
95
|
+
if (configIsStaging !== routingIsStaging) {
|
|
96
|
+
return {
|
|
97
|
+
config: configIsStaging ? 'staging' : 'production',
|
|
98
|
+
routing: routingIsStaging ? 'staging' : 'production'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
// Read config with integrity checking
|
|
106
|
+
const config = loadConfig();
|
|
107
|
+
if (!config) {
|
|
59
108
|
console.log('Not configured. Run /plexor-login first.');
|
|
60
109
|
process.exit(1);
|
|
61
110
|
}
|
|
@@ -72,6 +121,13 @@ async function main() {
|
|
|
72
121
|
process.exit(1);
|
|
73
122
|
}
|
|
74
123
|
|
|
124
|
+
// Validate API key format
|
|
125
|
+
if (!isValidApiKeyFormat(apiKey)) {
|
|
126
|
+
console.log('Invalid API key format. Keys must start with "plx_" and be at least 20 characters.');
|
|
127
|
+
console.log('Run /plexor-login with a valid API key.');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
75
131
|
// Fetch user info and stats
|
|
76
132
|
let user = { email: 'Unknown', tier: { name: 'Free', limits: {} } };
|
|
77
133
|
let stats = { period: {}, summary: {} };
|
|
@@ -165,6 +221,16 @@ ${line(`└── Cost saved: $${sessionCostSaved}`)}
|
|
|
165
221
|
const routingIndicator = routing.active ? '🟢 PLEXOR MODE: ON' : '🔴 PLEXOR MODE: OFF';
|
|
166
222
|
const envLabel = routing.isStaging ? '(staging)' : '(production)';
|
|
167
223
|
|
|
224
|
+
// Check for environment mismatch
|
|
225
|
+
const envMismatch = checkEnvironmentMismatch(apiUrl, routing.baseUrl);
|
|
226
|
+
const mismatchWarning = envMismatch
|
|
227
|
+
? ` ⚠ Warning: Config uses ${envMismatch.config} but routing is ${envMismatch.routing}\n`
|
|
228
|
+
: '';
|
|
229
|
+
|
|
230
|
+
if (mismatchWarning) {
|
|
231
|
+
console.log(mismatchWarning);
|
|
232
|
+
}
|
|
233
|
+
|
|
168
234
|
console.log(` ┌─────────────────────────────────────────────┐
|
|
169
235
|
${line(routingIndicator + (routing.active ? ' ' + envLabel : ''))}
|
|
170
236
|
├─────────────────────────────────────────────┤
|
|
@@ -192,7 +258,8 @@ ${line('Settings')}
|
|
|
192
258
|
${line(`├── Optimization: ${optEnabled}`)}
|
|
193
259
|
${line(`├── Local cache: ${cacheEnabled}`)}
|
|
194
260
|
${line(`├── Mode: ${mode}`)}
|
|
195
|
-
${line(
|
|
261
|
+
${line(`├── Provider routing: ${provider}`)}
|
|
262
|
+
${line(`└── Endpoint: ${routing.baseUrl ? routing.baseUrl.replace('https://', '').substring(0, 30) : 'not configured'}`)}
|
|
196
263
|
└─────────────────────────────────────────────┘
|
|
197
264
|
|
|
198
265
|
Dashboard: ${dashboardUrl}
|
|
@@ -201,7 +268,13 @@ ${line(`└── Provider routing: ${provider}`)}
|
|
|
201
268
|
|
|
202
269
|
function fetchJson(apiUrl, endpoint, apiKey) {
|
|
203
270
|
return new Promise((resolve, reject) => {
|
|
204
|
-
|
|
271
|
+
let url;
|
|
272
|
+
try {
|
|
273
|
+
url = new URL(`${apiUrl}${endpoint}`);
|
|
274
|
+
} catch {
|
|
275
|
+
reject(new Error('Invalid API URL'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
205
278
|
|
|
206
279
|
const options = {
|
|
207
280
|
hostname: url.hostname,
|
|
@@ -217,18 +290,48 @@ function fetchJson(apiUrl, endpoint, apiKey) {
|
|
|
217
290
|
let data = '';
|
|
218
291
|
res.on('data', chunk => data += chunk);
|
|
219
292
|
res.on('end', () => {
|
|
293
|
+
// Check HTTP status code first
|
|
294
|
+
if (res.statusCode === 401) {
|
|
295
|
+
reject(new Error('Invalid API key'));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (res.statusCode === 403) {
|
|
299
|
+
reject(new Error('Access denied'));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (res.statusCode >= 500) {
|
|
303
|
+
reject(new Error('Server error'));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (res.statusCode !== 200) {
|
|
307
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check for empty response
|
|
312
|
+
if (!data || data.trim() === '') {
|
|
313
|
+
reject(new Error('Empty response'));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Parse JSON
|
|
220
318
|
try {
|
|
221
|
-
|
|
319
|
+
const parsed = JSON.parse(data);
|
|
320
|
+
if (parsed === null) {
|
|
321
|
+
reject(new Error('Null response'));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
resolve(parsed);
|
|
222
325
|
} catch {
|
|
223
|
-
reject(new Error('Invalid response'));
|
|
326
|
+
reject(new Error('Invalid JSON response'));
|
|
224
327
|
}
|
|
225
328
|
});
|
|
226
329
|
});
|
|
227
330
|
|
|
228
|
-
req.on('error', reject);
|
|
331
|
+
req.on('error', (err) => reject(new Error(`Connection failed: ${err.message}`)));
|
|
229
332
|
req.setTimeout(5000, () => {
|
|
230
333
|
req.destroy();
|
|
231
|
-
reject(new Error('
|
|
334
|
+
reject(new Error('Request timeout'));
|
|
232
335
|
});
|
|
233
336
|
req.end();
|
|
234
337
|
});
|
package/lib/constants.js
CHANGED
|
@@ -4,7 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Get the user's home directory with proper validation
|
|
9
|
+
* @returns {string} Home directory path
|
|
10
|
+
* @throws {Error} If HOME is not set or empty
|
|
11
|
+
*/
|
|
12
|
+
function getHomeDir() {
|
|
13
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
14
|
+
if (!home || home.trim() === '') {
|
|
15
|
+
console.error('Error: HOME environment variable is not set.');
|
|
16
|
+
console.error(' Please set HOME to your user directory before running Plexor commands.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
return home;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const HOME_DIR = getHomeDir();
|
|
23
|
+
const PLEXOR_DIR = path.join(HOME_DIR, '.plexor');
|
|
8
24
|
const CONFIG_PATH = path.join(PLEXOR_DIR, 'config.json');
|
|
9
25
|
const SESSION_PATH = path.join(PLEXOR_DIR, 'session.json');
|
|
10
26
|
const CACHE_PATH = path.join(PLEXOR_DIR, 'cache.json');
|
|
@@ -15,6 +31,8 @@ const DEFAULT_API_URL = 'https://api.plexor.dev';
|
|
|
15
31
|
const DEFAULT_TIMEOUT = 5000;
|
|
16
32
|
|
|
17
33
|
module.exports = {
|
|
34
|
+
getHomeDir,
|
|
35
|
+
HOME_DIR,
|
|
18
36
|
PLEXOR_DIR,
|
|
19
37
|
CONFIG_PATH,
|
|
20
38
|
SESSION_PATH,
|
package/lib/settings-manager.js
CHANGED
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const crypto = require('crypto');
|
|
15
17
|
|
|
16
18
|
const CLAUDE_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude');
|
|
17
19
|
const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
|
|
20
|
+
const LOCK_TIMEOUT_MS = 5000; // 5 second lock timeout
|
|
18
21
|
|
|
19
22
|
// Plexor gateway endpoints
|
|
20
23
|
const PLEXOR_STAGING_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
|
|
@@ -27,8 +30,8 @@ class ClaudeSettingsManager {
|
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
|
-
* Load current Claude settings
|
|
31
|
-
* @returns {Object} settings object or empty object if not found
|
|
33
|
+
* Load current Claude settings with integrity checking
|
|
34
|
+
* @returns {Object} settings object or empty object if not found/corrupted
|
|
32
35
|
*/
|
|
33
36
|
load() {
|
|
34
37
|
try {
|
|
@@ -36,18 +39,60 @@ class ClaudeSettingsManager {
|
|
|
36
39
|
return {};
|
|
37
40
|
}
|
|
38
41
|
const data = fs.readFileSync(this.settingsPath, 'utf8');
|
|
39
|
-
|
|
42
|
+
|
|
43
|
+
// Check for empty file
|
|
44
|
+
if (!data || data.trim() === '') {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const parsed = JSON.parse(data);
|
|
49
|
+
|
|
50
|
+
// Basic schema validation - must be an object
|
|
51
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
52
|
+
console.warn('Warning: Claude settings file has invalid format, using defaults');
|
|
53
|
+
this._backupCorruptedFile();
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parsed;
|
|
40
58
|
} catch (err) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
if (err.code === 'ENOENT') {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
// JSON parse error or corrupted file
|
|
63
|
+
if (err instanceof SyntaxError) {
|
|
64
|
+
console.warn('Warning: Claude settings file is corrupted, using defaults');
|
|
65
|
+
console.warn(' A backup has been saved to settings.json.corrupted');
|
|
66
|
+
this._backupCorruptedFile();
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
// Permission error
|
|
70
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
71
|
+
console.warn(`Warning: Cannot read ${this.settingsPath} (permission denied)`);
|
|
72
|
+
return {};
|
|
44
73
|
}
|
|
74
|
+
console.warn('Warning: Failed to load Claude settings:', err.message);
|
|
45
75
|
return {};
|
|
46
76
|
}
|
|
47
77
|
}
|
|
48
78
|
|
|
49
79
|
/**
|
|
50
|
-
*
|
|
80
|
+
* Backup a corrupted settings file for debugging
|
|
81
|
+
*/
|
|
82
|
+
_backupCorruptedFile() {
|
|
83
|
+
try {
|
|
84
|
+
if (fs.existsSync(this.settingsPath)) {
|
|
85
|
+
const backupPath = this.settingsPath + '.corrupted';
|
|
86
|
+
fs.copyFileSync(this.settingsPath, backupPath);
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// Ignore backup errors
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Save Claude settings using atomic write pattern
|
|
95
|
+
* Prevents race conditions by writing to temp file then renaming
|
|
51
96
|
* @param {Object} settings - settings object to save
|
|
52
97
|
* @returns {boolean} success status
|
|
53
98
|
*/
|
|
@@ -58,10 +103,27 @@ class ClaudeSettingsManager {
|
|
|
58
103
|
fs.mkdirSync(this.claudeDir, { recursive: true });
|
|
59
104
|
}
|
|
60
105
|
|
|
61
|
-
|
|
106
|
+
// Atomic write: write to temp file, then rename
|
|
107
|
+
// This prevents race conditions where concurrent writes corrupt the file
|
|
108
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
109
|
+
const tempPath = path.join(this.claudeDir, `.settings.${tempId}.tmp`);
|
|
110
|
+
|
|
111
|
+
// Write to temp file
|
|
112
|
+
const content = JSON.stringify(settings, null, 2);
|
|
113
|
+
fs.writeFileSync(tempPath, content, { mode: 0o600 });
|
|
114
|
+
|
|
115
|
+
// Atomic rename (on POSIX systems, rename is atomic)
|
|
116
|
+
fs.renameSync(tempPath, this.settingsPath);
|
|
117
|
+
|
|
62
118
|
return true;
|
|
63
119
|
} catch (err) {
|
|
64
|
-
|
|
120
|
+
// Clean error message for permission errors
|
|
121
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
122
|
+
console.error(`Error: Cannot write to ${this.settingsPath}`);
|
|
123
|
+
console.error(' Check file permissions or run with appropriate access.');
|
|
124
|
+
} else {
|
|
125
|
+
console.error('Failed to save Claude settings:', err.message);
|
|
126
|
+
}
|
|
65
127
|
return false;
|
|
66
128
|
}
|
|
67
129
|
}
|
|
@@ -77,7 +139,7 @@ class ClaudeSettingsManager {
|
|
|
77
139
|
* @returns {boolean} success status
|
|
78
140
|
*/
|
|
79
141
|
enablePlexorRouting(apiKey, options = {}) {
|
|
80
|
-
//
|
|
142
|
+
// PRODUCTION PACKAGE - defaults to production API
|
|
81
143
|
const { useStaging = false } = options;
|
|
82
144
|
const apiUrl = useStaging ? PLEXOR_STAGING_URL : PLEXOR_PROD_URL;
|
|
83
145
|
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -70,12 +70,15 @@ function chownRecursive(dirPath, uid, gid) {
|
|
|
70
70
|
|
|
71
71
|
const HOME_DIR = getHomeDir();
|
|
72
72
|
const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
|
|
73
|
+
const LIB_SOURCE = path.join(__dirname, '..', 'lib');
|
|
73
74
|
const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands');
|
|
74
75
|
const PLEXOR_PLUGINS_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'commands');
|
|
76
|
+
const PLEXOR_LIB_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'lib');
|
|
75
77
|
const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
|
|
76
78
|
const PLEXOR_CONFIG_FILE = path.join(PLEXOR_CONFIG_DIR, 'config.json');
|
|
77
79
|
|
|
78
80
|
// Default configuration for new installs
|
|
81
|
+
// PRODUCTION PACKAGE - uses production API
|
|
79
82
|
const DEFAULT_CONFIG = {
|
|
80
83
|
version: 1,
|
|
81
84
|
auth: {
|
|
@@ -101,6 +104,9 @@ function main() {
|
|
|
101
104
|
// Create ~/.claude/plugins/plexor/commands/ for JS executors
|
|
102
105
|
fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
|
|
103
106
|
|
|
107
|
+
// Create ~/.claude/plugins/plexor/lib/ for shared modules
|
|
108
|
+
fs.mkdirSync(PLEXOR_LIB_DIR, { recursive: true });
|
|
109
|
+
|
|
104
110
|
// Create ~/.plexor/ with secure permissions (owner only)
|
|
105
111
|
fs.mkdirSync(PLEXOR_CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
106
112
|
|
|
@@ -161,6 +167,40 @@ function main() {
|
|
|
161
167
|
jsInstalled.push(file);
|
|
162
168
|
}
|
|
163
169
|
|
|
170
|
+
// Copy lib files to ~/.claude/plugins/plexor/lib/
|
|
171
|
+
// CRITICAL: These are required for commands to work
|
|
172
|
+
const libInstalled = [];
|
|
173
|
+
if (fs.existsSync(LIB_SOURCE)) {
|
|
174
|
+
const libFiles = fs.readdirSync(LIB_SOURCE).filter(f => f.endsWith('.js'));
|
|
175
|
+
if (libFiles.length === 0) {
|
|
176
|
+
console.warn(' ⚠ Warning: No lib files found in package. Commands may not work.');
|
|
177
|
+
}
|
|
178
|
+
for (const file of libFiles) {
|
|
179
|
+
try {
|
|
180
|
+
const src = path.join(LIB_SOURCE, file);
|
|
181
|
+
const dest = path.join(PLEXOR_LIB_DIR, file);
|
|
182
|
+
fs.copyFileSync(src, dest);
|
|
183
|
+
libInstalled.push(file);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.error(` ✗ Failed to copy lib/${file}: ${err.message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
console.error(' ✗ CRITICAL: lib/ directory not found in package.');
|
|
190
|
+
console.error(' Commands will fail. Please reinstall the package.');
|
|
191
|
+
console.error(` Expected location: ${LIB_SOURCE}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Verify critical lib file exists
|
|
195
|
+
const criticalLibFile = path.join(PLEXOR_LIB_DIR, 'settings-manager.js');
|
|
196
|
+
if (!fs.existsSync(criticalLibFile)) {
|
|
197
|
+
console.error('');
|
|
198
|
+
console.error(' ✗ CRITICAL: settings-manager.js was not installed.');
|
|
199
|
+
console.error(' This file is required for commands to work.');
|
|
200
|
+
console.error(' Try reinstalling: npm install @plexor-dev/claude-code-plugin');
|
|
201
|
+
console.error('');
|
|
202
|
+
}
|
|
203
|
+
|
|
164
204
|
// Fix file ownership when running with sudo
|
|
165
205
|
// Files are created as root but should be owned by the original user
|
|
166
206
|
if (targetUser) {
|
|
@@ -190,7 +230,10 @@ function main() {
|
|
|
190
230
|
}
|
|
191
231
|
console.log(` ✓ Installed ${installed.length} slash commands to ~/.claude/commands/`);
|
|
192
232
|
if (jsInstalled.length > 0) {
|
|
193
|
-
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/`);
|
|
233
|
+
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/commands/`);
|
|
234
|
+
}
|
|
235
|
+
if (libInstalled.length > 0) {
|
|
236
|
+
console.log(` ✓ Installed ${libInstalled.length} lib modules to ~/.claude/plugins/plexor/lib/`);
|
|
194
237
|
}
|
|
195
238
|
if (targetUser) {
|
|
196
239
|
console.log(` ✓ Set file ownership to ${targetUser.user}`);
|
package/scripts/uninstall.js
CHANGED
|
@@ -11,11 +11,32 @@ const fs = require('fs');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const os = require('os');
|
|
13
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 settings manager can't be loaded during uninstall, continue anyway
|
|
21
|
+
settingsManager = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
|
|
15
25
|
const CLAUDE_COMMANDS_DIR = path.join(os.homedir(), '.claude', 'commands');
|
|
16
26
|
|
|
17
27
|
function main() {
|
|
18
28
|
try {
|
|
29
|
+
// CRITICAL: Disable Claude Code routing before removing commands
|
|
30
|
+
// This ensures users don't get stuck with Plexor routing after uninstall
|
|
31
|
+
let routingDisabled = false;
|
|
32
|
+
if (settingsManager) {
|
|
33
|
+
try {
|
|
34
|
+
routingDisabled = settingsManager.disablePlexorRouting();
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Continue with uninstall even if routing cleanup fails
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
19
40
|
// Get list of our command files
|
|
20
41
|
const files = fs.readdirSync(COMMANDS_SOURCE)
|
|
21
42
|
.filter(f => f.endsWith('.md'));
|
|
@@ -39,12 +60,21 @@ function main() {
|
|
|
39
60
|
}
|
|
40
61
|
}
|
|
41
62
|
|
|
42
|
-
if (removed.length > 0) {
|
|
63
|
+
if (removed.length > 0 || routingDisabled) {
|
|
43
64
|
console.log('');
|
|
44
65
|
console.log(' Plexor plugin uninstalled');
|
|
45
66
|
console.log('');
|
|
46
|
-
|
|
47
|
-
|
|
67
|
+
|
|
68
|
+
if (routingDisabled) {
|
|
69
|
+
console.log(' ✓ Claude Code routing disabled');
|
|
70
|
+
console.log(' (Claude Code now connects directly to Anthropic)');
|
|
71
|
+
console.log('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (removed.length > 0) {
|
|
75
|
+
console.log(' Removed commands:');
|
|
76
|
+
removed.forEach(cmd => console.log(` /${cmd}`));
|
|
77
|
+
}
|
|
48
78
|
|
|
49
79
|
if (restored.length > 0) {
|
|
50
80
|
console.log('');
|