troxy-cli 1.5.0 → 1.7.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
@@ -91,19 +91,17 @@ switch (command) {
91
91
  console.log(`
92
92
  troxy rotate-key [options]
93
93
 
94
- Creates a new MCP API key, saves it to ~/.troxy/config.json, and optionally
95
- revokes the old key. Login required.
94
+ Creates a new MCP API key, saves it to ~/.troxy/config.json, and revokes
95
+ the old key immediately. Login required.
96
96
 
97
97
  Options:
98
- --name <name> Name for the new key (default: "Rotated key")
99
- --revoke-old Revoke the old key immediately after creating the new one
98
+ --name <name> Name for the new key (default: keeps current name)
100
99
 
101
100
  Examples:
102
101
  troxy rotate-key
103
- troxy rotate-key --revoke-old
104
- troxy rotate-key --name "My Agent Key" --revoke-old
102
+ troxy rotate-key --name "My Agent Key"
105
103
 
106
- After rotating: update TROXY_API_KEY in your MCP config and restart the MCP server.
104
+ After rotating: run troxy restart to apply the new key.
107
105
  `);
108
106
  process.exit(0);
109
107
  }
@@ -111,11 +109,10 @@ switch (command) {
111
109
  const { loadConfig, saveConfig } = await import('../src/config.js');
112
110
  const oldKey = loadConfig()?.apiKey;
113
111
  const oldPrefix = oldKey ? oldKey.substring(0, 11) : null;
114
- const name = flags.name || 'Rotated key';
115
- const revokeOld = !!flags['revoke-old'];
112
+ const name = flags.name || null; // keep existing name if not specified
116
113
 
117
114
  process.stdout.write('\n Creating new key... ');
118
- const result = await api.createToken(jwt, { name });
115
+ const result = await api.createToken(jwt, { name: name || 'Rotated key' });
119
116
  const newKey = result.key;
120
117
  const newPrefix = result.prefix;
121
118
  console.log('✓');
@@ -123,7 +120,7 @@ switch (command) {
123
120
  saveConfig({ apiKey: newKey });
124
121
  console.log(' Saved to ~/.troxy/config.json ✓');
125
122
 
126
- if (revokeOld && oldPrefix) {
123
+ if (oldPrefix) {
127
124
  process.stdout.write(` Revoking old key ${oldPrefix}... `);
128
125
  const { tokens = [] } = await api.listTokens(jwt);
129
126
  const old = tokens.find(t => t.prefix === oldPrefix);
@@ -133,16 +130,14 @@ switch (command) {
133
130
 
134
131
  console.log(`
135
132
  Key rotated:
136
- Old: ${oldPrefix ? oldPrefix + '...' : '(none)'}
133
+ Old: ${oldPrefix ? oldPrefix + '... (revoked)' : '(none)'}
137
134
  New: ${newPrefix}...
138
135
 
139
136
  New key (shown once — save it now):
140
137
  ${newKey}
141
138
 
142
139
  ~/.troxy/config.json updated ✓
143
-
144
- ⚠ If this key runs an MCP, update TROXY_API_KEY in your MCP
145
- config and restart the MCP server.
140
+ Run troxy restart to apply the new key.
146
141
  `);
147
142
  break;
148
143
  }
@@ -152,6 +147,13 @@ switch (command) {
152
147
  await runMcp();
153
148
  break;
154
149
 
150
+ // ── Heartbeat daemon (background service) ─────────────────────
151
+ case 'daemon': {
152
+ const { runDaemon } = await import('../src/daemon.js');
153
+ await runDaemon();
154
+ break;
155
+ }
156
+
155
157
  // ── Pause / resume payment evaluations ───────────────────────
156
158
  case 'pause':
157
159
  await runPause();
@@ -360,6 +362,29 @@ switch (command) {
360
362
  break;
361
363
  }
362
364
 
365
+ // ── Restart MCP service ───────────────────────────────────────
366
+ case 'restart': {
367
+ const { execSync: _execSyncRestart } = await import('child_process');
368
+ const os = (await import('os')).default;
369
+ console.log('\n Restarting Troxy MCP service...');
370
+ try {
371
+ if (os.platform() === 'linux') {
372
+ _execSyncRestart('sudo systemctl restart troxy-mcp', { stdio: 'inherit' });
373
+ } else if (os.platform() === 'darwin') {
374
+ const { homedir } = await import('os');
375
+ _execSyncRestart(`launchctl unload ~/Library/LaunchAgents/io.troxy.mcp.plist 2>/dev/null; launchctl load ~/Library/LaunchAgents/io.troxy.mcp.plist`, { shell: true, stdio: 'inherit' });
376
+ } else {
377
+ console.error('\n Restart is only supported on Linux and macOS.\n On Windows, restart the Troxy MCP process manually.\n');
378
+ process.exit(1);
379
+ }
380
+ console.log(' MCP service restarted ✓\n');
381
+ } catch {
382
+ console.error('\n Could not restart service. Try manually:\n Linux: sudo systemctl restart troxy-mcp\n macOS: launchctl unload/load ~/Library/LaunchAgents/io.troxy.mcp.plist\n');
383
+ process.exit(1);
384
+ }
385
+ break;
386
+ }
387
+
363
388
  // ── Self-update ───────────────────────────────────────────────
364
389
  case 'update': {
365
390
  const { execSync } = await import('child_process');
@@ -431,6 +456,7 @@ switch (command) {
431
456
 
432
457
  Setup (one time, API key required)
433
458
  troxy init --key <api-key> Connect this machine as an MCP + save key
459
+ troxy restart Restart the MCP background service
434
460
  troxy uninstall Remove Troxy from this machine
435
461
  troxy status API health + MCP status (no login needed)
436
462
  troxy update Update to the latest version
@@ -444,8 +470,7 @@ switch (command) {
444
470
  troxy mcps rename --name "x" Rename this machine's MCP
445
471
 
446
472
  Keys
447
- troxy rotate-key Create new MCP key + save it locally
448
- troxy rotate-key --revoke-old Same + revoke the old key immediately
473
+ troxy rotate-key Rotate MCP key (revokes old, saves new locally)
449
474
 
450
475
  Policies
451
476
  troxy policies list
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "AI payment control — protect your agent's payments with policies",
5
5
  "type": "module",
6
6
  "bin": {
package/src/daemon.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Troxy heartbeat daemon.
3
+ * Runs as a background service (systemd / launchd). Sends a heartbeat to the
4
+ * Troxy API every 60 seconds so the dashboard shows this agent as connected.
5
+ * Does NOT start an MCP stdio server — that only makes sense when an MCP client
6
+ * (Claude Desktop, Cursor, etc.) is present.
7
+ */
8
+ import { loadConfig } from './config.js';
9
+ import * as api from './api.js';
10
+
11
+ const INTERVAL_MS = 60_000;
12
+
13
+ export async function runDaemon() {
14
+ const cfg = loadConfig();
15
+ if (!cfg?.apiKey) {
16
+ process.stderr.write('[troxy-daemon] No API key found. Run troxy init first.\n');
17
+ process.exit(1);
18
+ }
19
+
20
+ const { apiKey, agentName } = cfg;
21
+
22
+ const beat = () =>
23
+ api.mcpHeartbeat(apiKey, agentName)
24
+ .then(() => process.stderr.write(`[troxy-daemon] heartbeat ok\n`))
25
+ .catch(err => process.stderr.write(`[troxy-daemon] heartbeat failed: ${err.message}\n`));
26
+
27
+ process.stderr.write(`[troxy-daemon] starting (agent: ${agentName || 'unnamed'})\n`);
28
+ await beat();
29
+ setInterval(beat, INTERVAL_MS);
30
+
31
+ // Keep process alive indefinitely
32
+ process.stdin.resume();
33
+ }
package/src/init.js CHANGED
@@ -154,7 +154,7 @@ export async function runInit({ key } = {}) {
154
154
  console.log(' Background service installed ✓');
155
155
  } catch (err) {
156
156
  console.log(` Background service ✗ (${err.message})`);
157
- console.log(' You can start it manually with: troxy mcp &');
157
+ console.log(' You can start it manually with: troxy daemon &');
158
158
  }
159
159
 
160
160
  console.log('\n Your payments are now protected.');
@@ -179,7 +179,7 @@ Description=Troxy MCP Server
179
179
  After=network.target
180
180
 
181
181
  [Service]
182
- ExecStart=${troxy} mcp
182
+ ExecStart=${troxy} daemon
183
183
  Restart=always
184
184
  RestartSec=10
185
185
  User=${os.userInfo().username}
@@ -205,7 +205,7 @@ WantedBy=multi-user.target
205
205
  <key>ProgramArguments</key>
206
206
  <array>
207
207
  <string>${troxy}</string>
208
- <string>mcp</string>
208
+ <string>daemon</string>
209
209
  </array>
210
210
  <key>EnvironmentVariables</key>
211
211
  <dict>