troxy-cli 1.8.7 → 1.8.9
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 +2 -0
- package/package.json +1 -1
- package/src/api.js +1 -0
- package/src/init.js +13 -4
- package/src/mcp-server.js +72 -1
- package/src/tests/init.test.js +27 -0
package/bin/troxy.js
CHANGED
|
@@ -510,6 +510,8 @@ switch (command) {
|
|
|
510
510
|
|
|
511
511
|
Setup (one time, API key required)
|
|
512
512
|
troxy init --key <api-key> Connect this machine as an MCP + save key
|
|
513
|
+
troxy init --key <api-key> --name "My Agent" Same, no interactive prompt
|
|
514
|
+
(for cloud/scripted agents that can't answer stdin)
|
|
513
515
|
troxy restart Restart the MCP background service
|
|
514
516
|
troxy uninstall Remove Troxy from this machine
|
|
515
517
|
troxy status API health + MCP status (no login needed)
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -72,6 +72,7 @@ export const api = {
|
|
|
72
72
|
// Evaluate + confirm (agent API key)
|
|
73
73
|
evaluate: (body, apiKey) => request('POST', '/evaluate', { apiKey, body }),
|
|
74
74
|
evaluateEmail: (body, apiKey) => request('POST', '/evaluate/email', { apiKey, body }),
|
|
75
|
+
evaluateLogin: (body, apiKey) => request('POST', '/evaluate/login', { apiKey, body }),
|
|
75
76
|
selfRevoke: (apiKey) => request('POST', '/mcp/self-revoke', { apiKey }),
|
|
76
77
|
confirmPayment: (auditId, body, apiKey) => request('POST', `/payments/${auditId}/confirm`, { apiKey, body }),
|
|
77
78
|
waitApprovalStatus: (token) => request('GET', `/approvals/${encodeURIComponent(token)}/wait`),
|
package/src/init.js
CHANGED
|
@@ -121,7 +121,7 @@ const MCP_CLIENTS = [
|
|
|
121
121
|
},
|
|
122
122
|
];
|
|
123
123
|
|
|
124
|
-
export async function runInit({ key } = {}) {
|
|
124
|
+
export async function runInit({ key, name } = {}) {
|
|
125
125
|
if (!key || !key.startsWith('txy-')) {
|
|
126
126
|
console.error('\n Error: --key is required and must start with txy-');
|
|
127
127
|
console.error(' Usage: npx troxy-cli init --key txy-...\n');
|
|
@@ -155,10 +155,19 @@ export async function runInit({ key } = {}) {
|
|
|
155
155
|
console.log('✓');
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
// Ask for agent name (default to hostname so users don't get stuck)
|
|
158
|
+
// Ask for agent name (default to hostname so users don't get stuck) — unless
|
|
159
|
+
// --name was passed, in which case skip the interactive prompt entirely.
|
|
160
|
+
// Needed for cloud agents (Base44 Superagent, etc.) that can't reliably
|
|
161
|
+
// respond to a stdin prompt and would otherwise hang init indefinitely.
|
|
159
162
|
const defaultName = os.hostname() || 'my-agent';
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
let agentName;
|
|
164
|
+
if (name) {
|
|
165
|
+
agentName = name;
|
|
166
|
+
console.log(` Naming this agent "${agentName}"`);
|
|
167
|
+
} else {
|
|
168
|
+
const answer = await prompt(` Name this agent (press Enter for "${defaultName}"): `);
|
|
169
|
+
agentName = answer || defaultName;
|
|
170
|
+
}
|
|
162
171
|
|
|
163
172
|
// Save config
|
|
164
173
|
saveConfig({ apiKey: key, agentName });
|
package/src/mcp-server.js
CHANGED
|
@@ -8,6 +8,7 @@ import { loadConfig } from './config.js';
|
|
|
8
8
|
import { evaluatePayment, api } from './api.js';
|
|
9
9
|
const confirmPayment = (auditId, body, apiKey) => api.confirmPayment(auditId, body, apiKey);
|
|
10
10
|
const evaluateEmail = (body, apiKey) => api.evaluateEmail(body, apiKey);
|
|
11
|
+
const evaluateLogin = (body, apiKey) => api.evaluateLogin(body, apiKey);
|
|
11
12
|
|
|
12
13
|
export async function runMcp() {
|
|
13
14
|
const config = loadConfig();
|
|
@@ -40,7 +41,8 @@ export async function runMcp() {
|
|
|
40
41
|
'If the decision is NOTIFY, proceed but the user will be notified. ' +
|
|
41
42
|
'When calling evaluate_payment, use the merchant\'s domain name as merchant_name when available (e.g. "amazon.com", "indigobloom.co.il"); this ensures consistent matching against policies. ' +
|
|
42
43
|
'After every ALLOW or NOTIFY decision, attempt the payment, then call confirm_payment with the audit_id and the outcome (success/failed/cancelled). Always include a reason when the charge fails. ' +
|
|
43
|
-
'You MUST also call the evaluate_email tool before sending any email on the user\'s behalf. Always include the full plaintext "body" argument, exactly as you intend to send it — Troxy policies inspect the body for secrets and confidential content, and cannot do so if it is omitted, summarized, or truncated. Never send an email without first receiving an ALLOW or NOTIFY decision. If BLOCK, do not send it. If ESCALATE, call wait_for_payment_approval with the returned approval_token (same wait mechanism payments use), then call evaluate_email again with the approval_token once approved.'
|
|
44
|
+
'You MUST also call the evaluate_email tool before sending any email on the user\'s behalf. Always include the full plaintext "body" argument, exactly as you intend to send it — Troxy policies inspect the body for secrets and confidential content, and cannot do so if it is omitted, summarized, or truncated. Never send an email without first receiving an ALLOW or NOTIFY decision. If BLOCK, do not send it. If ESCALATE, call wait_for_payment_approval with the returned approval_token (same wait mechanism payments use), then call evaluate_email again with the approval_token once approved. ' +
|
|
45
|
+
'You MUST also call the evaluate_login tool before logging into or signing up for any website on the user\'s behalf, giving the site\'s domain and whether it is a login or a signup. Never submit login credentials or a signup form without first receiving an ALLOW or NOTIFY decision. If BLOCK, do not proceed. If ESCALATE, call wait_for_payment_approval with the returned approval_token, then call evaluate_login again with the approval_token once approved.',
|
|
44
46
|
},
|
|
45
47
|
{ capabilities: { tools: {} } },
|
|
46
48
|
);
|
|
@@ -170,6 +172,35 @@ export async function runMcp() {
|
|
|
170
172
|
},
|
|
171
173
|
},
|
|
172
174
|
},
|
|
175
|
+
{
|
|
176
|
+
name: 'evaluate_login',
|
|
177
|
+
description:
|
|
178
|
+
'Evaluate whether logging into or signing up for a website should be allowed, blocked, or escalated ' +
|
|
179
|
+
'based on your Troxy policies. Call this before submitting login credentials or a signup form on the user\'s behalf.',
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
required: ['site', 'login_action'],
|
|
183
|
+
properties: {
|
|
184
|
+
site: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'The domain of the site being logged into or signed up for, e.g. "example.com"',
|
|
187
|
+
},
|
|
188
|
+
login_action: {
|
|
189
|
+
type: 'string',
|
|
190
|
+
enum: ['login', 'signup'],
|
|
191
|
+
description: '"login" if authenticating to an existing account, "signup" if creating a new account',
|
|
192
|
+
},
|
|
193
|
+
agent: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
description: 'Name of the agent performing the login/signup (optional)',
|
|
196
|
+
},
|
|
197
|
+
approval_token: {
|
|
198
|
+
type: 'string',
|
|
199
|
+
description: 'Approval token from a previous ESCALATE response. Include this to proceed after the user has approved.',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
173
204
|
],
|
|
174
205
|
}));
|
|
175
206
|
|
|
@@ -279,6 +310,46 @@ export async function runMcp() {
|
|
|
279
310
|
};
|
|
280
311
|
}
|
|
281
312
|
|
|
313
|
+
if (toolName === 'evaluate_login') {
|
|
314
|
+
if (agentName && !args.agent) args.agent = agentName;
|
|
315
|
+
let result;
|
|
316
|
+
try {
|
|
317
|
+
result = await evaluateLogin(args, apiKey);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
return {
|
|
320
|
+
content: [{ type: 'text', text: `Troxy error: ${err.message}` }],
|
|
321
|
+
isError: true,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (result.error) {
|
|
325
|
+
return {
|
|
326
|
+
content: [{ type: 'text', text: `Troxy error: ${result.error}` }],
|
|
327
|
+
isError: true,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const { decision, reason, audit_id, approval_token } = result;
|
|
331
|
+
const verb = args.login_action === 'signup' ? 'Signup' : 'Login';
|
|
332
|
+
let loginText;
|
|
333
|
+
switch (decision) {
|
|
334
|
+
case 'ALLOW':
|
|
335
|
+
case 'NOTIFY':
|
|
336
|
+
loginText = `✓ ${verb} approved.${reason ? ` ${reason}` : ''} You may proceed. (audit: ${audit_id})`;
|
|
337
|
+
break;
|
|
338
|
+
case 'BLOCK':
|
|
339
|
+
loginText = `✗ ${verb} blocked.${reason ? ` ${reason}` : ''} Do not proceed. (audit: ${audit_id})`;
|
|
340
|
+
break;
|
|
341
|
+
case 'ESCALATE':
|
|
342
|
+
loginText = `⏳ ${verb} requires human approval; a request has been sent to the account owner.\n\nApproval token: ${approval_token}\n\nNow call wait_for_payment_approval(approval_token="${approval_token}") to automatically detect approval, then call evaluate_login again with the same site/login_action PLUS this approval_token. Do not proceed until it returns approved.`;
|
|
343
|
+
break;
|
|
344
|
+
default:
|
|
345
|
+
loginText = JSON.stringify(result);
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
content: [{ type: 'text', text: loginText }],
|
|
349
|
+
isError: decision === 'BLOCK',
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
282
353
|
if (toolName !== 'evaluate_payment') {
|
|
283
354
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
284
355
|
}
|
package/src/tests/init.test.js
CHANGED
|
@@ -20,3 +20,30 @@ describe('flag validation', () => {
|
|
|
20
20
|
assert.ok(key.startsWith('txy-'));
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
|
+
|
|
24
|
+
describe('agent name resolution (--name flag)', () => {
|
|
25
|
+
// Mirrors the branch added to runInit in src/init.js: a cloud/scripted agent
|
|
26
|
+
// (Base44 Superagent, etc.) can't answer an interactive stdin prompt, so
|
|
27
|
+
// `troxy init --key ... --name "X"` must skip the prompt entirely rather
|
|
28
|
+
// than hang waiting for input that will never come — found live testing a
|
|
29
|
+
// real Base44 agent against this exact CLI (TXY-128).
|
|
30
|
+
function resolveAgentName(name, promptAnswer, defaultName) {
|
|
31
|
+
if (name) return name;
|
|
32
|
+
return promptAnswer || defaultName;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
it('uses --name directly, without ever consulting the prompt answer', () => {
|
|
36
|
+
const agentName = resolveAgentName('Lyra SuperAgent', undefined, 'some-hostname');
|
|
37
|
+
assert.equal(agentName, 'Lyra SuperAgent');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('falls back to the interactive prompt answer when --name is not given', () => {
|
|
41
|
+
const agentName = resolveAgentName(undefined, 'Chosen Name', 'some-hostname');
|
|
42
|
+
assert.equal(agentName, 'Chosen Name');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('falls back to the hostname default when neither --name nor a prompt answer is given', () => {
|
|
46
|
+
const agentName = resolveAgentName(undefined, '', 'some-hostname');
|
|
47
|
+
assert.equal(agentName, 'some-hostname');
|
|
48
|
+
});
|
|
49
|
+
});
|