@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
|
@@ -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');
|
|
@@ -16,6 +32,8 @@ const DEFAULT_API_URL = 'https://staging.api.plexor.dev';
|
|
|
16
32
|
const DEFAULT_TIMEOUT = 5000;
|
|
17
33
|
|
|
18
34
|
module.exports = {
|
|
35
|
+
getHomeDir,
|
|
36
|
+
HOME_DIR,
|
|
19
37
|
PLEXOR_DIR,
|
|
20
38
|
CONFIG_PATH,
|
|
21
39
|
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,15 +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
|
-
|
|
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 {};
|
|
73
|
+
}
|
|
74
|
+
console.warn('Warning: Failed to load Claude settings:', err.message);
|
|
42
75
|
return {};
|
|
43
76
|
}
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
/**
|
|
47
|
-
*
|
|
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
|
|
48
96
|
* @param {Object} settings - settings object to save
|
|
49
97
|
* @returns {boolean} success status
|
|
50
98
|
*/
|
|
@@ -55,10 +103,27 @@ class ClaudeSettingsManager {
|
|
|
55
103
|
fs.mkdirSync(this.claudeDir, { recursive: true });
|
|
56
104
|
}
|
|
57
105
|
|
|
58
|
-
|
|
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
|
+
|
|
59
118
|
return true;
|
|
60
119
|
} catch (err) {
|
|
61
|
-
|
|
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
|
+
}
|
|
62
127
|
return false;
|
|
63
128
|
}
|
|
64
129
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plexor-dev/claude-code-plugin-staging",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.10",
|
|
4
4
|
"description": "STAGING - LLM cost optimization plugin for Claude Code (internal testing)",
|
|
5
5
|
"main": "lib/constants.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"plexor-status": "./commands/plexor-status.js",
|
|
8
|
-
"plexor-mode": "./commands/plexor-mode.js",
|
|
9
8
|
"plexor-enabled": "./commands/plexor-enabled.js",
|
|
10
|
-
"plexor-provider": "./commands/plexor-provider.js",
|
|
11
9
|
"plexor-login": "./commands/plexor-login.js",
|
|
12
|
-
"plexor-logout": "./commands/plexor-logout.js"
|
|
13
|
-
"plexor-settings": "./commands/plexor-settings.js",
|
|
14
|
-
"plexor-config": "./commands/plexor-config.js"
|
|
10
|
+
"plexor-logout": "./commands/plexor-logout.js"
|
|
15
11
|
},
|
|
16
12
|
"scripts": {
|
|
17
13
|
"postinstall": "node scripts/postinstall.js",
|
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
|
+
// STAGING PACKAGE - uses staging API
|
|
79
82
|
const DEFAULT_CONFIG = {
|
|
80
83
|
version: 1,
|
|
81
84
|
auth: {
|
|
@@ -84,7 +87,7 @@ const DEFAULT_CONFIG = {
|
|
|
84
87
|
},
|
|
85
88
|
settings: {
|
|
86
89
|
enabled: true,
|
|
87
|
-
apiUrl: "https://api.plexor.dev",
|
|
90
|
+
apiUrl: "https://staging.api.plexor.dev",
|
|
88
91
|
mode: "balanced",
|
|
89
92
|
localCacheEnabled: true
|
|
90
93
|
}
|
|
@@ -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-staging');
|
|
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}`);
|
|
@@ -218,10 +261,10 @@ function main() {
|
|
|
218
261
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
219
262
|
console.log('');
|
|
220
263
|
console.log(' Available commands:');
|
|
221
|
-
console.log(' /plexor-
|
|
222
|
-
console.log(' /plexor-mode - Switch modes (eco/balanced/quality)');
|
|
264
|
+
console.log(' /plexor-setup - First-time setup wizard');
|
|
223
265
|
console.log(' /plexor-login - Authenticate with API key');
|
|
224
|
-
console.log(' /plexor-
|
|
266
|
+
console.log(' /plexor-status - Check connection and see savings');
|
|
267
|
+
console.log(' /plexor-enabled - Enable/disable Plexor routing');
|
|
225
268
|
console.log('');
|
|
226
269
|
console.log(' Documentation: https://plexor.dev/docs');
|
|
227
270
|
console.log('');
|
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('');
|