troxy-cli 1.21.0 → 1.22.0

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 CHANGED
@@ -15,18 +15,27 @@ import { runAccount } from '../src/account.js';
15
15
  import { runSecrets } from '../src/secrets.js';
16
16
  import { api } from '../src/api.js';
17
17
  import { table } from '../src/print.js';
18
+ import { printSimHelp, runSend, runSignin, runAction, runModelCall, runScan } from '../src/simulate.js';
18
19
 
19
20
  const [,, command, sub, ...rest] = process.argv;
20
21
  const allArgs = [sub, ...rest].filter(Boolean);
21
22
 
22
- // Parse --flag value pairs
23
+ // Parse --flag value pairs. `flags[key]` keeps its original "last occurrence
24
+ // wins" behavior unchanged, for every existing caller that reads it as a
25
+ // plain string. `multiFlags[key]` is new and purely additive: every
26
+ // occurrence of a repeatable flag (e.g. `--and field=... --and field=...`,
27
+ // used by `policies create` to build multi-condition policies), in order.
28
+ // Nothing that doesn't opt into reading multiFlags is affected.
23
29
  const flags = {};
30
+ const multiFlags = {};
24
31
  const positional = [];
25
32
  for (let i = 0; i < allArgs.length; i++) {
26
33
  if (allArgs[i].startsWith('--')) {
27
34
  const key = allArgs[i].slice(2);
28
35
  const next = allArgs[i + 1];
29
- flags[key] = next && !next.startsWith('--') ? allArgs[++i] : true;
36
+ const val = next && !next.startsWith('--') ? allArgs[++i] : true;
37
+ flags[key] = val;
38
+ (multiFlags[key] ||= []).push(val);
30
39
  } else {
31
40
  positional.push(allArgs[i]);
32
41
  }
@@ -77,6 +86,33 @@ switch (command) {
77
86
  await runInit(flags);
78
87
  break;
79
88
 
89
+ case 'tools': {
90
+ if (flags.help || flags.h) {
91
+ console.log(`
92
+ troxy tools
93
+
94
+ Re-scans this machine for AI coding tools (Claude Code, Cursor, GitHub
95
+ Copilot, Windsurf, Continue, Aider) and lets you set the subscription plan
96
+ for each, so the dashboard shows what you actually pay instead of raw
97
+ token estimates. Run this any time your plan changes.
98
+ `);
99
+ process.exit(0);
100
+ }
101
+ const apiKey = loadConfig()?.apiKey || process.env.TROXY_API_KEY;
102
+ if (!apiKey) {
103
+ console.error('\n Not connected. Run "troxy init --key <your-key>" first.\n');
104
+ process.exit(1);
105
+ }
106
+ const { runToolDetection, detectAiTools } = await import('../src/tool_detect.js');
107
+ if (detectAiTools().every(d => !d.installed)) {
108
+ console.log('\n No supported AI coding tools detected on this machine.\n');
109
+ break;
110
+ }
111
+ await runToolDetection(apiKey, { interactive: true });
112
+ console.log(' Saved ✓ See it at https://dash.troxy.io\n');
113
+ break;
114
+ }
115
+
80
116
  case 'uninstall':
81
117
  await runUninstall();
82
118
  break;
@@ -286,9 +322,39 @@ switch (command) {
286
322
  break;
287
323
  }
288
324
 
325
+ // ── Simulate the other five checkpoints ────────────────────────
326
+ // `pay` above covers /evaluate (payment). Until these existed it was the
327
+ // only checkpoint testable from the CLI, even though the product had
328
+ // grown five more - api.js already had wrappers for four of them sitting
329
+ // unused, and evaluateSecrets was missing outright. See src/simulate.js.
330
+ case 'send':
331
+ if (flags.help || flags.h) { printSimHelp('send'); process.exit(0); }
332
+ await runSend(flags);
333
+ break;
334
+
335
+ case 'signin':
336
+ if (flags.help || flags.h) { printSimHelp('signin'); process.exit(0); }
337
+ await runSignin(flags);
338
+ break;
339
+
340
+ case 'action':
341
+ if (flags.help || flags.h) { printSimHelp('action'); process.exit(0); }
342
+ await runAction(flags);
343
+ break;
344
+
345
+ case 'model-call':
346
+ if (flags.help || flags.h) { printSimHelp('model-call'); process.exit(0); }
347
+ await runModelCall(flags);
348
+ break;
349
+
350
+ case 'scan':
351
+ if (flags.help || flags.h) { printSimHelp('scan'); process.exit(0); }
352
+ await runScan(flags);
353
+ break;
354
+
289
355
  // ── Resources (read-only: --key or saved config; write: login) ─
290
356
  case 'policies':
291
- await runPolicies(positional, flags);
357
+ await runPolicies(positional, flags, multiFlags);
292
358
  break;
293
359
 
294
360
  case 'mcps':
@@ -500,7 +566,7 @@ switch (command) {
500
566
  // is its picture of which checkpoints exist, and that lives on the server.
501
567
  // This is a no-op on a machine with an MCP client, which gets a fresh tool
502
568
  // list from the MCP handshake every session anyway.
503
- const { refreshHostedInstructions } = await import('../src/init.js');
569
+ const { refreshHostedInstructions, reprovisionKeyConsumers } = await import('../src/init.js');
504
570
  const refresh = async () => {
505
571
  try {
506
572
  return await refreshHostedInstructions((loadConfig() || {}).apiKey);
@@ -531,7 +597,26 @@ switch (command) {
531
597
  }
532
598
  // Also refresh the npx cache so MCP servers (e.g. OpenClaw) pick up the new version
533
599
  try { execSync(`npx --yes troxy-cli@${latest} --version`, { stdio: 'pipe' }); } catch { /* non-fatal */ }
534
- console.log(`✓\n\n Updated to ${latest}. Restart your terminal and any MCP clients to use the new version.`);
600
+ console.log(`✓\n\n Updated to ${latest}.`);
601
+
602
+ // Re-run the exact same client/hook provisioning `troxy init` does,
603
+ // using the key already saved on this machine - a version bump can
604
+ // change what gets registered (e.g. 1.21.0 added a PreToolUse hook
605
+ // alongside the existing Stop hook), and `update` silently leaving
606
+ // that unregistered meant the new version was installed but inert
607
+ // until someone thought to re-run `init` by hand. Best-effort: a
608
+ // failure here must not make `update` itself look like it failed when
609
+ // the actual package upgrade already succeeded.
610
+ const savedConfig = loadConfig();
611
+ if (savedConfig?.apiKey) {
612
+ try {
613
+ await reprovisionKeyConsumers(savedConfig.apiKey, savedConfig.agentName);
614
+ } catch (err) {
615
+ console.error(`\n Could not refresh MCP client config: ${err.message}`);
616
+ console.error(' Run "troxy init --key <your-key>" by hand to finish updating.\n');
617
+ }
618
+ }
619
+ console.log('\n Restart your terminal and any MCP clients to use the new version.');
535
620
  if (!await refresh()) console.log('');
536
621
  } catch (err) {
537
622
  const stderr = err.stderr?.toString() || err.message || '';
@@ -569,6 +654,7 @@ switch (command) {
569
654
  troxy init --key <api-key> Connect this machine as an MCP + save key
570
655
  troxy init --key <api-key> --name "My Agent" Same, no interactive prompt
571
656
  (for cloud/scripted agents that can't answer stdin)
657
+ troxy tools Scan for AI tools + set your subscription plans
572
658
  troxy restart Restart the MCP background service
573
659
  troxy uninstall Remove Troxy from this machine
574
660
  troxy status API health + MCP status (no login needed)
@@ -590,17 +676,25 @@ switch (command) {
590
676
  Keys
591
677
  troxy rotate-key Rotate MCP key (revokes old, saves new locally)
592
678
 
593
- Policies
594
- troxy policies list
679
+ Policies (all six checkpoints: payment, comms, access, destructive, model, secrets)
680
+ troxy policies list / list --org
595
681
  troxy policies describe --name "Block Amazon"
596
- troxy policies create --describe "block Amazon purchases over $200" (AI builder)
597
- troxy policies create --name "X" --action BLOCK --field amount --operator gte --value 500
682
+ troxy policies templates (browse the built-in library)
683
+ troxy policies fields --domain secrets (see what each checkpoint supports)
684
+ troxy policies create --describe "block Amazon purchases over $200" (AI builder, payment/comms only)
685
+ troxy policies create --name "X" --template "Block a secret going somewhere public"
686
+ troxy policies create --name "X" --action BLOCK --domain payment --field amount --operator gte --value 500
687
+ troxy policies create --name "X" --action BLOCK --domain access --field login_action --operator eq --value signup
688
+ troxy policies create --name "X" --action BLOCK --domain comms --or "field=recipient_domain,operator=contains,value=gmail.com"
598
689
  troxy policies create --name "X" --action BLOCK --mcp "My Laptop,Server" (scoped to specific MCPs)
599
690
  troxy policies create --name "X" --action ESCALATE --chat (scoped to Troxy Chat only)
600
- troxy policies set-priority --name "X" --priority 10
601
- troxy policies pause --name "X"
602
- troxy policies resume --name "X"
603
- troxy policies delete --name "X"
691
+ troxy policies create --name "X" --action BLOCK --domain secrets --field has_api_key --operator eq --value true --org
692
+ troxy policies test --domain payment --field amount --operator gt --value 100 --example '{"amount": 150}'
693
+ troxy policies set-priority --name "X" --priority 10 [--org]
694
+ troxy policies pause --name "X" [--org]
695
+ troxy policies resume --name "X" [--org]
696
+ troxy policies delete --name "X" [--org]
697
+ (--org needs admin access; run 'troxy policies create --help' for the full condition syntax)
604
698
 
605
699
  Approvals (ESCALATE holds)
606
700
  troxy approvals list
@@ -626,9 +720,14 @@ switch (command) {
626
720
  troxy activity [--limit 50] [--mine]
627
721
  troxy insights [--period 7]
628
722
 
629
- Simulate
723
+ Simulate (test your policies against every checkpoint, not just payment)
630
724
  troxy pay --merchant "Amazon" --amount 50
631
725
  troxy pay --merchant "Google" --amount 300 --category software
726
+ troxy send --recipient jane@gmail.com --body "the password is hunter2"
727
+ troxy signin --site coinbase.com --type signup
728
+ troxy action --verb delete --resource "prod users table" --items 500
729
+ troxy model-call --model claude-opus-5 --provider anthropic --cost 4.50
730
+ troxy scan --content "AWS_SECRET_ACCESS_KEY=..." --destination public --do push
632
731
  `);
633
732
  process.exit(command ? 1 : 0);
634
733
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.21.0",
4
- "description": "Control layer for AI agents: check payments, emails, logins and destructive actions against your policies",
3
+ "version": "1.22.0",
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": {
7
7
  "email": "support@troxy.io"
@@ -26,9 +26,12 @@
26
26
  "keywords": [
27
27
  "mcp",
28
28
  "ai",
29
- "payments",
29
+ "agents",
30
30
  "policy",
31
- "agents"
31
+ "guardrails",
32
+ "control-layer",
33
+ "payments",
34
+ "secrets"
32
35
  ],
33
36
  "license": "MIT"
34
37
  }
package/src/api.js CHANGED
@@ -52,6 +52,17 @@ export const api = {
52
52
  createPolicy: (jwt, b) => request('POST', '/dashboard/policies', { jwt, body: b }),
53
53
  updatePolicy: (jwt, id, b) => request('PATCH', `/dashboard/policies/${id}`, { jwt, body: b }),
54
54
  deletePolicy: (jwt, id) => request('DELETE', `/dashboard/policies/${id}`, { jwt }),
55
+ // Would-this-rule-fire, without saving it. Same evaluator the checkpoints
56
+ // use, so a condition that previews as firing is trusted, not guessed.
57
+ previewPolicy: (jwt, b) => request('POST', '/dashboard/policies/preview', { jwt, body: b }),
58
+
59
+ // Org (admin can write, any active member can read - see orgs.py's
60
+ // is_org_admin vs is_org_member split). org_id comes from currentOrg().
61
+ currentOrg: (jwt) => request('GET', '/orgs/current', { jwt }),
62
+ listOrgPolicies: (jwt, orgId) => request('GET', `/orgs/${orgId}/policies`, { jwt }),
63
+ createOrgPolicy: (jwt, orgId, b) => request('POST', `/orgs/${orgId}/policies`, { jwt, body: b }),
64
+ updateOrgPolicy: (jwt, orgId, id, b) => request('PATCH', `/orgs/${orgId}/policies/${id}`, { jwt, body: b }),
65
+ deleteOrgPolicy: (jwt, orgId, id) => request('DELETE', `/orgs/${orgId}/policies/${id}`, { jwt }),
55
66
 
56
67
  // Activity + insights
57
68
  activity: (jwt, limit) => request('GET', `/dashboard/activity?limit=${limit || 20}`, { jwt }),
@@ -94,6 +105,9 @@ export const api = {
94
105
  // Only the pair produces usable data. The estimate alone is the agent's own
95
106
  // guess, and Token Optimization says so on every agent that never reports.
96
107
  evaluateModel: (body, apiKey) => request('POST', '/evaluate/model', { apiKey, body }),
108
+ // Was missing entirely - every other checkpoint had a wrapper here, secrets
109
+ // did not, which is part of why nothing in the CLI could exercise it.
110
+ evaluateSecrets: (body, apiKey) => request('POST', '/evaluate/secrets', { apiKey, body }),
97
111
  reportModelUsage: (body, apiKey) => request('POST', '/evaluate/model/complete', { apiKey, body }),
98
112
  // Real, host-captured usage for a turn that already happened - not an
99
113
  // agent's self-report of what it thinks it used. Called only by the
@@ -119,6 +133,10 @@ export const api = {
119
133
  // MCP status (agent API key — no login needed)
120
134
  mcpStatus: (apiKey) => request('GET', '/mcp/status', { apiKey }),
121
135
 
136
+ // AI tool inventory + subscription plans, self-reported by `troxy init` /
137
+ // `troxy tools`. Agent API key auth, same as the evaluate/heartbeat paths.
138
+ reportToolPlans: (apiKey, tools) => request('POST', '/agents/tool-plans', { apiKey, body: { tools } }),
139
+
122
140
  // Setup instructions for an agent with no MCP client to configure. Fetched
123
141
  // rather than hardcoded so a new checkpoint reaches agents without an npm
124
142
  // release — a cloud agent runs init once and never upgrades the package.