@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/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,84 @@ 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
|
+
const backupPath = this._backupCorruptedFile();
|
|
53
|
+
console.warn('');
|
|
54
|
+
console.warn('WARNING: Claude settings file has invalid format!');
|
|
55
|
+
if (backupPath) {
|
|
56
|
+
console.warn(` Corrupted file backed up to: ${backupPath}`);
|
|
57
|
+
}
|
|
58
|
+
console.warn(' Using default settings. Your previous settings may need manual recovery.');
|
|
59
|
+
console.warn('');
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return parsed;
|
|
40
64
|
} catch (err) {
|
|
41
|
-
|
|
65
|
+
if (err.code === 'ENOENT') {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
// JSON parse error or corrupted file
|
|
69
|
+
if (err instanceof SyntaxError) {
|
|
70
|
+
const backupPath = this._backupCorruptedFile();
|
|
71
|
+
console.warn('');
|
|
72
|
+
console.warn('WARNING: Claude settings file is corrupted (invalid JSON)!');
|
|
73
|
+
if (backupPath) {
|
|
74
|
+
console.warn(` Corrupted file backed up to: ${backupPath}`);
|
|
75
|
+
}
|
|
76
|
+
console.warn(' Using default settings. Your previous settings may need manual recovery.');
|
|
77
|
+
console.warn('');
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
// Permission error
|
|
81
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
82
|
+
console.warn(`Warning: Cannot read ${this.settingsPath} (permission denied)`);
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
console.warn('Warning: Failed to load Claude settings:', err.message);
|
|
42
86
|
return {};
|
|
43
87
|
}
|
|
44
88
|
}
|
|
45
89
|
|
|
46
90
|
/**
|
|
47
|
-
*
|
|
91
|
+
* Backup a corrupted settings file with numbered suffix for debugging
|
|
92
|
+
* Creates settings.json.corrupted.1, .corrupted.2, etc. to preserve history
|
|
93
|
+
* @returns {string|null} path to backup file, or null if backup failed
|
|
94
|
+
*/
|
|
95
|
+
_backupCorruptedFile() {
|
|
96
|
+
try {
|
|
97
|
+
if (fs.existsSync(this.settingsPath)) {
|
|
98
|
+
// Find next available numbered backup
|
|
99
|
+
let backupNum = 1;
|
|
100
|
+
let backupPath;
|
|
101
|
+
while (true) {
|
|
102
|
+
backupPath = `${this.settingsPath}.corrupted.${backupNum}`;
|
|
103
|
+
if (!fs.existsSync(backupPath)) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
backupNum++;
|
|
107
|
+
}
|
|
108
|
+
fs.copyFileSync(this.settingsPath, backupPath);
|
|
109
|
+
return backupPath;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore backup errors silently
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Save Claude settings using atomic write pattern
|
|
119
|
+
* Prevents race conditions by writing to temp file then renaming
|
|
48
120
|
* @param {Object} settings - settings object to save
|
|
49
121
|
* @returns {boolean} success status
|
|
50
122
|
*/
|
|
@@ -55,10 +127,27 @@ class ClaudeSettingsManager {
|
|
|
55
127
|
fs.mkdirSync(this.claudeDir, { recursive: true });
|
|
56
128
|
}
|
|
57
129
|
|
|
58
|
-
|
|
130
|
+
// Atomic write: write to temp file, then rename
|
|
131
|
+
// This prevents race conditions where concurrent writes corrupt the file
|
|
132
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
133
|
+
const tempPath = path.join(this.claudeDir, `.settings.${tempId}.tmp`);
|
|
134
|
+
|
|
135
|
+
// Write to temp file
|
|
136
|
+
const content = JSON.stringify(settings, null, 2);
|
|
137
|
+
fs.writeFileSync(tempPath, content, { mode: 0o600 });
|
|
138
|
+
|
|
139
|
+
// Atomic rename (on POSIX systems, rename is atomic)
|
|
140
|
+
fs.renameSync(tempPath, this.settingsPath);
|
|
141
|
+
|
|
59
142
|
return true;
|
|
60
143
|
} catch (err) {
|
|
61
|
-
|
|
144
|
+
// Clean error message for permission errors
|
|
145
|
+
if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
146
|
+
console.error(`Error: Cannot write to ${this.settingsPath}`);
|
|
147
|
+
console.error(' Check file permissions or run with appropriate access.');
|
|
148
|
+
} else {
|
|
149
|
+
console.error('Failed to save Claude settings:', err.message);
|
|
150
|
+
}
|
|
62
151
|
return false;
|
|
63
152
|
}
|
|
64
153
|
}
|
|
@@ -177,6 +266,30 @@ class ClaudeSettingsManager {
|
|
|
177
266
|
}
|
|
178
267
|
}
|
|
179
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Detect partial routing state where URL points to Plexor but auth is missing/invalid
|
|
271
|
+
* This can cause confusing auth errors for users
|
|
272
|
+
* @returns {Object} { partial: boolean, issue: string|null }
|
|
273
|
+
*/
|
|
274
|
+
detectPartialState() {
|
|
275
|
+
try {
|
|
276
|
+
const settings = this.load();
|
|
277
|
+
const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
278
|
+
const authToken = settings.env?.ANTHROPIC_AUTH_TOKEN || '';
|
|
279
|
+
const isPlexorUrl = baseUrl.includes('plexor') || baseUrl.includes('staging.api');
|
|
280
|
+
|
|
281
|
+
if (isPlexorUrl && !authToken) {
|
|
282
|
+
return { partial: true, issue: 'Plexor URL set but no auth token' };
|
|
283
|
+
}
|
|
284
|
+
if (isPlexorUrl && !authToken.startsWith('plx_')) {
|
|
285
|
+
return { partial: true, issue: 'Plexor URL set but auth token is not a Plexor key' };
|
|
286
|
+
}
|
|
287
|
+
return { partial: false, issue: null };
|
|
288
|
+
} catch {
|
|
289
|
+
return { partial: false, issue: null };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
180
293
|
/**
|
|
181
294
|
* Update just the API key without changing other settings
|
|
182
295
|
* @param {string} apiKey - new Plexor API key
|
package/package.json
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plexor-dev/claude-code-plugin-staging",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.11",
|
|
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
10
|
"plexor-logout": "./commands/plexor-logout.js",
|
|
13
|
-
"plexor-
|
|
14
|
-
"plexor-config": "./commands/plexor-config.js"
|
|
11
|
+
"plexor-uninstall": "./commands/plexor-uninstall.js"
|
|
15
12
|
},
|
|
16
13
|
"scripts": {
|
|
17
14
|
"postinstall": "node scripts/postinstall.js",
|
package/scripts/postinstall.js
CHANGED
|
@@ -70,12 +70,60 @@ 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
|
|
|
80
|
+
/**
|
|
81
|
+
* Check for orphaned Plexor routing in settings.json without valid config.
|
|
82
|
+
* This can happen if a previous uninstall was incomplete.
|
|
83
|
+
*/
|
|
84
|
+
function checkOrphanedRouting() {
|
|
85
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
86
|
+
if (!home) return;
|
|
87
|
+
|
|
88
|
+
const settingsPath = path.join(home, '.claude', 'settings.json');
|
|
89
|
+
const configPath = path.join(home, '.plexor', 'config.json');
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
if (!fs.existsSync(settingsPath)) return;
|
|
93
|
+
|
|
94
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
95
|
+
const env = settings.env || {};
|
|
96
|
+
|
|
97
|
+
const hasPlexorUrl = env.ANTHROPIC_BASE_URL &&
|
|
98
|
+
env.ANTHROPIC_BASE_URL.includes('plexor');
|
|
99
|
+
|
|
100
|
+
if (hasPlexorUrl) {
|
|
101
|
+
// Check if there's a valid Plexor config
|
|
102
|
+
let hasValidConfig = false;
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(configPath)) {
|
|
105
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
106
|
+
hasValidConfig = config.apiKey && config.apiKey.startsWith('plx_');
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {}
|
|
109
|
+
|
|
110
|
+
if (!hasValidConfig) {
|
|
111
|
+
console.log('\n Warning: Detected orphaned Plexor routing in Claude settings');
|
|
112
|
+
console.log(' This may be from a previous installation.\n');
|
|
113
|
+
console.log(' Run /plexor-login to reconfigure, or');
|
|
114
|
+
console.log(' Run /plexor-uninstall to clean up\n');
|
|
115
|
+
} else {
|
|
116
|
+
console.log('\n Existing Plexor configuration detected');
|
|
117
|
+
console.log(' Your previous settings have been preserved.\n');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
// Ignore errors in detection - don't break install
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
78
125
|
// Default configuration for new installs
|
|
126
|
+
// STAGING PACKAGE - uses staging API
|
|
79
127
|
const DEFAULT_CONFIG = {
|
|
80
128
|
version: 1,
|
|
81
129
|
auth: {
|
|
@@ -84,13 +132,16 @@ const DEFAULT_CONFIG = {
|
|
|
84
132
|
},
|
|
85
133
|
settings: {
|
|
86
134
|
enabled: true,
|
|
87
|
-
apiUrl: "https://api.plexor.dev",
|
|
135
|
+
apiUrl: "https://staging.api.plexor.dev",
|
|
88
136
|
mode: "balanced",
|
|
89
137
|
localCacheEnabled: true
|
|
90
138
|
}
|
|
91
139
|
};
|
|
92
140
|
|
|
93
141
|
function main() {
|
|
142
|
+
// Check for orphaned routing at start of postinstall
|
|
143
|
+
checkOrphanedRouting();
|
|
144
|
+
|
|
94
145
|
try {
|
|
95
146
|
// Get target user info for chown (if running with sudo)
|
|
96
147
|
const targetUser = getTargetUserIds();
|
|
@@ -101,6 +152,9 @@ function main() {
|
|
|
101
152
|
// Create ~/.claude/plugins/plexor/commands/ for JS executors
|
|
102
153
|
fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
|
|
103
154
|
|
|
155
|
+
// Create ~/.claude/plugins/plexor/lib/ for shared modules
|
|
156
|
+
fs.mkdirSync(PLEXOR_LIB_DIR, { recursive: true });
|
|
157
|
+
|
|
104
158
|
// Create ~/.plexor/ with secure permissions (owner only)
|
|
105
159
|
fs.mkdirSync(PLEXOR_CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
106
160
|
|
|
@@ -161,6 +215,40 @@ function main() {
|
|
|
161
215
|
jsInstalled.push(file);
|
|
162
216
|
}
|
|
163
217
|
|
|
218
|
+
// Copy lib files to ~/.claude/plugins/plexor/lib/
|
|
219
|
+
// CRITICAL: These are required for commands to work
|
|
220
|
+
const libInstalled = [];
|
|
221
|
+
if (fs.existsSync(LIB_SOURCE)) {
|
|
222
|
+
const libFiles = fs.readdirSync(LIB_SOURCE).filter(f => f.endsWith('.js'));
|
|
223
|
+
if (libFiles.length === 0) {
|
|
224
|
+
console.warn(' ⚠ Warning: No lib files found in package. Commands may not work.');
|
|
225
|
+
}
|
|
226
|
+
for (const file of libFiles) {
|
|
227
|
+
try {
|
|
228
|
+
const src = path.join(LIB_SOURCE, file);
|
|
229
|
+
const dest = path.join(PLEXOR_LIB_DIR, file);
|
|
230
|
+
fs.copyFileSync(src, dest);
|
|
231
|
+
libInstalled.push(file);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
console.error(` ✗ Failed to copy lib/${file}: ${err.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
console.error(' ✗ CRITICAL: lib/ directory not found in package.');
|
|
238
|
+
console.error(' Commands will fail. Please reinstall the package.');
|
|
239
|
+
console.error(` Expected location: ${LIB_SOURCE}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Verify critical lib file exists
|
|
243
|
+
const criticalLibFile = path.join(PLEXOR_LIB_DIR, 'settings-manager.js');
|
|
244
|
+
if (!fs.existsSync(criticalLibFile)) {
|
|
245
|
+
console.error('');
|
|
246
|
+
console.error(' ✗ CRITICAL: settings-manager.js was not installed.');
|
|
247
|
+
console.error(' This file is required for commands to work.');
|
|
248
|
+
console.error(' Try reinstalling: npm install @plexor-dev/claude-code-plugin-staging');
|
|
249
|
+
console.error('');
|
|
250
|
+
}
|
|
251
|
+
|
|
164
252
|
// Fix file ownership when running with sudo
|
|
165
253
|
// Files are created as root but should be owned by the original user
|
|
166
254
|
if (targetUser) {
|
|
@@ -190,7 +278,10 @@ function main() {
|
|
|
190
278
|
}
|
|
191
279
|
console.log(` ✓ Installed ${installed.length} slash commands to ~/.claude/commands/`);
|
|
192
280
|
if (jsInstalled.length > 0) {
|
|
193
|
-
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/`);
|
|
281
|
+
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/commands/`);
|
|
282
|
+
}
|
|
283
|
+
if (libInstalled.length > 0) {
|
|
284
|
+
console.log(` ✓ Installed ${libInstalled.length} lib modules to ~/.claude/plugins/plexor/lib/`);
|
|
194
285
|
}
|
|
195
286
|
if (targetUser) {
|
|
196
287
|
console.log(` ✓ Set file ownership to ${targetUser.user}`);
|
|
@@ -218,10 +309,10 @@ function main() {
|
|
|
218
309
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
219
310
|
console.log('');
|
|
220
311
|
console.log(' Available commands:');
|
|
221
|
-
console.log(' /plexor-
|
|
222
|
-
console.log(' /plexor-mode - Switch modes (eco/balanced/quality)');
|
|
312
|
+
console.log(' /plexor-setup - First-time setup wizard');
|
|
223
313
|
console.log(' /plexor-login - Authenticate with API key');
|
|
224
|
-
console.log(' /plexor-
|
|
314
|
+
console.log(' /plexor-status - Check connection and see savings');
|
|
315
|
+
console.log(' /plexor-enabled - Enable/disable Plexor routing');
|
|
225
316
|
console.log('');
|
|
226
317
|
console.log(' Documentation: https://plexor.dev/docs');
|
|
227
318
|
console.log('');
|
package/scripts/uninstall.js
CHANGED
|
@@ -1,67 +1,154 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Plexor Claude Code Plugin - Uninstall Script
|
|
4
|
+
* Plexor Claude Code Plugin (Staging) - Comprehensive Uninstall Script
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Runs on npm uninstall (when npm actually calls it).
|
|
7
|
+
* Also callable directly: node scripts/uninstall.js
|
|
8
|
+
*
|
|
9
|
+
* Performs complete cleanup:
|
|
10
|
+
* 1. Removes Plexor routing from ~/.claude/settings.json
|
|
11
|
+
* 2. Removes slash command files from ~/.claude/commands/
|
|
12
|
+
* 3. Removes plugin directory from ~/.claude/plugins/plexor/
|
|
13
|
+
* 4. Restores any backups if they exist
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
16
|
const fs = require('fs');
|
|
11
17
|
const path = require('path');
|
|
12
|
-
const os = require('os');
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
const
|
|
19
|
+
// Get home directory - support both Unix and Windows
|
|
20
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
21
|
+
if (!home) {
|
|
22
|
+
console.log('Warning: HOME not set, skipping cleanup');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log('');
|
|
27
|
+
console.log(' Plexor plugin cleanup...');
|
|
28
|
+
console.log('');
|
|
29
|
+
|
|
30
|
+
const results = {
|
|
31
|
+
routing: false,
|
|
32
|
+
commands: [],
|
|
33
|
+
restored: [],
|
|
34
|
+
pluginDir: false
|
|
35
|
+
};
|
|
16
36
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
// 1. Remove routing from settings.json
|
|
38
|
+
// This is CRITICAL - do NOT depend on settings-manager module since it may not load during uninstall
|
|
39
|
+
try {
|
|
40
|
+
const settingsPath = path.join(home, '.claude', 'settings.json');
|
|
41
|
+
if (fs.existsSync(settingsPath)) {
|
|
42
|
+
const data = fs.readFileSync(settingsPath, 'utf8');
|
|
43
|
+
if (data && data.trim()) {
|
|
44
|
+
const settings = JSON.parse(data);
|
|
45
|
+
if (settings.env) {
|
|
46
|
+
const hadBaseUrl = !!settings.env.ANTHROPIC_BASE_URL;
|
|
47
|
+
const hadAuthToken = !!settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
48
|
+
|
|
49
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
50
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
51
|
+
|
|
52
|
+
// Clean up empty env block
|
|
53
|
+
if (Object.keys(settings.env).length === 0) {
|
|
54
|
+
delete settings.env;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (hadBaseUrl || hadAuthToken) {
|
|
58
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
59
|
+
results.routing = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.log(` Warning: Could not clean settings.json: ${e.message}`);
|
|
66
|
+
}
|
|
22
67
|
|
|
23
|
-
|
|
24
|
-
|
|
68
|
+
// 2. Remove slash command files
|
|
69
|
+
// These are the Plexor-specific command files that get installed to ~/.claude/commands/
|
|
70
|
+
const plexorCommands = [
|
|
71
|
+
'plexor-config.md',
|
|
72
|
+
'plexor-enabled.md',
|
|
73
|
+
'plexor-login.md',
|
|
74
|
+
'plexor-logout.md',
|
|
75
|
+
'plexor-mode.md',
|
|
76
|
+
'plexor-provider.md',
|
|
77
|
+
'plexor-settings.md',
|
|
78
|
+
'plexor-setup.md',
|
|
79
|
+
'plexor-status.md',
|
|
80
|
+
'plexor-uninstall.md'
|
|
81
|
+
];
|
|
25
82
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
83
|
+
try {
|
|
84
|
+
const commandsDir = path.join(home, '.claude', 'commands');
|
|
85
|
+
if (fs.existsSync(commandsDir)) {
|
|
86
|
+
for (const cmd of plexorCommands) {
|
|
87
|
+
const cmdPath = path.join(commandsDir, cmd);
|
|
88
|
+
const backupPath = cmdPath + '.backup';
|
|
29
89
|
|
|
30
|
-
if (fs.existsSync(
|
|
31
|
-
fs.unlinkSync(
|
|
32
|
-
|
|
90
|
+
if (fs.existsSync(cmdPath)) {
|
|
91
|
+
fs.unlinkSync(cmdPath);
|
|
92
|
+
results.commands.push(cmd.replace('.md', ''));
|
|
33
93
|
|
|
34
94
|
// Restore backup if it exists
|
|
35
95
|
if (fs.existsSync(backupPath)) {
|
|
36
|
-
fs.renameSync(backupPath,
|
|
37
|
-
restored.push(
|
|
96
|
+
fs.renameSync(backupPath, cmdPath);
|
|
97
|
+
results.restored.push(cmd);
|
|
38
98
|
}
|
|
39
99
|
}
|
|
40
100
|
}
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.log(` Warning: Could not clean commands: ${e.message}`);
|
|
104
|
+
}
|
|
41
105
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
restored.forEach(f => console.log(` ${f}`));
|
|
53
|
-
}
|
|
106
|
+
// 3. Remove plugin directory
|
|
107
|
+
try {
|
|
108
|
+
const pluginDir = path.join(home, '.claude', 'plugins', 'plexor');
|
|
109
|
+
if (fs.existsSync(pluginDir)) {
|
|
110
|
+
fs.rmSync(pluginDir, { recursive: true, force: true });
|
|
111
|
+
results.pluginDir = true;
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.log(` Warning: Could not remove plugin directory: ${e.message}`);
|
|
115
|
+
}
|
|
54
116
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
117
|
+
// Output results
|
|
118
|
+
if (results.routing || results.commands.length > 0 || results.pluginDir) {
|
|
119
|
+
console.log(' Plexor plugin uninstalled');
|
|
120
|
+
console.log('');
|
|
60
121
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.
|
|
122
|
+
if (results.routing) {
|
|
123
|
+
console.log(' Removed Plexor routing from Claude settings');
|
|
124
|
+
console.log(' (Claude Code now connects directly to Anthropic)');
|
|
125
|
+
console.log('');
|
|
64
126
|
}
|
|
127
|
+
|
|
128
|
+
if (results.commands.length > 0) {
|
|
129
|
+
console.log(' Removed commands:');
|
|
130
|
+
results.commands.forEach(cmd => console.log(` /${cmd}`));
|
|
131
|
+
console.log('');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (results.restored.length > 0) {
|
|
135
|
+
console.log(' Restored from backup:');
|
|
136
|
+
results.restored.forEach(f => console.log(` ${f}`));
|
|
137
|
+
console.log('');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (results.pluginDir) {
|
|
141
|
+
console.log(' Removed plugin directory');
|
|
142
|
+
console.log('');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(' Note: ~/.plexor/ config directory was preserved.');
|
|
146
|
+
console.log(' To remove it: rm -rf ~/.plexor');
|
|
147
|
+
console.log('');
|
|
148
|
+
} else {
|
|
149
|
+
console.log(' No Plexor components found to clean up.');
|
|
150
|
+
console.log('');
|
|
65
151
|
}
|
|
66
152
|
|
|
67
|
-
|
|
153
|
+
console.log(' Cleanup complete');
|
|
154
|
+
console.log('');
|