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 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
- console.log(`\n ${icon} ${result.decision}${result.policy ? ` "${result.policy}"` : ' (default action)'}`);
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 apiKey = requireKey(flags);
166
+ const jwt = requireJwt();
165
167
  const period = Number(flags.period || 30);
166
- const data = await api.agentInsights(apiKey, period);
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
- // If we have a key, show enriched status
207
- try {
208
- const apiKey = requireKey(flags);
209
- const source = getKeySource();
210
- const data = await api.agentStatus(apiKey);
211
- const { token, account } = data;
212
- const keyNote = source === 'config'
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
- } catch (err) { if (err.code === 'UNAUTHORIZED') throw err; console.log(); }
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
- This saves your key to ~/.troxy/config.json no need to pass --key again.
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 connect --key <api-key> Save API key (CLI only no MCP setup)
255
- troxy init --key <api-key> Full setup: save key + configure MCP
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 + which key is in use
263
+ troxy status API health + account overview
260
264
 
261
- Simulate
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: npx troxy login)
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
- Override key for a single command: --key txy-...
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "AI payment control — protect your agent's payments with policies",
5
5
  "type": "module",
6
6
  "bin": {
package/src/activity.js CHANGED
@@ -1,22 +1,22 @@
1
1
  import { api } from './api.js';
2
- import { requireKey } from './auth.js';
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 apiKey = requireKey(flags);
9
- const limit = Number(flags.limit || 20);
10
- const mine = !!flags.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(apiKey, limit, mine);
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', 'Card', 'Agent', 'When'],
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 (API key auth — no JWT required)
59
- agentStatus: (apiKey) => request('GET', '/agent/status', { apiKey }),
60
- agentPolicies: (apiKey) => request('GET', '/agent/policies', { apiKey }),
61
- agentMcps: (apiKey) => request('GET', '/agent/mcps', { apiKey }),
62
- agentCards: (apiKey) => request('GET', '/agent/cards', { apiKey }),
63
- agentActivity: (apiKey, limit, mine) => request('GET', `/agent/activity?limit=${limit || 20}${mine ? '&mine=true' : ''}`, { apiKey }),
64
- agentInsights: (apiKey, period) => request('GET', `/agent/insights?period=${period || 30}`, { apiKey }),
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: npx troxy login\n');
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 } from './api.js';
2
- import { loadConfig } from './config.js';
3
- import { requireKey } from './auth.js';
4
- import { table } from './print.js';
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 apiKey = requireKey(flags);
7
+ const jwt = requireJwt();
8
8
 
9
9
  switch (sub || 'list') {
10
10
  case 'list': {
11
- const data = await api.agentMcps(apiKey);
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, requireKey } from './auth.js';
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 just an API key
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 apiKey = requireKey(flags);
18
+ const jwt = requireJwt();
19
19
  switch (sub || 'list') {
20
20
  case 'list': {
21
- const data = await api.agentPolicies(apiKey);
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(apiKey);
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, { enabled: sub === 'enable' });
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
- }