troxy-cli 1.2.7 → 1.3.0

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
@@ -251,9 +251,8 @@ switch (command) {
251
251
  console.log(`
252
252
  Troxy — AI payment control
253
253
 
254
- First time on a machine? Run: npx troxy init --key <api-key>
255
- MCP setup (once per machine): troxy init --key <api-key>
256
- Login for CLI commands (12h): troxy login
254
+ MCP setup (once per machine): troxy init --key <api-key>
255
+ Login for CLI commands (12h): troxy login
257
256
 
258
257
  MCP Setup
259
258
  troxy init --key <api-key> Connect this machine as an MCP + save key
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "description": "AI payment control — protect your agent's payments with policies",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -24,9 +24,12 @@ async function request(method, path, { apiKey, jwt, body } = {}) {
24
24
 
25
25
  export const api = {
26
26
  // Auth
27
- health: () => request('GET', '/health'),
28
- magicLink: (email) => request('POST', '/auth/magic-link', { body: { email } }),
29
- verify: (token) => request('POST', '/auth/verify', { body: { token } }),
27
+ health: () => request('GET', '/health'),
28
+ magicLink: (email) => request('POST', '/auth/magic-link', { body: { email } }),
29
+ verify: (token) => request('POST', '/auth/verify', { body: { token } }),
30
+ cliStart: () => request('POST', '/auth/cli/start', {}),
31
+ cliAuthorize: (jwt, session_id) => request('POST', '/auth/cli/authorize', { jwt, body: { session_id } }),
32
+ cliExchange: (session_id, code) => request('POST', '/auth/cli/exchange', { body: { session_id, code } }),
30
33
 
31
34
  // Cards
32
35
  listCards: (jwt) => request('GET', '/cards', { jwt }),
package/src/auth.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs';
2
2
  import os from 'os';
3
3
  import path from 'path';
4
4
  import readline from 'readline';
5
+ import { exec } from 'child_process';
5
6
  import { api } from './api.js';
6
7
 
7
8
  const SESSION_FILE = path.join(os.homedir(), '.troxy', 'session.json');
@@ -66,34 +67,47 @@ function loadConfig() {
66
67
  try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
67
68
  }
68
69
 
69
- /** Interactive magic-link login flow. */
70
- export async function runLogin({ email } = {}) {
71
- if (!email) {
72
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
73
- email = await new Promise(resolve => rl.question(' Email: ', ans => { rl.close(); resolve(ans.trim()); }));
74
- }
70
+ function _openBrowser(url) {
71
+ const cmd = process.platform === 'darwin' ? `open "${url}"`
72
+ : process.platform === 'win32' ? `start "" "${url}"`
73
+ : `xdg-open "${url}"`;
74
+ exec(cmd, () => {});
75
+ }
75
76
 
76
- process.stdout.write(`\n Sending magic link to ${email}... `);
77
+ /** Device-code login flow — opens browser, user copies code back to CLI. */
78
+ export async function runLogin() {
79
+ // 1. Start a CLI auth session
80
+ let session;
77
81
  try {
78
- await api.magicLink(email);
79
- console.log('✓');
82
+ session = await api.cliStart();
80
83
  } catch (err) {
81
- console.log('✗');
82
- console.error(` Error: ${err.message}\n`);
84
+ console.error(`\n Error starting login: ${err.message}\n`);
83
85
  process.exit(1);
84
86
  }
85
87
 
88
+ // 2. Open browser
89
+ console.log('\n Opening browser to complete login...');
90
+ console.log(` If it didn't open, visit:\n ${session.url}\n`);
91
+ _openBrowser(session.url);
92
+
93
+ // 3. Prompt for the code shown in the browser
86
94
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
87
- const token = await new Promise(resolve =>
88
- rl.question(' Enter the code from your email: ', ans => { rl.close(); resolve(ans.trim()); })
95
+ const code = await new Promise(resolve =>
96
+ rl.question(' Paste the code from your browser: ', ans => { rl.close(); resolve(ans.trim()); })
89
97
  );
90
98
 
99
+ if (!code) {
100
+ console.error('\n No code entered. Run troxy login to try again.\n');
101
+ process.exit(1);
102
+ }
103
+
104
+ // 4. Exchange code for JWT
91
105
  process.stdout.write(' Verifying... ');
92
106
  try {
93
- const result = await api.verify(token);
94
- saveSession({ jwt: result.access_token, email });
107
+ const result = await api.cliExchange(session.session_id, code);
108
+ saveSession({ jwt: result.access_token, email: result.email });
95
109
  console.log('✓');
96
- console.log(`\n Logged in as ${email}\n`);
110
+ console.log(`\n Logged in as ${result.email} (session valid for 12 hours)\n`);
97
111
  } catch (err) {
98
112
  console.log('✗');
99
113
  console.error(` Error: ${err.message}\n`);