@plexor-dev/claude-code-plugin-localhost 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.
@@ -0,0 +1,294 @@
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
+ // Default configuration for new installs
81
+ // LOCALHOST PACKAGE - uses ngrok tunnel URL (replaced by configure script)
82
+ const DEFAULT_CONFIG = {
83
+ version: 1,
84
+ auth: {
85
+ mode: "pending",
86
+ authenticated_at: null
87
+ },
88
+ settings: {
89
+ enabled: true,
90
+ apiUrl: "LOCALHOST_TUNNEL_URL",
91
+ mode: "balanced",
92
+ localCacheEnabled: true
93
+ }
94
+ };
95
+
96
+ function main() {
97
+ try {
98
+ // Get target user info for chown (if running with sudo)
99
+ const targetUser = getTargetUserIds();
100
+
101
+ // Create ~/.claude/commands/ if not exists
102
+ fs.mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
103
+
104
+ // Create ~/.claude/plugins/plexor/commands/ for JS executors
105
+ fs.mkdirSync(PLEXOR_PLUGINS_DIR, { recursive: true });
106
+
107
+ // Create ~/.claude/plugins/plexor/lib/ for shared modules
108
+ fs.mkdirSync(PLEXOR_LIB_DIR, { recursive: true });
109
+
110
+ // Create ~/.plexor/ with secure permissions (owner only)
111
+ fs.mkdirSync(PLEXOR_CONFIG_DIR, { recursive: true, mode: 0o700 });
112
+
113
+ // Create default config.json if it doesn't exist
114
+ let configCreated = false;
115
+ if (!fs.existsSync(PLEXOR_CONFIG_FILE)) {
116
+ fs.writeFileSync(
117
+ PLEXOR_CONFIG_FILE,
118
+ JSON.stringify(DEFAULT_CONFIG, null, 2),
119
+ { mode: 0o600 }
120
+ );
121
+ configCreated = true;
122
+ }
123
+
124
+ // Get list of command files (.md for Claude, .js for executors)
125
+ const mdFiles = fs.readdirSync(COMMANDS_SOURCE)
126
+ .filter(f => f.endsWith('.md'));
127
+ const jsFiles = fs.readdirSync(COMMANDS_SOURCE)
128
+ .filter(f => f.endsWith('.js'));
129
+
130
+ if (mdFiles.length === 0) {
131
+ console.error('No command files found in package. Installation may be corrupt.');
132
+ process.exit(1);
133
+ }
134
+
135
+ const installed = [];
136
+ const backed_up = [];
137
+
138
+ // Copy .md command files to ~/.claude/commands/
139
+ for (const file of mdFiles) {
140
+ const src = path.join(COMMANDS_SOURCE, file);
141
+ const dest = path.join(CLAUDE_COMMANDS_DIR, file);
142
+
143
+ // Backup existing file if present and different
144
+ if (fs.existsSync(dest)) {
145
+ const existingContent = fs.readFileSync(dest, 'utf8');
146
+ const newContent = fs.readFileSync(src, 'utf8');
147
+
148
+ if (existingContent !== newContent) {
149
+ const backupPath = dest + '.backup';
150
+ fs.copyFileSync(dest, backupPath);
151
+ backed_up.push(file);
152
+ }
153
+ }
154
+
155
+ fs.copyFileSync(src, dest);
156
+ installed.push(file.replace('.md', ''));
157
+ }
158
+
159
+ // Copy .js executor files to ~/.claude/plugins/plexor/commands/
160
+ const jsInstalled = [];
161
+ for (const file of jsFiles) {
162
+ const src = path.join(COMMANDS_SOURCE, file);
163
+ const dest = path.join(PLEXOR_PLUGINS_DIR, file);
164
+ fs.copyFileSync(src, dest);
165
+ // Make executable
166
+ fs.chmodSync(dest, 0o755);
167
+ jsInstalled.push(file);
168
+ }
169
+
170
+ // Copy lib files to ~/.claude/plugins/plexor/lib/
171
+ // CRITICAL: These are required for commands to work
172
+ const libInstalled = [];
173
+ if (fs.existsSync(LIB_SOURCE)) {
174
+ const libFiles = fs.readdirSync(LIB_SOURCE).filter(f => f.endsWith('.js'));
175
+ if (libFiles.length === 0) {
176
+ console.warn(' ⚠ Warning: No lib files found in package. Commands may not work.');
177
+ }
178
+ for (const file of libFiles) {
179
+ try {
180
+ const src = path.join(LIB_SOURCE, file);
181
+ const dest = path.join(PLEXOR_LIB_DIR, file);
182
+ fs.copyFileSync(src, dest);
183
+ libInstalled.push(file);
184
+ } catch (err) {
185
+ console.error(` ✗ Failed to copy lib/${file}: ${err.message}`);
186
+ }
187
+ }
188
+ } else {
189
+ console.error(' ✗ CRITICAL: lib/ directory not found in package.');
190
+ console.error(' Commands will fail. Please reinstall the package.');
191
+ console.error(` Expected location: ${LIB_SOURCE}`);
192
+ }
193
+
194
+ // Verify critical lib file exists
195
+ const criticalLibFile = path.join(PLEXOR_LIB_DIR, 'settings-manager.js');
196
+ if (!fs.existsSync(criticalLibFile)) {
197
+ console.error('');
198
+ console.error(' ✗ CRITICAL: settings-manager.js was not installed.');
199
+ console.error(' This file is required for commands to work.');
200
+ console.error(' Try reinstalling: npm install @plexor-dev/claude-code-plugin-localhost');
201
+ console.error('');
202
+ }
203
+
204
+ // Fix file ownership when running with sudo
205
+ // Files are created as root but should be owned by the original user
206
+ if (targetUser) {
207
+ const { uid, gid } = targetUser;
208
+ // Chown the .claude directory and all contents we created
209
+ chownRecursive(path.join(HOME_DIR, '.claude'), uid, gid);
210
+ // Chown the .plexor directory and all contents
211
+ chownRecursive(PLEXOR_CONFIG_DIR, uid, gid);
212
+ }
213
+
214
+ // Detect shell type
215
+ const shell = process.env.SHELL || '';
216
+ const isZsh = shell.includes('zsh');
217
+ const shellRc = isZsh ? '~/.zshrc' : '~/.bashrc';
218
+
219
+ // Print success message with clear onboarding steps
220
+ console.log('');
221
+ console.log(' ╔═══════════════════════════════════════════════════════════════════╗');
222
+ console.log(' ║ ║');
223
+ console.log(' ║ Plexor Claude Code Plugin installed successfully! ║');
224
+ console.log(' ║ ║');
225
+ console.log(' ╚═══════════════════════════════════════════════════════════════════╝');
226
+ console.log('');
227
+
228
+ if (configCreated) {
229
+ console.log(' ✓ Created ~/.plexor/config.json');
230
+ }
231
+ console.log(` ✓ Installed ${installed.length} slash commands to ~/.claude/commands/`);
232
+ if (jsInstalled.length > 0) {
233
+ console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/commands/`);
234
+ }
235
+ if (libInstalled.length > 0) {
236
+ console.log(` ✓ Installed ${libInstalled.length} lib modules to ~/.claude/plugins/plexor/lib/`);
237
+ }
238
+ if (targetUser) {
239
+ console.log(` ✓ Set file ownership to ${targetUser.user}`);
240
+ }
241
+ console.log('');
242
+
243
+ // CRITICAL: Make the required step VERY obvious
244
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
245
+ console.log(' │ REQUIRED: Run this command to enable Plexor routing: │');
246
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
247
+ console.log('');
248
+ console.log(' For LOCALHOST testing (tunnel URL):');
249
+ console.log('');
250
+ console.log(` echo 'export ANTHROPIC_BASE_URL="LOCALHOST_TUNNEL_URL/gateway/anthropic"' >> ${shellRc}`);
251
+ console.log(` source ${shellRc}`);
252
+ console.log('');
253
+ console.log(' For API key users (use your test account key):');
254
+ console.log('');
255
+ console.log(` echo 'export ANTHROPIC_BASE_URL="LOCALHOST_TUNNEL_URL/gateway/anthropic"' >> ${shellRc}`);
256
+ console.log(` echo 'export ANTHROPIC_API_KEY="plx_your_test_key_here"' >> ${shellRc}`);
257
+ console.log(` source ${shellRc}`);
258
+ console.log('');
259
+ console.log(' ┌─────────────────────────────────────────────────────────────────┐');
260
+ console.log(' │ Then start Claude Code and run: /plexor-status │');
261
+ console.log(' └─────────────────────────────────────────────────────────────────┘');
262
+ console.log('');
263
+ console.log(' Available commands:');
264
+ console.log(' /plexor-setup - First-time setup wizard');
265
+ console.log(' /plexor-login - Authenticate with API key');
266
+ console.log(' /plexor-status - Check connection and see savings');
267
+ console.log(' /plexor-enabled - Enable/disable Plexor routing');
268
+ console.log('');
269
+ console.log(' LOCALHOST DEV TESTING PLUGIN - Not for production use');
270
+ console.log('');
271
+
272
+ if (backed_up.length > 0) {
273
+ console.log(' Note: Existing files backed up (.backup):');
274
+ backed_up.forEach(f => console.log(` ${f}`));
275
+ console.log('');
276
+ }
277
+
278
+ } catch (error) {
279
+ console.error('');
280
+ console.error(' Plexor plugin installation failed');
281
+ console.error('');
282
+ console.error(` Error: ${error.message}`);
283
+ console.error('');
284
+ console.error(' Troubleshooting:');
285
+ console.error(' - Ensure you have write access to ~/.claude/commands/');
286
+ console.error(' - Try running with sudo if permission denied');
287
+ console.error('');
288
+ console.error(' Report issues: https://github.com/plexor-ai/claude-code-plugin/issues');
289
+ console.error('');
290
+ process.exit(1);
291
+ }
292
+ }
293
+
294
+ main();
@@ -0,0 +1,97 @@
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
+ // Import settings manager for Claude Code routing cleanup
15
+ let settingsManager;
16
+ try {
17
+ const lib = require('../lib/settings-manager');
18
+ settingsManager = lib.settingsManager;
19
+ } catch (err) {
20
+ // If settings manager can't be loaded during uninstall, continue anyway
21
+ settingsManager = null;
22
+ }
23
+
24
+ const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
25
+ const CLAUDE_COMMANDS_DIR = path.join(os.homedir(), '.claude', 'commands');
26
+
27
+ function main() {
28
+ try {
29
+ // CRITICAL: Disable Claude Code routing before removing commands
30
+ // This ensures users don't get stuck with Plexor routing after uninstall
31
+ let routingDisabled = false;
32
+ if (settingsManager) {
33
+ try {
34
+ routingDisabled = settingsManager.disablePlexorRouting();
35
+ } catch (e) {
36
+ // Continue with uninstall even if routing cleanup fails
37
+ }
38
+ }
39
+
40
+ // Get list of our command files
41
+ const files = fs.readdirSync(COMMANDS_SOURCE)
42
+ .filter(f => f.endsWith('.md'));
43
+
44
+ const removed = [];
45
+ const restored = [];
46
+
47
+ for (const file of files) {
48
+ const dest = path.join(CLAUDE_COMMANDS_DIR, file);
49
+ const backupPath = dest + '.backup';
50
+
51
+ if (fs.existsSync(dest)) {
52
+ fs.unlinkSync(dest);
53
+ removed.push(file.replace('.md', ''));
54
+
55
+ // Restore backup if it exists
56
+ if (fs.existsSync(backupPath)) {
57
+ fs.renameSync(backupPath, dest);
58
+ restored.push(file);
59
+ }
60
+ }
61
+ }
62
+
63
+ if (removed.length > 0 || routingDisabled) {
64
+ console.log('');
65
+ console.log(' Plexor plugin uninstalled');
66
+ console.log('');
67
+
68
+ if (routingDisabled) {
69
+ console.log(' ✓ Claude Code routing disabled');
70
+ console.log(' (Claude Code now connects directly to Anthropic)');
71
+ console.log('');
72
+ }
73
+
74
+ if (removed.length > 0) {
75
+ console.log(' Removed commands:');
76
+ removed.forEach(cmd => console.log(` /${cmd}`));
77
+ }
78
+
79
+ if (restored.length > 0) {
80
+ console.log('');
81
+ console.log(' Restored from backup:');
82
+ restored.forEach(f => console.log(` ${f}`));
83
+ }
84
+
85
+ console.log('');
86
+ console.log(' Note: ~/.plexor/ config directory was preserved.');
87
+ console.log(' To remove it: rm -rf ~/.plexor');
88
+ console.log('');
89
+ }
90
+
91
+ } catch (error) {
92
+ // Don't fail uninstall on errors - just warn
93
+ console.warn(` Warning: Could not fully uninstall: ${error.message}`);
94
+ }
95
+ }
96
+
97
+ main();