@plexor-dev/claude-code-plugin-localhost 0.1.0-localhost.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,342 @@
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 LIB_SOURCE = path.join(__dirname, '..', 'lib');
74
+ const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands');
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');
77
+ const PLEXOR_CONFIG_DIR = path.join(HOME_DIR, '.plexor');
78
+ const PLEXOR_CONFIG_FILE = path.join(PLEXOR_CONFIG_DIR, 'config.json');
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
+
125
+ // Default configuration for new installs
126
+ // LOCALHOST PACKAGE
127
+ const DEFAULT_CONFIG = {
128
+ version: 1,
129
+ auth: {
130
+ mode: "pending",
131
+ authenticated_at: null
132
+ },
133
+ settings: {
134
+ enabled: true,
135
+ apiUrl: "http://localhost:8000",
136
+ mode: "balanced",
137
+ localCacheEnabled: true
138
+ }
139
+ };
140
+
141
+ function main() {
142
+ // Check for orphaned routing at start of postinstall
143
+ checkOrphanedRouting();
144
+
145
+ try {
146
+ // Get target user info for chown (if running with sudo)
147
+ const targetUser = getTargetUserIds();
148
+
149
+ // Create ~/.claude/commands/ if not exists
150
+ fs.mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
151
+
152
+ // Create ~/.claude/plugins/plexor/commands/ for JS executors
153
+ fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
154
+
155
+ // Create ~/.claude/plugins/plexor/lib/ for shared modules
156
+ fs.mkdirSync(PLEXOR_LIB_DIR, { recursive: true });
157
+
158
+ // Create ~/.plexor/ with secure permissions (owner only)
159
+ fs.mkdirSync(PLEXOR_CONFIG_DIR, { recursive: true, mode: 0o700 });
160
+
161
+ // Create default config.json if it doesn't exist
162
+ let configCreated = false;
163
+ if (!fs.existsSync(PLEXOR_CONFIG_FILE)) {
164
+ fs.writeFileSync(
165
+ PLEXOR_CONFIG_FILE,
166
+ JSON.stringify(DEFAULT_CONFIG, null, 2),
167
+ { mode: 0o600 }
168
+ );
169
+ configCreated = true;
170
+ }
171
+
172
+ // Get list of command files (.md for Claude, .js for executors)
173
+ const mdFiles = fs.readdirSync(COMMANDS_SOURCE)
174
+ .filter(f => f.endsWith('.md'));
175
+ const jsFiles = fs.readdirSync(COMMANDS_SOURCE)
176
+ .filter(f => f.endsWith('.js'));
177
+
178
+ if (mdFiles.length === 0) {
179
+ console.error('No command files found in package. Installation may be corrupt.');
180
+ process.exit(1);
181
+ }
182
+
183
+ const installed = [];
184
+ const backed_up = [];
185
+
186
+ // Copy .md command files to ~/.claude/commands/
187
+ for (const file of mdFiles) {
188
+ const src = path.join(COMMANDS_SOURCE, file);
189
+ const dest = path.join(CLAUDE_COMMANDS_DIR, file);
190
+
191
+ // Backup existing file if present and different
192
+ if (fs.existsSync(dest)) {
193
+ const existingContent = fs.readFileSync(dest, 'utf8');
194
+ const newContent = fs.readFileSync(src, 'utf8');
195
+
196
+ if (existingContent !== newContent) {
197
+ const backupPath = dest + '.backup';
198
+ fs.copyFileSync(dest, backupPath);
199
+ backed_up.push(file);
200
+ }
201
+ }
202
+
203
+ fs.copyFileSync(src, dest);
204
+ installed.push(file.replace('.md', ''));
205
+ }
206
+
207
+ // Copy .js executor files to ~/.claude/plugins/plexor/commands/
208
+ const jsInstalled = [];
209
+ for (const file of jsFiles) {
210
+ const src = path.join(COMMANDS_SOURCE, file);
211
+ const dest = path.join(PLEXOR_PLUGINS_DIR, file);
212
+ fs.copyFileSync(src, dest);
213
+ // Make executable
214
+ fs.chmodSync(dest, 0o755);
215
+ jsInstalled.push(file);
216
+ }
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-localhost');
249
+ console.error('');
250
+ }
251
+
252
+ // Fix file ownership when running with sudo
253
+ // Files are created as root but should be owned by the original user
254
+ if (targetUser) {
255
+ const { uid, gid } = targetUser;
256
+ // Chown the .claude directory and all contents we created
257
+ chownRecursive(path.join(HOME_DIR, '.claude'), uid, gid);
258
+ // Chown the .plexor directory and all contents
259
+ chownRecursive(PLEXOR_CONFIG_DIR, uid, gid);
260
+ }
261
+
262
+ // Detect shell type
263
+ const shell = process.env.SHELL || '';
264
+ const isZsh = shell.includes('zsh');
265
+ const shellRc = isZsh ? '~/.zshrc' : '~/.bashrc';
266
+
267
+ // Print success message with clear onboarding steps
268
+ console.log('');
269
+ console.log(' ╔═══════════════════════════════════════════════════════════════════╗');
270
+ console.log(' ║ ║');
271
+ console.log(' ║ Plexor Claude Code Plugin installed successfully! ║');
272
+ console.log(' ║ ║');
273
+ console.log(' ╚═══════════════════════════════════════════════════════════════════╝');
274
+ console.log('');
275
+
276
+ if (configCreated) {
277
+ console.log(' ✓ Created ~/.plexor/config.json');
278
+ }
279
+ console.log(` ✓ Installed ${installed.length} slash commands to ~/.claude/commands/`);
280
+ if (jsInstalled.length > 0) {
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/`);
285
+ }
286
+ if (targetUser) {
287
+ console.log(` ✓ Set file ownership to ${targetUser.user}`);
288
+ }
289
+ console.log('');
290
+
291
+ // CRITICAL: Make the required step VERY obvious
292
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
293
+ console.log(' │ REQUIRED: Run this command to enable Plexor routing: │');
294
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
295
+ console.log('');
296
+ console.log(' For Claude MAX users (OAuth):');
297
+ console.log('');
298
+ console.log(` echo 'export ANTHROPIC_BASE_URL="http://localhost:8000/gateway/anthropic"' >> ${shellRc}`);
299
+ console.log(` source ${shellRc}`);
300
+ console.log('');
301
+ console.log(' For API key users (get key at https://plexor.dev/dashboard):');
302
+ console.log('');
303
+ console.log(` echo 'export ANTHROPIC_BASE_URL="http://localhost:8000/gateway/anthropic"' >> ${shellRc}`);
304
+ console.log(` echo 'export ANTHROPIC_API_KEY="plx_your_key_here"' >> ${shellRc}`);
305
+ console.log(` source ${shellRc}`);
306
+ console.log('');
307
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
308
+ console.log(' │ Then start Claude Code and run: /plexor-status │');
309
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
310
+ console.log('');
311
+ console.log(' Available commands:');
312
+ console.log(' /plexor-setup - First-time setup wizard');
313
+ console.log(' /plexor-login - Authenticate with API key');
314
+ console.log(' /plexor-status - Check connection and see savings');
315
+ console.log(' /plexor-enabled - Enable/disable Plexor routing');
316
+ console.log('');
317
+ console.log(' Documentation: https://plexor.dev/docs');
318
+ console.log('');
319
+
320
+ if (backed_up.length > 0) {
321
+ console.log(' Note: Existing files backed up (.backup):');
322
+ backed_up.forEach(f => console.log(` ${f}`));
323
+ console.log('');
324
+ }
325
+
326
+ } catch (error) {
327
+ console.error('');
328
+ console.error(' Plexor plugin installation failed');
329
+ console.error('');
330
+ console.error(` Error: ${error.message}`);
331
+ console.error('');
332
+ console.error(' Troubleshooting:');
333
+ console.error(' - Ensure you have write access to ~/.claude/commands/');
334
+ console.error(' - Try running with sudo if permission denied');
335
+ console.error('');
336
+ console.error(' Report issues: https://github.com/plexor-ai/claude-code-plugin/issues');
337
+ console.error('');
338
+ process.exit(1);
339
+ }
340
+ }
341
+
342
+ main();
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Plexor Claude Code Plugin (Staging) - Comprehensive Uninstall Script
5
+ *
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
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
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
+ };
36
+
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
+ }
67
+
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
+ ];
82
+
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';
89
+
90
+ if (fs.existsSync(cmdPath)) {
91
+ fs.unlinkSync(cmdPath);
92
+ results.commands.push(cmd.replace('.md', ''));
93
+
94
+ // Restore backup if it exists
95
+ if (fs.existsSync(backupPath)) {
96
+ fs.renameSync(backupPath, cmdPath);
97
+ results.restored.push(cmd);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ } catch (e) {
103
+ console.log(` Warning: Could not clean commands: ${e.message}`);
104
+ }
105
+
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
+ }
116
+
117
+ // Output results
118
+ if (results.routing || results.commands.length > 0 || results.pluginDir) {
119
+ console.log(' Plexor plugin uninstalled');
120
+ console.log('');
121
+
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('');
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('');
151
+ }
152
+
153
+ console.log(' Cleanup complete');
154
+ console.log('');