troxy-cli 1.21.1 → 1.23.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/README.md +2 -1
- package/bin/troxy.js +97 -22
- package/package.json +7 -4
- package/src/api.js +18 -1
- package/src/data/policy-spec.json +496 -0
- package/src/data/templates.json +1429 -0
- package/src/init.js +13 -2
- package/src/mcp-server.js +86 -1
- package/src/mcps.js +1 -0
- package/src/policies.js +351 -114
- package/src/policy-fields.js +107 -0
- package/src/secrets.js +1 -1
- package/src/settings.js +0 -3
- package/src/simulate.js +251 -0
- package/src/tests/mcp-checkpoints.test.js +79 -0
- package/src/tests/model-checkpoint.test.js +5 -1
- package/src/tests/policies-create.test.js +218 -0
- package/src/tests/policies-org.test.js +60 -0
- package/src/tests/simulate.test.js +127 -0
- package/src/tests/templates.test.js +46 -0
- package/src/tests/tool_detect.test.js +389 -0
- package/src/tool_detect.js +181 -0
- package/src/chat-budget.js +0 -73
package/README.md
CHANGED
|
@@ -50,8 +50,9 @@ npx troxy-cli <command>
|
|
|
50
50
|
|---------|-------------|
|
|
51
51
|
| `troxy init` | Connect an agent to Troxy — validates API key, sets agent name, patches MCP configs |
|
|
52
52
|
| `troxy login` | Start a 12-hour CLI session (opens browser → copy code → paste into terminal) |
|
|
53
|
+
| `troxy tools` | Scan for installed AI coding tools and set your subscription plan for each |
|
|
53
54
|
| `troxy mcps` | List connected MCP agents and their status |
|
|
54
|
-
| `troxy policies` | List
|
|
55
|
+
| `troxy policies` | List every org/space policy that applies to you (creating one is admin-only) |
|
|
55
56
|
| `troxy activity` | View recent transaction audit log |
|
|
56
57
|
| `troxy insights` | Spending stats and decision breakdown |
|
|
57
58
|
| `troxy status` | Show connection status and account overview |
|
package/bin/troxy.js
CHANGED
|
@@ -9,24 +9,32 @@ import { runPolicies } from '../src/policies.js';
|
|
|
9
9
|
import { runMcps } from '../src/mcps.js';
|
|
10
10
|
import { runActivity } from '../src/activity.js';
|
|
11
11
|
import { runSettings } from '../src/settings.js';
|
|
12
|
-
import { runChatBudget } from '../src/chat-budget.js';
|
|
13
12
|
import { runApprovals } from '../src/approvals.js';
|
|
14
13
|
import { runAccount } from '../src/account.js';
|
|
15
14
|
import { runSecrets } from '../src/secrets.js';
|
|
16
15
|
import { api } from '../src/api.js';
|
|
17
16
|
import { table } from '../src/print.js';
|
|
17
|
+
import { printSimHelp, runSend, runSignin, runAction, runModelCall, runScan } from '../src/simulate.js';
|
|
18
18
|
|
|
19
19
|
const [,, command, sub, ...rest] = process.argv;
|
|
20
20
|
const allArgs = [sub, ...rest].filter(Boolean);
|
|
21
21
|
|
|
22
|
-
// Parse --flag value pairs
|
|
22
|
+
// Parse --flag value pairs. `flags[key]` keeps its original "last occurrence
|
|
23
|
+
// wins" behavior unchanged, for every existing caller that reads it as a
|
|
24
|
+
// plain string. `multiFlags[key]` is new and purely additive: every
|
|
25
|
+
// occurrence of a repeatable flag (e.g. `--and field=... --and field=...`,
|
|
26
|
+
// used by `policies create` to build multi-condition policies), in order.
|
|
27
|
+
// Nothing that doesn't opt into reading multiFlags is affected.
|
|
23
28
|
const flags = {};
|
|
29
|
+
const multiFlags = {};
|
|
24
30
|
const positional = [];
|
|
25
31
|
for (let i = 0; i < allArgs.length; i++) {
|
|
26
32
|
if (allArgs[i].startsWith('--')) {
|
|
27
33
|
const key = allArgs[i].slice(2);
|
|
28
34
|
const next = allArgs[i + 1];
|
|
29
|
-
|
|
35
|
+
const val = next && !next.startsWith('--') ? allArgs[++i] : true;
|
|
36
|
+
flags[key] = val;
|
|
37
|
+
(multiFlags[key] ||= []).push(val);
|
|
30
38
|
} else {
|
|
31
39
|
positional.push(allArgs[i]);
|
|
32
40
|
}
|
|
@@ -77,6 +85,33 @@ switch (command) {
|
|
|
77
85
|
await runInit(flags);
|
|
78
86
|
break;
|
|
79
87
|
|
|
88
|
+
case 'tools': {
|
|
89
|
+
if (flags.help || flags.h) {
|
|
90
|
+
console.log(`
|
|
91
|
+
troxy tools
|
|
92
|
+
|
|
93
|
+
Re-scans this machine for AI coding tools (Claude Code, Cursor, GitHub
|
|
94
|
+
Copilot, Windsurf, Continue, Aider) and lets you set the subscription plan
|
|
95
|
+
for each, so the dashboard shows what you actually pay instead of raw
|
|
96
|
+
token estimates. Run this any time your plan changes.
|
|
97
|
+
`);
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
const apiKey = loadConfig()?.apiKey || process.env.TROXY_API_KEY;
|
|
101
|
+
if (!apiKey) {
|
|
102
|
+
console.error('\n Not connected. Run "troxy init --key <your-key>" first.\n');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const { runToolDetection, detectAiTools } = await import('../src/tool_detect.js');
|
|
106
|
+
if (detectAiTools().every(d => !d.installed)) {
|
|
107
|
+
console.log('\n No supported AI coding tools detected on this machine.\n');
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
await runToolDetection(apiKey, { interactive: true });
|
|
111
|
+
console.log(' Saved ✓ See it at https://dash.troxy.io\n');
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
case 'uninstall':
|
|
81
116
|
await runUninstall();
|
|
82
117
|
break;
|
|
@@ -286,9 +321,39 @@ switch (command) {
|
|
|
286
321
|
break;
|
|
287
322
|
}
|
|
288
323
|
|
|
324
|
+
// ── Simulate the other five checkpoints ────────────────────────
|
|
325
|
+
// `pay` above covers /evaluate (payment). Until these existed it was the
|
|
326
|
+
// only checkpoint testable from the CLI, even though the product had
|
|
327
|
+
// grown five more - api.js already had wrappers for four of them sitting
|
|
328
|
+
// unused, and evaluateSecrets was missing outright. See src/simulate.js.
|
|
329
|
+
case 'send':
|
|
330
|
+
if (flags.help || flags.h) { printSimHelp('send'); process.exit(0); }
|
|
331
|
+
await runSend(flags);
|
|
332
|
+
break;
|
|
333
|
+
|
|
334
|
+
case 'signin':
|
|
335
|
+
if (flags.help || flags.h) { printSimHelp('signin'); process.exit(0); }
|
|
336
|
+
await runSignin(flags);
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case 'action':
|
|
340
|
+
if (flags.help || flags.h) { printSimHelp('action'); process.exit(0); }
|
|
341
|
+
await runAction(flags);
|
|
342
|
+
break;
|
|
343
|
+
|
|
344
|
+
case 'model-call':
|
|
345
|
+
if (flags.help || flags.h) { printSimHelp('model-call'); process.exit(0); }
|
|
346
|
+
await runModelCall(flags);
|
|
347
|
+
break;
|
|
348
|
+
|
|
349
|
+
case 'scan':
|
|
350
|
+
if (flags.help || flags.h) { printSimHelp('scan'); process.exit(0); }
|
|
351
|
+
await runScan(flags);
|
|
352
|
+
break;
|
|
353
|
+
|
|
289
354
|
// ── Resources (read-only: --key or saved config; write: login) ─
|
|
290
355
|
case 'policies':
|
|
291
|
-
await runPolicies(positional, flags);
|
|
356
|
+
await runPolicies(positional, flags, multiFlags);
|
|
292
357
|
break;
|
|
293
358
|
|
|
294
359
|
case 'mcps':
|
|
@@ -303,10 +368,6 @@ switch (command) {
|
|
|
303
368
|
await runSettings(positional, flags);
|
|
304
369
|
break;
|
|
305
370
|
|
|
306
|
-
case 'chat-budget':
|
|
307
|
-
await runChatBudget(positional, flags);
|
|
308
|
-
break;
|
|
309
|
-
|
|
310
371
|
case 'approvals':
|
|
311
372
|
await runApprovals(positional, flags);
|
|
312
373
|
break;
|
|
@@ -588,6 +649,7 @@ switch (command) {
|
|
|
588
649
|
troxy init --key <api-key> Connect this machine as an MCP + save key
|
|
589
650
|
troxy init --key <api-key> --name "My Agent" Same, no interactive prompt
|
|
590
651
|
(for cloud/scripted agents that can't answer stdin)
|
|
652
|
+
troxy tools Scan for AI tools + set your subscription plans
|
|
591
653
|
troxy restart Restart the MCP background service
|
|
592
654
|
troxy uninstall Remove Troxy from this machine
|
|
593
655
|
troxy status API health + MCP status (no login needed)
|
|
@@ -609,17 +671,27 @@ switch (command) {
|
|
|
609
671
|
Keys
|
|
610
672
|
troxy rotate-key Rotate MCP key (revokes old, saves new locally)
|
|
611
673
|
|
|
612
|
-
Policies
|
|
613
|
-
|
|
674
|
+
Policies (all six checkpoints: payment, comms, access, destructive, model, secrets)
|
|
675
|
+
Admin-only: every write below requires --org. Personal and per-MCP policies
|
|
676
|
+
no longer exist - a policy is always org-wide or space-wide, and org acts as
|
|
677
|
+
a ceiling a space policy can never be looser than. list/describe/test need
|
|
678
|
+
no --org and show every org + space policy that applies to you.
|
|
679
|
+
troxy policies list / list --org
|
|
614
680
|
troxy policies describe --name "Block Amazon"
|
|
615
|
-
troxy policies
|
|
616
|
-
troxy policies
|
|
617
|
-
troxy policies create --
|
|
618
|
-
troxy policies create --name "X" --
|
|
619
|
-
troxy policies
|
|
620
|
-
troxy policies
|
|
621
|
-
troxy policies
|
|
622
|
-
troxy policies
|
|
681
|
+
troxy policies templates (browse the built-in library)
|
|
682
|
+
troxy policies fields --domain secrets (see what each checkpoint supports)
|
|
683
|
+
troxy policies create --describe "block Amazon purchases over $200" --org (AI builder, payment/comms only)
|
|
684
|
+
troxy policies create --name "X" --template "Block a secret going somewhere public" --org
|
|
685
|
+
troxy policies create --name "X" --action BLOCK --domain payment --field amount --operator gte --value 500 --org
|
|
686
|
+
troxy policies create --name "X" --action BLOCK --domain access --field login_action --operator eq --value signup --org
|
|
687
|
+
troxy policies create --name "X" --action BLOCK --domain comms --or "field=recipient_domain,operator=contains,value=gmail.com" --org
|
|
688
|
+
troxy policies create --name "X" --action BLOCK --domain secrets --field has_api_key --operator eq --value true --org
|
|
689
|
+
troxy policies test --domain payment --field amount --operator gt --value 100 --example '{"amount": 150}'
|
|
690
|
+
troxy policies set-priority --name "X" --priority 10 --org
|
|
691
|
+
troxy policies pause --name "X" --org
|
|
692
|
+
troxy policies resume --name "X" --org
|
|
693
|
+
troxy policies delete --name "X" --org
|
|
694
|
+
(--org needs admin access; run 'troxy policies create --help' for the full condition syntax)
|
|
623
695
|
|
|
624
696
|
Approvals (ESCALATE holds)
|
|
625
697
|
troxy approvals list
|
|
@@ -629,10 +701,8 @@ switch (command) {
|
|
|
629
701
|
Settings
|
|
630
702
|
troxy settings show
|
|
631
703
|
troxy settings set --default-action BLOCK --approval-timeout 4
|
|
632
|
-
troxy chat-budget show
|
|
633
|
-
troxy chat-budget set --currency USD --limit 500
|
|
634
704
|
|
|
635
|
-
LLM keys
|
|
705
|
+
LLM provider keys
|
|
636
706
|
troxy secrets list
|
|
637
707
|
troxy secrets set --name "OpenAI"
|
|
638
708
|
troxy secrets delete --name "OpenAI"
|
|
@@ -645,9 +715,14 @@ switch (command) {
|
|
|
645
715
|
troxy activity [--limit 50] [--mine]
|
|
646
716
|
troxy insights [--period 7]
|
|
647
717
|
|
|
648
|
-
Simulate
|
|
718
|
+
Simulate (test your policies against every checkpoint, not just payment)
|
|
649
719
|
troxy pay --merchant "Amazon" --amount 50
|
|
650
720
|
troxy pay --merchant "Google" --amount 300 --category software
|
|
721
|
+
troxy send --recipient jane@gmail.com --body "the password is hunter2"
|
|
722
|
+
troxy signin --site coinbase.com --type signup
|
|
723
|
+
troxy action --verb delete --resource "prod users table" --items 500
|
|
724
|
+
troxy model-call --model claude-opus-5 --provider anthropic --cost 4.50
|
|
725
|
+
troxy scan --content "AWS_SECRET_ACCESS_KEY=..." --destination public --do push
|
|
651
726
|
`);
|
|
652
727
|
process.exit(command ? 1 : 0);
|
|
653
728
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "troxy-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.23.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
|
-
"
|
|
29
|
+
"agents",
|
|
30
30
|
"policy",
|
|
31
|
-
"
|
|
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 }),
|
|
@@ -67,7 +78,6 @@ export const api = {
|
|
|
67
78
|
// Account settings
|
|
68
79
|
getSettings: (jwt) => request('GET', '/dashboard/settings', { jwt }),
|
|
69
80
|
updateSettings: (jwt, b) => request('PATCH', '/dashboard/settings', { jwt, body: b }),
|
|
70
|
-
updateChatBudget: (jwt, b) => request('PATCH', '/dashboard/chat-budget', { jwt, body: b }),
|
|
71
81
|
|
|
72
82
|
// Approvals
|
|
73
83
|
listApprovals: (jwt) => request('GET', '/dashboard/approvals', { jwt }),
|
|
@@ -94,6 +104,9 @@ export const api = {
|
|
|
94
104
|
// Only the pair produces usable data. The estimate alone is the agent's own
|
|
95
105
|
// guess, and Token Optimization says so on every agent that never reports.
|
|
96
106
|
evaluateModel: (body, apiKey) => request('POST', '/evaluate/model', { apiKey, body }),
|
|
107
|
+
// Was missing entirely - every other checkpoint had a wrapper here, secrets
|
|
108
|
+
// did not, which is part of why nothing in the CLI could exercise it.
|
|
109
|
+
evaluateSecrets: (body, apiKey) => request('POST', '/evaluate/secrets', { apiKey, body }),
|
|
97
110
|
reportModelUsage: (body, apiKey) => request('POST', '/evaluate/model/complete', { apiKey, body }),
|
|
98
111
|
// Real, host-captured usage for a turn that already happened - not an
|
|
99
112
|
// agent's self-report of what it thinks it used. Called only by the
|
|
@@ -119,6 +132,10 @@ export const api = {
|
|
|
119
132
|
// MCP status (agent API key — no login needed)
|
|
120
133
|
mcpStatus: (apiKey) => request('GET', '/mcp/status', { apiKey }),
|
|
121
134
|
|
|
135
|
+
// AI tool inventory + subscription plans, self-reported by `troxy init` /
|
|
136
|
+
// `troxy tools`. Agent API key auth, same as the evaluate/heartbeat paths.
|
|
137
|
+
reportToolPlans: (apiKey, tools) => request('POST', '/agents/tool-plans', { apiKey, body: { tools } }),
|
|
138
|
+
|
|
122
139
|
// Setup instructions for an agent with no MCP client to configure. Fetched
|
|
123
140
|
// rather than hardcoded so a new checkpoint reaches agents without an npm
|
|
124
141
|
// release — a cloud agent runs init once and never upgrades the package.
|