@plexor-dev/claude-code-plugin 0.1.0-beta.1 → 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.
- package/commands/plexor-config.md +29 -30
- package/commands/plexor-enabled.md +46 -28
- package/commands/plexor-login.md +46 -57
- package/commands/plexor-logout.md +19 -27
- package/commands/plexor-mode.md +39 -17
- package/commands/plexor-provider.md +40 -18
- package/commands/plexor-settings.md +37 -72
- package/commands/plexor-status.js +203 -0
- package/commands/plexor-status.md +57 -34
- package/hooks/intercept.js +552 -0
- package/hooks/track-response.js +110 -0
- package/package.json +2 -1
- package/lib/constants.js +0 -40
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
|
|
12
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
13
|
+
const SESSION_PATH = path.join(process.env.HOME, '.plexor', 'session.json');
|
|
14
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
15
|
+
|
|
16
|
+
function loadSessionStats() {
|
|
17
|
+
try {
|
|
18
|
+
const data = fs.readFileSync(SESSION_PATH, 'utf8');
|
|
19
|
+
const session = JSON.parse(data);
|
|
20
|
+
// Check if session has expired
|
|
21
|
+
if (Date.now() - session.last_activity > SESSION_TIMEOUT_MS) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return session;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
// Read config
|
|
32
|
+
let config;
|
|
33
|
+
try {
|
|
34
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
35
|
+
config = JSON.parse(data);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.log('Not configured. Run /plexor-login first.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const apiKey = config.auth?.api_key;
|
|
42
|
+
const enabled = config.settings?.enabled ?? false;
|
|
43
|
+
const mode = config.settings?.mode || 'balanced';
|
|
44
|
+
const provider = config.settings?.preferred_provider || 'auto';
|
|
45
|
+
const localCache = config.settings?.localCacheEnabled ?? false;
|
|
46
|
+
const apiUrl = config.settings?.apiUrl || 'https://api.plexor.dev';
|
|
47
|
+
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
console.log('Not authenticated. Run /plexor-login first.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fetch user info and stats
|
|
54
|
+
let user = { email: 'Unknown', tier: { name: 'Free', limits: {} } };
|
|
55
|
+
let stats = { period: {}, summary: {} };
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
[user, stats] = await Promise.all([
|
|
59
|
+
fetchJson(apiUrl, '/v1/user', apiKey),
|
|
60
|
+
fetchJson(apiUrl, '/v1/stats', apiKey)
|
|
61
|
+
]);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
// Continue with defaults if API fails
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Load session stats
|
|
67
|
+
const session = loadSessionStats();
|
|
68
|
+
|
|
69
|
+
// Extract data
|
|
70
|
+
const email = user.email || 'Unknown';
|
|
71
|
+
const tierName = user.tier?.name || 'Free';
|
|
72
|
+
const monthlyOpts = user.tier?.limits?.monthly_optimizations || '∞';
|
|
73
|
+
const monthlyComps = user.tier?.limits?.monthly_completions || '∞';
|
|
74
|
+
|
|
75
|
+
const period = stats.period || {};
|
|
76
|
+
const summary = stats.summary || {};
|
|
77
|
+
|
|
78
|
+
const formatDate = (iso) => {
|
|
79
|
+
if (!iso) return '?';
|
|
80
|
+
const d = new Date(iso);
|
|
81
|
+
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
82
|
+
};
|
|
83
|
+
const weekRange = `${formatDate(period.start)} - ${formatDate(period.end)}`;
|
|
84
|
+
|
|
85
|
+
// Format numbers
|
|
86
|
+
const formatNum = (n) => (n || 0).toLocaleString();
|
|
87
|
+
const formatPct = (n) => (n || 0).toFixed(1);
|
|
88
|
+
const formatCost = (n) => (n || 0).toFixed(2);
|
|
89
|
+
|
|
90
|
+
const status = enabled ? '● Active' : '○ Inactive';
|
|
91
|
+
const optEnabled = enabled ? 'Enabled' : 'Disabled';
|
|
92
|
+
const cacheEnabled = localCache ? 'Enabled' : 'Disabled';
|
|
93
|
+
const cacheRate = formatPct((summary.cache_hit_rate || 0) * 100);
|
|
94
|
+
|
|
95
|
+
// Build dashboard URL
|
|
96
|
+
const dashboardUrl = apiUrl.replace('.api.', '.').replace('/api', '') + '/dashboard.html';
|
|
97
|
+
|
|
98
|
+
// Output formatted status - each line is exactly 43 chars inner width
|
|
99
|
+
const line = (content) => ` │ ${content.padEnd(43)}│`;
|
|
100
|
+
|
|
101
|
+
// Session stats formatting
|
|
102
|
+
const formatDuration = (startedAt) => {
|
|
103
|
+
if (!startedAt) return '0m';
|
|
104
|
+
const elapsed = Date.now() - new Date(startedAt).getTime();
|
|
105
|
+
const minutes = Math.floor(elapsed / 60000);
|
|
106
|
+
if (minutes < 60) return `${minutes}m`;
|
|
107
|
+
const hours = Math.floor(minutes / 60);
|
|
108
|
+
return `${hours}h ${minutes % 60}m`;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const sessionDuration = session ? formatDuration(session.started_at) : '0m';
|
|
112
|
+
const sessionRequests = session ? formatNum(session.requests) : '0';
|
|
113
|
+
const sessionOptimizations = session ? formatNum(session.optimizations) : '0';
|
|
114
|
+
const sessionCacheHits = session ? formatNum(session.cache_hits) : '0';
|
|
115
|
+
const sessionTokensSaved = session ? formatNum(session.tokens_saved) : '0';
|
|
116
|
+
const sessionTokensSavedPct = session && session.original_tokens > 0
|
|
117
|
+
? formatPct((session.tokens_saved / session.original_tokens) * 100)
|
|
118
|
+
: '0.0';
|
|
119
|
+
const sessionCostSaved = session ? formatCost(session.cost_saved) : '0.00';
|
|
120
|
+
|
|
121
|
+
// Build session section (only show if session exists)
|
|
122
|
+
const sessionSection = session ? ` ├─────────────────────────────────────────────┤
|
|
123
|
+
${line(`This Session (${sessionDuration})`)}
|
|
124
|
+
${line(`├── Requests: ${sessionRequests}`)}
|
|
125
|
+
${line(`├── Optimizations: ${sessionOptimizations}`)}
|
|
126
|
+
${line(`├── Cache hits: ${sessionCacheHits}`)}
|
|
127
|
+
${line(`├── Tokens saved: ${sessionTokensSaved} (${sessionTokensSavedPct}%)`)}
|
|
128
|
+
${line(`└── Cost saved: $${sessionCostSaved}`)}
|
|
129
|
+
` : '';
|
|
130
|
+
|
|
131
|
+
console.log(` ┌─────────────────────────────────────────────┐
|
|
132
|
+
${line('Plexor Status')}
|
|
133
|
+
├─────────────────────────────────────────────┤
|
|
134
|
+
${line(`Account: ${tierName}`)}
|
|
135
|
+
${line(`Email: ${email}`)}
|
|
136
|
+
${line(`Status: ${status}`)}
|
|
137
|
+
${sessionSection} ├─────────────────────────────────────────────┤
|
|
138
|
+
${line(`This Week (${weekRange})`)}
|
|
139
|
+
${line(`├── Requests: ${formatNum(summary.total_requests)}`)}
|
|
140
|
+
${line(`├── Original tokens: ${formatNum(summary.original_tokens)}`)}
|
|
141
|
+
${line(`├── Optimized tokens: ${formatNum(summary.optimized_tokens)}`)}
|
|
142
|
+
${line(`├── Tokens saved: ${formatNum(summary.tokens_saved)} (${formatPct(summary.tokens_saved_percent)}%)`)}
|
|
143
|
+
${line(`├── Baseline cost: $${formatCost(summary.baseline_cost)}`)}
|
|
144
|
+
${line(`├── Actual cost: $${formatCost(summary.total_cost)}`)}
|
|
145
|
+
${line(`└── Cost saved: $${formatCost(summary.cost_saved)} (${formatPct(summary.cost_saved_percent)}%)`)}
|
|
146
|
+
├─────────────────────────────────────────────┤
|
|
147
|
+
${line('Performance')}
|
|
148
|
+
${line(`└── Cache hit rate: ${cacheRate}%`)}
|
|
149
|
+
├─────────────────────────────────────────────┤
|
|
150
|
+
${line('Limits')}
|
|
151
|
+
${line(`├── Monthly optimizations: ${formatNum(monthlyOpts)}`)}
|
|
152
|
+
${line(`└── Monthly completions: ${formatNum(monthlyComps)}`)}
|
|
153
|
+
├─────────────────────────────────────────────┤
|
|
154
|
+
${line('Settings')}
|
|
155
|
+
${line(`├── Optimization: ${optEnabled}`)}
|
|
156
|
+
${line(`├── Local cache: ${cacheEnabled}`)}
|
|
157
|
+
${line(`├── Mode: ${mode}`)}
|
|
158
|
+
${line(`└── Provider routing: ${provider}`)}
|
|
159
|
+
└─────────────────────────────────────────────┘
|
|
160
|
+
|
|
161
|
+
Dashboard: ${dashboardUrl}
|
|
162
|
+
`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function fetchJson(apiUrl, endpoint, apiKey) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const url = new URL(`${apiUrl}${endpoint}`);
|
|
168
|
+
|
|
169
|
+
const options = {
|
|
170
|
+
hostname: url.hostname,
|
|
171
|
+
port: 443,
|
|
172
|
+
path: url.pathname,
|
|
173
|
+
method: 'GET',
|
|
174
|
+
headers: {
|
|
175
|
+
'X-Plexor-Key': apiKey
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const req = https.request(options, (res) => {
|
|
180
|
+
let data = '';
|
|
181
|
+
res.on('data', chunk => data += chunk);
|
|
182
|
+
res.on('end', () => {
|
|
183
|
+
try {
|
|
184
|
+
resolve(JSON.parse(data));
|
|
185
|
+
} catch {
|
|
186
|
+
reject(new Error('Invalid response'));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
req.on('error', reject);
|
|
192
|
+
req.setTimeout(5000, () => {
|
|
193
|
+
req.destroy();
|
|
194
|
+
reject(new Error('Timeout'));
|
|
195
|
+
});
|
|
196
|
+
req.end();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main().catch(err => {
|
|
201
|
+
console.error('Error:', err.message);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
@@ -1,46 +1,69 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Show Plexor optimization statistics and savings
|
|
2
|
+
description: Show Plexor optimization statistics and savings (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Plexor Status
|
|
6
6
|
|
|
7
|
-
Display
|
|
7
|
+
Display Plexor optimization statistics in a clean, professional format.
|
|
8
8
|
|
|
9
9
|
## Instructions
|
|
10
10
|
|
|
11
|
-
1. Read
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
- This week: total requests, total tokens saved, total savings
|
|
18
|
-
- Current settings: optimization enabled, local cache status
|
|
19
|
-
|
|
20
|
-
## Output Format
|
|
11
|
+
1. Read `~/.plexor/config.json` to get settings and API key
|
|
12
|
+
2. Read `~/.plexor/session.json` to get current session stats (if exists and not expired after 30min inactivity)
|
|
13
|
+
3. Call the Plexor APIs to get user info and stats:
|
|
14
|
+
- `GET {apiUrl}/v1/user` with header `X-Plexor-Key: {api_key}`
|
|
15
|
+
- `GET {apiUrl}/v1/stats` with header `X-Plexor-Key: {api_key}`
|
|
16
|
+
4. Output the formatted status box directly as text (not in a code block):
|
|
21
17
|
|
|
22
18
|
```
|
|
23
|
-
┌─────────────────────────────────────────────┐
|
|
24
|
-
│ Plexor Status │
|
|
25
|
-
├─────────────────────────────────────────────┤
|
|
26
|
-
│ Account:
|
|
27
|
-
│
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│
|
|
31
|
-
│ ├──
|
|
32
|
-
│
|
|
33
|
-
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
|
|
37
|
-
│
|
|
38
|
-
|
|
39
|
-
│
|
|
40
|
-
│ ├──
|
|
41
|
-
│ ├──
|
|
42
|
-
│
|
|
43
|
-
|
|
19
|
+
┌─────────────────────────────────────────────┐
|
|
20
|
+
│ Plexor Status │
|
|
21
|
+
├─────────────────────────────────────────────┤
|
|
22
|
+
│ Account: {tier.name} │
|
|
23
|
+
│ Email: {email} │
|
|
24
|
+
│ Status: ● Active │
|
|
25
|
+
├─────────────────────────────────────────────┤
|
|
26
|
+
│ This Session ({duration}) │
|
|
27
|
+
│ ├── Requests: {session_requests} │
|
|
28
|
+
│ ├── Optimizations: {session_optimizations} │
|
|
29
|
+
│ ├── Cache hits: {session_cache_hits} │
|
|
30
|
+
│ ├── Tokens saved: {tokens} ({%}) │
|
|
31
|
+
│ └── Cost saved: ${session_cost_saved} │
|
|
32
|
+
├─────────────────────────────────────────────┤
|
|
33
|
+
│ This Week ({period.start} - {period.end}) │
|
|
34
|
+
│ ├── Requests: {total_requests} │
|
|
35
|
+
│ ├── Original tokens: {original_tokens} │
|
|
36
|
+
│ ├── Optimized tokens: {optimized_tokens} │
|
|
37
|
+
│ ├── Tokens saved: {tokens_saved} ({%}) │
|
|
38
|
+
│ ├── Baseline cost: ${baseline_cost} │
|
|
39
|
+
│ ├── Actual cost: ${total_cost} │
|
|
40
|
+
│ └── Cost saved: ${cost_saved} ({%}) │
|
|
41
|
+
├─────────────────────────────────────────────┤
|
|
42
|
+
│ Performance │
|
|
43
|
+
│ └── Cache hit rate: {cache_hit_rate}% │
|
|
44
|
+
├─────────────────────────────────────────────┤
|
|
45
|
+
│ Limits │
|
|
46
|
+
│ ├── Monthly optimizations: {limit} │
|
|
47
|
+
│ └── Monthly completions: {limit} │
|
|
48
|
+
├─────────────────────────────────────────────┤
|
|
49
|
+
│ Settings │
|
|
50
|
+
│ ├── Optimization: {Enabled/Disabled} │
|
|
51
|
+
│ ├── Local cache: {Enabled/Disabled} │
|
|
52
|
+
│ ├── Mode: {mode} │
|
|
53
|
+
│ └── Provider routing: {provider} │
|
|
54
|
+
└─────────────────────────────────────────────┘
|
|
44
55
|
|
|
45
|
-
Dashboard:
|
|
56
|
+
Dashboard: {dashboard_url}
|
|
46
57
|
```
|
|
58
|
+
|
|
59
|
+
IMPORTANT:
|
|
60
|
+
- Output ONLY the status box as plain text, exactly as shown above
|
|
61
|
+
- Do NOT wrap in markdown code blocks
|
|
62
|
+
- Do NOT add any commentary before or after
|
|
63
|
+
- Format numbers with commas (e.g., 50,000)
|
|
64
|
+
- Format costs with 2 decimals (e.g., $4.50)
|
|
65
|
+
- Format dates as "Mon D" (e.g., "Jan 7")
|
|
66
|
+
- Use "● Active" if enabled, "○ Inactive" if disabled
|
|
67
|
+
- Multiply cache_hit_rate by 100 for percentage
|
|
68
|
+
- Only show "This Session" section if session.json exists and is not expired (30min timeout)
|
|
69
|
+
- Session duration format: "Xm" for minutes, "Xh Ym" for hours
|