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 +64 -22
- package/package.json +8 -2
- package/src/api.js +5 -1
- package/src/auth.js +21 -6
- package/src/policies.js +7 -2
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
|
|
118
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
|
160
|
-
troxy
|
|
161
|
-
troxy
|
|
162
|
-
troxy
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
troxy policies
|
|
167
|
-
troxy
|
|
168
|
-
troxy
|
|
169
|
-
troxy
|
|
170
|
-
troxy
|
|
171
|
-
|
|
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.
|
|
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": [
|
|
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)
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
+
_scope(p),
|
|
27
32
|
p.enabled ? 'enabled' : 'disabled',
|
|
28
33
|
_condSummary(p),
|
|
29
34
|
p.applies_to_me ? '✓' : '—',
|