troxy-cli 1.29.41 → 1.29.42
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.42",
|
|
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/init.js
CHANGED
|
@@ -31,6 +31,10 @@ export async function installTroxyGuardMod(home = os.homedir(), detectFn = detec
|
|
|
31
31
|
const destDir = path.join(home, '.claude', 'skills', 'troxy-guard');
|
|
32
32
|
fs.rmSync(destDir, { recursive: true, force: true });
|
|
33
33
|
fs.cpSync(TROXY_GUARD_MOD_SOURCE_DIR, destDir, { recursive: true });
|
|
34
|
+
// The mod can't see the CLI's version on its own; /troxy status reads it from here.
|
|
35
|
+
const manifest = path.join(destDir, '.claude-plugin', 'plugin.json');
|
|
36
|
+
const { version: troxyVersion } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
37
|
+
fs.writeFileSync(manifest, JSON.stringify({ ...JSON.parse(fs.readFileSync(manifest, 'utf8')), version: troxyVersion }, null, 2) + '\n');
|
|
34
38
|
return true;
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -157,6 +157,19 @@ function sessionLine(t) {
|
|
|
157
157
|
Object.entries(t.meter).map(([kind, percentUsed]) => ({ kind, percentUsed })));
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// /troxy status: everything about this session's Troxy in one place.
|
|
161
|
+
function statusText({ version, paused, totals, rateLimits = [] }) {
|
|
162
|
+
const lines = [`Troxy ${version || ''}`.trim() + ` · routing is ${paused ? 'off' : 'on'}`];
|
|
163
|
+
lines.push(sessionLine(totals) ? `This session: ${sessionLine(totals).replace(/^.*?This session: /, '')}`
|
|
164
|
+
: 'This session: nothing moved to a cheaper model yet');
|
|
165
|
+
const meter = rateLimits.filter(w => WINDOW_LABEL[w.kind]).map(w => `${WINDOW_LABEL[w.kind]} ${Math.round(w.percentUsed)}%`);
|
|
166
|
+
if (meter.length) lines.push(`Plan usage now: ${meter.join(' · ')}`);
|
|
167
|
+
lines.push(`Prompts with over ${ROUTE_CONTEXT_FLOOR_TOKENS / 1000}k tokens of context stay on your model (switching would rewrite the cache).`);
|
|
168
|
+
lines.push('All sessions: dash.troxy.io');
|
|
169
|
+
lines.push(paused ? 'Type /troxy on to resume.' : 'Type /troxy off to pause routing.');
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
160
173
|
async function snapshot($) {
|
|
161
174
|
const u = await $.session.usage();
|
|
162
175
|
const meter = {};
|
|
@@ -246,7 +259,10 @@ export function register(on) {
|
|
|
246
259
|
on('command.run', { command: 'troxy' }, async ($, e) => {
|
|
247
260
|
const r = troxyCommand(e.args, Boolean(await $.store.get('paused')));
|
|
248
261
|
await $.store.set('paused', r.paused);
|
|
249
|
-
return { text: r.text };
|
|
262
|
+
if (/^\s*(on|off)\s*$/i.test(String(e.args || ''))) return { text: r.text };
|
|
263
|
+
const manifest = JSON.parse(await $.fs.read(`${$.plugin.root}/.claude-plugin/plugin.json`).catch(() => '{}'));
|
|
264
|
+
const { value: totals } = await $.state.get(SAVED);
|
|
265
|
+
return { text: statusText({ version: manifest.version, paused: r.paused, totals, rateLimits: (await $.session.usage()).rateLimits }) };
|
|
250
266
|
});
|
|
251
267
|
// Routing fails open: a scorer or hook error must never break the session,
|
|
252
268
|
// it just means the requested model is used as-is.
|
|
@@ -277,4 +293,4 @@ export function register(on) {
|
|
|
277
293
|
}
|
|
278
294
|
|
|
279
295
|
// Exported for tests only - not part of the mod's runtime contract.
|
|
280
|
-
export const _internal = { toDomainAndFields, guardBash, failClosed, parseScore, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS, savedLine, sessionLine, addTurn };
|
|
296
|
+
export const _internal = { toDomainAndFields, guardBash, failClosed, parseScore, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS, savedLine, sessionLine, addTurn, statusText };
|
|
@@ -12,6 +12,9 @@ test('installTroxyGuardMod: copies the mod when the version supports it', async
|
|
|
12
12
|
assert.equal(installed, true);
|
|
13
13
|
assert.ok(fs.existsSync(path.join(tmpHome, '.claude', 'skills', 'troxy-guard', '.claude-plugin', 'plugin.json')));
|
|
14
14
|
assert.ok(fs.existsSync(path.join(tmpHome, '.claude', 'skills', 'troxy-guard', 'hooks', 'register.js')));
|
|
15
|
+
// stamped with the CLI's version, which /troxy status shows
|
|
16
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(tmpHome, '.claude', 'skills', 'troxy-guard', '.claude-plugin', 'plugin.json'), 'utf8'));
|
|
17
|
+
assert.equal(manifest.version, JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version);
|
|
15
18
|
} finally {
|
|
16
19
|
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
17
20
|
}
|
|
@@ -90,3 +90,16 @@ test('addTurn: a session total from its own turns, a routed one counted at the a
|
|
|
90
90
|
// No routed turn yet: no band.
|
|
91
91
|
assert.equal(sessionLine(addTurn(undefined, { cost: 0, meter: {} }, { cost: 1, meter: {} }, 1)), undefined);
|
|
92
92
|
});
|
|
93
|
+
|
|
94
|
+
test('statusText: version, routing, this session, plan usage, where the account view is', () => {
|
|
95
|
+
const { statusText } = _internal;
|
|
96
|
+
const totals = { paid: 0.03, full: 0.12, meter: {} };
|
|
97
|
+
assert.equal(statusText({ version: '1.29.42', paused: false, totals, rateLimits: [{ kind: 'five_hour', percentUsed: 23.4 }, { kind: 'seven_day', percentUsed: 77 }] }),
|
|
98
|
+
'Troxy 1.29.42 · routing is on\n'
|
|
99
|
+
+ 'This session: your bar lasts 4.0x longer · $0.09 saved of $0.12 at list price · estimate\n'
|
|
100
|
+
+ 'Plan usage now: 5h 23% · 7d 77%\n'
|
|
101
|
+
+ 'Prompts with over 50k tokens of context stay on your model (switching would rewrite the cache).\n'
|
|
102
|
+
+ 'All sessions: dash.troxy.io\n'
|
|
103
|
+
+ 'Type /troxy off to pause routing.');
|
|
104
|
+
assert.match(statusText({ paused: true }), /^Troxy · routing is off\nThis session: nothing moved to a cheaper model yet\n/);
|
|
105
|
+
});
|