troxy-cli 1.29.32 → 1.29.34
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.34",
|
|
4
4
|
"description": "A secure control layer for AI agents: policies across payments, messages, logins, destructive actions, model usage, and secrets, all enforceable from the CLI",
|
|
5
5
|
"homepage": "https://troxy.io",
|
|
6
6
|
"bugs": {
|
package/src/install-mod.js
CHANGED
|
@@ -18,17 +18,17 @@ export async function installTroxySavingsModToClaudeCode() {
|
|
|
18
18
|
try {
|
|
19
19
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
20
20
|
|
|
21
|
-
// Add
|
|
21
|
+
// Add Notification hook to show usage bar after each response
|
|
22
22
|
if (!settings.hooks) settings.hooks = {};
|
|
23
|
-
if (!settings.hooks.
|
|
23
|
+
if (!settings.hooks.Notification) settings.hooks.Notification = [];
|
|
24
24
|
|
|
25
25
|
// Check if already installed
|
|
26
|
-
const alreadyInstalled = settings.hooks.
|
|
26
|
+
const alreadyInstalled = settings.hooks.Notification.some(h =>
|
|
27
27
|
h.hooks?.[0]?.command?.includes('troxy usage-bar')
|
|
28
28
|
);
|
|
29
29
|
|
|
30
30
|
if (!alreadyInstalled) {
|
|
31
|
-
settings.hooks.
|
|
31
|
+
settings.hooks.Notification.push({
|
|
32
32
|
matcher: '*',
|
|
33
33
|
hooks: [
|
|
34
34
|
{
|
|
@@ -115,6 +115,24 @@ function troxyCommand(args, paused) {
|
|
|
115
115
|
// Whether this session has shown its one "Troxy is active" notice yet.
|
|
116
116
|
let announced = false;
|
|
117
117
|
|
|
118
|
+
// Session savings tracking: cumulative % savings from routing
|
|
119
|
+
let sessionSavingsPercent = 0;
|
|
120
|
+
let routingCount = 0;
|
|
121
|
+
|
|
122
|
+
// Cost ratio of models (relative to Opus=1.0)
|
|
123
|
+
const MODEL_COST = {
|
|
124
|
+
'claude-haiku-4-5-20251001': 0.3,
|
|
125
|
+
'claude-sonnet-5-5': 0.6,
|
|
126
|
+
'claude-opus-5-5': 1.0,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
function calculateSavings(fromModel, toModel) {
|
|
130
|
+
const fromCost = MODEL_COST[fromModel] || 1.0;
|
|
131
|
+
const toCost = MODEL_COST[toModel] || 1.0;
|
|
132
|
+
if (toCost >= fromCost) return 0; // no savings if routing upward
|
|
133
|
+
return Math.round(((fromCost - toCost) / fromCost) * 100);
|
|
134
|
+
}
|
|
135
|
+
|
|
118
136
|
// Where the local daemon looks for this session's routing note (see interceptor.js).
|
|
119
137
|
// Only when installed under ~/.claude/skills, so a --plugin-dir test run never writes
|
|
120
138
|
// into a real user's folder.
|
|
@@ -154,11 +172,16 @@ async function* routeStep($, e, next) {
|
|
|
154
172
|
// Main loop only: a subagent keeps whatever model it was given.
|
|
155
173
|
if (!decision || e.agentId) return yield* next(e);
|
|
156
174
|
const model = pickModel(decision.score, e.model, decision.tokens);
|
|
157
|
-
if (model !== e.model && !announced) {
|
|
158
|
-
announced = true;
|
|
159
|
-
$.ui.toast('Troxy is active');
|
|
160
|
-
}
|
|
161
175
|
if (model !== e.model) {
|
|
176
|
+
const savings = calculateSavings(e.model, model);
|
|
177
|
+
if (savings > 0) {
|
|
178
|
+
routingCount += 1;
|
|
179
|
+
sessionSavingsPercent = Math.round((sessionSavingsPercent * (routingCount - 1) + savings) / routingCount);
|
|
180
|
+
}
|
|
181
|
+
if (!announced) {
|
|
182
|
+
announced = true;
|
|
183
|
+
$.ui.toast(`Troxy is active · Session: -${sessionSavingsPercent}% saved`);
|
|
184
|
+
}
|
|
162
185
|
const sid = await $.session.id();
|
|
163
186
|
const file = modRouteFile($.plugin.root, sid);
|
|
164
187
|
const key = `${sid}:${e.model}>${model}`;
|
|
@@ -181,6 +204,15 @@ export function register(on) {
|
|
|
181
204
|
await $.store.set('paused', r.paused);
|
|
182
205
|
return { text: r.text };
|
|
183
206
|
});
|
|
207
|
+
// Status line showing live savings
|
|
208
|
+
on('turn.start', async ($, e, next) => {
|
|
209
|
+
const paused = Boolean(await $.store.get('paused'));
|
|
210
|
+
const status = paused
|
|
211
|
+
? 'Troxy: off'
|
|
212
|
+
: (routingCount > 0 ? `Troxy: -${sessionSavingsPercent}% saved` : 'Troxy: active');
|
|
213
|
+
$.statusLine.set(status);
|
|
214
|
+
return next(e);
|
|
215
|
+
});
|
|
184
216
|
// Routing fails open: a scorer or hook error must never break the session,
|
|
185
217
|
// it just means the requested model is used as-is.
|
|
186
218
|
on('prompt.submit', scorePrompt).catch(($, e, next) => { decision = null; return next(e); });
|