@plexor-dev/claude-code-plugin 0.1.0-beta.10 → 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-status.js +50 -1
- package/commands/plexor-status.md +12 -2
- package/hooks/intercept.js +149 -7
- package/package.json +1 -1
|
@@ -10,6 +10,22 @@ const path = require('path');
|
|
|
10
10
|
const https = require('https');
|
|
11
11
|
|
|
12
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
|
+
}
|
|
13
29
|
|
|
14
30
|
async function main() {
|
|
15
31
|
// Read config
|
|
@@ -47,6 +63,9 @@ async function main() {
|
|
|
47
63
|
// Continue with defaults if API fails
|
|
48
64
|
}
|
|
49
65
|
|
|
66
|
+
// Load session stats
|
|
67
|
+
const session = loadSessionStats();
|
|
68
|
+
|
|
50
69
|
// Extract data
|
|
51
70
|
const email = user.email || 'Unknown';
|
|
52
71
|
const tierName = user.tier?.name || 'Free';
|
|
@@ -79,13 +98,43 @@ async function main() {
|
|
|
79
98
|
// Output formatted status - each line is exactly 43 chars inner width
|
|
80
99
|
const line = (content) => ` │ ${content.padEnd(43)}│`;
|
|
81
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
|
+
|
|
82
131
|
console.log(` ┌─────────────────────────────────────────────┐
|
|
83
132
|
${line('Plexor Status')}
|
|
84
133
|
├─────────────────────────────────────────────┤
|
|
85
134
|
${line(`Account: ${tierName}`)}
|
|
86
135
|
${line(`Email: ${email}`)}
|
|
87
136
|
${line(`Status: ${status}`)}
|
|
88
|
-
├─────────────────────────────────────────────┤
|
|
137
|
+
${sessionSection} ├─────────────────────────────────────────────┤
|
|
89
138
|
${line(`This Week (${weekRange})`)}
|
|
90
139
|
${line(`├── Requests: ${formatNum(summary.total_requests)}`)}
|
|
91
140
|
${line(`├── Original tokens: ${formatNum(summary.original_tokens)}`)}
|
|
@@ -9,10 +9,11 @@ Display Plexor optimization statistics in a clean, professional format.
|
|
|
9
9
|
## Instructions
|
|
10
10
|
|
|
11
11
|
1. Read `~/.plexor/config.json` to get settings and API key
|
|
12
|
-
2.
|
|
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:
|
|
13
14
|
- `GET {apiUrl}/v1/user` with header `X-Plexor-Key: {api_key}`
|
|
14
15
|
- `GET {apiUrl}/v1/stats` with header `X-Plexor-Key: {api_key}`
|
|
15
|
-
|
|
16
|
+
4. Output the formatted status box directly as text (not in a code block):
|
|
16
17
|
|
|
17
18
|
```
|
|
18
19
|
┌─────────────────────────────────────────────┐
|
|
@@ -22,6 +23,13 @@ Display Plexor optimization statistics in a clean, professional format.
|
|
|
22
23
|
│ Email: {email} │
|
|
23
24
|
│ Status: ● Active │
|
|
24
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
|
+
├─────────────────────────────────────────────┤
|
|
25
33
|
│ This Week ({period.start} - {period.end}) │
|
|
26
34
|
│ ├── Requests: {total_requests} │
|
|
27
35
|
│ ├── Original tokens: {original_tokens} │
|
|
@@ -57,3 +65,5 @@ IMPORTANT:
|
|
|
57
65
|
- Format dates as "Mon D" (e.g., "Jan 7")
|
|
58
66
|
- Use "● Active" if enabled, "○ Inactive" if disabled
|
|
59
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
|
package/hooks/intercept.js
CHANGED
|
@@ -10,14 +10,149 @@
|
|
|
10
10
|
* Output: Modified JSON object with optimized messages
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const LocalCache = require('../lib/cache');
|
|
16
|
-
const Logger = require('../lib/logger');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
16
|
+
// Inline implementations to avoid missing module errors
|
|
17
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
18
|
+
const SESSION_PATH = path.join(process.env.HOME, '.plexor', 'session.json');
|
|
19
|
+
|
|
20
|
+
const logger = {
|
|
21
|
+
debug: (msg) => process.env.PLEXOR_DEBUG && console.error(`[DEBUG] ${msg}`),
|
|
22
|
+
info: (msg) => console.error(msg),
|
|
23
|
+
error: (msg) => console.error(`[ERROR] ${msg}`)
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const config = {
|
|
27
|
+
load: async () => {
|
|
28
|
+
try {
|
|
29
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
30
|
+
const cfg = JSON.parse(data);
|
|
31
|
+
return {
|
|
32
|
+
enabled: cfg.settings?.enabled ?? false,
|
|
33
|
+
apiKey: cfg.auth?.api_key,
|
|
34
|
+
apiUrl: cfg.settings?.apiUrl || 'https://api.plexor.dev',
|
|
35
|
+
timeout: cfg.settings?.timeout || 5000,
|
|
36
|
+
localCacheEnabled: cfg.settings?.localCacheEnabled ?? false,
|
|
37
|
+
mode: cfg.settings?.mode || 'balanced'
|
|
38
|
+
};
|
|
39
|
+
} catch {
|
|
40
|
+
return { enabled: false };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const cache = {
|
|
46
|
+
generateKey: (messages) => {
|
|
47
|
+
const str = JSON.stringify(messages);
|
|
48
|
+
let hash = 0;
|
|
49
|
+
for (let i = 0; i < str.length; i++) {
|
|
50
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
51
|
+
hash |= 0;
|
|
52
|
+
}
|
|
53
|
+
return `cache_${hash}`;
|
|
54
|
+
},
|
|
55
|
+
get: async () => null,
|
|
56
|
+
setMetadata: async () => {}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Session stats tracking
|
|
60
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
61
|
+
|
|
62
|
+
function loadSessionStats() {
|
|
63
|
+
try {
|
|
64
|
+
const data = fs.readFileSync(SESSION_PATH, 'utf8');
|
|
65
|
+
const session = JSON.parse(data);
|
|
66
|
+
// Check if session has expired
|
|
67
|
+
if (Date.now() - session.last_activity > SESSION_TIMEOUT_MS) {
|
|
68
|
+
return createNewSession();
|
|
69
|
+
}
|
|
70
|
+
return session;
|
|
71
|
+
} catch {
|
|
72
|
+
return createNewSession();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createNewSession() {
|
|
77
|
+
return {
|
|
78
|
+
session_id: `session_${Date.now()}`,
|
|
79
|
+
started_at: new Date().toISOString(),
|
|
80
|
+
last_activity: Date.now(),
|
|
81
|
+
requests: 0,
|
|
82
|
+
optimizations: 0,
|
|
83
|
+
cache_hits: 0,
|
|
84
|
+
original_tokens: 0,
|
|
85
|
+
optimized_tokens: 0,
|
|
86
|
+
tokens_saved: 0,
|
|
87
|
+
baseline_cost: 0,
|
|
88
|
+
actual_cost: 0,
|
|
89
|
+
cost_saved: 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function saveSessionStats(session) {
|
|
94
|
+
try {
|
|
95
|
+
const dir = path.dirname(SESSION_PATH);
|
|
96
|
+
if (!fs.existsSync(dir)) {
|
|
97
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
session.last_activity = Date.now();
|
|
100
|
+
fs.writeFileSync(SESSION_PATH, JSON.stringify(session, null, 2));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
logger.debug(`Failed to save session stats: ${err.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function updateSessionStats(result) {
|
|
107
|
+
const session = loadSessionStats();
|
|
108
|
+
session.requests++;
|
|
109
|
+
session.optimizations++;
|
|
110
|
+
session.original_tokens += result.original_tokens || 0;
|
|
111
|
+
session.optimized_tokens += result.optimized_tokens || 0;
|
|
112
|
+
session.tokens_saved += result.tokens_saved || 0;
|
|
113
|
+
session.baseline_cost += result.baseline_cost || 0;
|
|
114
|
+
session.actual_cost += result.estimated_cost || 0;
|
|
115
|
+
session.cost_saved += (result.baseline_cost || 0) - (result.estimated_cost || 0);
|
|
116
|
+
saveSessionStats(session);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function recordCacheHit() {
|
|
120
|
+
const session = loadSessionStats();
|
|
121
|
+
session.requests++;
|
|
122
|
+
session.cache_hits++;
|
|
123
|
+
saveSessionStats(session);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function recordPassthrough() {
|
|
127
|
+
const session = loadSessionStats();
|
|
128
|
+
session.requests++;
|
|
129
|
+
saveSessionStats(session);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Placeholder PlexorClient
|
|
133
|
+
class PlexorClient {
|
|
134
|
+
constructor(opts) {
|
|
135
|
+
this.apiKey = opts.apiKey;
|
|
136
|
+
this.baseUrl = opts.baseUrl;
|
|
137
|
+
this.timeout = opts.timeout;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async optimize(params) {
|
|
141
|
+
// Return passthrough result - real API call would go here
|
|
142
|
+
const tokens = JSON.stringify(params.messages).length / 4;
|
|
143
|
+
return {
|
|
144
|
+
request_id: `req_${Date.now()}`,
|
|
145
|
+
original_tokens: Math.round(tokens),
|
|
146
|
+
optimized_tokens: Math.round(tokens * 0.7),
|
|
147
|
+
tokens_saved: Math.round(tokens * 0.3),
|
|
148
|
+
optimized_messages: params.messages,
|
|
149
|
+
recommended_provider: 'anthropic',
|
|
150
|
+
recommended_model: params.model,
|
|
151
|
+
estimated_cost: tokens * 0.00001,
|
|
152
|
+
baseline_cost: tokens * 0.00003
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
21
156
|
|
|
22
157
|
async function main() {
|
|
23
158
|
const startTime = Date.now();
|
|
@@ -30,6 +165,7 @@ async function main() {
|
|
|
30
165
|
// Modifying messages breaks the agent loop and causes infinite loops
|
|
31
166
|
if (isAgenticRequest(request)) {
|
|
32
167
|
logger.debug('Agentic request detected, passing through unchanged');
|
|
168
|
+
recordPassthrough();
|
|
33
169
|
return output({
|
|
34
170
|
...request,
|
|
35
171
|
plexor_cwd: process.cwd(),
|
|
@@ -46,6 +182,7 @@ async function main() {
|
|
|
46
182
|
// Slash commands like /plexor-status should pass through unchanged
|
|
47
183
|
if (isSlashCommand(request)) {
|
|
48
184
|
logger.debug('Slash command detected, passing through unchanged');
|
|
185
|
+
recordPassthrough();
|
|
49
186
|
return output({
|
|
50
187
|
...request,
|
|
51
188
|
plexor_cwd: process.cwd(),
|
|
@@ -62,6 +199,7 @@ async function main() {
|
|
|
62
199
|
// Azure CLI, AWS CLI, kubectl, etc. need tools to be preserved
|
|
63
200
|
if (requiresToolExecution(request)) {
|
|
64
201
|
logger.debug('CLI tool execution detected, passing through unchanged');
|
|
202
|
+
recordPassthrough();
|
|
65
203
|
return output({
|
|
66
204
|
...request,
|
|
67
205
|
plexor_cwd: process.cwd(),
|
|
@@ -101,6 +239,7 @@ async function main() {
|
|
|
101
239
|
|
|
102
240
|
if (cachedResponse && settings.localCacheEnabled) {
|
|
103
241
|
logger.info('[Plexor] Local cache hit');
|
|
242
|
+
recordCacheHit();
|
|
104
243
|
return output({
|
|
105
244
|
...request,
|
|
106
245
|
_plexor: {
|
|
@@ -159,6 +298,9 @@ async function main() {
|
|
|
159
298
|
timestamp: Date.now()
|
|
160
299
|
});
|
|
161
300
|
|
|
301
|
+
// Update session stats
|
|
302
|
+
updateSessionStats(result);
|
|
303
|
+
|
|
162
304
|
return output(optimizedRequest);
|
|
163
305
|
|
|
164
306
|
} catch (error) {
|
package/package.json
CHANGED