@plexor-dev/claude-code-plugin-staging 0.1.0-beta.2 → 0.1.0-beta.20
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-agent.js +84 -0
- package/commands/plexor-agent.md +36 -0
- package/commands/plexor-enabled.js +177 -18
- package/commands/plexor-enabled.md +31 -13
- package/commands/plexor-login.js +211 -42
- package/commands/plexor-login.md +4 -21
- package/commands/plexor-logout.js +72 -14
- package/commands/plexor-logout.md +2 -20
- package/commands/plexor-provider.js +62 -81
- package/commands/plexor-provider.md +23 -13
- package/commands/plexor-routing.js +77 -0
- package/commands/plexor-routing.md +37 -0
- package/commands/plexor-settings.js +161 -123
- package/commands/plexor-settings.md +38 -14
- package/commands/plexor-setup.js +253 -0
- package/commands/plexor-setup.md +16 -160
- package/commands/plexor-status.js +244 -18
- package/commands/plexor-status.md +1 -13
- package/commands/plexor-uninstall.js +319 -0
- package/commands/plexor-uninstall.md +12 -0
- package/hooks/intercept.js +211 -32
- package/hooks/track-response.js +302 -2
- package/lib/config-utils.js +314 -0
- package/lib/config.js +22 -3
- package/lib/constants.js +19 -1
- package/lib/logger.js +64 -5
- package/lib/settings-manager.js +233 -24
- package/lib/verify-route.js +77 -0
- package/package.json +6 -4
- package/scripts/postinstall.js +271 -44
- package/scripts/uninstall.js +194 -41
- 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/scripts/postinstall.js
CHANGED
|
@@ -13,42 +13,109 @@ const os = require('os');
|
|
|
13
13
|
const { execSync } = require('child_process');
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Resolve the home directory for a given username by querying /etc/passwd.
|
|
17
|
+
* This is the authoritative source and handles non-standard home paths
|
|
18
|
+
* (e.g., /root, /opt/users/foo, NIS/LDAP users, etc.).
|
|
19
|
+
* Returns null if lookup fails (Windows, missing getent, etc.).
|
|
20
|
+
*/
|
|
21
|
+
function getHomeDirFromPasswd(username) {
|
|
22
|
+
try {
|
|
23
|
+
const entry = execSync(`getent passwd ${username}`, { encoding: 'utf8' }).trim();
|
|
24
|
+
// Format: username:x:uid:gid:gecos:homedir:shell
|
|
25
|
+
const fields = entry.split(':');
|
|
26
|
+
if (fields.length >= 6 && fields[5]) {
|
|
27
|
+
return fields[5];
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// getent not available or user not found
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the correct home directory for the process's effective user.
|
|
37
|
+
*
|
|
38
|
+
* Handles three scenarios:
|
|
39
|
+
* 1. Normal execution: HOME is correct, os.homedir() is correct.
|
|
40
|
+
* 2. `sudo npm install`: SUDO_USER is set, os.homedir() returns /root,
|
|
41
|
+
* but we want the SUDO_USER's home.
|
|
42
|
+
* 3. `sudo -u target npm install`:
|
|
43
|
+
* HOME may still be the *caller's* home (e.g.,
|
|
44
|
+
* /home/azureuser), SUDO_USER is the *caller*
|
|
45
|
+
* (not the target), but process.getuid() returns
|
|
46
|
+
* the *target* UID. We must resolve home from
|
|
47
|
+
* /etc/passwd by UID.
|
|
48
|
+
*
|
|
49
|
+
* Resolution order (most authoritative first):
|
|
50
|
+
* a) Look up the effective UID in /etc/passwd via getent (handles sudo -u)
|
|
51
|
+
* b) Fall back to os.homedir() (works for normal execution)
|
|
52
|
+
* c) Fall back to HOME / USERPROFILE env vars (last resort)
|
|
19
53
|
*/
|
|
20
54
|
function getHomeDir() {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
// On non-Windows, resolve via the effective UID's passwd entry.
|
|
56
|
+
// This is the most reliable method and correctly handles both
|
|
57
|
+
// `sudo` and `sudo -u <target>` scenarios.
|
|
58
|
+
if (os.platform() !== 'win32') {
|
|
59
|
+
try {
|
|
60
|
+
const uid = process.getuid();
|
|
61
|
+
const entry = execSync(`getent passwd ${uid}`, { encoding: 'utf8' }).trim();
|
|
62
|
+
const fields = entry.split(':');
|
|
63
|
+
if (fields.length >= 6 && fields[5]) {
|
|
64
|
+
return fields[5];
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Fall through to other methods
|
|
29
68
|
}
|
|
30
69
|
}
|
|
31
|
-
|
|
70
|
+
|
|
71
|
+
// Fallback: os.homedir() (reads HOME env var, then passwd on Unix)
|
|
72
|
+
const home = os.homedir();
|
|
73
|
+
if (home) return home;
|
|
74
|
+
|
|
75
|
+
// Last resort: environment variables
|
|
76
|
+
return process.env.HOME || process.env.USERPROFILE || '/tmp';
|
|
32
77
|
}
|
|
33
78
|
|
|
34
79
|
/**
|
|
35
|
-
* Get uid/gid for the
|
|
36
|
-
*
|
|
80
|
+
* Get uid/gid for the effective user running this process.
|
|
81
|
+
* Under `sudo`, the effective user is root but we want to chown to the
|
|
82
|
+
* original (SUDO_USER) or target (`sudo -u target`) user.
|
|
83
|
+
* Under `sudo -u target`, process.getuid() IS the target, so we use that.
|
|
84
|
+
* Returns null on Windows or if no privilege elevation detected.
|
|
37
85
|
*/
|
|
38
86
|
function getTargetUserIds() {
|
|
39
|
-
|
|
40
|
-
if (!sudoUser || os.platform() === 'win32') {
|
|
87
|
+
if (os.platform() === 'win32') {
|
|
41
88
|
return null;
|
|
42
89
|
}
|
|
43
90
|
|
|
44
91
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
92
|
+
const effectiveUid = process.getuid();
|
|
93
|
+
|
|
94
|
+
// If we're running as root (uid 0), we were likely invoked via `sudo`.
|
|
95
|
+
// Chown files to SUDO_USER (the human who ran sudo).
|
|
96
|
+
if (effectiveUid === 0 && process.env.SUDO_USER) {
|
|
97
|
+
const uid = parseInt(execSync(`id -u ${process.env.SUDO_USER}`, { encoding: 'utf8' }).trim(), 10);
|
|
98
|
+
const gid = parseInt(execSync(`id -g ${process.env.SUDO_USER}`, { encoding: 'utf8' }).trim(), 10);
|
|
99
|
+
return { uid, gid, user: process.env.SUDO_USER };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If we're NOT root but SUDO_USER is set, we were invoked via `sudo -u target`.
|
|
103
|
+
// The effective UID is already the target user. Chown to that user.
|
|
104
|
+
if (effectiveUid !== 0 && process.env.SUDO_USER) {
|
|
105
|
+
const entry = execSync(`getent passwd ${effectiveUid}`, { encoding: 'utf8' }).trim();
|
|
106
|
+
const fields = entry.split(':');
|
|
107
|
+
if (fields.length >= 4) {
|
|
108
|
+
const username = fields[0];
|
|
109
|
+
const uid = parseInt(fields[2], 10);
|
|
110
|
+
const gid = parseInt(fields[3], 10);
|
|
111
|
+
return { uid, gid, user: username };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
49
114
|
} catch {
|
|
50
|
-
|
|
115
|
+
// Fall through
|
|
51
116
|
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
52
119
|
}
|
|
53
120
|
|
|
54
121
|
/**
|
|
@@ -70,12 +137,139 @@ function chownRecursive(dirPath, uid, gid) {
|
|
|
70
137
|
|
|
71
138
|
const HOME_DIR = getHomeDir();
|
|
72
139
|
const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
|
|
140
|
+
const LIB_SOURCE = path.join(__dirname, '..', 'lib');
|
|
73
141
|
const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands');
|
|
74
142
|
const PLEXOR_PLUGINS_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'commands');
|
|
143
|
+
const PLEXOR_LIB_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'lib');
|
|
75
144
|
const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
|
|
76
145
|
const PLEXOR_CONFIG_FILE = path.join(PLEXOR_CONFIG_DIR, 'config.json');
|
|
77
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Check if a base URL is a Plexor-managed gateway URL.
|
|
149
|
+
* Detects all variants: production, staging, localhost, tunnels.
|
|
150
|
+
*/
|
|
151
|
+
function isManagedGatewayUrl(baseUrl) {
|
|
152
|
+
if (!baseUrl) return false;
|
|
153
|
+
return (
|
|
154
|
+
baseUrl.includes('plexor') ||
|
|
155
|
+
baseUrl.includes('staging.api') ||
|
|
156
|
+
baseUrl.includes('localhost') ||
|
|
157
|
+
baseUrl.includes('127.0.0.1') ||
|
|
158
|
+
baseUrl.includes('ngrok') ||
|
|
159
|
+
baseUrl.includes('localtunnel')
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The expected base URL for THIS plugin variant.
|
|
165
|
+
* Used to detect when a different variant was previously installed.
|
|
166
|
+
*/
|
|
167
|
+
const THIS_VARIANT_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
|
|
168
|
+
const PREVIOUS_API_KEY_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_API_KEY';
|
|
169
|
+
const PREVIOUS_AUTH_TOKEN_ENV = 'PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN';
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check for orphaned Plexor routing in settings.json without valid config.
|
|
173
|
+
* Also detects variant mismatch (e.g., localhost plugin was installed, now
|
|
174
|
+
* installing staging plugin) and migrates ANTHROPIC_BASE_URL + syncs
|
|
175
|
+
* Claude auth env vars for Plexor-managed gateways.
|
|
176
|
+
*/
|
|
177
|
+
function selectManagedAuthKey(env = {}) {
|
|
178
|
+
const apiKey = env.ANTHROPIC_API_KEY || '';
|
|
179
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
180
|
+
|
|
181
|
+
if (apiKey.startsWith('plx_')) return apiKey;
|
|
182
|
+
if (authToken.startsWith('plx_')) return authToken;
|
|
183
|
+
return apiKey || authToken || '';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function syncManagedAuthEnv(env, managedAuthKey) {
|
|
187
|
+
const currentApiKey = env.ANTHROPIC_API_KEY || '';
|
|
188
|
+
const currentAuthToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
189
|
+
|
|
190
|
+
if (currentApiKey && !currentApiKey.startsWith('plx_') && currentApiKey !== managedAuthKey) {
|
|
191
|
+
env[PREVIOUS_API_KEY_ENV] = currentApiKey;
|
|
192
|
+
}
|
|
193
|
+
if (currentAuthToken && !currentAuthToken.startsWith('plx_') && currentAuthToken !== managedAuthKey) {
|
|
194
|
+
env[PREVIOUS_AUTH_TOKEN_ENV] = currentAuthToken;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
env.ANTHROPIC_API_KEY = managedAuthKey;
|
|
198
|
+
env.ANTHROPIC_AUTH_TOKEN = managedAuthKey;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function checkOrphanedRouting() {
|
|
202
|
+
// Use the resolved HOME_DIR (not process.env.HOME which may be wrong under sudo -u)
|
|
203
|
+
const settingsPath = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
204
|
+
const configPath = path.join(HOME_DIR, '.plexor', 'config.json');
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
if (!fs.existsSync(settingsPath)) return;
|
|
208
|
+
|
|
209
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
210
|
+
const env = settings.env || {};
|
|
211
|
+
let settingsChanged = false;
|
|
212
|
+
|
|
213
|
+
const hasPlexorUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL);
|
|
214
|
+
|
|
215
|
+
if (hasPlexorUrl) {
|
|
216
|
+
// Keep both Claude auth env vars aligned to the Plexor key so Claude API
|
|
217
|
+
// auth cannot override the gateway after plugin setup.
|
|
218
|
+
const managedAuthKey = selectManagedAuthKey(env);
|
|
219
|
+
if (managedAuthKey &&
|
|
220
|
+
(env.ANTHROPIC_API_KEY !== managedAuthKey || env.ANTHROPIC_AUTH_TOKEN !== managedAuthKey)) {
|
|
221
|
+
syncManagedAuthEnv(env, managedAuthKey);
|
|
222
|
+
settings.env = env;
|
|
223
|
+
settingsChanged = true;
|
|
224
|
+
console.log('\n Synced Plexor auth into ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN');
|
|
225
|
+
}
|
|
226
|
+
// Check if there's a valid Plexor config
|
|
227
|
+
let hasValidConfig = false;
|
|
228
|
+
try {
|
|
229
|
+
if (fs.existsSync(configPath)) {
|
|
230
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
231
|
+
hasValidConfig = (config.auth?.api_key || config.apiKey || '').startsWith('plx_');
|
|
232
|
+
}
|
|
233
|
+
} catch (e) {}
|
|
234
|
+
|
|
235
|
+
if (!hasValidConfig) {
|
|
236
|
+
console.log('\n Warning: Detected orphaned Plexor routing in Claude settings');
|
|
237
|
+
console.log(' This may be from a previous installation.\n');
|
|
238
|
+
console.log(' Run /plexor-login to reconfigure, or');
|
|
239
|
+
console.log(' Run /plexor-uninstall to clean up\n');
|
|
240
|
+
} else {
|
|
241
|
+
// Fix #2176: Detect variant mismatch and migrate URL
|
|
242
|
+
const currentUrl = env.ANTHROPIC_BASE_URL;
|
|
243
|
+
if (currentUrl !== THIS_VARIANT_URL) {
|
|
244
|
+
env.ANTHROPIC_BASE_URL = THIS_VARIANT_URL;
|
|
245
|
+
settings.env = env;
|
|
246
|
+
settingsChanged = true;
|
|
247
|
+
console.log(`\n Migrated ANTHROPIC_BASE_URL to this variant's gateway:`);
|
|
248
|
+
console.log(` Old: ${currentUrl}`);
|
|
249
|
+
console.log(` New: ${THIS_VARIANT_URL}\n`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log('\n Existing Plexor configuration detected');
|
|
252
|
+
console.log(' Your previous settings have been preserved.\n');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Write back settings if any migration was applied
|
|
258
|
+
if (settingsChanged) {
|
|
259
|
+
const crypto = require('crypto');
|
|
260
|
+
const claudeDir = path.join(HOME_DIR, '.claude');
|
|
261
|
+
const tempId = crypto.randomBytes(8).toString('hex');
|
|
262
|
+
const tempPath = path.join(claudeDir, `.settings.${tempId}.tmp`);
|
|
263
|
+
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
264
|
+
fs.renameSync(tempPath, settingsPath);
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
// Ignore errors in detection - don't break install
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
78
271
|
// Default configuration for new installs
|
|
272
|
+
// STAGING PACKAGE - uses staging API
|
|
79
273
|
const DEFAULT_CONFIG = {
|
|
80
274
|
version: 1,
|
|
81
275
|
auth: {
|
|
@@ -84,13 +278,16 @@ const DEFAULT_CONFIG = {
|
|
|
84
278
|
},
|
|
85
279
|
settings: {
|
|
86
280
|
enabled: true,
|
|
87
|
-
apiUrl: "https://api.plexor.dev",
|
|
281
|
+
apiUrl: "https://staging.api.plexor.dev",
|
|
88
282
|
mode: "balanced",
|
|
89
283
|
localCacheEnabled: true
|
|
90
284
|
}
|
|
91
285
|
};
|
|
92
286
|
|
|
93
287
|
function main() {
|
|
288
|
+
// Check for orphaned routing at start of postinstall
|
|
289
|
+
checkOrphanedRouting();
|
|
290
|
+
|
|
94
291
|
try {
|
|
95
292
|
// Get target user info for chown (if running with sudo)
|
|
96
293
|
const targetUser = getTargetUserIds();
|
|
@@ -101,6 +298,9 @@ function main() {
|
|
|
101
298
|
// Create ~/.claude/plugins/plexor/commands/ for JS executors
|
|
102
299
|
fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
|
|
103
300
|
|
|
301
|
+
// Create ~/.claude/plugins/plexor/lib/ for shared modules
|
|
302
|
+
fs.mkdirSync(PLEXOR_LIB_DIR, { recursive: true });
|
|
303
|
+
|
|
104
304
|
// Create ~/.plexor/ with secure permissions (owner only)
|
|
105
305
|
fs.mkdirSync(PLEXOR_CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
106
306
|
|
|
@@ -161,6 +361,40 @@ function main() {
|
|
|
161
361
|
jsInstalled.push(file);
|
|
162
362
|
}
|
|
163
363
|
|
|
364
|
+
// Copy lib files to ~/.claude/plugins/plexor/lib/
|
|
365
|
+
// CRITICAL: These are required for commands to work
|
|
366
|
+
const libInstalled = [];
|
|
367
|
+
if (fs.existsSync(LIB_SOURCE)) {
|
|
368
|
+
const libFiles = fs.readdirSync(LIB_SOURCE).filter(f => f.endsWith('.js'));
|
|
369
|
+
if (libFiles.length === 0) {
|
|
370
|
+
console.warn(' ⚠ Warning: No lib files found in package. Commands may not work.');
|
|
371
|
+
}
|
|
372
|
+
for (const file of libFiles) {
|
|
373
|
+
try {
|
|
374
|
+
const src = path.join(LIB_SOURCE, file);
|
|
375
|
+
const dest = path.join(PLEXOR_LIB_DIR, file);
|
|
376
|
+
fs.copyFileSync(src, dest);
|
|
377
|
+
libInstalled.push(file);
|
|
378
|
+
} catch (err) {
|
|
379
|
+
console.error(` ✗ Failed to copy lib/${file}: ${err.message}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
console.error(' ✗ CRITICAL: lib/ directory not found in package.');
|
|
384
|
+
console.error(' Commands will fail. Please reinstall the package.');
|
|
385
|
+
console.error(` Expected location: ${LIB_SOURCE}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Verify critical lib file exists
|
|
389
|
+
const criticalLibFile = path.join(PLEXOR_LIB_DIR, 'settings-manager.js');
|
|
390
|
+
if (!fs.existsSync(criticalLibFile)) {
|
|
391
|
+
console.error('');
|
|
392
|
+
console.error(' ✗ CRITICAL: settings-manager.js was not installed.');
|
|
393
|
+
console.error(' This file is required for commands to work.');
|
|
394
|
+
console.error(' Try reinstalling: npm install @plexor-dev/claude-code-plugin-staging');
|
|
395
|
+
console.error('');
|
|
396
|
+
}
|
|
397
|
+
|
|
164
398
|
// Fix file ownership when running with sudo
|
|
165
399
|
// Files are created as root but should be owned by the original user
|
|
166
400
|
if (targetUser) {
|
|
@@ -171,11 +405,6 @@ function main() {
|
|
|
171
405
|
chownRecursive(PLEXOR_CONFIG_DIR, uid, gid);
|
|
172
406
|
}
|
|
173
407
|
|
|
174
|
-
// Detect shell type
|
|
175
|
-
const shell = process.env.SHELL || '';
|
|
176
|
-
const isZsh = shell.includes('zsh');
|
|
177
|
-
const shellRc = isZsh ? '~/.zshrc' : '~/.bashrc';
|
|
178
|
-
|
|
179
408
|
// Print success message with clear onboarding steps
|
|
180
409
|
console.log('');
|
|
181
410
|
console.log(' ╔═══════════════════════════════════════════════════════════════════╗');
|
|
@@ -190,38 +419,36 @@ function main() {
|
|
|
190
419
|
}
|
|
191
420
|
console.log(` ✓ Installed ${installed.length} slash commands to ~/.claude/commands/`);
|
|
192
421
|
if (jsInstalled.length > 0) {
|
|
193
|
-
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/`);
|
|
422
|
+
console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/commands/`);
|
|
423
|
+
}
|
|
424
|
+
if (libInstalled.length > 0) {
|
|
425
|
+
console.log(` ✓ Installed ${libInstalled.length} lib modules to ~/.claude/plugins/plexor/lib/`);
|
|
194
426
|
}
|
|
195
427
|
if (targetUser) {
|
|
196
428
|
console.log(` ✓ Set file ownership to ${targetUser.user}`);
|
|
197
429
|
}
|
|
198
430
|
console.log('');
|
|
199
431
|
|
|
200
|
-
// CRITICAL: Make the required step VERY obvious
|
|
201
432
|
console.log(' ┌─────────────────────────────────────────────────────────────────┐');
|
|
202
|
-
console.log(' │
|
|
433
|
+
console.log(' │ NEXT: Start Claude Code and run /plexor-setup │');
|
|
203
434
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
204
435
|
console.log('');
|
|
205
|
-
console.log('
|
|
206
|
-
console.log('');
|
|
207
|
-
console.log(
|
|
208
|
-
console.log(
|
|
209
|
-
console.log('');
|
|
210
|
-
console.log('
|
|
211
|
-
console.log('');
|
|
212
|
-
console.log(` echo 'export ANTHROPIC_BASE_URL="https://api.plexor.dev/gateway/anthropic"' >> ${shellRc}`);
|
|
213
|
-
console.log(` echo 'export ANTHROPIC_API_KEY="plx_your_key_here"' >> ${shellRc}`);
|
|
214
|
-
console.log(` source ${shellRc}`);
|
|
436
|
+
console.log(' /plexor-setup will:');
|
|
437
|
+
console.log(' 1. Ask for your Plexor API key');
|
|
438
|
+
console.log(' 2. Write ~/.plexor/config.json');
|
|
439
|
+
console.log(' 3. Point Claude at the Plexor staging gateway');
|
|
440
|
+
console.log(' 4. Preserve prior Claude auth for restore on logout');
|
|
441
|
+
console.log(' 5. Verify Claude routing with a deterministic check');
|
|
215
442
|
console.log('');
|
|
216
443
|
console.log(' ┌─────────────────────────────────────────────────────────────────┐');
|
|
217
|
-
console.log(' │
|
|
444
|
+
console.log(' │ No shell edits or Claude restart required after setup │');
|
|
218
445
|
console.log(' └─────────────────────────────────────────────────────────────────┘');
|
|
219
446
|
console.log('');
|
|
220
447
|
console.log(' Available commands:');
|
|
448
|
+
console.log(' /plexor-setup - First-time setup wizard');
|
|
449
|
+
console.log(' /plexor-login - Advanced/manual auth path');
|
|
221
450
|
console.log(' /plexor-status - Check connection and see savings');
|
|
222
|
-
console.log(' /plexor-
|
|
223
|
-
console.log(' /plexor-login - Authenticate with API key');
|
|
224
|
-
console.log(' /plexor-settings - View/modify settings');
|
|
451
|
+
console.log(' /plexor-enabled - Enable/disable Plexor routing');
|
|
225
452
|
console.log('');
|
|
226
453
|
console.log(' Documentation: https://plexor.dev/docs');
|
|
227
454
|
console.log('');
|
package/scripts/uninstall.js
CHANGED
|
@@ -1,67 +1,220 @@
|
|
|
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
18
|
const os = require('os');
|
|
19
|
+
const { execSync } = require('child_process');
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Get the correct home directory for the process's effective user.
|
|
23
|
+
* Resolves via /etc/passwd to handle sudo and sudo -u correctly.
|
|
24
|
+
*/
|
|
25
|
+
function getHomeDir() {
|
|
26
|
+
if (os.platform() !== 'win32') {
|
|
27
|
+
try {
|
|
28
|
+
const uid = process.getuid();
|
|
29
|
+
const entry = execSync(`getent passwd ${uid}`, { encoding: 'utf8' }).trim();
|
|
30
|
+
const fields = entry.split(':');
|
|
31
|
+
if (fields.length >= 6 && fields[5]) {
|
|
32
|
+
return fields[5];
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Fall through
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const h = os.homedir();
|
|
39
|
+
if (h) return h;
|
|
40
|
+
return process.env.HOME || process.env.USERPROFILE || null;
|
|
41
|
+
}
|
|
16
42
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
43
|
+
const home = getHomeDir();
|
|
44
|
+
if (!home) {
|
|
45
|
+
console.log('Warning: Could not determine home directory, skipping cleanup');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(' Plexor plugin cleanup...');
|
|
51
|
+
console.log('');
|
|
52
|
+
|
|
53
|
+
const results = {
|
|
54
|
+
routing: false,
|
|
55
|
+
commands: [],
|
|
56
|
+
restored: [],
|
|
57
|
+
pluginDir: false
|
|
58
|
+
};
|
|
22
59
|
|
|
23
|
-
|
|
24
|
-
|
|
60
|
+
function isManagedGatewayUrl(baseUrl = '') {
|
|
61
|
+
return (
|
|
62
|
+
baseUrl.includes('plexor') ||
|
|
63
|
+
baseUrl.includes('staging.api') ||
|
|
64
|
+
baseUrl.includes('localhost') ||
|
|
65
|
+
baseUrl.includes('127.0.0.1') ||
|
|
66
|
+
baseUrl.includes('ngrok') ||
|
|
67
|
+
baseUrl.includes('localtunnel')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPlexorApiKey(value = '') {
|
|
72
|
+
return typeof value === 'string' && value.startsWith('plx_');
|
|
73
|
+
}
|
|
25
74
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
75
|
+
function clearPlexorRoutingEnv(env = {}) {
|
|
76
|
+
const hasManagedBaseUrl = isManagedGatewayUrl(env.ANTHROPIC_BASE_URL || '');
|
|
77
|
+
const hasPlexorAuthToken = isPlexorApiKey(env.ANTHROPIC_AUTH_TOKEN || '');
|
|
78
|
+
const hasPlexorApiKey = isPlexorApiKey(env.ANTHROPIC_API_KEY || '');
|
|
79
|
+
|
|
80
|
+
if (!hasManagedBaseUrl && !hasPlexorAuthToken && !hasPlexorApiKey) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasManagedBaseUrl) {
|
|
85
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
86
|
+
}
|
|
87
|
+
if (hasPlexorAuthToken) {
|
|
88
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
89
|
+
}
|
|
90
|
+
if (hasPlexorApiKey) {
|
|
91
|
+
delete env.ANTHROPIC_API_KEY;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!env.ANTHROPIC_API_KEY && env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY) {
|
|
95
|
+
env.ANTHROPIC_API_KEY = env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
|
|
96
|
+
}
|
|
97
|
+
if (!env.ANTHROPIC_AUTH_TOKEN && env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN) {
|
|
98
|
+
env.ANTHROPIC_AUTH_TOKEN = env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
delete env.PLEXOR_PREVIOUS_ANTHROPIC_API_KEY;
|
|
102
|
+
delete env.PLEXOR_PREVIOUS_ANTHROPIC_AUTH_TOKEN;
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
29
106
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
107
|
+
// 1. Remove routing from settings.json
|
|
108
|
+
// This is CRITICAL - do NOT depend on settings-manager module since it may not load during uninstall
|
|
109
|
+
try {
|
|
110
|
+
const settingsPath = path.join(home, '.claude', 'settings.json');
|
|
111
|
+
if (fs.existsSync(settingsPath)) {
|
|
112
|
+
const data = fs.readFileSync(settingsPath, 'utf8');
|
|
113
|
+
if (data && data.trim()) {
|
|
114
|
+
const settings = JSON.parse(data);
|
|
115
|
+
if (settings.env) {
|
|
116
|
+
const routingChanged = clearPlexorRoutingEnv(settings.env);
|
|
117
|
+
|
|
118
|
+
// Clean up empty env block
|
|
119
|
+
if (Object.keys(settings.env).length === 0) {
|
|
120
|
+
delete settings.env;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (routingChanged) {
|
|
124
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
125
|
+
results.routing = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.log(` Warning: Could not clean settings.json: ${e.message}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Remove slash command files
|
|
135
|
+
// These are the Plexor-specific command files that get installed to ~/.claude/commands/
|
|
136
|
+
const plexorCommands = [
|
|
137
|
+
'plexor-config.md',
|
|
138
|
+
'plexor-enabled.md',
|
|
139
|
+
'plexor-login.md',
|
|
140
|
+
'plexor-logout.md',
|
|
141
|
+
'plexor-mode.md',
|
|
142
|
+
'plexor-provider.md',
|
|
143
|
+
'plexor-settings.md',
|
|
144
|
+
'plexor-setup.md',
|
|
145
|
+
'plexor-status.md',
|
|
146
|
+
'plexor-uninstall.md'
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const commandsDir = path.join(home, '.claude', 'commands');
|
|
151
|
+
if (fs.existsSync(commandsDir)) {
|
|
152
|
+
for (const cmd of plexorCommands) {
|
|
153
|
+
const cmdPath = path.join(commandsDir, cmd);
|
|
154
|
+
const backupPath = cmdPath + '.backup';
|
|
155
|
+
|
|
156
|
+
if (fs.existsSync(cmdPath)) {
|
|
157
|
+
fs.unlinkSync(cmdPath);
|
|
158
|
+
results.commands.push(cmd.replace('.md', ''));
|
|
33
159
|
|
|
34
160
|
// Restore backup if it exists
|
|
35
161
|
if (fs.existsSync(backupPath)) {
|
|
36
|
-
fs.renameSync(backupPath,
|
|
37
|
-
restored.push(
|
|
162
|
+
fs.renameSync(backupPath, cmdPath);
|
|
163
|
+
results.restored.push(cmd);
|
|
38
164
|
}
|
|
39
165
|
}
|
|
40
166
|
}
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.log(` Warning: Could not clean commands: ${e.message}`);
|
|
170
|
+
}
|
|
41
171
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
restored.forEach(f => console.log(` ${f}`));
|
|
53
|
-
}
|
|
172
|
+
// 3. Remove plugin directory
|
|
173
|
+
try {
|
|
174
|
+
const pluginDir = path.join(home, '.claude', 'plugins', 'plexor');
|
|
175
|
+
if (fs.existsSync(pluginDir)) {
|
|
176
|
+
fs.rmSync(pluginDir, { recursive: true, force: true });
|
|
177
|
+
results.pluginDir = true;
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.log(` Warning: Could not remove plugin directory: ${e.message}`);
|
|
181
|
+
}
|
|
54
182
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
183
|
+
// Output results
|
|
184
|
+
if (results.routing || results.commands.length > 0 || results.pluginDir) {
|
|
185
|
+
console.log(' Plexor plugin uninstalled');
|
|
186
|
+
console.log('');
|
|
187
|
+
|
|
188
|
+
if (results.routing) {
|
|
189
|
+
console.log(' Removed Plexor routing from Claude settings');
|
|
190
|
+
console.log(' (Claude Code now connects directly to Anthropic)');
|
|
191
|
+
console.log('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (results.commands.length > 0) {
|
|
195
|
+
console.log(' Removed commands:');
|
|
196
|
+
results.commands.forEach(cmd => console.log(` /${cmd}`));
|
|
197
|
+
console.log('');
|
|
198
|
+
}
|
|
60
199
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.
|
|
200
|
+
if (results.restored.length > 0) {
|
|
201
|
+
console.log(' Restored from backup:');
|
|
202
|
+
results.restored.forEach(f => console.log(` ${f}`));
|
|
203
|
+
console.log('');
|
|
64
204
|
}
|
|
205
|
+
|
|
206
|
+
if (results.pluginDir) {
|
|
207
|
+
console.log(' Removed plugin directory');
|
|
208
|
+
console.log('');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(' Note: ~/.plexor/ config directory was preserved.');
|
|
212
|
+
console.log(' To remove it: rm -rf ~/.plexor');
|
|
213
|
+
console.log('');
|
|
214
|
+
} else {
|
|
215
|
+
console.log(' No Plexor components found to clean up.');
|
|
216
|
+
console.log('');
|
|
65
217
|
}
|
|
66
218
|
|
|
67
|
-
|
|
219
|
+
console.log(' Cleanup complete');
|
|
220
|
+
console.log('');
|