troxy-cli 1.29.54 → 1.29.56

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.54",
3
+ "version": "1.29.56",
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/daemon.js CHANGED
@@ -23,7 +23,7 @@ import { api } from './api.js';
23
23
  import { createInterceptor } from './interceptor.js';
24
24
  import { enabledProviders, interceptHostsFor, troxyRouteFor } from './providers.js';
25
25
  import { ensureInterceptionCerts } from './tls-ca.js';
26
- import { migrateBaseUrlProxyToInterception, claudeCodeSettingsPath, refreshTroxyGuardMod } from './init.js';
26
+ import { migrateBaseUrlProxyToInterception, claudeCodeSettingsPath } from './init.js';
27
27
 
28
28
  const INTERVAL_MS = 60_000;
29
29
 
@@ -139,8 +139,6 @@ export async function runDaemon() {
139
139
  process.on('unhandledRejection', (err) => {
140
140
  process.stderr.write(`[troxy-daemon] unhandled rejection (continuing): ${err?.stack || err}\n`);
141
141
  });
142
- // A CLI upgrade (npm or troxy update) then a daemon restart brings the mod up to date.
143
- refreshTroxyGuardMod().catch((err) => process.stderr.write(`[troxy-daemon] mod refresh failed: ${err.message}\n`));
144
142
 
145
143
  const cfg = loadConfig();
146
144
  if (!cfg?.apiKey) {
package/src/init.js CHANGED
@@ -38,9 +38,10 @@ export async function installTroxyGuardMod(home = os.homedir(), detectFn = detec
38
38
  return true;
39
39
  }
40
40
 
41
- // Only `troxy init` used to install the mod, so a CLI upgrade left the old mod running.
42
- // Re-copy it when the installed one is stamped with a different CLI version; a machine
43
- // without the mod (never ran init, or uninstalled) is left alone.
41
+ // Only `troxy init` used to install the mod, so `troxy update` left the old mod running.
42
+ // Called from `troxy update` only: re-copies it when the installed one is stamped with a
43
+ // different CLI version. A machine without the mod (never ran init, or uninstalled) is
44
+ // left alone.
44
45
  export async function refreshTroxyGuardMod(home = os.homedir(), detectFn = detectInstalledClaudeCodeVersion) {
45
46
  const manifest = path.join(home, '.claude', 'skills', 'troxy-guard', '.claude-plugin', 'plugin.json');
46
47
  let installed;
@@ -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, at list prices. The account-wide view is the dashboard's.
139
- const PRICE = { haiku: 1, sonnet: 2, opus: 4 }; // list $/MTok in; output keeps the same ratios
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,7 +171,7 @@ 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(`$${kept.toFixed(2)} saved of $${(paid + kept).toFixed(2)} at list price`);
174
+ parts.push('meter not moved yet');
172
175
  }
173
176
  parts.push('estimate');
174
177
  return `${bar(1 / scale)} ${parts.join(' · ')}`;
@@ -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 or the dollars', () => {
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 · $4.53 saved of $6.17 at list price · estimate');
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 · $8.00 saved of $100.00 at list price · estimate');
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);
@@ -97,7 +97,7 @@ test('statusText: version, routing, this session, plan usage, where the account
97
97
  const totals = { paid: 0.03, full: 0.12, meter: {} };
98
98
  assert.equal(statusText({ version: '1.29.42', paused: false, totals, rateLimits: [{ kind: 'five_hour', percentUsed: 23.4 }, { kind: 'seven_day', percentUsed: 77 }] }),
99
99
  'Troxy 1.29.42 · routing is on\n'
100
- + 'This session: your bar lasts 4.0x longer · $0.09 saved of $0.12 at list price · estimate\n'
100
+ + 'This session: your bar lasts 4.0x longer · meter not moved yet · estimate\n'
101
101
  + 'Plan usage now: 5h 23% · 7d 77%\n'
102
102
  + '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
103
  + 'Idle compaction is on: sessions over 100k tokens are compacted after 55 min idle (/troxy compact off).\n'