@plexor-dev/claude-code-plugin 0.1.0-beta.23 → 0.1.0-beta.25

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.
@@ -3,11 +3,20 @@
3
3
  /**
4
4
  * Plexor Enabled Command
5
5
  * Enable or disable Plexor optimization proxy
6
+ *
7
+ * KEY FEATURE: When toggling on/off, automatically updates ~/.claude/settings.json
8
+ * to route or un-route ALL Claude Code sessions through Plexor gateway.
9
+ *
10
+ * THE DREAM: User just runs "/plexor-enabled off" to disable, "/plexor-enabled on" to enable.
11
+ * No manual environment variables or config updates required!
6
12
  */
7
13
 
8
14
  const fs = require('fs');
9
15
  const path = require('path');
10
16
 
17
+ // Import settings manager for automatic Claude Code configuration
18
+ const { settingsManager, PLEXOR_STAGING_URL, PLEXOR_PROD_URL } = require('../lib/settings-manager');
19
+
11
20
  const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
12
21
  const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
13
22
 
@@ -31,14 +40,23 @@ function main() {
31
40
  const args = process.argv.slice(2);
32
41
  const config = loadConfig();
33
42
  const currentEnabled = config.settings?.enabled ?? false;
43
+ const apiKey = config.auth?.api_key;
44
+
45
+ // Get current Claude settings.json routing status
46
+ const routingStatus = settingsManager.getRoutingStatus();
34
47
 
35
48
  // No args - show current status
36
49
  if (args.length === 0) {
37
50
  const status = currentEnabled ? '● Enabled' : '○ Disabled';
51
+ const routingStr = routingStatus.enabled ? '● Active' : '○ Inactive';
38
52
  console.log(`┌─────────────────────────────────────────────┐`);
39
53
  console.log(`│ Plexor Proxy Status │`);
40
54
  console.log(`├─────────────────────────────────────────────┤`);
41
- console.log(`│ Status: ${status.padEnd(34)}│`);
55
+ console.log(`│ Plugin Config: ${status.padEnd(27)}│`);
56
+ console.log(`│ Claude Routing: ${routingStr.padEnd(26)}│`);
57
+ if (routingStatus.enabled) {
58
+ console.log(`│ Endpoint: ${(routingStatus.isStaging ? 'Staging' : 'Production').padEnd(32)}│`);
59
+ }
42
60
  console.log(`├─────────────────────────────────────────────┤`);
43
61
  console.log(`│ Usage: │`);
44
62
  console.log(`│ /plexor-enabled true - Enable proxy │`);
@@ -62,21 +80,32 @@ function main() {
62
80
  process.exit(1);
63
81
  }
64
82
 
65
- if (newEnabled === currentEnabled) {
66
- const state = currentEnabled ? 'enabled' : 'disabled';
67
- console.log(`Plexor proxy is already ${state}`);
68
- return;
69
- }
70
-
83
+ // Update Plexor plugin config
71
84
  config.settings = config.settings || {};
72
85
  config.settings.enabled = newEnabled;
73
86
  saveConfig(config);
74
87
 
88
+ // THE KEY FEATURE: Update Claude Code settings.json routing
89
+ let routingUpdated = false;
90
+ if (newEnabled) {
91
+ // Enable routing - need API key from config
92
+ if (apiKey) {
93
+ const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
94
+ const useStaging = apiUrl.includes('staging');
95
+ routingUpdated = settingsManager.enablePlexorRouting(apiKey, { useStaging });
96
+ } else {
97
+ console.log('⚠ No API key found. Run /plexor-login first.');
98
+ }
99
+ } else {
100
+ // Disable routing - remove env vars from settings.json
101
+ routingUpdated = settingsManager.disablePlexorRouting();
102
+ }
103
+
75
104
  const newStatus = newEnabled ? '● Enabled' : '○ Disabled';
76
105
  const prevStatus = currentEnabled ? 'Enabled' : 'Disabled';
77
- const message = newEnabled
78
- ? 'All requests will be routed through Plexor'
79
- : 'Requests will go directly to providers';
106
+ const routingMsg = routingUpdated
107
+ ? (newEnabled ? 'Claude Code now routes through Plexor' : 'Claude Code now connects directly')
108
+ : 'Manual routing update may be needed';
80
109
 
81
110
  console.log(`┌─────────────────────────────────────────────┐`);
82
111
  console.log(`│ ✓ Plexor Proxy Updated │`);
@@ -84,7 +113,8 @@ function main() {
84
113
  console.log(`│ Previous: ${prevStatus.padEnd(32)}│`);
85
114
  console.log(`│ New: ${newStatus.padEnd(37)}│`);
86
115
  console.log(`├─────────────────────────────────────────────┤`);
87
- console.log(`│ ${message.padEnd(42)}│`);
116
+ console.log(`│ ${routingMsg.padEnd(42)}│`);
117
+ console.log(`│ Changes take effect immediately. │`);
88
118
  console.log(`└─────────────────────────────────────────────┘`);
89
119
  }
90
120
 
@@ -2,7 +2,10 @@
2
2
 
3
3
  /**
4
4
  * Plexor Login Command
5
- * Authenticate with Plexor API
5
+ * Authenticate with Plexor API and auto-configure Claude Code routing
6
+ *
7
+ * KEY FEATURE: After successful login, automatically configures ~/.claude/settings.json
8
+ * to route ALL Claude Code sessions through Plexor gateway.
6
9
  */
7
10
 
8
11
  const fs = require('fs');
@@ -10,6 +13,9 @@ const path = require('path');
10
13
  const https = require('https');
11
14
  const http = require('http');
12
15
 
16
+ // Import settings manager for automatic Claude Code configuration
17
+ const { settingsManager } = require('../lib/settings-manager');
18
+
13
19
  const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
14
20
  const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
15
21
  const DEFAULT_API_URL = 'https://api.plexor.dev';
@@ -141,6 +147,12 @@ async function main() {
141
147
  config.settings.enabled = true;
142
148
  saveConfig(config);
143
149
 
150
+ // AUTO-CONFIGURE CLAUDE CODE ROUTING
151
+ // This is the key feature: automatically set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN
152
+ // in ~/.claude/settings.json so ALL Claude Code sessions route through Plexor
153
+ const useStaging = apiUrl.includes('staging');
154
+ const routingEnabled = settingsManager.enablePlexorRouting(apiKey, { useStaging });
155
+
144
156
  const email = user.email || 'Unknown';
145
157
  const tier = user.tier?.name || 'Free';
146
158
 
@@ -150,7 +162,14 @@ async function main() {
150
162
  console.log(`│ Email: ${email.substring(0, 35).padEnd(35)}│`);
151
163
  console.log(`│ Tier: ${tier.padEnd(36)}│`);
152
164
  console.log(`├─────────────────────────────────────────────┤`);
153
- console.log(`│ Plexor proxy is now enabled. │`);
165
+ if (routingEnabled) {
166
+ console.log(`│ ✓ Claude Code routing: CONFIGURED │`);
167
+ console.log(`│ All sessions now route through Plexor │`);
168
+ } else {
169
+ console.log(`│ ⚠ Claude Code routing: MANUAL SETUP NEEDED │`);
170
+ console.log(`│ Set ANTHROPIC_BASE_URL in environment │`);
171
+ }
172
+ console.log(`├─────────────────────────────────────────────┤`);
154
173
  console.log(`│ Run /plexor-status to see your stats. │`);
155
174
  console.log(`└─────────────────────────────────────────────┘`);
156
175
  } catch (err) {
@@ -4,13 +4,16 @@ description: First-time setup wizard for Plexor with Claude Code (user)
4
4
 
5
5
  # Plexor Setup Wizard
6
6
 
7
- Guide users through first-time Plexor setup, handling both Claude MAX subscribers and API key users.
7
+ Guide users through first-time Plexor setup. **No manual environment variable configuration required!**
8
+
9
+ The plugin automatically configures `~/.claude/settings.json` to route all Claude Code sessions through Plexor.
8
10
 
9
11
  ## Steps
10
12
 
11
13
  **Step 1: Check if already configured**
12
14
 
13
15
  Use the Read tool to check if `~/.plexor/config.json` exists and has valid configuration.
16
+ Also check `~/.claude/settings.json` for routing status.
14
17
 
15
18
  If configured, show:
16
19
  ```
@@ -21,9 +24,11 @@ Already configured!
21
24
  API URL: [apiUrl from config]
22
25
  Mode: [mode from config]
23
26
  Status: [Enabled/Disabled]
27
+ Claude Routing: [Active/Inactive]
24
28
 
25
29
  Run /plexor-status to see your usage.
26
30
  Run /plexor-settings to modify configuration.
31
+ Run /plexor-enabled off to disable routing.
27
32
  ```
28
33
 
29
34
  **Step 2: Ask about Claude MAX subscription**
@@ -40,11 +45,17 @@ Options:
40
45
 
41
46
  If user selected "Yes, I have Claude MAX":
42
47
 
43
- 1. Use the Write tool to create `~/.plexor/config.json`:
48
+ 1. Ask for their Plexor API key:
49
+ "Please provide your Plexor API key (starts with 'plx_')."
50
+ "Get one at: https://plexor.dev/dashboard"
51
+ "Your MAX subscription will be used for Claude - the Plexor key is for tracking/optimization."
52
+
53
+ 2. Use the Write tool to create `~/.plexor/config.json`:
44
54
  ```json
45
55
  {
46
56
  "version": 1,
47
57
  "auth": {
58
+ "api_key": "[user's Plexor key]",
48
59
  "mode": "oauth_passthrough",
49
60
  "authenticated_at": "[current ISO timestamp]"
50
61
  },
@@ -57,24 +68,39 @@ If user selected "Yes, I have Claude MAX":
57
68
  }
58
69
  ```
59
70
 
60
- 2. Show the user:
71
+ 3. Use the Write tool to update `~/.claude/settings.json` env block:
72
+ ```json
73
+ {
74
+ "env": {
75
+ "ANTHROPIC_BASE_URL": "https://api.plexor.dev/gateway/anthropic",
76
+ "ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]"
77
+ }
78
+ }
79
+ ```
80
+ Note: Preserve any existing settings, just add/update the env block.
81
+
82
+ 4. Show the user:
61
83
  ```
62
84
  Plexor Setup - Claude MAX User
63
85
  ==============================
64
- Your Claude MAX subscription will be used with Plexor optimization.
86
+ Setup Complete! No manual configuration needed.
65
87
 
66
- Add this to your shell profile (~/.bashrc or ~/.zshrc):
67
-
68
- export ANTHROPIC_BASE_URL="https://api.plexor.dev/gateway/anthropic"
69
-
70
- Then restart your terminal or run: source ~/.bashrc
88
+ What was configured:
89
+ - ~/.plexor/config.json (Plexor plugin settings)
90
+ - ~/.claude/settings.json (automatic Claude Code routing)
71
91
 
72
92
  How it works:
73
- - Claude Code sends your OAuth token through Plexor
74
- - Plexor optimizes prompts and tracks usage
93
+ - All Claude Code sessions now route through Plexor
94
+ - Your MAX subscription OAuth token is passed through
75
95
  - You keep your MAX benefits ($0 cost, 20x rate limits)
96
+ - Plexor optimizes prompts and tracks usage
97
+
98
+ Commands:
99
+ - /plexor-status - See your usage stats
100
+ - /plexor-enabled off - Temporarily disable Plexor
101
+ - /plexor-enabled on - Re-enable Plexor
76
102
 
77
- Run /plexor-status to verify setup.
103
+ Changes take effect immediately in all Claude Code sessions!
78
104
  ```
79
105
 
80
106
  **Step 3B: API Key User Setup**
@@ -104,31 +130,43 @@ If user selected "No, I'll use a Plexor API key":
104
130
  }
105
131
  ```
106
132
 
107
- 3. Show the user:
133
+ 3. Use the Write tool to update `~/.claude/settings.json` env block:
134
+ ```json
135
+ {
136
+ "env": {
137
+ "ANTHROPIC_BASE_URL": "https://api.plexor.dev/gateway/anthropic",
138
+ "ANTHROPIC_AUTH_TOKEN": "[user's Plexor key]"
139
+ }
140
+ }
141
+ ```
142
+ Note: Preserve any existing settings, just add/update the env block.
143
+
144
+ 4. Show the user:
108
145
  ```
109
146
  Plexor Setup - API Key User
110
147
  ===========================
111
- Your Plexor API key has been configured.
112
-
113
- Add these lines to your shell profile (~/.bashrc or ~/.zshrc):
148
+ Setup Complete! No manual configuration needed.
114
149
 
115
- export ANTHROPIC_BASE_URL="https://api.plexor.dev/gateway/anthropic"
116
- export ANTHROPIC_API_KEY="[their plexor key]"
117
-
118
- Then restart your terminal or run: source ~/.bashrc
150
+ What was configured:
151
+ - ~/.plexor/config.json (Plexor plugin settings)
152
+ - ~/.claude/settings.json (automatic Claude Code routing)
119
153
 
120
154
  How it works:
121
- - Requests route through Plexor gateway
155
+ - All Claude Code sessions now route through Plexor
122
156
  - Plexor picks the best provider (can save up to 90%)
123
- - Your Plexor key goes in ANTHROPIC_API_KEY (this is correct!)
157
+ - Your usage is tracked and optimized
124
158
 
125
- Run /plexor-status to verify setup.
126
- ```
159
+ Commands:
160
+ - /plexor-status - See your usage and savings
161
+ - /plexor-mode eco - Maximize savings
162
+ - /plexor-mode quality - Maximize quality
163
+ - /plexor-enabled off - Temporarily disable Plexor
127
164
 
128
- **Step 4: Offer to auto-configure shell (optional)**
129
-
130
- Ask: "Would you like me to add this to your shell profile automatically?"
131
-
132
- If yes, use the Edit tool to append the export lines to `~/.bashrc` (or `~/.zshrc` if it exists).
165
+ Changes take effect immediately in all Claude Code sessions!
166
+ ```
133
167
 
134
- **IMPORTANT**: After completing setup, STOP. Do not run additional commands.
168
+ **IMPORTANT NOTES**:
169
+ - The `~/.claude/settings.json` env block is the KEY mechanism that routes Claude Code through Plexor
170
+ - ANTHROPIC_AUTH_TOKEN takes precedence over ANTHROPIC_API_KEY (use AUTH_TOKEN for the Plexor key)
171
+ - Changes take effect immediately - no shell restart needed
172
+ - After completing setup, STOP. Do not run additional commands.
@@ -0,0 +1,238 @@
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
+ const { useStaging = true } = options;
78
+ const apiUrl = useStaging ? PLEXOR_STAGING_URL : PLEXOR_PROD_URL;
79
+
80
+ try {
81
+ const settings = this.load();
82
+
83
+ // Initialize env block if doesn't exist
84
+ if (!settings.env) {
85
+ settings.env = {};
86
+ }
87
+
88
+ // Set the magic environment variables
89
+ // ANTHROPIC_AUTH_TOKEN has higher precedence than ANTHROPIC_API_KEY
90
+ settings.env.ANTHROPIC_BASE_URL = apiUrl;
91
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
92
+
93
+ const success = this.save(settings);
94
+
95
+ if (success) {
96
+ console.log(`✓ Plexor routing enabled`);
97
+ console.log(` Base URL: ${apiUrl}`);
98
+ console.log(` API Key: ${apiKey.substring(0, 12)}...`);
99
+ console.log(`\n All Claude Code sessions will now route through Plexor.`);
100
+ console.log(` Changes take effect immediately (no restart needed).`);
101
+ }
102
+
103
+ return success;
104
+ } catch (err) {
105
+ console.error('Failed to enable Plexor routing:', err.message);
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Disable Plexor routing by removing env vars from settings.json
112
+ *
113
+ * This restores direct connection to Anthropic API.
114
+ *
115
+ * @returns {boolean} success status
116
+ */
117
+ disablePlexorRouting() {
118
+ try {
119
+ const settings = this.load();
120
+
121
+ if (!settings.env) {
122
+ console.log('Plexor routing is not currently enabled.');
123
+ return true;
124
+ }
125
+
126
+ // Remove Plexor-specific env vars
127
+ delete settings.env.ANTHROPIC_BASE_URL;
128
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
129
+
130
+ // Clean up empty env block
131
+ if (Object.keys(settings.env).length === 0) {
132
+ delete settings.env;
133
+ }
134
+
135
+ const success = this.save(settings);
136
+
137
+ if (success) {
138
+ console.log('✓ Plexor routing disabled');
139
+ console.log(' Claude Code will now connect directly to Anthropic.');
140
+ console.log(' Changes take effect immediately (no restart needed).');
141
+ }
142
+
143
+ return success;
144
+ } catch (err) {
145
+ console.error('Failed to disable Plexor routing:', err.message);
146
+ return false;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Get current routing status
152
+ * @returns {Object} { enabled: boolean, baseUrl: string|null, hasToken: boolean }
153
+ */
154
+ getRoutingStatus() {
155
+ try {
156
+ const settings = this.load();
157
+
158
+ const baseUrl = settings.env?.ANTHROPIC_BASE_URL || null;
159
+ const hasToken = !!settings.env?.ANTHROPIC_AUTH_TOKEN;
160
+
161
+ // Check if routing to Plexor
162
+ const isPlexorRouting = baseUrl && (
163
+ baseUrl.includes('plexor') ||
164
+ baseUrl.includes('staging.api')
165
+ );
166
+
167
+ return {
168
+ enabled: isPlexorRouting,
169
+ baseUrl,
170
+ hasToken,
171
+ isStaging: baseUrl?.includes('staging') || false,
172
+ tokenPreview: hasToken ? settings.env.ANTHROPIC_AUTH_TOKEN.substring(0, 12) + '...' : null
173
+ };
174
+ } catch {
175
+ return { enabled: false, baseUrl: null, hasToken: false };
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Update just the API key without changing other settings
181
+ * @param {string} apiKey - new Plexor API key
182
+ * @returns {boolean} success status
183
+ */
184
+ updateApiKey(apiKey) {
185
+ try {
186
+ const settings = this.load();
187
+
188
+ if (!settings.env) {
189
+ settings.env = {};
190
+ }
191
+
192
+ settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
193
+ return this.save(settings);
194
+ } catch (err) {
195
+ console.error('Failed to update API key:', err.message);
196
+ return false;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Switch between staging and production
202
+ * @param {boolean} useStaging - true for staging, false for production
203
+ * @returns {boolean} success status
204
+ */
205
+ setEnvironment(useStaging) {
206
+ try {
207
+ const settings = this.load();
208
+
209
+ if (!settings.env?.ANTHROPIC_BASE_URL) {
210
+ console.log('Plexor routing is not enabled. Run /plexor-login first.');
211
+ return false;
212
+ }
213
+
214
+ settings.env.ANTHROPIC_BASE_URL = useStaging ? PLEXOR_STAGING_URL : PLEXOR_PROD_URL;
215
+
216
+ const success = this.save(settings);
217
+ if (success) {
218
+ console.log(`✓ Switched to ${useStaging ? 'staging' : 'production'} environment`);
219
+ }
220
+ return success;
221
+ } catch (err) {
222
+ console.error('Failed to switch environment:', err.message);
223
+ return false;
224
+ }
225
+ }
226
+ }
227
+
228
+ // Export singleton instance and class
229
+ const settingsManager = new ClaudeSettingsManager();
230
+
231
+ module.exports = {
232
+ ClaudeSettingsManager,
233
+ settingsManager,
234
+ CLAUDE_DIR,
235
+ SETTINGS_PATH,
236
+ PLEXOR_STAGING_URL,
237
+ PLEXOR_PROD_URL
238
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexor-dev/claude-code-plugin",
3
- "version": "0.1.0-beta.23",
3
+ "version": "0.1.0-beta.25",
4
4
  "description": "LLM cost optimization plugin for Claude Code - Save up to 90% on AI costs",
5
5
  "main": "lib/constants.js",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
12
  const os = require('os');
13
+ const { execSync } = require('child_process');
13
14
 
14
15
  /**
15
16
  * Get the correct home directory, accounting for sudo.
@@ -30,6 +31,43 @@ function getHomeDir() {
30
31
  return os.homedir();
31
32
  }
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
+
33
71
  const HOME_DIR = getHomeDir();
34
72
  const COMMANDS_SOURCE = path.join(__dirname, '..', 'commands');
35
73
  const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands');
@@ -54,6 +92,9 @@ const DEFAULT_CONFIG = {
54
92
 
55
93
  function main() {
56
94
  try {
95
+ // Get target user info for chown (if running with sudo)
96
+ const targetUser = getTargetUserIds();
97
+
57
98
  // Create ~/.claude/commands/ if not exists
58
99
  fs.mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
59
100
 
@@ -120,6 +161,16 @@ function main() {
120
161
  jsInstalled.push(file);
121
162
  }
122
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
+
123
174
  // Detect shell type
124
175
  const shell = process.env.SHELL || '';
125
176
  const isZsh = shell.includes('zsh');
@@ -141,6 +192,9 @@ function main() {
141
192
  if (jsInstalled.length > 0) {
142
193
  console.log(` ✓ Installed ${jsInstalled.length} executors to ~/.claude/plugins/plexor/`);
143
194
  }
195
+ if (targetUser) {
196
+ console.log(` ✓ Set file ownership to ${targetUser.user}`);
197
+ }
144
198
  console.log('');
145
199
 
146
200
  // CRITICAL: Make the required step VERY obvious