@plexor-dev/claude-code-plugin-staging 0.1.0-beta.1

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.
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Claude Code Settings Manager
3
+ *
4
+ * Manages ~/.claude/settings.json to enable automatic Plexor routing.
5
+ *
6
+ * KEY DISCOVERY: Claude Code reads settings.json at runtime and the `env` block
7
+ * overrides environment variables. This allows the plugin to redirect ALL Claude
8
+ * Code sessions to Plexor without users manually setting environment variables.
9
+ *
10
+ * Reference: Claude Code v2.0.1+ behavior - settings.json env takes precedence
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const CLAUDE_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude');
17
+ const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
18
+
19
+ // Plexor gateway endpoints
20
+ const PLEXOR_STAGING_URL = 'https://staging.api.plexor.dev/gateway/anthropic';
21
+ const PLEXOR_PROD_URL = 'https://api.plexor.dev/gateway/anthropic';
22
+
23
+ class ClaudeSettingsManager {
24
+ constructor() {
25
+ this.settingsPath = SETTINGS_PATH;
26
+ this.claudeDir = CLAUDE_DIR;
27
+ }
28
+
29
+ /**
30
+ * Load current Claude settings
31
+ * @returns {Object} settings object or empty object if not found
32
+ */
33
+ load() {
34
+ try {
35
+ if (!fs.existsSync(this.settingsPath)) {
36
+ return {};
37
+ }
38
+ const data = fs.readFileSync(this.settingsPath, 'utf8');
39
+ return JSON.parse(data);
40
+ } catch (err) {
41
+ // Return empty object if file doesn't exist or parse error
42
+ return {};
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Save Claude settings
48
+ * @param {Object} settings - settings object to save
49
+ * @returns {boolean} success status
50
+ */
51
+ save(settings) {
52
+ try {
53
+ // Ensure .claude directory exists
54
+ if (!fs.existsSync(this.claudeDir)) {
55
+ fs.mkdirSync(this.claudeDir, { recursive: true });
56
+ }
57
+
58
+ fs.writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2));
59
+ return true;
60
+ } catch (err) {
61
+ console.error('Failed to save Claude settings:', err.message);
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Enable Plexor routing by setting env vars in settings.json
68
+ *
69
+ * This is the KEY mechanism: setting ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN
70
+ * in the env block redirects ALL Claude Code sessions to Plexor automatically.
71
+ *
72
+ * @param {string} apiKey - Plexor API key (plx_*)
73
+ * @param {Object} options - { useStaging: boolean }
74
+ * @returns {boolean} success status
75
+ */
76
+ enablePlexorRouting(apiKey, options = {}) {
77
+ // STAGING PACKAGE - defaults to staging API
78
+ const { useStaging = true } = options;
79
+ const apiUrl = useStaging ? PLEXOR_STAGING_URL : PLEXOR_PROD_URL;
80
+
81
+ try {
82
+ const settings = this.load();
83
+
84
+ // Initialize env block if doesn't exist
85
+ if (!settings.env) {
86
+ settings.env = {};
87
+ }
88
+
89
+ // Set the magic environment variables
90
+ // ANTHROPIC_AUTH_TOKEN has higher precedence than ANTHROPIC_API_KEY
91
+ settings.env.ANTHROPIC_BASE_URL = apiUrl;
92
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
93
+
94
+ const success = this.save(settings);
95
+
96
+ if (success) {
97
+ console.log(`✓ Plexor routing enabled`);
98
+ console.log(` Base URL: ${apiUrl}`);
99
+ console.log(` API Key: ${apiKey.substring(0, 12)}...`);
100
+ console.log(`\n All Claude Code sessions will now route through Plexor.`);
101
+ console.log(` Changes take effect immediately (no restart needed).`);
102
+ }
103
+
104
+ return success;
105
+ } catch (err) {
106
+ console.error('Failed to enable Plexor routing:', err.message);
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Disable Plexor routing by removing env vars from settings.json
113
+ *
114
+ * This restores direct connection to Anthropic API.
115
+ *
116
+ * @returns {boolean} success status
117
+ */
118
+ disablePlexorRouting() {
119
+ try {
120
+ const settings = this.load();
121
+
122
+ if (!settings.env) {
123
+ console.log('Plexor routing is not currently enabled.');
124
+ return true;
125
+ }
126
+
127
+ // Remove Plexor-specific env vars
128
+ delete settings.env.ANTHROPIC_BASE_URL;
129
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
130
+
131
+ // Clean up empty env block
132
+ if (Object.keys(settings.env).length === 0) {
133
+ delete settings.env;
134
+ }
135
+
136
+ const success = this.save(settings);
137
+
138
+ if (success) {
139
+ console.log('✓ Plexor routing disabled');
140
+ console.log(' Claude Code will now connect directly to Anthropic.');
141
+ console.log(' Changes take effect immediately (no restart needed).');
142
+ }
143
+
144
+ return success;
145
+ } catch (err) {
146
+ console.error('Failed to disable Plexor routing:', err.message);
147
+ return false;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Get current routing status
153
+ * @returns {Object} { enabled: boolean, baseUrl: string|null, hasToken: boolean }
154
+ */
155
+ getRoutingStatus() {
156
+ try {
157
+ const settings = this.load();
158
+
159
+ const baseUrl = settings.env?.ANTHROPIC_BASE_URL || null;
160
+ const hasToken = !!settings.env?.ANTHROPIC_AUTH_TOKEN;
161
+
162
+ // Check if routing to Plexor
163
+ const isPlexorRouting = baseUrl && (
164
+ baseUrl.includes('plexor') ||
165
+ baseUrl.includes('staging.api')
166
+ );
167
+
168
+ return {
169
+ enabled: isPlexorRouting,
170
+ baseUrl,
171
+ hasToken,
172
+ isStaging: baseUrl?.includes('staging') || false,
173
+ tokenPreview: hasToken ? settings.env.ANTHROPIC_AUTH_TOKEN.substring(0, 12) + '...' : null
174
+ };
175
+ } catch {
176
+ return { enabled: false, baseUrl: null, hasToken: false };
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Update just the API key without changing other settings
182
+ * @param {string} apiKey - new Plexor API key
183
+ * @returns {boolean} success status
184
+ */
185
+ updateApiKey(apiKey) {
186
+ try {
187
+ const settings = this.load();
188
+
189
+ if (!settings.env) {
190
+ settings.env = {};
191
+ }
192
+
193
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
194
+ return this.save(settings);
195
+ } catch (err) {
196
+ console.error('Failed to update API key:', err.message);
197
+ return false;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Switch between staging and production
203
+ * @param {boolean} useStaging - true for staging, false for production
204
+ * @returns {boolean} success status
205
+ */
206
+ setEnvironment(useStaging) {
207
+ try {
208
+ const settings = this.load();
209
+
210
+ if (!settings.env?.ANTHROPIC_BASE_URL) {
211
+ console.log('Plexor routing is not enabled. Run /plexor-login first.');
212
+ return false;
213
+ }
214
+
215
+ settings.env.ANTHROPIC_BASE_URL = useStaging ? PLEXOR_STAGING_URL : PLEXOR_PROD_URL;
216
+
217
+ const success = this.save(settings);
218
+ if (success) {
219
+ console.log(`✓ Switched to ${useStaging ? 'staging' : 'production'} environment`);
220
+ }
221
+ return success;
222
+ } catch (err) {
223
+ console.error('Failed to switch environment:', err.message);
224
+ return false;
225
+ }
226
+ }
227
+ }
228
+
229
+ // Export singleton instance and class
230
+ const settingsManager = new ClaudeSettingsManager();
231
+
232
+ module.exports = {
233
+ ClaudeSettingsManager,
234
+ settingsManager,
235
+ CLAUDE_DIR,
236
+ SETTINGS_PATH,
237
+ PLEXOR_STAGING_URL,
238
+ PLEXOR_PROD_URL
239
+ };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@plexor-dev/claude-code-plugin-staging",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "STAGING - LLM cost optimization plugin for Claude Code (internal testing)",
5
+ "main": "lib/constants.js",
6
+ "bin": {
7
+ "plexor-status": "./commands/plexor-status.js",
8
+ "plexor-mode": "./commands/plexor-mode.js",
9
+ "plexor-enabled": "./commands/plexor-enabled.js",
10
+ "plexor-provider": "./commands/plexor-provider.js",
11
+ "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"
15
+ },
16
+ "scripts": {
17
+ "postinstall": "node scripts/postinstall.js",
18
+ "preuninstall": "node scripts/uninstall.js",
19
+ "test": "node --test"
20
+ },
21
+ "files": [
22
+ "commands/",
23
+ "hooks/",
24
+ "scripts/",
25
+ "lib/",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "keywords": [
33
+ "claude",
34
+ "claude-code",
35
+ "llm",
36
+ "optimization",
37
+ "plexor",
38
+ "cost-savings",
39
+ "ai",
40
+ "anthropic"
41
+ ],
42
+ "author": "Plexor <hello@plexor.dev>",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/plexor-dev/claude-code-plugin"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/plexor-dev/claude-code-plugin/issues"
50
+ },
51
+ "homepage": "https://plexor.dev",
52
+ "engines": {
53
+ "node": ">=16"
54
+ },
55
+ "os": [
56
+ "darwin",
57
+ "linux",
58
+ "win32"
59
+ ]
60
+ }
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # Plexor CLI - Claude Code with intelligent optimization
3
+ # This wrapper auto-enables hypervisor mode for direct gateway routing
4
+
5
+ # Colors
6
+ CYAN='\033[0;36m'
7
+ GREEN='\033[0;32m'
8
+ YELLOW='\033[0;33m'
9
+ DIM='\033[2m'
10
+ NC='\033[0m' # No Color
11
+
12
+ CONFIG_FILE="$HOME/.plexor/config.json"
13
+
14
+ # Auto-configure ANTHROPIC_BASE_URL if Plexor is enabled
15
+ if [ -f "$CONFIG_FILE" ]; then
16
+ ENABLED=$(python3 -c "import json; c=json.load(open('$CONFIG_FILE')); print(c.get('settings',{}).get('enabled', False))" 2>/dev/null)
17
+ API_URL=$(python3 -c "import json; c=json.load(open('$CONFIG_FILE')); print(c.get('settings',{}).get('apiUrl', ''))" 2>/dev/null)
18
+ API_KEY=$(python3 -c "import json; c=json.load(open('$CONFIG_FILE')); print(c.get('auth',{}).get('api_key', ''))" 2>/dev/null)
19
+ MODE=$(python3 -c "import json; c=json.load(open('$CONFIG_FILE')); print(c.get('settings',{}).get('mode', 'balanced'))" 2>/dev/null)
20
+
21
+ if [ "$ENABLED" = "True" ] && [ -n "$API_URL" ] && [ -n "$API_KEY" ]; then
22
+ # Set ANTHROPIC_BASE_URL to Plexor gateway (hypervisor mode)
23
+ export ANTHROPIC_BASE_URL="${API_URL}/gateway/anthropic/v1"
24
+ export ANTHROPIC_API_KEY="$API_KEY"
25
+
26
+ # Show Plexor branding
27
+ echo -e "${CYAN}"
28
+ cat << 'EOF'
29
+ ____ __
30
+ / __ \/ /__ _ ______ _____
31
+ / /_/ / / _ \| |/_/ __ \/ ___/
32
+ / ____/ / __/> </ /_/ / /
33
+ /_/ /_/\___/_/|_|\____/_/
34
+
35
+ EOF
36
+ echo -e "${NC}"
37
+ echo -e "${GREEN}●${NC} Hypervisor Mode: ${GREEN}Active${NC}"
38
+ echo -e " Gateway: ${DIM}${ANTHROPIC_BASE_URL}${NC}"
39
+ echo -e " Mode: ${MODE}"
40
+ echo ""
41
+ else
42
+ echo -e "${YELLOW}○${NC} Plexor: ${DIM}Disabled${NC} (run /plexor-login to enable)"
43
+ echo ""
44
+ fi
45
+ fi
46
+
47
+ # Launch claude with all arguments passed through
48
+ exec claude "$@"
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Plexor Claude Code Plugin - Postinstall Script
5
+ *
6
+ * Copies slash commands to ~/.claude/commands/ and creates
7
+ * the ~/.plexor/ configuration directory.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+ const { execSync } = require('child_process');
14
+
15
+ /**
16
+ * Get the correct home directory, accounting for sudo.
17
+ * When running with sudo, os.homedir() returns /root, but we want
18
+ * the actual user's home directory.
19
+ */
20
+ function getHomeDir() {
21
+ // Check if running with sudo - SUDO_USER contains the original username
22
+ if (process.env.SUDO_USER) {
23
+ // On Linux/Mac, home directories are typically /home/<user> or /Users/<user>
24
+ const platform = os.platform();
25
+ if (platform === 'darwin') {
26
+ return path.join('/Users', process.env.SUDO_USER);
27
+ } else if (platform === 'linux') {
28
+ return path.join('/home', process.env.SUDO_USER);
29
+ }
30
+ }
31
+ return os.homedir();
32
+ }
33
+
34
+ /**
35
+ * Get uid/gid for the target user (handles sudo case).
36
+ * Returns null if not running with sudo or on Windows.
37
+ */
38
+ function getTargetUserIds() {
39
+ const sudoUser = process.env.SUDO_USER;
40
+ if (!sudoUser || os.platform() === 'win32') {
41
+ return null;
42
+ }
43
+
44
+ try {
45
+ // Get uid and gid for the sudo user
46
+ const uid = parseInt(execSync(`id -u ${sudoUser}`, { encoding: 'utf8' }).trim(), 10);
47
+ const gid = parseInt(execSync(`id -g ${sudoUser}`, { encoding: 'utf8' }).trim(), 10);
48
+ return { uid, gid, user: sudoUser };
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Recursively chown a directory and all its contents.
56
+ */
57
+ function chownRecursive(dirPath, uid, gid) {
58
+ if (!fs.existsSync(dirPath)) return;
59
+
60
+ const stat = fs.statSync(dirPath);
61
+ fs.chownSync(dirPath, uid, gid);
62
+
63
+ if (stat.isDirectory()) {
64
+ const entries = fs.readdirSync(dirPath);
65
+ for (const entry of entries) {
66
+ chownRecursive(path.join(dirPath, entry), uid, gid);
67
+ }
68
+ }
69
+ }
70
+
71
+ const HOME_DIR = getHomeDir();
72
+ const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
73
+ const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands');
74
+ const PLEXOR_PLUGINS_DIR = path.join(HOME_DIR, '.claude', 'plugins', 'plexor', 'commands');
75
+ const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
76
+ const PLEXOR_CONFIG_FILE = path.join(PLEXOR_CONFIG_DIR, 'config.json');
77
+
78
+ // Default configuration for new installs
79
+ const DEFAULT_CONFIG = {
80
+ version: 1,
81
+ auth: {
82
+ mode: "pending",
83
+ authenticated_at: null
84
+ },
85
+ settings: {
86
+ enabled: true,
87
+ apiUrl: "https://api.plexor.dev",
88
+ mode: "balanced",
89
+ localCacheEnabled: true
90
+ }
91
+ };
92
+
93
+ function main() {
94
+ try {
95
+ // Get target user info for chown (if running with sudo)
96
+ const targetUser = getTargetUserIds();
97
+
98
+ // Create ~/.claude/commands/ if not exists
99
+ fs.mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
100
+
101
+ // Create ~/.claude/plugins/plexor/commands/ for JS executors
102
+ fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
103
+
104
+ // Create ~/.plexor/ with secure permissions (owner only)
105
+ fs.mkdirSync(PLEXOR_CONFIG_DIR, { recursive: true, mode: 0o700 });
106
+
107
+ // Create default config.json if it doesn't exist
108
+ let configCreated = false;
109
+ if (!fs.existsSync(PLEXOR_CONFIG_FILE)) {
110
+ fs.writeFileSync(
111
+ PLEXOR_CONFIG_FILE,
112
+ JSON.stringify(DEFAULT_CONFIG, null, 2),
113
+ { mode: 0o600 }
114
+ );
115
+ configCreated = true;
116
+ }
117
+
118
+ // Get list of command files (.md for Claude, .js for executors)
119
+ const mdFiles = fs.readdirSync(COMMANDS_SOURCE)
120
+ .filter(f => f.endsWith('.md'));
121
+ const jsFiles = fs.readdirSync(COMMANDS_SOURCE)
122
+ .filter(f => f.endsWith('.js'));
123
+
124
+ if (mdFiles.length === 0) {
125
+ console.error('No command files found in package. Installation may be corrupt.');
126
+ process.exit(1);
127
+ }
128
+
129
+ const installed = [];
130
+ const backed_up = [];
131
+
132
+ // Copy .md command files to ~/.claude/commands/
133
+ for (const file of mdFiles) {
134
+ const src = path.join(COMMANDS_SOURCE, file);
135
+ const dest = path.join(CLAUDE_COMMANDS_DIR, file);
136
+
137
+ // Backup existing file if present and different
138
+ if (fs.existsSync(dest)) {
139
+ const existingContent = fs.readFileSync(dest, 'utf8');
140
+ const newContent = fs.readFileSync(src, 'utf8');
141
+
142
+ if (existingContent !== newContent) {
143
+ const backupPath = dest + '.backup';
144
+ fs.copyFileSync(dest, backupPath);
145
+ backed_up.push(file);
146
+ }
147
+ }
148
+
149
+ fs.copyFileSync(src, dest);
150
+ installed.push(file.replace('.md', ''));
151
+ }
152
+
153
+ // Copy .js executor files to ~/.claude/plugins/plexor/commands/
154
+ const jsInstalled = [];
155
+ for (const file of jsFiles) {
156
+ const src = path.join(COMMANDS_SOURCE, file);
157
+ const dest = path.join(PLEXOR_PLUGINS_DIR, file);
158
+ fs.copyFileSync(src, dest);
159
+ // Make executable
160
+ fs.chmodSync(dest, 0o755);
161
+ jsInstalled.push(file);
162
+ }
163
+
164
+ // Fix file ownership when running with sudo
165
+ // Files are created as root but should be owned by the original user
166
+ if (targetUser) {
167
+ const { uid, gid } = targetUser;
168
+ // Chown the .claude directory and all contents we created
169
+ chownRecursive(path.join(HOME_DIR, '.claude'), uid, gid);
170
+ // Chown the .plexor directory and all contents
171
+ chownRecursive(PLEXOR_CONFIG_DIR, uid, gid);
172
+ }
173
+
174
+ // Detect shell type
175
+ const shell = process.env.SHELL || '';
176
+ const isZsh = shell.includes('zsh');
177
+ const shellRc = isZsh ? '~/.zshrc' : '~/.bashrc';
178
+
179
+ // Print success message with clear onboarding steps
180
+ console.log('');
181
+ console.log(' ╔═══════════════════════════════════════════════════════════════════╗');
182
+ console.log(' ║ ║');
183
+ console.log(' ║ Plexor Claude Code Plugin installed successfully! ║');
184
+ console.log(' ║ ║');
185
+ console.log(' ╚═══════════════════════════════════════════════════════════════════╝');
186
+ console.log('');
187
+
188
+ if (configCreated) {
189
+ console.log(' ✓ Created ~/.plexor/config.json');
190
+ }
191
+ console.log(` ✓ Installed ${installed.length} slash commands to ~/.claude/commands/`);
192
+ if (jsInstalled.length > 0) {
193
+ console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/`);
194
+ }
195
+ if (targetUser) {
196
+ console.log(` ✓ Set file ownership to ${targetUser.user}`);
197
+ }
198
+ console.log('');
199
+
200
+ // CRITICAL: Make the required step VERY obvious
201
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
202
+ console.log(' │ REQUIRED: Run this command to enable Plexor routing: │');
203
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
204
+ console.log('');
205
+ console.log(' For Claude MAX users (OAuth):');
206
+ console.log('');
207
+ console.log(` echo 'export ANTHROPIC_BASE_URL="https://api.plexor.dev/gateway/anthropic"' >> ${shellRc}`);
208
+ console.log(` source ${shellRc}`);
209
+ console.log('');
210
+ console.log(' For API key users (get key at https://plexor.dev/dashboard):');
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}`);
215
+ console.log('');
216
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
217
+ console.log(' │ Then start Claude Code and run: /plexor-status │');
218
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
219
+ console.log('');
220
+ console.log(' Available commands:');
221
+ console.log(' /plexor-status - Check connection and see savings');
222
+ console.log(' /plexor-mode - Switch modes (eco/balanced/quality)');
223
+ console.log(' /plexor-login - Authenticate with API key');
224
+ console.log(' /plexor-settings - View/modify settings');
225
+ console.log('');
226
+ console.log(' Documentation: https://plexor.dev/docs');
227
+ console.log('');
228
+
229
+ if (backed_up.length > 0) {
230
+ console.log(' Note: Existing files backed up (.backup):');
231
+ backed_up.forEach(f => console.log(` ${f}`));
232
+ console.log('');
233
+ }
234
+
235
+ } catch (error) {
236
+ console.error('');
237
+ console.error(' Plexor plugin installation failed');
238
+ console.error('');
239
+ console.error(` Error: ${error.message}`);
240
+ console.error('');
241
+ console.error(' Troubleshooting:');
242
+ console.error(' - Ensure you have write access to ~/.claude/commands/');
243
+ console.error(' - Try running with sudo if permission denied');
244
+ console.error('');
245
+ console.error(' Report issues: https://github.com/plexor-ai/claude-code-plugin/issues');
246
+ console.error('');
247
+ process.exit(1);
248
+ }
249
+ }
250
+
251
+ main();
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Plexor Claude Code Plugin - Uninstall Script
5
+ *
6
+ * Removes slash commands from ~/.claude/commands/
7
+ * Optionally restores backups if they exist.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+
14
+ const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
15
+ const CLAUDE_COMMANDS_DIR = path.join(os.homedir(), '.claude', 'commands');
16
+
17
+ function main() {
18
+ try {
19
+ // Get list of our command files
20
+ const files = fs.readdirSync(COMMANDS_SOURCE)
21
+ .filter(f => f.endsWith('.md'));
22
+
23
+ const removed = [];
24
+ const restored = [];
25
+
26
+ for (const file of files) {
27
+ const dest = path.join(CLAUDE_COMMANDS_DIR, file);
28
+ const backupPath = dest + '.backup';
29
+
30
+ if (fs.existsSync(dest)) {
31
+ fs.unlinkSync(dest);
32
+ removed.push(file.replace('.md', ''));
33
+
34
+ // Restore backup if it exists
35
+ if (fs.existsSync(backupPath)) {
36
+ fs.renameSync(backupPath, dest);
37
+ restored.push(file);
38
+ }
39
+ }
40
+ }
41
+
42
+ if (removed.length > 0) {
43
+ console.log('');
44
+ console.log(' Plexor plugin uninstalled');
45
+ console.log('');
46
+ console.log(' Removed commands:');
47
+ removed.forEach(cmd => console.log(` /${cmd}`));
48
+
49
+ if (restored.length > 0) {
50
+ console.log('');
51
+ console.log(' Restored from backup:');
52
+ restored.forEach(f => console.log(` ${f}`));
53
+ }
54
+
55
+ console.log('');
56
+ console.log(' Note: ~/.plexor/ config directory was preserved.');
57
+ console.log(' To remove it: rm -rf ~/.plexor');
58
+ console.log('');
59
+ }
60
+
61
+ } catch (error) {
62
+ // Don't fail uninstall on errors - just warn
63
+ console.warn(` Warning: Could not fully uninstall: ${error.message}`);
64
+ }
65
+ }
66
+
67
+ main();