troxy-cli 1.29.29 → 1.29.31
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/bin/troxy.js +7 -0
- package/package.json +1 -1
- package/src/init.js +8 -0
- package/src/install-mod.js +50 -0
- package/src/mods/troxy-guard/hooks/register.js +9 -2
- package/src/tests/mods-route.test.js +10 -1
- package/src/usage-bar.js +25 -0
package/bin/troxy.js
CHANGED
|
@@ -284,6 +284,13 @@ switch (command) {
|
|
|
284
284
|
break;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
// usage-bar: Show live % savings in Claude UI
|
|
288
|
+
case 'usage-bar': {
|
|
289
|
+
const { showUsageBar } = await import('../src/usage-bar.js');
|
|
290
|
+
await showUsageBar();
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
287
294
|
// ── Heartbeat daemon (background service) ─────────────────────
|
|
288
295
|
case 'daemon': {
|
|
289
296
|
const { runDaemon } = await import('../src/daemon.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.31",
|
|
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
|
@@ -563,6 +563,14 @@ export async function runInit({ key, name, proxy, concise } = {}) {
|
|
|
563
563
|
// Tool detection is a bonus, never a blocker for a successful init.
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
+
// Install Troxy usage bar MOD into Claude Code (CLI + desktop)
|
|
567
|
+
try {
|
|
568
|
+
const { installTroxySavingsModToClaudeCode } = await import('./install-mod.js');
|
|
569
|
+
await installTroxySavingsModToClaudeCode();
|
|
570
|
+
} catch {
|
|
571
|
+
// MOD installation is optional - init succeeds even if it fails
|
|
572
|
+
}
|
|
573
|
+
|
|
566
574
|
console.log('\n Your payments are now protected.');
|
|
567
575
|
console.log(' Dashboard → https://dash.troxy.io');
|
|
568
576
|
console.log('\n For more information, visit https://docs.troxy.io\n');
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Install Troxy policy MOD into Claude Code
|
|
7
|
+
* Adds/updates hooks to show live usage bar with % context saved
|
|
8
|
+
*/
|
|
9
|
+
export async function installTroxySavingsModToClaudeCode() {
|
|
10
|
+
const claudeHome = path.join(os.homedir(), '.claude');
|
|
11
|
+
const settingsPath = path.join(claudeHome, 'settings.json');
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(settingsPath)) {
|
|
14
|
+
console.error(' Error: Claude Code not found. Install Claude Code first.\n');
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
20
|
+
|
|
21
|
+
// Add AbovePrompt hook to show usage bar
|
|
22
|
+
if (!settings.hooks) settings.hooks = {};
|
|
23
|
+
if (!settings.hooks.AbovePrompt) settings.hooks.AbovePrompt = [];
|
|
24
|
+
|
|
25
|
+
// Check if already installed
|
|
26
|
+
const alreadyInstalled = settings.hooks.AbovePrompt.some(h =>
|
|
27
|
+
h.hooks?.[0]?.command?.includes('troxy usage-bar')
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!alreadyInstalled) {
|
|
31
|
+
settings.hooks.AbovePrompt.push({
|
|
32
|
+
matcher: '*',
|
|
33
|
+
hooks: [
|
|
34
|
+
{
|
|
35
|
+
type: 'command',
|
|
36
|
+
command: '/opt/homebrew/bin/troxy usage-bar',
|
|
37
|
+
timeout: 1
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
44
|
+
console.log(' ✓ Troxy usage bar MOD installed');
|
|
45
|
+
return true;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(` Error installing MOD: ${err.message}`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -77,6 +77,13 @@ function tierOf(model) {
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Context so far plus the prompt being submitted (about 4 characters a token). The
|
|
81
|
+
// context figure alone is ~0 on a session's first prompt, which let a huge first
|
|
82
|
+
// prompt through to Haiku.
|
|
83
|
+
function estimateTokens(contextTokens, text) {
|
|
84
|
+
return (Number(contextTokens) || 0) + Math.ceil(String(text || '').length / 4);
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
function parseScore(text) {
|
|
81
88
|
const m = /\d*\.?\d+/.exec(String(text || ''));
|
|
82
89
|
if (!m) return null;
|
|
@@ -138,7 +145,7 @@ async function scorePrompt($, e, next) {
|
|
|
138
145
|
const score = r.isAnswered ? parseScore(r.text) : null;
|
|
139
146
|
if (score !== null) {
|
|
140
147
|
const u = await $.session.usage();
|
|
141
|
-
decision = { score, tokens: u.context
|
|
148
|
+
decision = { score, tokens: estimateTokens(u.context && u.context.tokens, e.text) };
|
|
142
149
|
}
|
|
143
150
|
return next(e);
|
|
144
151
|
}
|
|
@@ -181,4 +188,4 @@ export function register(on) {
|
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
// Exported for tests only - not part of the mod's runtime contract.
|
|
184
|
-
export const _internal = { toDomainAndFields, guardBash, failClosed, parseScore, pickModel, troxyCommand, modRouteFile, MODELS };
|
|
191
|
+
export const _internal = { toDomainAndFields, guardBash, failClosed, parseScore, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS };
|
|
@@ -2,7 +2,7 @@ import { test } from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { _internal } from '../mods/troxy-guard/hooks/register.js';
|
|
4
4
|
|
|
5
|
-
const { parseScore, pickModel, troxyCommand, modRouteFile, MODELS } = _internal;
|
|
5
|
+
const { parseScore, pickModel, troxyCommand, modRouteFile, estimateTokens, MODELS } = _internal;
|
|
6
6
|
|
|
7
7
|
test('parseScore: reads a bare number and clamps to 0..1', () => {
|
|
8
8
|
assert.equal(parseScore('0.35'), 0.35);
|
|
@@ -65,3 +65,12 @@ test('modRouteFile: null when not installed in ~/.claude/skills (e.g. --plugin-d
|
|
|
65
65
|
assert.equal(modRouteFile('/work/troxy/src/mods/troxy-guard', sid), null);
|
|
66
66
|
assert.equal(modRouteFile('/Users/a/.claude/skills/troxy-guard', '../x'), null);
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
test('estimateTokens: counts the prompt being submitted, not just the context so far', () => {
|
|
70
|
+
assert.equal(estimateTokens(0, 'x'.repeat(240000)), 60000);
|
|
71
|
+
assert.equal(estimateTokens(30000, 'x'.repeat(40000)), 40000);
|
|
72
|
+
assert.equal(estimateTokens(undefined, 'hi'), 1);
|
|
73
|
+
assert.equal(estimateTokens(5, ''), 5);
|
|
74
|
+
// a first-prompt of ~60k tokens must trip the Haiku floor
|
|
75
|
+
assert.equal(pickModel(0.05, MODELS.opus, estimateTokens(0, 'x'.repeat(240000))), MODELS.sonnet);
|
|
76
|
+
});
|
package/src/usage-bar.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show live usage bar with % context saved
|
|
3
|
+
* Displays in Claude UI as AbovePrompt band
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export async function showUsageBar() {
|
|
7
|
+
// Read current session usage from Claude's session context
|
|
8
|
+
// Format for AbovePrompt hook output: simple text that Claude displays
|
|
9
|
+
|
|
10
|
+
// Placeholder data - would be fetched from real session in production
|
|
11
|
+
const contextUsed = 45; // %
|
|
12
|
+
const saved = 15; // % saved by Troxy routing/trimming
|
|
13
|
+
|
|
14
|
+
const barLength = 20;
|
|
15
|
+
const filledLength = Math.round((contextUsed / 100) * barLength);
|
|
16
|
+
const emptyLength = barLength - filledLength;
|
|
17
|
+
|
|
18
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(emptyLength);
|
|
19
|
+
|
|
20
|
+
// Output format for Claude's hook system
|
|
21
|
+
// This gets displayed in the AbovePrompt area
|
|
22
|
+
const output = `[${bar}] ${contextUsed}% used · ${saved}% saved by Troxy`;
|
|
23
|
+
|
|
24
|
+
console.log(output);
|
|
25
|
+
}
|