troxy-cli 1.2.0 → 1.2.1

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,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';
5
+ import { runLogin, clearSession, requireKey, getKeySource } from '../src/auth.js';
6
6
  import { runCards } from '../src/cards.js';
7
7
  import { runPolicies } from '../src/policies.js';
8
8
  import { runMcps } from '../src/mcps.js';
@@ -26,6 +26,26 @@ for (let i = 0; i < allArgs.length; i++) {
26
26
  }
27
27
  }
28
28
 
29
+ try { await _run(); } catch (err) { _handleError(err); }
30
+
31
+ function _handleError(err) {
32
+ if (err.code === 'UNAUTHORIZED') {
33
+ const source = getKeySource();
34
+ if (source === 'config') {
35
+ console.error('\n API key revoked or invalid.');
36
+ console.error(' Your saved key is no longer accepted by Troxy.');
37
+ console.error(' Run: npx troxy init --key <new-key> to reconnect.\n');
38
+ } else {
39
+ console.error('\n API key invalid or revoked.');
40
+ console.error(' Check the key in your Troxy dashboard → Connections.\n');
41
+ }
42
+ } else {
43
+ console.error(`\n Error: ${err.message}\n`);
44
+ }
45
+ process.exit(1);
46
+ }
47
+
48
+ async function _run() {
29
49
  switch (command) {
30
50
  // ── Setup ─────────────────────────────────────────────────────
31
51
  case 'init':
@@ -37,6 +57,22 @@ switch (command) {
37
57
  break;
38
58
 
39
59
  // ── Auth ──────────────────────────────────────────────────────
60
+ case 'connect': {
61
+ const k = flags.key;
62
+ if (!k || !k.startsWith('txy-')) {
63
+ console.error('\n Usage: troxy connect --key txy-...\n');
64
+ process.exit(1);
65
+ }
66
+ // Validate key before saving
67
+ process.stdout.write('\n Validating key... ');
68
+ await api.agentStatus(k);
69
+ console.log('✓');
70
+ const { saveConfig } = await import('../src/config.js');
71
+ saveConfig({ apiKey: k });
72
+ console.log(' Key saved to ~/.troxy/config.json\n');
73
+ break;
74
+ }
75
+
40
76
  case 'login':
41
77
  await runLogin(flags);
42
78
  break;
@@ -114,11 +150,15 @@ switch (command) {
114
150
 
115
151
  // If we have a key, show enriched status
116
152
  try {
117
- const apiKey = requireKey(flags);
118
- const data = await api.agentStatus(apiKey);
153
+ const apiKey = requireKey(flags);
154
+ const source = getKeySource();
155
+ const data = await api.agentStatus(apiKey);
119
156
  const { token, account } = data;
157
+ const keyNote = source === 'config'
158
+ ? '(saved — run `troxy init` to change)'
159
+ : source === 'env' ? '(from TROXY_API_KEY env)' : '(passed via --key)';
120
160
  console.log(`
121
- Token: ${token.prefix} (${token.name})
161
+ Key: ${token.prefix} ${keyNote}
122
162
  MCP: ${token.connected ? '● connected' : '○ offline'} last seen ${token.last_seen}
123
163
  Fallback: ${token.default_action}
124
164
 
@@ -128,7 +168,7 @@ switch (command) {
128
168
  Requests 24h: ${account.requests_24h}
129
169
  Default action: ${account.default_action}
130
170
  `);
131
- } catch { console.log(); }
171
+ } catch (err) { if (err.code === 'UNAUTHORIZED') throw err; console.log(); }
132
172
 
133
173
  // Version check
134
174
  try {
@@ -152,31 +192,33 @@ switch (command) {
152
192
  console.log(`
153
193
  Troxy — AI payment control
154
194
 
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
195
+ First time on a machine? Run: npx troxy init --key <api-key>
196
+ This saves your key to ~/.troxy/config.json — no need to pass --key again.
157
197
 
158
198
  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)
199
+ troxy connect --key <api-key> Save API key (CLI only — no MCP setup)
200
+ troxy init --key <api-key> Full setup: save key + configure MCP
201
+ troxy uninstall Remove Troxy from this machine
202
+ troxy status API health + which key is in use
203
+
204
+ Inspect (uses saved key — no flags needed after init)
205
+ troxy policies list
206
+ troxy policies describe --name "Block Amazon"
207
+ troxy mcps list
208
+ troxy cards list
209
+ troxy activity [--limit 50] [--mine]
210
+ troxy insights [--period 7]
211
+
212
+ Manage (requires: npx troxy login)
174
213
  troxy policies create --name "X" --action BLOCK --field amount --operator gte --value 500
175
214
  troxy policies enable --name "X"
176
215
  troxy policies disable --name "X"
177
216
  troxy policies delete --name "X"
178
217
  troxy cards create --name "Personal" [--budget 500]
179
218
  troxy cards delete --name "Personal"
219
+
220
+ Override key for a single command: --key txy-...
180
221
  `);
181
222
  process.exit(command ? 1 : 0);
182
223
  }
224
+ } // 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.1",
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 ? '✓' : '—',