@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,404 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Plexor Status Command
5
+ * Displays formatted status with usage statistics
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const https = require('https');
11
+ const http = require('http');
12
+
13
+ // Import centralized constants with HOME directory validation
14
+ const { HOME_DIR, CONFIG_PATH, SESSION_PATH, SESSION_TIMEOUT_MS } = require('../lib/constants');
15
+ const CLAUDE_SETTINGS_PATH = path.join(HOME_DIR, '.claude', 'settings.json');
16
+
17
+ /**
18
+ * Check if Claude Code is actually routing through Plexor
19
+ * by reading ~/.claude/settings.json
20
+ */
21
+ function getRoutingStatus() {
22
+ try {
23
+ const data = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
24
+ const settings = JSON.parse(data);
25
+ const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
26
+ const hasToken = !!settings.env?.ANTHROPIC_AUTH_TOKEN;
27
+ const isPlexorRouting = baseUrl.includes('plexor') || baseUrl.includes('staging.api') || baseUrl.includes('localhost');
28
+ return {
29
+ active: isPlexorRouting && hasToken,
30
+ baseUrl,
31
+ isStaging: baseUrl.includes('staging')
32
+ };
33
+ } catch {
34
+ return { active: false, baseUrl: null, isStaging: false };
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Detect partial routing state where URL points to Plexor but auth is missing/invalid
40
+ * This can cause confusing auth errors for users
41
+ * @returns {Object} { partial: boolean, issue: string|null }
42
+ */
43
+ function detectPartialState() {
44
+ try {
45
+ const data = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8');
46
+ const settings = JSON.parse(data);
47
+ const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
48
+ const authToken = settings.env?.ANTHROPIC_AUTH_TOKEN || '';
49
+ const isPlexorUrl = baseUrl.includes('plexor') || baseUrl.includes('staging.api') || baseUrl.includes('localhost');
50
+
51
+ if (isPlexorUrl && !authToken) {
52
+ return { partial: true, issue: 'Plexor URL set but no auth token' };
53
+ }
54
+ if (isPlexorUrl && !authToken.startsWith('plx_')) {
55
+ return { partial: true, issue: 'Plexor URL set but auth token is not a Plexor key' };
56
+ }
57
+ return { partial: false, issue: null };
58
+ } catch {
59
+ return { partial: false, issue: null };
60
+ }
61
+ }
62
+
63
+ function loadSessionStats() {
64
+ try {
65
+ const data = fs.readFileSync(SESSION_PATH, 'utf8');
66
+ const session = JSON.parse(data);
67
+ // Check if session has expired
68
+ if (Date.now() - session.last_activity > SESSION_TIMEOUT_MS) {
69
+ return null;
70
+ }
71
+ return session;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Validate API key format
79
+ * @param {string} key - API key to validate
80
+ * @returns {boolean} true if valid format
81
+ */
82
+ function isValidApiKeyFormat(key) {
83
+ return key && typeof key === 'string' && key.startsWith('plx_') && key.length >= 20;
84
+ }
85
+
86
+ /**
87
+ * Load config file with integrity checking
88
+ * @returns {Object|null} config object or null if invalid
89
+ */
90
+ function loadConfig() {
91
+ try {
92
+ if (!fs.existsSync(CONFIG_PATH)) {
93
+ return null;
94
+ }
95
+ const data = fs.readFileSync(CONFIG_PATH, 'utf8');
96
+ if (!data || data.trim() === '') {
97
+ return null;
98
+ }
99
+ const config = JSON.parse(data);
100
+ if (typeof config !== 'object' || config === null) {
101
+ return null;
102
+ }
103
+ return config;
104
+ } catch (err) {
105
+ if (err instanceof SyntaxError) {
106
+ console.log('Config file is corrupted. Run /plexor-login to reconfigure.');
107
+ }
108
+ return null;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check for environment mismatch between config and routing
114
+ */
115
+ function checkEnvironmentMismatch(configApiUrl, routingBaseUrl) {
116
+ if (!configApiUrl || !routingBaseUrl) return null;
117
+
118
+ const configIsStaging = configApiUrl.includes('staging');
119
+ const routingIsStaging = routingBaseUrl.includes('staging');
120
+
121
+ if (configIsStaging !== routingIsStaging) {
122
+ return {
123
+ config: configIsStaging ? 'staging' : 'production',
124
+ routing: routingIsStaging ? 'staging' : 'production'
125
+ };
126
+ }
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Check for state mismatch between config.json enabled flag and settings.json routing
132
+ * @param {boolean} configEnabled - enabled flag from config.json
133
+ * @param {boolean} routingActive - whether settings.json has Plexor routing configured
134
+ * @returns {Object|null} mismatch details or null if states are consistent
135
+ */
136
+ function checkStateMismatch(configEnabled, routingActive) {
137
+ if (configEnabled && !routingActive) {
138
+ return {
139
+ type: 'config-enabled-routing-inactive',
140
+ message: 'Config shows enabled but Claude routing is not configured',
141
+ suggestion: 'Run /plexor-enabled true to sync and configure routing'
142
+ };
143
+ }
144
+ if (!configEnabled && routingActive) {
145
+ return {
146
+ type: 'config-disabled-routing-active',
147
+ message: 'Config shows disabled but Claude routing is active',
148
+ suggestion: 'Run /plexor-enabled false to sync and disable routing'
149
+ };
150
+ }
151
+ return null;
152
+ }
153
+
154
+ async function main() {
155
+ // Read config with integrity checking
156
+ const config = loadConfig();
157
+ if (!config) {
158
+ console.log('Not configured. Run /plexor-login first.');
159
+ process.exit(1);
160
+ }
161
+
162
+ const apiKey = config.auth?.api_key;
163
+ const enabled = config.settings?.enabled ?? false;
164
+ const mode = config.settings?.mode || 'balanced';
165
+ const provider = config.settings?.preferred_provider || 'auto';
166
+ const localCache = config.settings?.localCacheEnabled ?? false;
167
+ const apiUrl = config.settings?.apiUrl || 'http://localhost:8000';
168
+
169
+ if (!apiKey) {
170
+ console.log('Not authenticated. Run /plexor-login first.');
171
+ process.exit(1);
172
+ }
173
+
174
+ // Validate API key format
175
+ if (!isValidApiKeyFormat(apiKey)) {
176
+ console.log('Invalid API key format. Keys must start with "plx_" and be at least 20 characters.');
177
+ console.log('Run /plexor-login with a valid API key.');
178
+ process.exit(1);
179
+ }
180
+
181
+ // Fetch user info and stats
182
+ let user = { email: 'Unknown', tier: { name: 'Free', limits: {} } };
183
+ let stats = { period: {}, summary: {} };
184
+
185
+ try {
186
+ [user, stats] = await Promise.all([
187
+ fetchJson(apiUrl, '/v1/user', apiKey),
188
+ fetchJson(apiUrl, '/v1/stats', apiKey)
189
+ ]);
190
+ } catch (err) {
191
+ // Continue with defaults if API fails
192
+ }
193
+
194
+ // Load session stats
195
+ const session = loadSessionStats();
196
+
197
+ // Extract data
198
+ const email = user.email || 'Unknown';
199
+ const tierName = user.tier?.name || 'Free';
200
+ const monthlyOpts = user.tier?.limits?.monthly_optimizations || '∞';
201
+ const monthlyComps = user.tier?.limits?.monthly_completions || '∞';
202
+
203
+ const period = stats.period || {};
204
+ const summary = stats.summary || {};
205
+
206
+ const formatDate = (iso) => {
207
+ if (!iso) return '?';
208
+ const d = new Date(iso);
209
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
210
+ };
211
+ const weekRange = `${formatDate(period.start)} - ${formatDate(period.end)}`;
212
+
213
+ // Format numbers
214
+ const formatNum = (n) => (n || 0).toLocaleString();
215
+ const formatPct = (n) => (n || 0).toFixed(1);
216
+ const formatCost = (n) => (n || 0).toFixed(2);
217
+
218
+ const status = enabled ? '● Active' : '○ Inactive';
219
+ const optEnabled = enabled ? 'Enabled' : 'Disabled';
220
+ const cacheEnabled = localCache ? 'Enabled' : 'Disabled';
221
+ const cacheRate = formatPct((summary.cache_hit_rate || 0) * 100);
222
+
223
+ // Build dashboard URL from API URL
224
+ // API: http://localhost:8000 (localhost package)
225
+ // Dashboard: https://plexor.dev/dashboard or https://staging.plexor.dev/dashboard
226
+ let dashboardUrl = 'https://plexor.dev/dashboard';
227
+ try {
228
+ const url = new URL(apiUrl);
229
+ // Remove 'api.' prefix from hostname if present
230
+ const host = url.hostname.replace(/^api\./, '').replace(/\.api\./, '.');
231
+ dashboardUrl = `${url.protocol}//${host}/dashboard`;
232
+ } catch {
233
+ // If URL parsing fails, use default
234
+ }
235
+
236
+ // Output formatted status - each line is exactly 43 chars inner width
237
+ const line = (content) => ` │ ${content.padEnd(43)}│`;
238
+
239
+ // Session stats formatting
240
+ const formatDuration = (startedAt) => {
241
+ if (!startedAt) return '0m';
242
+ const elapsed = Date.now() - new Date(startedAt).getTime();
243
+ const minutes = Math.floor(elapsed / 60000);
244
+ if (minutes < 60) return `${minutes}m`;
245
+ const hours = Math.floor(minutes / 60);
246
+ return `${hours}h ${minutes % 60}m`;
247
+ };
248
+
249
+ const sessionDuration = session ? formatDuration(session.started_at) : '0m';
250
+ const sessionRequests = session ? formatNum(session.requests) : '0';
251
+ const sessionOptimizations = session ? formatNum(session.optimizations) : '0';
252
+ const sessionCacheHits = session ? formatNum(session.cache_hits) : '0';
253
+ const sessionTokensSaved = session ? formatNum(session.tokens_saved) : '0';
254
+ const sessionTokensSavedPct = session && session.original_tokens > 0
255
+ ? formatPct((session.tokens_saved / session.original_tokens) * 100)
256
+ : '0.0';
257
+ const sessionCostSaved = session ? formatCost(session.cost_saved) : '0.00';
258
+
259
+ // Build session section (only show if session exists)
260
+ const sessionSection = session ? ` ├─────────────────────────────────────────────┤
261
+ ${line(`This Session (${sessionDuration})`)}
262
+ ${line(`├── Requests: ${sessionRequests}`)}
263
+ ${line(`├── Optimizations: ${sessionOptimizations}`)}
264
+ ${line(`├── Cache hits: ${sessionCacheHits}`)}
265
+ ${line(`├── Tokens saved: ${sessionTokensSaved} (${sessionTokensSavedPct}%)`)}
266
+ ${line(`└── Cost saved: $${sessionCostSaved}`)}
267
+ ` : '';
268
+
269
+ // Get routing status from Claude settings.json
270
+ const routing = getRoutingStatus();
271
+ const routingIndicator = routing.active ? '🟢 PLEXOR MODE: ON' : '🔴 PLEXOR MODE: OFF';
272
+ const envLabel = routing.isStaging ? '(staging)' : '(production)';
273
+
274
+ // Note: Environment mismatch warning removed - it caused false positives during
275
+ // concurrent operations and transient states. The partial state and config/routing
276
+ // mismatch warnings below provide more actionable feedback.
277
+
278
+ // Check for partial routing state (Plexor URL without valid auth)
279
+ const partialState = detectPartialState();
280
+ if (partialState.partial) {
281
+ console.log(` ⚠ PARTIAL STATE DETECTED: ${partialState.issue}`);
282
+ console.log(` Run /plexor-login to fix, or /plexor-logout to disable routing\n`);
283
+ }
284
+
285
+ // Check for state mismatch between config enabled flag and routing status
286
+ const stateMismatch = checkStateMismatch(enabled, routing.active);
287
+ if (stateMismatch) {
288
+ console.log(` ⚠ State mismatch: ${stateMismatch.message}`);
289
+ console.log(` └─ ${stateMismatch.suggestion}\n`);
290
+ }
291
+
292
+ console.log(` ┌─────────────────────────────────────────────┐
293
+ ${line(routingIndicator + (routing.active ? ' ' + envLabel : ''))}
294
+ ├─────────────────────────────────────────────┤
295
+ ${line(`Account: ${tierName}`)}
296
+ ${line(`Email: ${email}`)}
297
+ ${line(`Status: ${status}`)}
298
+ ${sessionSection} ├─────────────────────────────────────────────┤
299
+ ${line(`This Week (${weekRange})`)}
300
+ ${line(`├── Requests: ${formatNum(summary.total_requests)}`)}
301
+ ${line(`├── Original tokens: ${formatNum(summary.original_tokens)}`)}
302
+ ${line(`├── Optimized tokens: ${formatNum(summary.optimized_tokens)}`)}
303
+ ${line(`├── Tokens saved: ${formatNum(summary.tokens_saved)} (${formatPct(summary.tokens_saved_percent)}%)`)}
304
+ ${line(`├── Baseline cost: $${formatCost(summary.baseline_cost)}`)}
305
+ ${line(`├── Actual cost: $${formatCost(summary.total_cost)}`)}
306
+ ${line(`└── Cost saved: $${formatCost(summary.cost_saved)} (${formatPct(summary.cost_saved_percent)}%)`)}
307
+ ├─────────────────────────────────────────────┤
308
+ ${line('Performance')}
309
+ ${line(`└── Cache hit rate: ${cacheRate}%`)}
310
+ ├─────────────────────────────────────────────┤
311
+ ${line('Limits')}
312
+ ${line(`├── Monthly optimizations: ${formatNum(monthlyOpts)}`)}
313
+ ${line(`└── Monthly completions: ${formatNum(monthlyComps)}`)}
314
+ ├─────────────────────────────────────────────┤
315
+ ${line('Settings')}
316
+ ${line(`├── Optimization: ${optEnabled}`)}
317
+ ${line(`├── Local cache: ${cacheEnabled}`)}
318
+ ${line(`├── Mode: ${mode}`)}
319
+ ${line(`├── Provider routing: ${provider}`)}
320
+ ${line(`└── Endpoint: ${routing.baseUrl ? routing.baseUrl.replace('https://', '').substring(0, 30) : 'not configured'}`)}
321
+ └─────────────────────────────────────────────┘
322
+
323
+ Dashboard: ${dashboardUrl}
324
+ `);
325
+ }
326
+
327
+ function fetchJson(apiUrl, endpoint, apiKey) {
328
+ return new Promise((resolve, reject) => {
329
+ let url;
330
+ try {
331
+ url = new URL(`${apiUrl}${endpoint}`);
332
+ } catch {
333
+ reject(new Error('Invalid API URL'));
334
+ return;
335
+ }
336
+
337
+ const isHttps = url.protocol === 'https:';
338
+ const lib = isHttps ? https : http;
339
+
340
+ const options = {
341
+ hostname: url.hostname,
342
+ port: url.port || (isHttps ? 443 : 80),
343
+ path: url.pathname,
344
+ method: 'GET',
345
+ headers: {
346
+ 'X-Plexor-Key': apiKey
347
+ }
348
+ };
349
+
350
+ const req = lib.request(options, (res) => {
351
+ let data = '';
352
+ res.on('data', chunk => data += chunk);
353
+ res.on('end', () => {
354
+ // Check HTTP status code first
355
+ if (res.statusCode === 401) {
356
+ reject(new Error('Invalid API key'));
357
+ return;
358
+ }
359
+ if (res.statusCode === 403) {
360
+ reject(new Error('Access denied'));
361
+ return;
362
+ }
363
+ if (res.statusCode >= 500) {
364
+ reject(new Error('Server error'));
365
+ return;
366
+ }
367
+ if (res.statusCode !== 200) {
368
+ reject(new Error(`HTTP ${res.statusCode}`));
369
+ return;
370
+ }
371
+
372
+ // Check for empty response
373
+ if (!data || data.trim() === '') {
374
+ reject(new Error('Empty response'));
375
+ return;
376
+ }
377
+
378
+ // Parse JSON
379
+ try {
380
+ const parsed = JSON.parse(data);
381
+ if (parsed === null) {
382
+ reject(new Error('Null response'));
383
+ return;
384
+ }
385
+ resolve(parsed);
386
+ } catch {
387
+ reject(new Error('Invalid JSON response'));
388
+ }
389
+ });
390
+ });
391
+
392
+ req.on('error', (err) => reject(new Error(`Connection failed: ${err.message}`)));
393
+ req.setTimeout(5000, () => {
394
+ req.destroy();
395
+ reject(new Error('Request timeout'));
396
+ });
397
+ req.end();
398
+ });
399
+ }
400
+
401
+ main().catch(err => {
402
+ console.error('Error:', err.message);
403
+ process.exit(1);
404
+ });
@@ -0,0 +1,21 @@
1
+ ---
2
+ description: Show Plexor optimization statistics and savings (user)
3
+ ---
4
+
5
+ # Plexor Status
6
+
7
+ Run this command to display Plexor statistics:
8
+
9
+ ```bash
10
+ node ~/.claude/plugins/plexor/commands/plexor-status.js
11
+ ```
12
+
13
+ Use the Bash tool to execute this single command.
14
+
15
+ **IMPORTANT**: After running this command and displaying the output, STOP. Do not:
16
+ - Read any files
17
+ - Explore the codebase
18
+ - Run additional commands
19
+ - Ask follow-up questions
20
+
21
+ The command output is the complete response. Simply show the output and wait for the user's next input.