@plexor-dev/claude-code-plugin 0.1.0-beta.2 → 0.1.0-beta.21
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.js +170 -0
- package/commands/plexor-config.md +31 -30
- package/commands/plexor-enabled.js +91 -0
- package/commands/plexor-enabled.md +48 -28
- package/commands/plexor-login.js +169 -0
- package/commands/plexor-login.md +51 -52
- package/commands/plexor-logout.js +92 -0
- package/commands/plexor-logout.md +21 -27
- package/commands/plexor-mode.js +107 -0
- package/commands/plexor-mode.md +41 -17
- package/commands/plexor-provider.js +110 -0
- package/commands/plexor-provider.md +42 -18
- package/commands/plexor-settings.js +155 -0
- package/commands/plexor-settings.md +39 -72
- package/commands/plexor-setup.md +134 -0
- package/commands/plexor-status.js +213 -0
- package/commands/plexor-status.md +12 -37
- package/hooks/intercept.js +634 -0
- package/hooks/track-response.js +376 -0
- package/lib/cache.js +107 -0
- package/lib/config.js +67 -0
- package/lib/constants.js +16 -31
- package/lib/index.js +19 -0
- package/lib/logger.js +36 -0
- package/lib/plexor-client.js +122 -0
- package/lib/server-sync.js +237 -0
- package/lib/session.js +156 -0
- package/package.json +12 -1
- package/scripts/plexor-cli.sh +48 -0
- package/scripts/postinstall.js +53 -7
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Settings Command
|
|
5
|
+
* View and update Plexor configuration settings
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const CONFIG_PATH = path.join(process.env.HOME, '.plexor', 'config.json');
|
|
12
|
+
const PLEXOR_DIR = path.join(process.env.HOME, '.plexor');
|
|
13
|
+
|
|
14
|
+
const SETTING_KEYS = {
|
|
15
|
+
'api-url': 'apiUrl',
|
|
16
|
+
'apiurl': 'apiUrl',
|
|
17
|
+
'url': 'apiUrl',
|
|
18
|
+
'timeout': 'timeout',
|
|
19
|
+
'cache': 'localCacheEnabled',
|
|
20
|
+
'local-cache': 'localCacheEnabled'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function loadConfig() {
|
|
24
|
+
try {
|
|
25
|
+
const data = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
26
|
+
return JSON.parse(data);
|
|
27
|
+
} catch {
|
|
28
|
+
return { version: 1, auth: {}, settings: {} };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function saveConfig(config) {
|
|
33
|
+
if (!fs.existsSync(PLEXOR_DIR)) {
|
|
34
|
+
fs.mkdirSync(PLEXOR_DIR, { recursive: true, mode: 0o700 });
|
|
35
|
+
}
|
|
36
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseValue(key, value) {
|
|
40
|
+
const normalizedKey = SETTING_KEYS[key.toLowerCase()] || key;
|
|
41
|
+
|
|
42
|
+
switch (normalizedKey) {
|
|
43
|
+
case 'localCacheEnabled':
|
|
44
|
+
return ['true', 'on', 'yes', '1'].includes(value.toLowerCase());
|
|
45
|
+
case 'timeout':
|
|
46
|
+
const num = parseInt(value, 10);
|
|
47
|
+
if (isNaN(num) || num < 1000 || num > 60000) {
|
|
48
|
+
throw new Error('Timeout must be between 1000-60000ms');
|
|
49
|
+
}
|
|
50
|
+
return num;
|
|
51
|
+
case 'apiUrl':
|
|
52
|
+
if (!value.startsWith('http://') && !value.startsWith('https://')) {
|
|
53
|
+
throw new Error('API URL must start with http:// or https://');
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
default:
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function main() {
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
const config = loadConfig();
|
|
64
|
+
const settings = config.settings || {};
|
|
65
|
+
|
|
66
|
+
// No args - show current settings
|
|
67
|
+
if (args.length === 0) {
|
|
68
|
+
const enabled = settings.enabled ?? false;
|
|
69
|
+
const mode = settings.mode || 'balanced';
|
|
70
|
+
const provider = settings.preferred_provider || 'auto';
|
|
71
|
+
const apiUrl = settings.apiUrl || 'https://api.plexor.dev';
|
|
72
|
+
const timeout = settings.timeout || 5000;
|
|
73
|
+
const cache = settings.localCacheEnabled ?? false;
|
|
74
|
+
const hasApiKey = !!config.auth?.api_key;
|
|
75
|
+
|
|
76
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
77
|
+
console.log(`│ Plexor Settings │`);
|
|
78
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
79
|
+
console.log(`│ Authentication │`);
|
|
80
|
+
console.log(`│ └── API Key: ${(hasApiKey ? 'Configured' : 'Not set').padEnd(29)}│`);
|
|
81
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
82
|
+
console.log(`│ Proxy │`);
|
|
83
|
+
console.log(`│ ├── Enabled: ${(enabled ? 'Yes' : 'No').padEnd(29)}│`);
|
|
84
|
+
console.log(`│ └── API URL: ${apiUrl.substring(0, 29).padEnd(29)}│`);
|
|
85
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
86
|
+
console.log(`│ Optimization │`);
|
|
87
|
+
console.log(`│ ├── Mode: ${mode.padEnd(32)}│`);
|
|
88
|
+
console.log(`│ └── Provider: ${provider.padEnd(27)}│`);
|
|
89
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
90
|
+
console.log(`│ Performance │`);
|
|
91
|
+
console.log(`│ ├── Local Cache: ${(cache ? 'Enabled' : 'Disabled').padEnd(24)}│`);
|
|
92
|
+
console.log(`│ └── Timeout: ${(timeout + 'ms').padEnd(29)}│`);
|
|
93
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
94
|
+
console.log(`│ Modify settings: │`);
|
|
95
|
+
console.log(`│ /plexor-settings <key> <value> │`);
|
|
96
|
+
console.log(`│ │`);
|
|
97
|
+
console.log(`│ Available keys: │`);
|
|
98
|
+
console.log(`│ api-url, timeout, cache │`);
|
|
99
|
+
console.log(`│ │`);
|
|
100
|
+
console.log(`│ Other commands: │`);
|
|
101
|
+
console.log(`│ /plexor-mode - Set optimization mode │`);
|
|
102
|
+
console.log(`│ /plexor-provider - Set provider routing │`);
|
|
103
|
+
console.log(`│ /plexor-enabled - Enable/disable proxy │`);
|
|
104
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get setting
|
|
109
|
+
if (args.length === 1) {
|
|
110
|
+
const key = args[0].toLowerCase();
|
|
111
|
+
const normalizedKey = SETTING_KEYS[key] || key;
|
|
112
|
+
const value = settings[normalizedKey];
|
|
113
|
+
|
|
114
|
+
if (value === undefined) {
|
|
115
|
+
console.error(`Unknown setting: ${args[0]}`);
|
|
116
|
+
console.error(`Available: api-url, timeout, cache`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(`${normalizedKey}: ${value}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Set setting
|
|
125
|
+
const key = args[0].toLowerCase();
|
|
126
|
+
const normalizedKey = SETTING_KEYS[key];
|
|
127
|
+
|
|
128
|
+
if (!normalizedKey) {
|
|
129
|
+
console.error(`Unknown setting: ${args[0]}`);
|
|
130
|
+
console.error(`Available: api-url, timeout, cache`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const value = parseValue(key, args[1]);
|
|
136
|
+
const oldValue = settings[normalizedKey];
|
|
137
|
+
|
|
138
|
+
config.settings = config.settings || {};
|
|
139
|
+
config.settings[normalizedKey] = value;
|
|
140
|
+
saveConfig(config);
|
|
141
|
+
|
|
142
|
+
console.log(`┌─────────────────────────────────────────────┐`);
|
|
143
|
+
console.log(`│ ✓ Setting Updated │`);
|
|
144
|
+
console.log(`├─────────────────────────────────────────────┤`);
|
|
145
|
+
console.log(`│ Key: ${normalizedKey.padEnd(37)}│`);
|
|
146
|
+
console.log(`│ Previous: ${String(oldValue ?? 'not set').substring(0, 32).padEnd(32)}│`);
|
|
147
|
+
console.log(`│ New: ${String(value).substring(0, 37).padEnd(37)}│`);
|
|
148
|
+
console.log(`└─────────────────────────────────────────────┘`);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(`Error: ${err.message}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
main();
|
|
@@ -1,93 +1,60 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Configure Plexor settings (API URL, mode, provider preferences)
|
|
2
|
+
description: Configure Plexor settings (API URL, mode, provider preferences) (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Plexor Settings
|
|
6
6
|
|
|
7
|
-
View and configure Plexor settings.
|
|
7
|
+
View and configure all Plexor settings.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Steps
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- `/plexor-settings api-url <url>` - Set the Plexor API URL
|
|
13
|
-
- `/plexor-settings mode <eco|balanced|quality|passthrough>` - Set optimization mode
|
|
14
|
-
- `/plexor-settings provider <auto|claude|openai|deepseek>` - Set provider preference
|
|
11
|
+
**Step 1: Read current configuration**
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json`.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Config File Location
|
|
24
|
-
|
|
25
|
-
~/.plexor/config.json
|
|
26
|
-
|
|
27
|
-
## Config Schema
|
|
28
|
-
|
|
29
|
-
```json
|
|
30
|
-
{
|
|
31
|
-
"enabled": true,
|
|
32
|
-
"apiUrl": "http://localhost:8000",
|
|
33
|
-
"apiKey": "plx_...",
|
|
34
|
-
"mode": "balanced",
|
|
35
|
-
"preferredProvider": "auto",
|
|
36
|
-
"localCacheEnabled": true,
|
|
37
|
-
"timeout": 5000
|
|
38
|
-
}
|
|
15
|
+
If the file doesn't exist, show:
|
|
16
|
+
```
|
|
17
|
+
Plexor Settings
|
|
18
|
+
===============
|
|
19
|
+
No configuration found. Run /plexor-login to set up Plexor.
|
|
39
20
|
```
|
|
40
21
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- **Development**: `http://localhost:8000`
|
|
44
|
-
- **Production**: `https://api.plexor.dev` (default)
|
|
22
|
+
**Step 2: Display current settings**
|
|
45
23
|
|
|
46
|
-
|
|
24
|
+
Show the current configuration:
|
|
47
25
|
|
|
48
|
-
### Show settings:
|
|
49
|
-
```
|
|
50
|
-
┌─────────────────────────────────────────────┐
|
|
51
|
-
│ Plexor Settings │
|
|
52
|
-
├─────────────────────────────────────────────┤
|
|
53
|
-
│ API URL: http://localhost:8000 │
|
|
54
|
-
│ Mode: balanced │
|
|
55
|
-
│ Provider: auto │
|
|
56
|
-
│ Local Cache: enabled │
|
|
57
|
-
│ Timeout: 5000ms │
|
|
58
|
-
├─────────────────────────────────────────────┤
|
|
59
|
-
│ Proxy Status: NOT ACTIVE │
|
|
60
|
-
│ │
|
|
61
|
-
│ To activate proxy, run: │
|
|
62
|
-
│ export ANTHROPIC_BASE_URL="http://localhost:8000/gateway/anthropic"
|
|
63
|
-
│ claude --continue │
|
|
64
|
-
└─────────────────────────────────────────────┘
|
|
65
26
|
```
|
|
27
|
+
Plexor Settings
|
|
28
|
+
===============
|
|
29
|
+
Enabled: [enabled - true/false]
|
|
30
|
+
API URL: [apiUrl]
|
|
31
|
+
API Key: plx_****[last 4 chars] (configured/not configured)
|
|
32
|
+
Mode: [mode]
|
|
33
|
+
Provider: [preferredProvider]
|
|
34
|
+
Local Cache: [localCacheEnabled - enabled/disabled]
|
|
35
|
+
Timeout: [timeout]ms
|
|
66
36
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/plexor-
|
|
70
|
-
|
|
37
|
+
Quick Commands:
|
|
38
|
+
- /plexor-enabled - Toggle proxy on/off
|
|
39
|
+
- /plexor-mode - Change optimization mode
|
|
40
|
+
- /plexor-provider - Change provider preference
|
|
41
|
+
- /plexor-status - View usage statistics
|
|
71
42
|
|
|
72
|
-
|
|
43
|
+
Dashboard: https://plexor.dev/dashboard
|
|
73
44
|
```
|
|
74
|
-
✓ API URL set to: http://localhost:8000
|
|
75
45
|
|
|
76
|
-
|
|
77
|
-
export ANTHROPIC_BASE_URL="http://localhost:8000/gateway/anthropic"
|
|
78
|
-
claude --continue
|
|
79
|
-
```
|
|
46
|
+
**Step 3: If user wants to change a setting**
|
|
80
47
|
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
/plexor-settings api-url https://api.plexor.dev
|
|
84
|
-
```
|
|
48
|
+
If the user specifies they want to change something, use the `AskUserQuestion` tool:
|
|
85
49
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
50
|
+
Question: "Which setting would you like to change?"
|
|
51
|
+
Header: "Setting"
|
|
52
|
+
Options:
|
|
53
|
+
1. **API URL** - Change the Plexor API endpoint
|
|
54
|
+
2. **Mode** - Change optimization mode (eco/balanced/quality)
|
|
55
|
+
3. **Provider** - Change preferred provider
|
|
56
|
+
4. **Timeout** - Change request timeout
|
|
89
57
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```
|
|
58
|
+
Then update `~/.plexor/config.json` with the new value using the Write tool.
|
|
59
|
+
|
|
60
|
+
**IMPORTANT**: After completing the task, STOP. Do not run additional commands or explore the codebase.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: First-time setup wizard for Plexor with Claude Code (user)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Plexor Setup Wizard
|
|
6
|
+
|
|
7
|
+
Guide users through first-time Plexor setup, handling both Claude MAX subscribers and API key users.
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
**Step 1: Check if already configured**
|
|
12
|
+
|
|
13
|
+
Use the Read tool to check if `~/.plexor/config.json` exists and has valid configuration.
|
|
14
|
+
|
|
15
|
+
If configured, show:
|
|
16
|
+
```
|
|
17
|
+
Plexor Setup
|
|
18
|
+
============
|
|
19
|
+
Already configured!
|
|
20
|
+
|
|
21
|
+
API URL: [apiUrl from config]
|
|
22
|
+
Mode: [mode from config]
|
|
23
|
+
Status: [Enabled/Disabled]
|
|
24
|
+
|
|
25
|
+
Run /plexor-status to see your usage.
|
|
26
|
+
Run /plexor-settings to modify configuration.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Step 2: Ask about Claude MAX subscription**
|
|
30
|
+
|
|
31
|
+
Use the AskUserQuestion tool:
|
|
32
|
+
|
|
33
|
+
Question: "Do you have a Claude MAX subscription (Pro/Team/Enterprise)?"
|
|
34
|
+
Header: "Subscription"
|
|
35
|
+
Options:
|
|
36
|
+
1. **Yes, I have Claude MAX** - I'm already logged into Claude Code with OAuth
|
|
37
|
+
2. **No, I'll use a Plexor API key** - I want to use Plexor's provider routing
|
|
38
|
+
|
|
39
|
+
**Step 3A: Claude MAX User Setup**
|
|
40
|
+
|
|
41
|
+
If user selected "Yes, I have Claude MAX":
|
|
42
|
+
|
|
43
|
+
1. Use the Write tool to create `~/.plexor/config.json`:
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"version": 1,
|
|
47
|
+
"auth": {
|
|
48
|
+
"mode": "oauth_passthrough",
|
|
49
|
+
"authenticated_at": "[current ISO timestamp]"
|
|
50
|
+
},
|
|
51
|
+
"settings": {
|
|
52
|
+
"enabled": true,
|
|
53
|
+
"apiUrl": "https://api.plexor.dev",
|
|
54
|
+
"mode": "balanced",
|
|
55
|
+
"localCacheEnabled": true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
2. Show the user:
|
|
61
|
+
```
|
|
62
|
+
Plexor Setup - Claude MAX User
|
|
63
|
+
==============================
|
|
64
|
+
Your Claude MAX subscription will be used with Plexor optimization.
|
|
65
|
+
|
|
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
|
|
71
|
+
|
|
72
|
+
How it works:
|
|
73
|
+
- Claude Code sends your OAuth token through Plexor
|
|
74
|
+
- Plexor optimizes prompts and tracks usage
|
|
75
|
+
- You keep your MAX benefits ($0 cost, 20x rate limits)
|
|
76
|
+
|
|
77
|
+
Run /plexor-status to verify setup.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Step 3B: API Key User Setup**
|
|
81
|
+
|
|
82
|
+
If user selected "No, I'll use a Plexor API key":
|
|
83
|
+
|
|
84
|
+
1. Ask for their Plexor API key:
|
|
85
|
+
"Please provide your Plexor API key (starts with 'plx_')."
|
|
86
|
+
"Get one at: https://plexor.dev/dashboard"
|
|
87
|
+
|
|
88
|
+
2. Once they provide the key, use the Write tool to create `~/.plexor/config.json`:
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"version": 1,
|
|
92
|
+
"auth": {
|
|
93
|
+
"api_key": "[user's API key]",
|
|
94
|
+
"mode": "api_key",
|
|
95
|
+
"authenticated_at": "[current ISO timestamp]"
|
|
96
|
+
},
|
|
97
|
+
"settings": {
|
|
98
|
+
"enabled": true,
|
|
99
|
+
"apiUrl": "https://api.plexor.dev",
|
|
100
|
+
"preferred_provider": "auto",
|
|
101
|
+
"mode": "balanced",
|
|
102
|
+
"localCacheEnabled": true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
3. Show the user:
|
|
108
|
+
```
|
|
109
|
+
Plexor Setup - API Key User
|
|
110
|
+
===========================
|
|
111
|
+
Your Plexor API key has been configured.
|
|
112
|
+
|
|
113
|
+
Add these lines to your shell profile (~/.bashrc or ~/.zshrc):
|
|
114
|
+
|
|
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
|
|
119
|
+
|
|
120
|
+
How it works:
|
|
121
|
+
- Requests route through Plexor gateway
|
|
122
|
+
- Plexor picks the best provider (can save up to 90%)
|
|
123
|
+
- Your Plexor key goes in ANTHROPIC_API_KEY (this is correct!)
|
|
124
|
+
|
|
125
|
+
Run /plexor-status to verify setup.
|
|
126
|
+
```
|
|
127
|
+
|
|
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).
|
|
133
|
+
|
|
134
|
+
**IMPORTANT**: After completing setup, STOP. Do not run additional commands.
|
|
@@ -0,0 +1,213 @@
|
|
|
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 from API URL
|
|
96
|
+
// API: https://api.plexor.dev or https://staging.api.plexor.dev
|
|
97
|
+
// Dashboard: https://plexor.dev/dashboard or https://staging.plexor.dev/dashboard
|
|
98
|
+
let dashboardUrl = 'https://plexor.dev/dashboard';
|
|
99
|
+
try {
|
|
100
|
+
const url = new URL(apiUrl);
|
|
101
|
+
// Remove 'api.' prefix from hostname if present
|
|
102
|
+
const host = url.hostname.replace(/^api\./, '').replace(/\.api\./, '.');
|
|
103
|
+
dashboardUrl = `${url.protocol}//${host}/dashboard`;
|
|
104
|
+
} catch {
|
|
105
|
+
// If URL parsing fails, use default
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Output formatted status - each line is exactly 43 chars inner width
|
|
109
|
+
const line = (content) => ` │ ${content.padEnd(43)}│`;
|
|
110
|
+
|
|
111
|
+
// Session stats formatting
|
|
112
|
+
const formatDuration = (startedAt) => {
|
|
113
|
+
if (!startedAt) return '0m';
|
|
114
|
+
const elapsed = Date.now() - new Date(startedAt).getTime();
|
|
115
|
+
const minutes = Math.floor(elapsed / 60000);
|
|
116
|
+
if (minutes < 60) return `${minutes}m`;
|
|
117
|
+
const hours = Math.floor(minutes / 60);
|
|
118
|
+
return `${hours}h ${minutes % 60}m`;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const sessionDuration = session ? formatDuration(session.started_at) : '0m';
|
|
122
|
+
const sessionRequests = session ? formatNum(session.requests) : '0';
|
|
123
|
+
const sessionOptimizations = session ? formatNum(session.optimizations) : '0';
|
|
124
|
+
const sessionCacheHits = session ? formatNum(session.cache_hits) : '0';
|
|
125
|
+
const sessionTokensSaved = session ? formatNum(session.tokens_saved) : '0';
|
|
126
|
+
const sessionTokensSavedPct = session && session.original_tokens > 0
|
|
127
|
+
? formatPct((session.tokens_saved / session.original_tokens) * 100)
|
|
128
|
+
: '0.0';
|
|
129
|
+
const sessionCostSaved = session ? formatCost(session.cost_saved) : '0.00';
|
|
130
|
+
|
|
131
|
+
// Build session section (only show if session exists)
|
|
132
|
+
const sessionSection = session ? ` ├─────────────────────────────────────────────┤
|
|
133
|
+
${line(`This Session (${sessionDuration})`)}
|
|
134
|
+
${line(`├── Requests: ${sessionRequests}`)}
|
|
135
|
+
${line(`├── Optimizations: ${sessionOptimizations}`)}
|
|
136
|
+
${line(`├── Cache hits: ${sessionCacheHits}`)}
|
|
137
|
+
${line(`├── Tokens saved: ${sessionTokensSaved} (${sessionTokensSavedPct}%)`)}
|
|
138
|
+
${line(`└── Cost saved: $${sessionCostSaved}`)}
|
|
139
|
+
` : '';
|
|
140
|
+
|
|
141
|
+
console.log(` ┌─────────────────────────────────────────────┐
|
|
142
|
+
${line('Plexor Status')}
|
|
143
|
+
├─────────────────────────────────────────────┤
|
|
144
|
+
${line(`Account: ${tierName}`)}
|
|
145
|
+
${line(`Email: ${email}`)}
|
|
146
|
+
${line(`Status: ${status}`)}
|
|
147
|
+
${sessionSection} ├─────────────────────────────────────────────┤
|
|
148
|
+
${line(`This Week (${weekRange})`)}
|
|
149
|
+
${line(`├── Requests: ${formatNum(summary.total_requests)}`)}
|
|
150
|
+
${line(`├── Original tokens: ${formatNum(summary.original_tokens)}`)}
|
|
151
|
+
${line(`├── Optimized tokens: ${formatNum(summary.optimized_tokens)}`)}
|
|
152
|
+
${line(`├── Tokens saved: ${formatNum(summary.tokens_saved)} (${formatPct(summary.tokens_saved_percent)}%)`)}
|
|
153
|
+
${line(`├── Baseline cost: $${formatCost(summary.baseline_cost)}`)}
|
|
154
|
+
${line(`├── Actual cost: $${formatCost(summary.total_cost)}`)}
|
|
155
|
+
${line(`└── Cost saved: $${formatCost(summary.cost_saved)} (${formatPct(summary.cost_saved_percent)}%)`)}
|
|
156
|
+
├─────────────────────────────────────────────┤
|
|
157
|
+
${line('Performance')}
|
|
158
|
+
${line(`└── Cache hit rate: ${cacheRate}%`)}
|
|
159
|
+
├─────────────────────────────────────────────┤
|
|
160
|
+
${line('Limits')}
|
|
161
|
+
${line(`├── Monthly optimizations: ${formatNum(monthlyOpts)}`)}
|
|
162
|
+
${line(`└── Monthly completions: ${formatNum(monthlyComps)}`)}
|
|
163
|
+
├─────────────────────────────────────────────┤
|
|
164
|
+
${line('Settings')}
|
|
165
|
+
${line(`├── Optimization: ${optEnabled}`)}
|
|
166
|
+
${line(`├── Local cache: ${cacheEnabled}`)}
|
|
167
|
+
${line(`├── Mode: ${mode}`)}
|
|
168
|
+
${line(`└── Provider routing: ${provider}`)}
|
|
169
|
+
└─────────────────────────────────────────────┘
|
|
170
|
+
|
|
171
|
+
Dashboard: ${dashboardUrl}
|
|
172
|
+
`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function fetchJson(apiUrl, endpoint, apiKey) {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const url = new URL(`${apiUrl}${endpoint}`);
|
|
178
|
+
|
|
179
|
+
const options = {
|
|
180
|
+
hostname: url.hostname,
|
|
181
|
+
port: 443,
|
|
182
|
+
path: url.pathname,
|
|
183
|
+
method: 'GET',
|
|
184
|
+
headers: {
|
|
185
|
+
'X-Plexor-Key': apiKey
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const req = https.request(options, (res) => {
|
|
190
|
+
let data = '';
|
|
191
|
+
res.on('data', chunk => data += chunk);
|
|
192
|
+
res.on('end', () => {
|
|
193
|
+
try {
|
|
194
|
+
resolve(JSON.parse(data));
|
|
195
|
+
} catch {
|
|
196
|
+
reject(new Error('Invalid response'));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
req.on('error', reject);
|
|
202
|
+
req.setTimeout(5000, () => {
|
|
203
|
+
req.destroy();
|
|
204
|
+
reject(new Error('Timeout'));
|
|
205
|
+
});
|
|
206
|
+
req.end();
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
main().catch(err => {
|
|
211
|
+
console.error('Error:', err.message);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
});
|