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 +113 -14
- package/package.json +7 -4
- package/src/api.js +18 -0
- package/src/data/policy-spec.json +496 -0
- package/src/data/templates.json +1429 -0
- package/src/init.js +11 -0
- package/src/mcp-server.js +81 -0
- package/src/policies.js +341 -46
- package/src/policy-fields.js +107 -0
- 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/tool_detect.js +179 -0
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
|
-
|
|
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}
|
|
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
|
|
597
|
-
troxy policies
|
|
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
|
|
601
|
-
troxy policies
|
|
602
|
-
troxy policies
|
|
603
|
-
troxy policies
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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 }),
|
|
@@ -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.
|