troxy-cli 1.29.55 → 1.29.57
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.57",
|
|
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": {
|
|
@@ -135,8 +135,11 @@ function troxyCommand(args, paused) {
|
|
|
135
135
|
// The band above the prompt: how much longer this session made the plan meter last.
|
|
136
136
|
// Each turn adds what it cost (the session's own /cost ledger) and how far it moved the
|
|
137
137
|
// 5h/7d meter; a routed turn also counts what it would have cost on the model asked
|
|
138
|
-
// for,
|
|
139
|
-
|
|
138
|
+
// for, priced the way the usage bar weighs models. The account-wide view is the dashboard's.
|
|
139
|
+
// List $/MTok in (output keeps the same ratios) times usage-bar points per list dollar,
|
|
140
|
+
// measured from the bar's own readings (usage-audit bar_weights.py, 2026-10-13). The bar
|
|
141
|
+
// charges Haiku more per dollar than list prices imply; same weights as the dashboard.
|
|
142
|
+
const PRICE = { haiku: 1 * 4.45, sonnet: 2 * 3.19, opus: 4 * 2.80 };
|
|
140
143
|
const WINDOW_LABEL = { five_hour: '5h', seven_day: '7d' };
|
|
141
144
|
// Drawn in Troxy's teal: the status line can't be colored and shows as a yellow warning.
|
|
142
145
|
const LINE = { plugin: 'troxy-guard', key: 'line' };
|
|
@@ -168,14 +171,16 @@ function savedLine(money, rateLimits = []) {
|
|
|
168
171
|
parts.push(`${WINDOW_LABEL[w.kind]}: ${used < 1 ? 'not moved yet' : `${used} instead of ${Math.round(used * scale)}`}`);
|
|
169
172
|
}
|
|
170
173
|
} else {
|
|
171
|
-
parts.push(
|
|
174
|
+
parts.push('meter not moved yet');
|
|
172
175
|
}
|
|
173
176
|
parts.push('estimate');
|
|
174
177
|
return `${bar(1 / scale)} ${parts.join(' · ')}`;
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
// t.kept: what the content Troxy cleared or compressed would have cost (server's
|
|
181
|
+
// session-savings kept_usd), on top of the model swaps in t.full.
|
|
177
182
|
function sessionLine(t) {
|
|
178
|
-
return t && savedLine({ cost_paid_usd: t.paid, dollars_saved_usd: t.full - t.paid },
|
|
183
|
+
return t && savedLine({ cost_paid_usd: t.paid, dollars_saved_usd: t.full - t.paid + (t.kept || 0) },
|
|
179
184
|
Object.entries(t.meter).map(([kind, percentUsed]) => ({ kind, percentUsed })));
|
|
180
185
|
}
|
|
181
186
|
|
|
@@ -244,7 +249,7 @@ function addTurn(totals, start, end, ratio) {
|
|
|
244
249
|
for (const k of Object.keys(end.meter)) {
|
|
245
250
|
if (k in start.meter) meter[k] = (meter[k] || 0) + Math.max(0, end.meter[k] - start.meter[k]);
|
|
246
251
|
}
|
|
247
|
-
return { paid: t.paid + paid, full: t.full + paid * ratio, meter };
|
|
252
|
+
return { ...t, paid: t.paid + paid, full: t.full + paid * ratio, meter }; // keeps t.kept
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
// Idle compaction: shrink a big session just before its prompt cache expires (1h), so
|
|
@@ -525,21 +530,25 @@ export function register(on) {
|
|
|
525
530
|
// After the answer, so counting never delays a prompt.
|
|
526
531
|
on('turn.complete', async ($, e, next) => {
|
|
527
532
|
const r = await next(e);
|
|
533
|
+
let savings = null;
|
|
534
|
+
try {
|
|
535
|
+
savings = await fetchSavings($);
|
|
536
|
+
if (savings) await $.state.set(SAVINGS, savingsText(savings));
|
|
537
|
+
} catch {} // a missed fetch only leaves the band as it was
|
|
528
538
|
if (turn) {
|
|
529
539
|
const { start, ratio } = turn;
|
|
530
540
|
turn = null;
|
|
531
541
|
try {
|
|
532
542
|
const { value } = await $.state.get(SAVED);
|
|
533
543
|
const totals = addTurn(value, start, await snapshot($), ratio);
|
|
544
|
+
// ponytail: kept_usd is the whole session's server total, paid only counts turns
|
|
545
|
+
// since the mod started tracking; close enough once the session has a few turns.
|
|
546
|
+
if (savings && savings.kept_usd >= 0) totals.kept = savings.kept_usd;
|
|
534
547
|
await $.state.set(SAVED, totals);
|
|
535
548
|
const line = sessionLine(totals);
|
|
536
549
|
if (line) await $.state.set(LINE, line);
|
|
537
550
|
} catch {} // a missed turn only leaves the band as it was
|
|
538
551
|
}
|
|
539
|
-
try {
|
|
540
|
-
const text = savingsText(await fetchSavings($));
|
|
541
|
-
if (text) await $.state.set(SAVINGS, text);
|
|
542
|
-
} catch {} // a missed fetch only leaves the band as it was
|
|
543
552
|
let stayAwake = false;
|
|
544
553
|
try { stayAwake = await bigSession($); } catch {}
|
|
545
554
|
armCompact($, COMPACT_IDLE_MS, { stayAwake, persist: true });
|
|
@@ -63,7 +63,7 @@ test('real subprocess: a real Bash command still runs normally through the mod (
|
|
|
63
63
|
}
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
test('savedLine: how much longer the bar lasts, then the meter
|
|
66
|
+
test('savedLine: how much longer the bar lasts, then the meter', () => {
|
|
67
67
|
const { savedLine } = _internal;
|
|
68
68
|
const meter = [{ kind: 'five_hour', percentUsed: 19 }, { kind: 'seven_day', percentUsed: 12.4 }];
|
|
69
69
|
assert.equal(savedLine({ cost_paid_usd: 19, dollars_saved_usd: 13 }, meter),
|
|
@@ -71,9 +71,9 @@ test('savedLine: how much longer the bar lasts, then the meter or the dollars',
|
|
|
71
71
|
assert.equal(savedLine({ cost_paid_usd: 92, dollars_saved_usd: 8 }, [{ kind: 'five_hour', percentUsed: 12 }, { kind: 'seven_day', percentUsed: 0 }]),
|
|
72
72
|
'[█████████░] This session: your bar lasts 1.09x longer · 5h: 12 instead of 13 · 7d: not moved yet · estimate');
|
|
73
73
|
assert.equal(savedLine({ cost_paid_usd: 1.64, dollars_saved_usd: 4.53 }, [{ kind: 'five_hour', percentUsed: 0.3 }]),
|
|
74
|
-
'[███░░░░░░░] This session: your bar lasts 3.8x longer ·
|
|
74
|
+
'[███░░░░░░░] This session: your bar lasts 3.8x longer · meter not moved yet · estimate');
|
|
75
75
|
assert.equal(savedLine({ cost_paid_usd: 92, dollars_saved_usd: 8 }, []),
|
|
76
|
-
'[█████████░] This session: your bar lasts 1.09x longer ·
|
|
76
|
+
'[█████████░] This session: your bar lasts 1.09x longer · meter not moved yet · estimate');
|
|
77
77
|
assert.equal(savedLine({ cost_paid_usd: 10, dollars_saved_usd: 0 }, meter),
|
|
78
78
|
'[██████████] This session: your bar lasts 1.00x longer · 5h: 19 instead of 19 · 7d: 12 instead of 12 · estimate');
|
|
79
79
|
assert.equal(savedLine(null, meter), undefined);
|
|
@@ -88,6 +88,10 @@ test('addTurn: a session total from its own turns, a routed one counted at the a
|
|
|
88
88
|
t = addTurn(t, { cost: 1.5, meter: { five_hour: 13, seven_day: 5 } }, { cost: 2, meter: { five_hour: 1, seven_day: 6 } }, 1);
|
|
89
89
|
assert.deepEqual(t, { paid: 1, full: 1.5, meter: { five_hour: 3, seven_day: 2 } });
|
|
90
90
|
assert.equal(sessionLine(t), '[███████░░░] This session: your bar lasts 1.5x longer · 5h: 3 instead of 5 · 7d: 2 instead of 3 · estimate');
|
|
91
|
+
// Cleared/compressed content counts too, and survives the next turn: $1 paid, $0.5 swap + $1.5 kept = 3x.
|
|
92
|
+
t = addTurn({ ...t, kept: 1.5 }, { cost: 2, meter: {} }, { cost: 2, meter: {} }, 1);
|
|
93
|
+
assert.equal(t.kept, 1.5);
|
|
94
|
+
assert.match(sessionLine(t), /your bar lasts 3\.0x longer · 5h: 3 instead of 9/);
|
|
91
95
|
// No routed turn yet: no band.
|
|
92
96
|
assert.equal(sessionLine(addTurn(undefined, { cost: 0, meter: {} }, { cost: 1, meter: {} }, 1)), undefined);
|
|
93
97
|
});
|
|
@@ -97,7 +101,7 @@ test('statusText: version, routing, this session, plan usage, where the account
|
|
|
97
101
|
const totals = { paid: 0.03, full: 0.12, meter: {} };
|
|
98
102
|
assert.equal(statusText({ version: '1.29.42', paused: false, totals, rateLimits: [{ kind: 'five_hour', percentUsed: 23.4 }, { kind: 'seven_day', percentUsed: 77 }] }),
|
|
99
103
|
'Troxy 1.29.42 · routing is on\n'
|
|
100
|
-
+ 'This session: your bar lasts 4.0x longer ·
|
|
104
|
+
+ 'This session: your bar lasts 4.0x longer · meter not moved yet · estimate\n'
|
|
101
105
|
+ 'Plan usage now: 5h 23% · 7d 77%\n'
|
|
102
106
|
+ 'The model is picked at the start, after an hour idle or after a compaction, then kept (switching would rewrite the cache). /model always wins.\n'
|
|
103
107
|
+ 'Idle compaction is on: sessions over 100k tokens are compacted after 55 min idle (/troxy compact off).\n'
|