troxy-cli 1.2.0 → 1.2.2

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,8 +2,7 @@
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 } from '../src/auth.js';
6
- import { runCards } from '../src/cards.js';
5
+ import { runLogin, clearSession, requireKey, getKeySource } from '../src/auth.js';
7
6
  import { runPolicies } from '../src/policies.js';
8
7
  import { runMcps } from '../src/mcps.js';
9
8
  import { runActivity } from '../src/activity.js';
@@ -26,6 +25,26 @@ for (let i = 0; i < allArgs.length; i++) {
26
25
  }
27
26
  }
28
27
 
28
+ try { await _run(); } catch (err) { _handleError(err); }
29
+
30
+ function _handleError(err) {
31
+ if (err.code === 'UNAUTHORIZED') {
32
+ const source = getKeySource();
33
+ if (source === 'config') {
34
+ console.error('\n API key revoked or invalid.');
35
+ console.error(' Your saved key is no longer accepted by Troxy.');
36
+ console.error(' Run: npx troxy init --key <new-key> to reconnect.\n');
37
+ } else {
38
+ console.error('\n API key invalid or revoked.');
39
+ console.error(' Check the key in your Troxy dashboard → Connections.\n');
40
+ }
41
+ } else {
42
+ console.error(`\n Error: ${err.message}\n`);
43
+ }
44
+ process.exit(1);
45
+ }
46
+
47
+ async function _run() {
29
48
  switch (command) {
30
49
  // ── Setup ─────────────────────────────────────────────────────
31
50
  case 'init':
@@ -37,6 +56,22 @@ switch (command) {
37
56
  break;
38
57
 
39
58
  // ── Auth ──────────────────────────────────────────────────────
59
+ case 'connect': {
60
+ const k = flags.key;
61
+ if (!k || !k.startsWith('txy-')) {
62
+ console.error('\n Usage: troxy connect --key txy-...\n');
63
+ process.exit(1);
64
+ }
65
+ // Validate key before saving
66
+ process.stdout.write('\n Validating key... ');
67
+ await api.agentStatus(k);
68
+ console.log('✓');
69
+ const { saveConfig } = await import('../src/config.js');
70
+ saveConfig({ apiKey: k });
71
+ console.log(' Key saved to ~/.troxy/config.json\n');
72
+ break;
73
+ }
74
+
40
75
  case 'login':
41
76
  await runLogin(flags);
42
77
  break;
@@ -52,10 +87,6 @@ switch (command) {
52
87
  break;
53
88
 
54
89
  // ── Resources (read-only: --key or saved config; write: login) ─
55
- case 'cards':
56
- await runCards(positional, flags);
57
- break;
58
-
59
90
  case 'policies':
60
91
  await runPolicies(positional, flags);
61
92
  break;
@@ -98,11 +129,10 @@ switch (command) {
98
129
 
99
130
  // ── Shorthand: troxy list [resource] ──────────────────────────
100
131
  case 'list':
101
- if (!sub || sub === 'cards') { await runCards(['list'], flags); break; }
102
- if (sub === 'policies') { await runPolicies(['list'], flags); break; }
132
+ if (!sub || sub === 'policies') { await runPolicies(['list'], flags); break; }
103
133
  if (sub === 'mcps') { await runMcps(['list'], flags); break; }
104
134
  if (sub === 'activity') { await runActivity(flags); break; }
105
- console.error(` Unknown resource: ${sub}. Try: cards, policies, mcps, activity\n`);
135
+ console.error(` Unknown resource: ${sub}. Try: policies, mcps, activity\n`);
106
136
  process.exit(1);
107
137
 
108
138
  // ── Status ────────────────────────────────────────────────────
@@ -114,11 +144,15 @@ switch (command) {
114
144
 
115
145
  // If we have a key, show enriched status
116
146
  try {
117
- const apiKey = requireKey(flags);
118
- const data = await api.agentStatus(apiKey);
147
+ const apiKey = requireKey(flags);
148
+ const source = getKeySource();
149
+ const data = await api.agentStatus(apiKey);
119
150
  const { token, account } = data;
151
+ const keyNote = source === 'config'
152
+ ? '(saved — run `troxy init` to change)'
153
+ : source === 'env' ? '(from TROXY_API_KEY env)' : '(passed via --key)';
120
154
  console.log(`
121
- Token: ${token.prefix} (${token.name})
155
+ Key: ${token.prefix} ${keyNote}
122
156
  MCP: ${token.connected ? '● connected' : '○ offline'} last seen ${token.last_seen}
123
157
  Fallback: ${token.default_action}
124
158
 
@@ -128,7 +162,7 @@ switch (command) {
128
162
  Requests 24h: ${account.requests_24h}
129
163
  Default action: ${account.default_action}
130
164
  `);
131
- } catch { console.log(); }
165
+ } catch (err) { if (err.code === 'UNAUTHORIZED') throw err; console.log(); }
132
166
 
133
167
  // Version check
134
168
  try {
@@ -152,31 +186,30 @@ switch (command) {
152
186
  console.log(`
153
187
  Troxy — AI payment control
154
188
 
155
- Read-only commands work with just an API key (--key txy-... or TROXY_API_KEY env).
156
- Write commands (create/delete/enable/disable) require: npx troxy login
189
+ First time on a machine? Run: npx troxy init --key <api-key>
190
+ This saves your key to ~/.troxy/config.json — no need to pass --key again.
157
191
 
158
192
  Setup
159
- troxy init --key <api-key> Initialize agent on this machine
160
- troxy uninstall Remove Troxy from this machine
161
- troxy login Log in for write access
162
- troxy logout Clear session
163
- troxy status [--key <key>] API health + account summary
164
-
165
- Inspect (API key only — works from EC2)
166
- troxy policies list [--key] All policies with scope + conditions
167
- troxy policies describe --name "X" [--key]
168
- troxy mcps list [--key] All MCP connections + status
169
- troxy cards list [--key] Cards with budget usage
170
- troxy activity [--key] [--limit 50] [--mine] Recent decisions
171
- troxy insights [--key] [--period 7] Spend stats
172
-
173
- Manage (requires login)
193
+ troxy connect --key <api-key> Save API key (CLI only — no MCP setup)
194
+ troxy init --key <api-key> Full setup: save key + configure MCP
195
+ troxy uninstall Remove Troxy from this machine
196
+ troxy status API health + which key is in use
197
+
198
+ Inspect (uses saved key — no flags needed after init)
199
+ troxy policies list
200
+ troxy policies describe --name "Block Amazon"
201
+ troxy mcps list
202
+ troxy activity [--limit 50] [--mine]
203
+ troxy insights [--period 7]
204
+
205
+ Manage (requires: npx troxy login)
174
206
  troxy policies create --name "X" --action BLOCK --field amount --operator gte --value 500
175
207
  troxy policies enable --name "X"
176
208
  troxy policies disable --name "X"
177
209
  troxy policies delete --name "X"
178
- troxy cards create --name "Personal" [--budget 500]
179
- troxy cards delete --name "Personal"
210
+
211
+ Override key for a single command: --key txy-...
180
212
  `);
181
213
  process.exit(command ? 1 : 0);
182
214
  }
215
+ } // end _run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "AI payment control — protect your agent's payments with policies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,12 @@
19
19
  "bin/",
20
20
  "src/"
21
21
  ],
22
- "keywords": ["mcp", "ai", "payments", "policy", "agents"],
22
+ "keywords": [
23
+ "mcp",
24
+ "ai",
25
+ "payments",
26
+ "policy",
27
+ "agents"
28
+ ],
23
29
  "license": "MIT"
24
30
  }
package/src/api.js CHANGED
@@ -14,7 +14,11 @@ async function request(method, path, { apiKey, jwt, body } = {}) {
14
14
  });
15
15
 
16
16
  const data = await res.json();
17
- if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`);
17
+ if (!res.ok) {
18
+ const err = new Error(data?.error || `HTTP ${res.status}`);
19
+ if (res.status === 401) err.code = 'UNAUTHORIZED';
20
+ throw err;
21
+ }
18
22
  return data;
19
23
  }
20
24
 
package/src/auth.js CHANGED
@@ -33,17 +33,32 @@ export function requireJwt() {
33
33
  return session.jwt;
34
34
  }
35
35
 
36
+ // Tracks how the last key was resolved — read by bin/troxy.js error handler
37
+ let _lastKeySource = null;
38
+
39
+ export function getKeySource() { return _lastKeySource; }
40
+
36
41
  /**
37
- * Resolve API key: --key flag → TROXY_API_KEY env → saved config.
42
+ * Resolve API key: --key flag → TROXY_API_KEY env → saved config (~/.troxy/config.json).
38
43
  * Exits with a helpful message if nothing is found.
39
44
  */
40
45
  export function requireKey(flags = {}) {
41
- const key = flags.key || process.env.TROXY_API_KEY || loadConfig()?.apiKey;
42
- if (!key) {
43
- console.error('\n No API key found. Pass --key txy-... or run: npx troxy init\n');
44
- process.exit(1);
46
+ if (flags.key) {
47
+ _lastKeySource = 'flag';
48
+ return flags.key;
49
+ }
50
+ if (process.env.TROXY_API_KEY) {
51
+ _lastKeySource = 'env';
52
+ return process.env.TROXY_API_KEY;
53
+ }
54
+ const saved = loadConfig()?.apiKey;
55
+ if (saved) {
56
+ _lastKeySource = 'config';
57
+ return saved;
45
58
  }
46
- return key;
59
+ console.error('\n No API key found.');
60
+ console.error(' Run: npx troxy init --key txy-... to connect this machine.\n');
61
+ process.exit(1);
47
62
  }
48
63
 
49
64
  function loadConfig() {
package/src/policies.js CHANGED
@@ -3,7 +3,12 @@ import { requireJwt, requireKey } from './auth.js';
3
3
  import { table } from './print.js';
4
4
 
5
5
  const DECISION_ICON = { ALLOW: '✓', BLOCK: '✗', ESCALATE: '⏳', NOTIFY: '~', TIERED: '⊕' };
6
- const SCOPE_COLOR = { 'all MCPs': 'all MCPs', 'this MCP': '→ me', 'other MCPs': 'other' };
6
+
7
+ function _scope(p) {
8
+ if (p.global !== false) return 'all MCPs';
9
+ if (p.mcps && p.mcps.length > 0) return p.mcps.map(m => m.name || m.token_prefix || 'MCP').join(', ');
10
+ return 'no MCPs applied';
11
+ }
7
12
 
8
13
  export async function runPolicies([sub, ...args], flags) {
9
14
  // Read-only subcommands work with just an API key
@@ -23,7 +28,7 @@ export async function runPolicies([sub, ...args], flags) {
23
28
  p.priority,
24
29
  p.name,
25
30
  p.action,
26
- SCOPE_COLOR[p.scope] || p.scope,
31
+ _scope(p),
27
32
  p.enabled ? 'enabled' : 'disabled',
28
33
  _condSummary(p),
29
34
  p.applies_to_me ? '✓' : '—',