troxy-cli 1.2.5 → 1.2.7
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 +34 -32
- package/package.json +1 -1
- package/src/activity.js +7 -8
- package/src/api.js +7 -7
- package/src/auth.js +1 -1
- package/src/mcps.js +6 -6
- package/src/policies.js +16 -7
- package/src/cards.js +0 -63
package/bin/troxy.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { runInit } from '../src/init.js';
|
|
3
3
|
import { runUninstall } from '../src/uninstall.js';
|
|
4
4
|
import { runMcp } from '../src/mcp-server.js';
|
|
5
|
-
import { runLogin, clearSession, requireKey, requireJwt, getKeySource } from '../src/auth.js';
|
|
5
|
+
import { runLogin, clearSession, requireKey, requireJwt, loadSession, getKeySource } from '../src/auth.js';
|
|
6
|
+
import { loadConfig } from '../src/config.js';
|
|
6
7
|
import { runPolicies } from '../src/policies.js';
|
|
7
8
|
import { runMcps } from '../src/mcps.js';
|
|
8
9
|
import { runActivity } from '../src/activity.js';
|
|
@@ -141,7 +142,8 @@ switch (command) {
|
|
|
141
142
|
const result = await api.evaluate(body, apiKey);
|
|
142
143
|
const ICON = { ALLOW: '✓', BLOCK: '✗', ESCALATE: '⏳', NOTIFY: '~' };
|
|
143
144
|
const icon = ICON[result.decision] || '?';
|
|
144
|
-
|
|
145
|
+
const suffix = result.policy ? ` ← "${result.policy}"` : result.reason ? ` — ${result.reason}` : ' (default action)';
|
|
146
|
+
console.log(`\n ${icon} ${result.decision}${suffix}`);
|
|
145
147
|
if (result.audit_id) console.log(` audit: ${result.audit_id}`);
|
|
146
148
|
console.log();
|
|
147
149
|
break;
|
|
@@ -161,9 +163,9 @@ switch (command) {
|
|
|
161
163
|
break;
|
|
162
164
|
|
|
163
165
|
case 'insights': {
|
|
164
|
-
const
|
|
166
|
+
const jwt = requireJwt();
|
|
165
167
|
const period = Number(flags.period || 30);
|
|
166
|
-
const data = await api.agentInsights(
|
|
168
|
+
const data = await api.agentInsights(jwt, period);
|
|
167
169
|
const d = data;
|
|
168
170
|
console.log(`
|
|
169
171
|
Insights — last ${d.period_days} days
|
|
@@ -203,27 +205,29 @@ switch (command) {
|
|
|
203
205
|
process.stdout.write(` DB: ${health.db}\n`);
|
|
204
206
|
process.stdout.write(` Env: ${health.env}\n`);
|
|
205
207
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
? '(saved — run `troxy init` to change)'
|
|
214
|
-
: source === 'env' ? '(from TROXY_API_KEY env)' : '(passed via --key)';
|
|
215
|
-
console.log(`
|
|
216
|
-
Key: ${token.prefix} ${keyNote}
|
|
217
|
-
MCP: ${token.connected ? '● connected' : '○ offline'} last seen ${token.last_seen}
|
|
218
|
-
Fallback: ${token.default_action}
|
|
219
|
-
|
|
208
|
+
// Account info from login session
|
|
209
|
+
const session = loadSession();
|
|
210
|
+
if (session?.jwt) {
|
|
211
|
+
try {
|
|
212
|
+
const data = await api.agentStatus(session.jwt);
|
|
213
|
+
const { account } = data;
|
|
214
|
+
console.log(`
|
|
220
215
|
Account
|
|
221
216
|
Active policies: ${account.active_policies}
|
|
222
217
|
MCPs total: ${account.total_mcps} (${account.connected_mcps} connected)
|
|
223
218
|
Requests 24h: ${account.requests_24h}
|
|
224
219
|
Default action: ${account.default_action}
|
|
225
220
|
`);
|
|
226
|
-
|
|
221
|
+
} catch { console.log(); }
|
|
222
|
+
} else {
|
|
223
|
+
console.log('\n Not logged in. Run: troxy login\n');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// MCP connection info from local config (set by troxy init)
|
|
227
|
+
const localKey = loadConfig()?.apiKey;
|
|
228
|
+
if (localKey) {
|
|
229
|
+
console.log(` MCP key: ${localKey.substring(0, 12)}... (saved — run \`troxy init\` to change)\n`);
|
|
230
|
+
}
|
|
227
231
|
|
|
228
232
|
// Version check
|
|
229
233
|
try {
|
|
@@ -248,34 +252,32 @@ switch (command) {
|
|
|
248
252
|
Troxy — AI payment control
|
|
249
253
|
|
|
250
254
|
First time on a machine? Run: npx troxy init --key <api-key>
|
|
251
|
-
|
|
255
|
+
MCP setup (once per machine): troxy init --key <api-key>
|
|
256
|
+
Login for CLI commands (12h): troxy login
|
|
252
257
|
|
|
253
|
-
Setup
|
|
254
|
-
troxy
|
|
255
|
-
troxy
|
|
256
|
-
troxy rotate-key Create new key + save it (requires login)
|
|
258
|
+
MCP Setup
|
|
259
|
+
troxy init --key <api-key> Connect this machine as an MCP + save key
|
|
260
|
+
troxy rotate-key Create new MCP key + save it
|
|
257
261
|
troxy rotate-key --revoke-old Same + revoke the old key immediately
|
|
258
262
|
troxy uninstall Remove Troxy from this machine
|
|
259
|
-
troxy status API health +
|
|
263
|
+
troxy status API health + account overview
|
|
260
264
|
|
|
261
|
-
|
|
262
|
-
troxy pay --merchant "Amazon" --amount 50 --card "Work"
|
|
263
|
-
troxy pay --merchant "Google" --amount 300 --card "Work" --category software
|
|
264
|
-
|
|
265
|
-
Inspect (uses saved key — no flags needed after init)
|
|
265
|
+
Inspect (requires: troxy login)
|
|
266
266
|
troxy policies list
|
|
267
267
|
troxy policies describe --name "Block Amazon"
|
|
268
268
|
troxy mcps list
|
|
269
269
|
troxy activity [--limit 50] [--mine]
|
|
270
270
|
troxy insights [--period 7]
|
|
271
271
|
|
|
272
|
-
Manage (requires:
|
|
272
|
+
Manage (requires: troxy login)
|
|
273
273
|
troxy policies create --name "X" --action BLOCK --field amount --operator gte --value 500
|
|
274
274
|
troxy policies enable --name "X"
|
|
275
275
|
troxy policies disable --name "X"
|
|
276
276
|
troxy policies delete --name "X"
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
Simulate (requires MCP key — run troxy init first)
|
|
279
|
+
troxy pay --merchant "Amazon" --amount 50 --card "Work"
|
|
280
|
+
troxy pay --merchant "Google" --amount 300 --card "Work" --category software
|
|
279
281
|
`);
|
|
280
282
|
process.exit(command ? 1 : 0);
|
|
281
283
|
}
|
package/package.json
CHANGED
package/src/activity.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { api } from './api.js';
|
|
2
|
-
import {
|
|
2
|
+
import { requireJwt } from './auth.js';
|
|
3
3
|
import { table } from './print.js';
|
|
4
4
|
|
|
5
5
|
const ICON = { ALLOW: '✓', BLOCK: '✗', ESCALATE: '⏳', NOTIFY: '~' };
|
|
6
6
|
|
|
7
7
|
export async function runActivity(flags) {
|
|
8
|
-
const
|
|
9
|
-
const limit
|
|
10
|
-
const mine
|
|
8
|
+
const jwt = requireJwt();
|
|
9
|
+
const limit = Number(flags.limit || 20);
|
|
10
|
+
const mine = !!flags.mine;
|
|
11
11
|
|
|
12
|
-
const data = await api.agentActivity(
|
|
12
|
+
const data = await api.agentActivity(jwt, limit, mine);
|
|
13
13
|
const rows = data?.activity || [];
|
|
14
14
|
|
|
15
15
|
if (!rows.length) { console.log('\n No activity yet.\n'); return; }
|
|
16
16
|
|
|
17
17
|
console.log();
|
|
18
18
|
table(
|
|
19
|
-
['Decision', 'Merchant', 'Category', 'Amount', 'Policy', '
|
|
19
|
+
['Decision', 'Merchant', 'Category', 'Amount', 'Policy', 'Agent', 'When'],
|
|
20
20
|
rows.map(r => {
|
|
21
21
|
const icon = ICON[r.decision?.split('→')[0]] || ' ';
|
|
22
22
|
return [
|
|
@@ -24,8 +24,7 @@ export async function runActivity(flags) {
|
|
|
24
24
|
r.merchant,
|
|
25
25
|
r.category,
|
|
26
26
|
r.amount ? `$${Number(r.amount).toFixed(2)}` : '—',
|
|
27
|
-
r.policy,
|
|
28
|
-
r.card,
|
|
27
|
+
r.decision_source === 'budget' ? 'budget limit' : r.policy,
|
|
29
28
|
r.agent,
|
|
30
29
|
r.when,
|
|
31
30
|
];
|
package/src/api.js
CHANGED
|
@@ -55,13 +55,13 @@ export const api = {
|
|
|
55
55
|
// MCP heartbeat (agent API key)
|
|
56
56
|
mcpHeartbeat: (apiKey, agentName) => request('POST', '/mcp/heartbeat', { apiKey, body: agentName ? { agent_name: agentName } : undefined }),
|
|
57
57
|
|
|
58
|
-
// Agent read-only API (
|
|
59
|
-
agentStatus: (
|
|
60
|
-
agentPolicies: (
|
|
61
|
-
agentMcps: (
|
|
62
|
-
agentCards: (
|
|
63
|
-
agentActivity: (
|
|
64
|
-
agentInsights: (
|
|
58
|
+
// Agent read-only API (JWT session auth — run: troxy login)
|
|
59
|
+
agentStatus: (jwt) => request('GET', '/agent/status', { jwt }),
|
|
60
|
+
agentPolicies: (jwt) => request('GET', '/agent/policies', { jwt }),
|
|
61
|
+
agentMcps: (jwt) => request('GET', '/agent/mcps', { jwt }),
|
|
62
|
+
agentCards: (jwt) => request('GET', '/agent/cards', { jwt }),
|
|
63
|
+
agentActivity: (jwt, limit, mine) => request('GET', `/agent/activity?limit=${limit || 20}${mine ? '&mine=true' : ''}`, { jwt }),
|
|
64
|
+
agentInsights: (jwt, period) => request('GET', `/agent/insights?period=${period || 30}`, { jwt }),
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// Named export for backwards compat with init.js + mcp-server.js
|
package/src/auth.js
CHANGED
|
@@ -27,7 +27,7 @@ export function clearSession() {
|
|
|
27
27
|
export function requireJwt() {
|
|
28
28
|
const session = loadSession();
|
|
29
29
|
if (!session?.jwt) {
|
|
30
|
-
console.error('\n Not logged in. Run:
|
|
30
|
+
console.error('\n Not logged in. Run: troxy login\n');
|
|
31
31
|
process.exit(1);
|
|
32
32
|
}
|
|
33
33
|
return session.jwt;
|
package/src/mcps.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { api }
|
|
2
|
-
import { loadConfig }
|
|
3
|
-
import {
|
|
4
|
-
import { table }
|
|
1
|
+
import { api } from './api.js';
|
|
2
|
+
import { loadConfig } from './config.js';
|
|
3
|
+
import { requireJwt } from './auth.js';
|
|
4
|
+
import { table } from './print.js';
|
|
5
5
|
|
|
6
6
|
export async function runMcps([sub], flags) {
|
|
7
|
-
const
|
|
7
|
+
const jwt = requireJwt();
|
|
8
8
|
|
|
9
9
|
switch (sub || 'list') {
|
|
10
10
|
case 'list': {
|
|
11
|
-
const data = await api.agentMcps(
|
|
11
|
+
const data = await api.agentMcps(jwt);
|
|
12
12
|
const mcps = data?.mcps || [];
|
|
13
13
|
if (!mcps.length) { console.log('\n No MCP connections yet.\n'); return; }
|
|
14
14
|
console.log();
|
package/src/policies.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { api } from './api.js';
|
|
2
|
-
import { requireJwt
|
|
2
|
+
import { requireJwt } from './auth.js';
|
|
3
3
|
import { table } from './print.js';
|
|
4
4
|
|
|
5
5
|
const DECISION_ICON = { ALLOW: '✓', BLOCK: '✗', ESCALATE: '⏳', NOTIFY: '~', TIERED: '⊕' };
|
|
@@ -11,14 +11,14 @@ function _scope(p) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export async function runPolicies([sub, ...args], flags) {
|
|
14
|
-
// Read-only subcommands work with
|
|
14
|
+
// Read-only subcommands work with a login session
|
|
15
15
|
const readOnly = !sub || sub === 'list' || sub === 'describe';
|
|
16
16
|
|
|
17
17
|
if (readOnly) {
|
|
18
|
-
const
|
|
18
|
+
const jwt = requireJwt();
|
|
19
19
|
switch (sub || 'list') {
|
|
20
20
|
case 'list': {
|
|
21
|
-
const data = await api.agentPolicies(
|
|
21
|
+
const data = await api.agentPolicies(jwt);
|
|
22
22
|
const policies = data?.policies || [];
|
|
23
23
|
if (!policies.length) { console.log('\n No policies yet.\n'); return; }
|
|
24
24
|
console.log();
|
|
@@ -39,8 +39,8 @@ export async function runPolicies([sub, ...args], flags) {
|
|
|
39
39
|
|
|
40
40
|
case 'describe': {
|
|
41
41
|
const name = flags.name;
|
|
42
|
-
if (!name) { console.error(' --name is required\n'); process.exit(1); }
|
|
43
|
-
const data = await api.agentPolicies(
|
|
42
|
+
if (!name) { console.error(' --name is required (tip: use single quotes for names with special chars)\n'); process.exit(1); }
|
|
43
|
+
const data = await api.agentPolicies(jwt);
|
|
44
44
|
const p = (data?.policies || []).find(x => x.name.toLowerCase() === name.toLowerCase());
|
|
45
45
|
if (!p) { console.error(` Policy "${name}" not found\n`); process.exit(1); }
|
|
46
46
|
|
|
@@ -105,7 +105,16 @@ export async function runPolicies([sub, ...args], flags) {
|
|
|
105
105
|
const { policies = [] } = await api.listPolicies(jwt);
|
|
106
106
|
const policy = policies.find(p => p.name === name);
|
|
107
107
|
if (!policy) { console.error(` Policy "${name}" not found\n`); process.exit(1); }
|
|
108
|
-
await api.updatePolicy(jwt, policy.id, {
|
|
108
|
+
await api.updatePolicy(jwt, policy.id, {
|
|
109
|
+
name: policy.name,
|
|
110
|
+
action: policy.action,
|
|
111
|
+
description: policy.description || '',
|
|
112
|
+
conditions: policy.conditions || [],
|
|
113
|
+
or_conditions: policy.or_conditions || [],
|
|
114
|
+
tiers: policy.tiers || null,
|
|
115
|
+
global: policy.global !== false,
|
|
116
|
+
enabled: sub === 'enable',
|
|
117
|
+
});
|
|
109
118
|
console.log(`\n Policy "${name}" ${sub}d ✓\n`);
|
|
110
119
|
break;
|
|
111
120
|
}
|
package/src/cards.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { api } from './api.js';
|
|
2
|
-
import { requireJwt, requireKey } from './auth.js';
|
|
3
|
-
import { table } from './print.js';
|
|
4
|
-
|
|
5
|
-
export async function runCards([sub, ...args], flags) {
|
|
6
|
-
// list is read-only — works with API key
|
|
7
|
-
if (!sub || sub === 'list') {
|
|
8
|
-
const apiKey = requireKey(flags);
|
|
9
|
-
const data = await api.agentCards(apiKey);
|
|
10
|
-
const cards = data?.cards || [];
|
|
11
|
-
if (!cards.length) { console.log('\n No cards yet.\n'); return; }
|
|
12
|
-
console.log();
|
|
13
|
-
table(
|
|
14
|
-
['Name', 'Last 4', 'Status', 'Budget', 'Used', 'Remaining', 'Default Action'],
|
|
15
|
-
cards.map(c => [
|
|
16
|
-
c.name,
|
|
17
|
-
c.last_four ? `···${c.last_four}` : '—',
|
|
18
|
-
c.status,
|
|
19
|
-
c.monthly_budget ? `$${c.monthly_budget}` : 'no limit',
|
|
20
|
-
`$${Number(c.budget_used || 0).toFixed(2)}`,
|
|
21
|
-
c.budget_remaining != null ? `$${Number(c.budget_remaining).toFixed(2)}` : '—',
|
|
22
|
-
c.default_action || 'ALLOW',
|
|
23
|
-
]),
|
|
24
|
-
);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Write operations need JWT
|
|
29
|
-
const jwt = requireJwt();
|
|
30
|
-
|
|
31
|
-
switch (sub) {
|
|
32
|
-
case 'create': {
|
|
33
|
-
const name = flags.name;
|
|
34
|
-
if (!name) { console.error(' --name is required\n'); process.exit(1); }
|
|
35
|
-
const body = {
|
|
36
|
-
alias_name: name,
|
|
37
|
-
monthly_budget: flags.budget ? Number(flags.budget) : null,
|
|
38
|
-
provider: flags.provider || null,
|
|
39
|
-
card_number: flags['card-number'] || null,
|
|
40
|
-
status: 'active',
|
|
41
|
-
};
|
|
42
|
-
const card = await api.createCard(jwt, body);
|
|
43
|
-
console.log(`\n Card "${card.alias_name}" created ✓\n`);
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
case 'delete': {
|
|
48
|
-
const name = flags.name;
|
|
49
|
-
if (!name) { console.error(' --name is required\n'); process.exit(1); }
|
|
50
|
-
const data = await api.listCards(jwt);
|
|
51
|
-
const card = (data?.cards || []).find(c => c.name === name);
|
|
52
|
-
if (!card) { console.error(` Card "${name}" not found\n`); process.exit(1); }
|
|
53
|
-
await api.deleteCard(jwt, card.id);
|
|
54
|
-
console.log(`\n Card "${name}" deleted ✓\n`);
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
default:
|
|
59
|
-
console.error(` Unknown subcommand: ${sub}`);
|
|
60
|
-
console.error(' Usage: troxy cards [list|create|delete]\n');
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
}
|