@plexor-dev/claude-code-plugin 0.1.0-beta.2 → 0.1.0-beta.4
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.md +48 -33
- package/hooks/intercept.js +299 -0
- package/hooks/track-response.js +110 -0
- package/package.json +2 -1
- package/lib/constants.js +0 -40
|
@@ -1,43 +1,42 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Configure Plexor settings
|
|
2
|
+
description: Configure Plexor settings (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Plexor Config
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
View and modify Plexor configuration. This is an alias for /plexor-settings.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Steps
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Step 1: Read current configuration**
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `plexor-config show` - Show current configuration
|
|
16
|
-
- `plexor-config enable` - Enable Plexor optimization
|
|
17
|
-
- `plexor-config disable` - Disable Plexor optimization
|
|
18
|
-
- `plexor-config cache on|off` - Enable/disable local cache
|
|
19
|
-
- `plexor-config provider <name>` - Set preferred provider (anthropic, deepseek, auto)
|
|
20
|
-
- `plexor-config reset` - Reset to default configuration
|
|
21
|
-
|
|
22
|
-
## Instructions
|
|
23
|
-
|
|
24
|
-
1. Parse the arguments to determine the command
|
|
25
|
-
2. Read current config from ~/.plexor/config.json
|
|
26
|
-
3. Apply the requested change
|
|
27
|
-
4. Save updated config
|
|
28
|
-
5. Display confirmation
|
|
29
|
-
|
|
30
|
-
## Output
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json`.
|
|
31
14
|
|
|
15
|
+
If the file doesn't exist, show:
|
|
16
|
+
```
|
|
17
|
+
Plexor Config
|
|
18
|
+
=============
|
|
19
|
+
No configuration found. Run /plexor-login to set up Plexor.
|
|
32
20
|
```
|
|
33
|
-
Plexor Configuration Updated
|
|
34
|
-
============================
|
|
35
21
|
|
|
36
|
-
|
|
22
|
+
**Step 2: Display current configuration**
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
24
|
+
```
|
|
25
|
+
Plexor Config
|
|
26
|
+
=============
|
|
27
|
+
Enabled: [enabled]
|
|
28
|
+
API URL: [apiUrl]
|
|
29
|
+
API Key: [show "configured" if apiKey exists, otherwise "not configured"]
|
|
30
|
+
Mode: [mode]
|
|
31
|
+
Provider: [preferredProvider]
|
|
32
|
+
Local Cache: [localCacheEnabled]
|
|
33
|
+
Timeout: [timeout]ms
|
|
34
|
+
|
|
35
|
+
Configuration file: ~/.plexor/config.json
|
|
36
|
+
|
|
37
|
+
Other commands:
|
|
38
|
+
- /plexor-settings - View/edit all settings
|
|
39
|
+
- /plexor-mode - Change optimization mode
|
|
40
|
+
- /plexor-provider - Change provider
|
|
41
|
+
- /plexor-enabled - Enable/disable proxy
|
|
43
42
|
```
|
|
@@ -1,36 +1,54 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Enable or disable Plexor proxy (routes all traffic through Plexor API)
|
|
2
|
+
description: Enable or disable Plexor proxy (routes all traffic through Plexor API) (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
# Plexor Enabled
|
|
6
|
+
|
|
5
7
|
Toggle Plexor proxy mode on or off.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
**Step 1: Read current configuration**
|
|
12
|
+
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json` and check the current `enabled` status.
|
|
14
|
+
|
|
15
|
+
**Step 2: Ask user what they want to do**
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
Use the `AskUserQuestion` tool to present options:
|
|
18
|
+
|
|
19
|
+
Question: "Enable or disable Plexor proxy?"
|
|
10
20
|
Header: "Proxy"
|
|
11
21
|
Options:
|
|
12
|
-
1. **Enable** - Route
|
|
13
|
-
2. **Disable** - Direct to Anthropic (no optimization)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
1. **Enable** - Route traffic through Plexor for optimization (60-90% cost savings)
|
|
23
|
+
2. **Disable** - Direct to Anthropic (no optimization, full price)
|
|
24
|
+
|
|
25
|
+
**Step 3: Update the configuration**
|
|
26
|
+
|
|
27
|
+
Use the Read tool to get the current config, then use the Write tool to update `~/.plexor/config.json`:
|
|
28
|
+
- If **Enable**: Set `"enabled": true`
|
|
29
|
+
- If **Disable**: Set `"enabled": false`
|
|
30
|
+
|
|
31
|
+
Keep all other settings unchanged.
|
|
32
|
+
|
|
33
|
+
**Step 4: Show confirmation**
|
|
34
|
+
|
|
35
|
+
If **Enabled**:
|
|
36
|
+
```
|
|
37
|
+
Plexor Proxy: ENABLED
|
|
38
|
+
|
|
39
|
+
Benefits:
|
|
40
|
+
- 60-90% cost reduction via intelligent provider routing
|
|
41
|
+
- Automatic selection of Mistral/DeepSeek/Gemini based on task
|
|
42
|
+
- Usage tracking and savings analytics
|
|
43
|
+
|
|
44
|
+
Your prompts will now be routed through Plexor for optimization.
|
|
45
|
+
Run /plexor-status to see your savings.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If **Disabled**:
|
|
49
|
+
```
|
|
50
|
+
Plexor Proxy: DISABLED
|
|
51
|
+
|
|
52
|
+
Your prompts will go directly to Anthropic at full price.
|
|
53
|
+
Run /plexor-enabled to re-enable optimization.
|
|
54
|
+
```
|
package/commands/plexor-login.md
CHANGED
|
@@ -1,87 +1,76 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Authenticate with Plexor to enable optimization
|
|
2
|
+
description: Authenticate with Plexor to enable optimization (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Plexor Login
|
|
6
6
|
|
|
7
7
|
Authenticate with your Plexor account to enable LLM cost optimization.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Steps
|
|
10
10
|
|
|
11
|
-
1
|
|
12
|
-
2. If already authenticated and valid, show current account info and status
|
|
13
|
-
3. If not authenticated, initiate device flow:
|
|
14
|
-
a. Call POST https://api.plexor.dev/v1/auth/device-code to get device_code and user_code
|
|
15
|
-
b. Display the user code and verification URL to the user
|
|
16
|
-
c. Open https://plexor.dev/auth/device in the user's browser
|
|
17
|
-
d. Poll POST https://api.plexor.dev/v1/auth/device-token every 5 seconds
|
|
18
|
-
e. On success, save API key to ~/.plexor/config.json
|
|
19
|
-
f. Verify the key works by calling GET https://api.plexor.dev/v1/user
|
|
11
|
+
**Step 1: Check existing authentication**
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
a. Set ANTHROPIC_BASE_URL to route traffic through Plexor gateway
|
|
23
|
-
b. Inform user they need to restart Claude Code
|
|
24
|
-
|
|
25
|
-
## Config File
|
|
26
|
-
|
|
27
|
-
Location: ~/.plexor/config.json
|
|
28
|
-
|
|
29
|
-
```json
|
|
30
|
-
{
|
|
31
|
-
"version": 1,
|
|
32
|
-
"auth": {
|
|
33
|
-
"api_key": "plx_...",
|
|
34
|
-
"email": "user@example.com",
|
|
35
|
-
"tier": "beta",
|
|
36
|
-
"authenticated_at": "2025-12-16T20:00:00Z"
|
|
37
|
-
},
|
|
38
|
-
"settings": {
|
|
39
|
-
"enabled": true,
|
|
40
|
-
"preferred_provider": "auto",
|
|
41
|
-
"mode": "balanced"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Output (New User)
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json`.
|
|
47
14
|
|
|
15
|
+
If the file exists and has an `apiKey` field that starts with "plx_", the user is already authenticated. Show them:
|
|
48
16
|
```
|
|
49
17
|
Plexor Login
|
|
50
18
|
============
|
|
19
|
+
Already authenticated!
|
|
20
|
+
API Key: plx_****[last 4 chars]
|
|
21
|
+
API URL: [apiUrl from config]
|
|
22
|
+
Status: [Enabled/Disabled]
|
|
51
23
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
2. Enter this code:
|
|
56
|
-
ABCD-1234
|
|
24
|
+
Run /plexor-status to see your usage statistics.
|
|
25
|
+
Run /plexor-logout to sign out.
|
|
26
|
+
```
|
|
57
27
|
|
|
58
|
-
|
|
28
|
+
**Step 2: If not authenticated, ask for API key**
|
|
59
29
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
30
|
+
Ask the user to provide their Plexor API key. They can get one from:
|
|
31
|
+
- https://plexor.dev/dashboard (if they have an account)
|
|
32
|
+
- https://plexor.dev/signup (to create an account)
|
|
63
33
|
|
|
64
|
-
|
|
34
|
+
Tell them: "Please provide your Plexor API key (starts with 'plx_'):"
|
|
65
35
|
|
|
66
|
-
|
|
36
|
+
**Step 3: Save the configuration**
|
|
67
37
|
|
|
68
|
-
|
|
38
|
+
Once the user provides an API key, use the Write tool to create/update `~/.plexor/config.json`:
|
|
69
39
|
|
|
70
|
-
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"enabled": true,
|
|
43
|
+
"apiUrl": "https://api.plexor.dev",
|
|
44
|
+
"apiKey": "[user's API key]",
|
|
45
|
+
"mode": "balanced",
|
|
46
|
+
"preferredProvider": "auto",
|
|
47
|
+
"localCacheEnabled": true,
|
|
48
|
+
"timeout": 5000
|
|
49
|
+
}
|
|
71
50
|
```
|
|
72
51
|
|
|
73
|
-
|
|
52
|
+
**Step 4: Verify the key works**
|
|
74
53
|
|
|
54
|
+
Make a test request to verify authentication:
|
|
55
|
+
```
|
|
56
|
+
GET https://api.plexor.dev/api/users/me
|
|
57
|
+
Authorization: Bearer [apiKey]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If successful, show:
|
|
75
61
|
```
|
|
76
62
|
Plexor Login
|
|
77
63
|
============
|
|
64
|
+
Authentication successful!
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
Plan: Beta ($1/month)
|
|
81
|
-
Status: Active
|
|
66
|
+
Your API key has been saved to ~/.plexor/config.json
|
|
82
67
|
|
|
83
|
-
|
|
68
|
+
Next steps:
|
|
69
|
+
1. Run /plexor-status to see your usage
|
|
70
|
+
2. Run /plexor-enabled true to enable the proxy
|
|
71
|
+
3. Start saving on LLM costs!
|
|
84
72
|
|
|
85
|
-
|
|
86
|
-
Run /plexor-logout to sign out.
|
|
73
|
+
Dashboard: https://plexor.dev/dashboard
|
|
87
74
|
```
|
|
75
|
+
|
|
76
|
+
If the API call fails, tell the user the key may be invalid and ask them to check it.
|
|
@@ -1,50 +1,42 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Log out from Plexor and clear credentials
|
|
2
|
+
description: Log out from Plexor and clear credentials (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Plexor Logout
|
|
6
6
|
|
|
7
7
|
Log out from your Plexor account and clear stored credentials.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Steps
|
|
10
10
|
|
|
11
|
-
1
|
|
12
|
-
2. If not authenticated, inform the user they are not logged in
|
|
13
|
-
3. If authenticated:
|
|
14
|
-
a. Clear the API key from the configuration
|
|
15
|
-
b. Preserve non-sensitive settings (mode, provider preferences)
|
|
16
|
-
c. Save the updated configuration
|
|
17
|
-
4. Remind user to restore original Anthropic URL
|
|
18
|
-
5. Display confirmation message
|
|
11
|
+
**Step 1: Read current configuration**
|
|
19
12
|
|
|
20
|
-
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json`.
|
|
21
14
|
|
|
15
|
+
If the file doesn't exist or has no `apiKey`, show:
|
|
22
16
|
```
|
|
23
17
|
Plexor Logout
|
|
24
18
|
=============
|
|
19
|
+
You are not currently logged in.
|
|
20
|
+
Run /plexor-login to authenticate.
|
|
21
|
+
```
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
✓ API key cleared from ~/.plexor/config.json
|
|
28
|
-
|
|
29
|
-
To restore direct Anthropic access, run:
|
|
30
|
-
|
|
31
|
-
unset ANTHROPIC_BASE_URL
|
|
23
|
+
**Step 2: Clear the API key**
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
If authenticated, use the Write tool to update `~/.plexor/config.json`:
|
|
26
|
+
- Remove or clear the `apiKey` field (set to empty string "")
|
|
27
|
+
- Keep all other settings (mode, preferredProvider, etc.)
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
To manage billing: https://plexor.dev/billing
|
|
37
|
-
|
|
38
|
-
To use Plexor again, run /plexor-login
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Output (Not Logged In)
|
|
29
|
+
**Step 3: Show confirmation**
|
|
42
30
|
|
|
43
31
|
```
|
|
44
32
|
Plexor Logout
|
|
45
33
|
=============
|
|
34
|
+
Successfully logged out!
|
|
46
35
|
|
|
47
|
-
|
|
36
|
+
- API key cleared from ~/.plexor/config.json
|
|
37
|
+
- Settings preserved (mode, provider preferences)
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
To use Plexor again, run /plexor-login
|
|
40
|
+
|
|
41
|
+
Dashboard: https://plexor.dev/dashboard
|
|
50
42
|
```
|
package/commands/plexor-mode.md
CHANGED
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Set Plexor optimization mode (eco/balanced/quality/passthrough)
|
|
2
|
+
description: Set Plexor optimization mode (eco/balanced/quality/passthrough) (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Plexor Mode
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Set the Plexor optimization mode to control cost vs quality trade-offs.
|
|
8
|
+
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
**Step 1: Read current configuration**
|
|
12
|
+
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json` and check the current `mode` setting.
|
|
14
|
+
|
|
15
|
+
**Step 2: Ask user which mode they want**
|
|
16
|
+
|
|
17
|
+
Use the `AskUserQuestion` tool:
|
|
8
18
|
|
|
9
19
|
Question: "Which optimization mode would you like to use?"
|
|
10
20
|
Header: "Mode"
|
|
11
21
|
Options:
|
|
12
|
-
1. **eco** - Maximum
|
|
13
|
-
2. **balanced** -
|
|
14
|
-
3. **quality** - Premium models (Claude Opus
|
|
15
|
-
4. **passthrough** - No optimization
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
1. **eco** - Maximum savings (~$0.04/1M tokens) - Uses Ministral 3B
|
|
23
|
+
2. **balanced** - Good balance (~$0.15/1M) - DeepSeek, Gemini Flash, Mistral Small
|
|
24
|
+
3. **quality** - Premium models (~$5/1M) - Claude Opus, Gemini Pro, Mistral Large
|
|
25
|
+
4. **passthrough** - No optimization - Direct to requested model
|
|
26
|
+
|
|
27
|
+
**Step 3: Update the configuration**
|
|
28
|
+
|
|
29
|
+
Use the Read tool to get the current config, then use the Write tool to update `~/.plexor/config.json`:
|
|
30
|
+
- Set the `mode` field to the selected value: "eco", "balanced", "quality", or "passthrough"
|
|
31
|
+
|
|
32
|
+
Keep all other settings unchanged.
|
|
33
|
+
|
|
34
|
+
**Step 4: Show confirmation**
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Plexor Mode: [SELECTED MODE]
|
|
38
|
+
|
|
39
|
+
Mode Details:
|
|
40
|
+
- eco: Maximum cost savings using Ministral 3B ($0.04/$0.04 per 1M tokens)
|
|
41
|
+
- balanced: Cost-first with quality fallbacks (DeepSeek → Gemini Flash → Mistral)
|
|
42
|
+
- quality: Premium models for complex tasks (Claude Opus, Gemini Pro)
|
|
43
|
+
- passthrough: No optimization, direct to Anthropic
|
|
44
|
+
|
|
45
|
+
Current mode: [mode]
|
|
46
|
+
Run /plexor-status to see your savings.
|
|
47
|
+
```
|
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Force a specific LLM provider (claude/openai/deepseek/mistral/gemini/auto)
|
|
2
|
+
description: Force a specific LLM provider (claude/openai/deepseek/mistral/gemini/auto) (user)
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Plexor Provider
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Set your preferred LLM provider for Plexor routing.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Steps
|
|
10
|
+
|
|
11
|
+
**Step 1: Read current configuration**
|
|
12
|
+
|
|
13
|
+
Use the Read tool to read `~/.plexor/config.json` and check the current `preferredProvider` setting.
|
|
14
|
+
|
|
15
|
+
**Step 2: Ask user which provider they prefer**
|
|
16
|
+
|
|
17
|
+
Use the `AskUserQuestion` tool:
|
|
18
|
+
|
|
19
|
+
Question: "Which LLM provider would you prefer?"
|
|
10
20
|
Header: "Provider"
|
|
11
21
|
Options:
|
|
12
|
-
1. **auto** -
|
|
13
|
-
2. **
|
|
14
|
-
3. **
|
|
15
|
-
4. **gemini** -
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
1. **auto** - Let Plexor choose the best provider based on task (Recommended)
|
|
23
|
+
2. **deepseek** - Use DeepSeek models (cheapest, good for code)
|
|
24
|
+
3. **mistral** - Use Mistral models (fast, good balance)
|
|
25
|
+
4. **gemini** - Use Google Gemini models (good for analysis)
|
|
26
|
+
|
|
27
|
+
**Step 3: Update the configuration**
|
|
28
|
+
|
|
29
|
+
Use the Read tool to get the current config, then use the Write tool to update `~/.plexor/config.json`:
|
|
30
|
+
- Set `preferredProvider` to: "auto", "deepseek", "mistral", or "gemini"
|
|
31
|
+
|
|
32
|
+
Keep all other settings unchanged.
|
|
33
|
+
|
|
34
|
+
**Step 4: Show confirmation**
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Plexor Provider: [SELECTED PROVIDER]
|
|
38
|
+
|
|
39
|
+
Provider options:
|
|
40
|
+
- auto: Plexor automatically selects the best provider for each task
|
|
41
|
+
- deepseek: DeepSeek models ($0.14/$0.28 per 1M tokens) - Great for code
|
|
42
|
+
- mistral: Mistral models ($0.10/$0.30 per 1M tokens) - Fast and versatile
|
|
43
|
+
- gemini: Google Gemini ($0.075/$0.30 per 1M tokens) - Good for analysis
|
|
44
|
+
|
|
45
|
+
Current provider: [preferredProvider]
|
|
46
|
+
Run /plexor-status to see your usage.
|
|
47
|
+
```
|
|
@@ -1,93 +1,58 @@
|
|
|
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
|
-
export ANTHROPIC_BASE_URL="https://api.plexor.dev/v1/gateway/anthropic"
|
|
92
|
-
claude --continue
|
|
93
|
-
```
|
|
58
|
+
Then update `~/.plexor/config.json` with the new value using the Write tool.
|
|
@@ -1,46 +1,61 @@
|
|
|
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
|
-
|
|
7
|
+
Show current Plexor proxy status, configuration, and usage statistics.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Steps
|
|
10
10
|
|
|
11
|
-
1
|
|
12
|
-
2. If not authenticated, show a message to run /plexor-login
|
|
13
|
-
3. If authenticated, call the Plexor API to get usage stats
|
|
14
|
-
4. Display a formatted summary including:
|
|
15
|
-
- Account status (Free/Pro/Team)
|
|
16
|
-
- This session: requests optimized, tokens saved, estimated savings
|
|
17
|
-
- This week: total requests, total tokens saved, total savings
|
|
18
|
-
- Current settings: optimization enabled, local cache status
|
|
11
|
+
**Step 1: Read the configuration file**
|
|
19
12
|
|
|
20
|
-
|
|
13
|
+
Use the Read tool to read the file `~/.plexor/config.json`. If the file doesn't exist, tell the user to run `/plexor-login` first.
|
|
21
14
|
|
|
15
|
+
**Step 2: Parse configuration and check authentication**
|
|
16
|
+
|
|
17
|
+
The config file contains:
|
|
18
|
+
- `enabled`: boolean - whether Plexor proxy is enabled
|
|
19
|
+
- `apiUrl`: string - the API URL (e.g., "https://api.plexor.dev")
|
|
20
|
+
- `apiKey`: string - the user's API key (starts with "plx_")
|
|
21
|
+
- `mode`: string - optimization mode ("eco", "balanced", "quality", "passthrough")
|
|
22
|
+
- `preferredProvider`: string - provider preference ("auto", "claude", "openai", etc.)
|
|
23
|
+
- `localCacheEnabled`: boolean - whether local caching is enabled
|
|
24
|
+
|
|
25
|
+
If `apiKey` is missing or empty, tell the user to run `/plexor-login` first.
|
|
26
|
+
|
|
27
|
+
**Step 3: Call the stats API**
|
|
28
|
+
|
|
29
|
+
Make a request to get usage statistics:
|
|
22
30
|
```
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
GET {apiUrl}/api/users/me/usage
|
|
32
|
+
Authorization: Bearer {apiKey}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Note: The apiKey IS the bearer token - use it directly in the Authorization header.
|
|
36
|
+
|
|
37
|
+
**Step 4: Display the status**
|
|
38
|
+
|
|
39
|
+
Show the user a formatted status display like this:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Plexor Status
|
|
43
|
+
=============
|
|
44
|
+
Account: [tier from API response, e.g., "Pro" or "Free"]
|
|
45
|
+
Status: [Enabled/Disabled based on config.enabled]
|
|
46
|
+
|
|
47
|
+
This Session
|
|
48
|
+
Requests: [totalRequests from API]
|
|
49
|
+
Tokens saved: [tokensUsed from API]
|
|
50
|
+
Est. savings: $[costSavings from API]
|
|
51
|
+
|
|
52
|
+
Settings
|
|
53
|
+
Mode: [config.mode]
|
|
54
|
+
Provider: [config.preferredProvider]
|
|
55
|
+
Local cache: [Enabled/Disabled based on config.localCacheEnabled]
|
|
56
|
+
API URL: [config.apiUrl]
|
|
44
57
|
|
|
45
58
|
Dashboard: https://plexor.dev/dashboard
|
|
46
59
|
```
|
|
60
|
+
|
|
61
|
+
If the API call fails, show the configuration status and mention the API is unavailable.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Interception Hook
|
|
5
|
+
*
|
|
6
|
+
* This script intercepts Claude Code prompts before they are sent to the LLM.
|
|
7
|
+
* It optimizes the prompt and optionally routes to a cheaper provider.
|
|
8
|
+
*
|
|
9
|
+
* Input: JSON object with messages, model, max_tokens, etc.
|
|
10
|
+
* Output: Modified JSON object with optimized messages
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const PlexorClient = require('../lib/plexor-client');
|
|
14
|
+
const ConfigManager = require('../lib/config');
|
|
15
|
+
const LocalCache = require('../lib/cache');
|
|
16
|
+
const Logger = require('../lib/logger');
|
|
17
|
+
|
|
18
|
+
const logger = new Logger('intercept');
|
|
19
|
+
const config = new ConfigManager();
|
|
20
|
+
const cache = new LocalCache();
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const input = await readStdin();
|
|
27
|
+
const request = JSON.parse(input);
|
|
28
|
+
|
|
29
|
+
// CRITICAL: Skip optimization for agentic/tool-using requests
|
|
30
|
+
// Modifying messages breaks the agent loop and causes infinite loops
|
|
31
|
+
if (isAgenticRequest(request)) {
|
|
32
|
+
logger.debug('Agentic request detected, passing through unchanged');
|
|
33
|
+
return output({
|
|
34
|
+
...request,
|
|
35
|
+
plexor_cwd: process.cwd(),
|
|
36
|
+
_plexor: {
|
|
37
|
+
source: 'passthrough_agentic',
|
|
38
|
+
reason: 'tool_use_detected',
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
latency_ms: Date.now() - startTime
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const settings = await config.load();
|
|
46
|
+
|
|
47
|
+
if (!settings.enabled) {
|
|
48
|
+
logger.debug('Plexor disabled, passing through');
|
|
49
|
+
return output(request);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!settings.apiKey) {
|
|
53
|
+
logger.info('Not authenticated. Run /plexor-login to enable optimization.');
|
|
54
|
+
return output(request);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const client = new PlexorClient({
|
|
58
|
+
apiKey: settings.apiKey,
|
|
59
|
+
baseUrl: settings.apiUrl || 'https://api.plexor.dev',
|
|
60
|
+
timeout: settings.timeout || 5000
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const messages = extractMessages(request);
|
|
64
|
+
const model = request.model || 'claude-sonnet-4-20250514';
|
|
65
|
+
const maxTokens = request.max_tokens || 4096;
|
|
66
|
+
|
|
67
|
+
const cacheKey = cache.generateKey(messages);
|
|
68
|
+
const cachedResponse = await cache.get(cacheKey);
|
|
69
|
+
|
|
70
|
+
if (cachedResponse && settings.localCacheEnabled) {
|
|
71
|
+
logger.info('[Plexor] Local cache hit');
|
|
72
|
+
return output({
|
|
73
|
+
...request,
|
|
74
|
+
_plexor: {
|
|
75
|
+
source: 'local_cache',
|
|
76
|
+
latency_ms: Date.now() - startTime
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
logger.debug('Calling Plexor API...');
|
|
82
|
+
|
|
83
|
+
const result = await client.optimize({
|
|
84
|
+
messages: messages,
|
|
85
|
+
model: model,
|
|
86
|
+
max_tokens: maxTokens,
|
|
87
|
+
task_hint: detectTaskType(messages),
|
|
88
|
+
context: {
|
|
89
|
+
session_id: request._session_id,
|
|
90
|
+
turn_number: request._turn_number,
|
|
91
|
+
cwd: process.cwd()
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const savingsPercent = ((result.original_tokens - result.optimized_tokens) / result.original_tokens * 100).toFixed(1);
|
|
96
|
+
|
|
97
|
+
logger.info(`[Plexor] Optimized: ${result.original_tokens} → ${result.optimized_tokens} tokens (${savingsPercent}% saved)`);
|
|
98
|
+
|
|
99
|
+
if (result.recommended_provider !== 'anthropic') {
|
|
100
|
+
logger.info(`[Plexor] Recommended: ${result.recommended_provider} (~$${result.estimated_cost.toFixed(4)})`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const optimizedRequest = {
|
|
104
|
+
...request,
|
|
105
|
+
messages: result.optimized_messages,
|
|
106
|
+
plexor_cwd: process.cwd(),
|
|
107
|
+
_plexor: {
|
|
108
|
+
request_id: result.request_id,
|
|
109
|
+
original_tokens: result.original_tokens,
|
|
110
|
+
optimized_tokens: result.optimized_tokens,
|
|
111
|
+
tokens_saved: result.tokens_saved,
|
|
112
|
+
savings_percent: parseFloat(savingsPercent),
|
|
113
|
+
recommended_provider: result.recommended_provider,
|
|
114
|
+
recommended_model: result.recommended_model,
|
|
115
|
+
estimated_cost: result.estimated_cost,
|
|
116
|
+
baseline_cost: result.baseline_cost,
|
|
117
|
+
latency_ms: Date.now() - startTime,
|
|
118
|
+
source: 'plexor_api',
|
|
119
|
+
cwd: process.cwd()
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
await cache.setMetadata(result.request_id, {
|
|
124
|
+
original_tokens: result.original_tokens,
|
|
125
|
+
optimized_tokens: result.optimized_tokens,
|
|
126
|
+
recommended_provider: result.recommended_provider,
|
|
127
|
+
timestamp: Date.now()
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return output(optimizedRequest);
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
logger.error(`[Plexor] Error: ${error.message}`);
|
|
134
|
+
logger.debug(error.stack);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const input = await readStdin();
|
|
138
|
+
const request = JSON.parse(input);
|
|
139
|
+
return output({
|
|
140
|
+
...request,
|
|
141
|
+
_plexor: {
|
|
142
|
+
error: error.message,
|
|
143
|
+
source: 'passthrough'
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
} catch {
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function readStdin() {
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const chunks = [];
|
|
155
|
+
|
|
156
|
+
process.stdin.on('data', (chunk) => {
|
|
157
|
+
chunks.push(chunk);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
process.stdin.on('end', () => {
|
|
161
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
process.stdin.on('error', reject);
|
|
165
|
+
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
reject(new Error('Stdin read timeout'));
|
|
168
|
+
}, 5000);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function output(data) {
|
|
173
|
+
const json = JSON.stringify(data);
|
|
174
|
+
process.stdout.write(json);
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function extractMessages(request) {
|
|
179
|
+
if (Array.isArray(request.messages)) {
|
|
180
|
+
return request.messages;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (request.prompt) {
|
|
184
|
+
return [{ role: 'user', content: request.prompt }];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (request.system && request.user) {
|
|
188
|
+
return [
|
|
189
|
+
{ role: 'system', content: request.system },
|
|
190
|
+
{ role: 'user', content: request.user }
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function detectTaskType(messages) {
|
|
198
|
+
if (!messages || messages.length === 0) {
|
|
199
|
+
return 'general';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const lastUserMessage = [...messages]
|
|
203
|
+
.reverse()
|
|
204
|
+
.find(m => m.role === 'user');
|
|
205
|
+
|
|
206
|
+
if (!lastUserMessage) {
|
|
207
|
+
return 'general';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const content = lastUserMessage.content.toLowerCase();
|
|
211
|
+
|
|
212
|
+
if (/```|function|class|import|export|const |let |var |def |async |await/.test(content)) {
|
|
213
|
+
return 'code_generation';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (/test|spec|jest|pytest|unittest|describe\(|it\(|expect\(/.test(content)) {
|
|
217
|
+
return 'test_generation';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (/fix|bug|error|issue|debug|trace|exception|crash/.test(content)) {
|
|
221
|
+
return 'debugging';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (/refactor|improve|optimize|clean|restructure/.test(content)) {
|
|
225
|
+
return 'refactoring';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (/document|readme|comment|explain|docstring/.test(content)) {
|
|
229
|
+
return 'documentation';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (/review|check|audit|assess|evaluate/.test(content)) {
|
|
233
|
+
return 'code_review';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (/analyze|understand|what does|how does|explain/.test(content)) {
|
|
237
|
+
return 'analysis';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return 'general';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Detect if this is an agentic/tool-using request that should not be optimized.
|
|
245
|
+
* Modifying messages in agent loops breaks the loop detection and causes infinite loops.
|
|
246
|
+
*/
|
|
247
|
+
function isAgenticRequest(request) {
|
|
248
|
+
// Check if request has tools defined
|
|
249
|
+
if (request.tools && request.tools.length > 0) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check if any message contains tool use or tool results
|
|
254
|
+
const messages = request.messages || [];
|
|
255
|
+
for (const msg of messages) {
|
|
256
|
+
// Tool use in content (Claude format)
|
|
257
|
+
if (msg.content && Array.isArray(msg.content)) {
|
|
258
|
+
for (const block of msg.content) {
|
|
259
|
+
if (block.type === 'tool_use' || block.type === 'tool_result') {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Tool role (OpenAI format)
|
|
266
|
+
if (msg.role === 'tool') {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Function call (OpenAI format)
|
|
271
|
+
if (msg.function_call || msg.tool_calls) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check for assistant messages with tool indicators
|
|
277
|
+
for (const msg of messages) {
|
|
278
|
+
if (msg.role === 'assistant' && msg.content) {
|
|
279
|
+
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
280
|
+
// Detect common tool use patterns in Claude Code
|
|
281
|
+
if (/\[Bash\]|\[Read\]|\[Write\]|\[Edit\]|\[Glob\]|\[Grep\]/.test(content)) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check for multi-turn conversations (likely agentic)
|
|
288
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
289
|
+
if (assistantMessages.length > 2) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
main().catch((error) => {
|
|
297
|
+
console.error(`[Plexor] Fatal error: ${error.message}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plexor Response Tracking Hook
|
|
5
|
+
*
|
|
6
|
+
* This script runs after the LLM response is received.
|
|
7
|
+
* It tracks response metrics for analytics and updates session stats.
|
|
8
|
+
*
|
|
9
|
+
* Input: JSON object with response content, tokens used, etc.
|
|
10
|
+
* Output: Passthrough (no modifications)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const PlexorClient = require('../lib/plexor-client');
|
|
14
|
+
const ConfigManager = require('../lib/config');
|
|
15
|
+
const LocalCache = require('../lib/cache');
|
|
16
|
+
const Logger = require('../lib/logger');
|
|
17
|
+
|
|
18
|
+
const logger = new Logger('track-response');
|
|
19
|
+
const config = new ConfigManager();
|
|
20
|
+
const cache = new LocalCache();
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
try {
|
|
24
|
+
const input = await readStdin();
|
|
25
|
+
const response = JSON.parse(input);
|
|
26
|
+
|
|
27
|
+
const settings = await config.load();
|
|
28
|
+
|
|
29
|
+
// If Plexor is disabled or no API key, just pass through
|
|
30
|
+
if (!settings.enabled || !settings.apiKey) {
|
|
31
|
+
return output(response);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if this response has Plexor metadata
|
|
35
|
+
const plexorMeta = response._plexor;
|
|
36
|
+
if (!plexorMeta || !plexorMeta.request_id) {
|
|
37
|
+
return output(response);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Get stored metadata for this request
|
|
41
|
+
const metadata = await cache.getMetadata(plexorMeta.request_id);
|
|
42
|
+
if (!metadata) {
|
|
43
|
+
return output(response);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Calculate output tokens (approximate)
|
|
47
|
+
const outputTokens = estimateTokens(response.content || '');
|
|
48
|
+
|
|
49
|
+
// Log response tracking
|
|
50
|
+
logger.info('[Plexor] Response tracked', {
|
|
51
|
+
request_id: plexorMeta.request_id,
|
|
52
|
+
input_tokens: metadata.optimized_tokens,
|
|
53
|
+
output_tokens: outputTokens,
|
|
54
|
+
provider: metadata.recommended_provider
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// In production, we would send this data to the API for analytics
|
|
58
|
+
// For now, just log locally
|
|
59
|
+
|
|
60
|
+
// Pass through unchanged
|
|
61
|
+
return output(response);
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.error(`[Plexor] Tracking error: ${error.message}`);
|
|
65
|
+
|
|
66
|
+
// On any error, pass through unchanged
|
|
67
|
+
try {
|
|
68
|
+
const input = await readStdin();
|
|
69
|
+
return output(JSON.parse(input));
|
|
70
|
+
} catch {
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function readStdin() {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const chunks = [];
|
|
79
|
+
|
|
80
|
+
process.stdin.on('data', (chunk) => {
|
|
81
|
+
chunks.push(chunk);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
process.stdin.on('end', () => {
|
|
85
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
process.stdin.on('error', reject);
|
|
89
|
+
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
reject(new Error('Stdin read timeout'));
|
|
92
|
+
}, 2000);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function output(data) {
|
|
97
|
+
const json = JSON.stringify(data);
|
|
98
|
+
process.stdout.write(json);
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function estimateTokens(text) {
|
|
103
|
+
// Approximate: ~4 characters per token
|
|
104
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
main().catch((error) => {
|
|
108
|
+
console.error(`[Plexor] Fatal error: ${error.message}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plexor-dev/claude-code-plugin",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
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
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"commands/",
|
|
13
|
+
"hooks/",
|
|
13
14
|
"scripts/",
|
|
14
15
|
"lib/",
|
|
15
16
|
"README.md",
|
package/lib/constants.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plexor Claude Code Plugin - Constants
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
module.exports = {
|
|
9
|
-
// API endpoints
|
|
10
|
-
PLEXOR_API_URL: process.env.PLEXOR_API_URL || 'https://api.plexor.dev',
|
|
11
|
-
PLEXOR_GATEWAY_URL: process.env.PLEXOR_GATEWAY_URL || 'https://api.plexor.dev/v1',
|
|
12
|
-
PLEXOR_AUTH_URL: 'https://plexor.dev/auth/device',
|
|
13
|
-
|
|
14
|
-
// File paths
|
|
15
|
-
PLEXOR_CONFIG_DIR: process.env.PLEXOR_CONFIG_DIR || path.join(os.homedir(), '.plexor'),
|
|
16
|
-
PLEXOR_CONFIG_FILE: path.join(
|
|
17
|
-
process.env.PLEXOR_CONFIG_DIR || path.join(os.homedir(), '.plexor'),
|
|
18
|
-
'config.json'
|
|
19
|
-
),
|
|
20
|
-
CLAUDE_COMMANDS_DIR: path.join(os.homedir(), '.claude', 'commands'),
|
|
21
|
-
|
|
22
|
-
// Config schema version
|
|
23
|
-
CONFIG_VERSION: 1,
|
|
24
|
-
|
|
25
|
-
// Default settings
|
|
26
|
-
DEFAULTS: {
|
|
27
|
-
enabled: true,
|
|
28
|
-
preferred_provider: 'auto',
|
|
29
|
-
telemetry: true,
|
|
30
|
-
local_cache: false
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
// API key prefix for identification
|
|
34
|
-
API_KEY_PREFIX: 'plx_',
|
|
35
|
-
|
|
36
|
-
// Timeouts (ms)
|
|
37
|
-
DEVICE_CODE_POLL_INTERVAL: 5000,
|
|
38
|
-
DEVICE_CODE_TIMEOUT: 900000, // 15 minutes
|
|
39
|
-
API_TIMEOUT: 30000
|
|
40
|
-
};
|