troxy-cli 1.6.0 → 1.7.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
@@ -147,6 +147,13 @@ switch (command) {
147
147
  await runMcp();
148
148
  break;
149
149
 
150
+ // ── Heartbeat daemon (background service) ─────────────────────
151
+ case 'daemon': {
152
+ const { runDaemon } = await import('../src/daemon.js');
153
+ await runDaemon();
154
+ break;
155
+ }
156
+
150
157
  // ── Pause / resume payment evaluations ───────────────────────
151
158
  case 'pause':
152
159
  await runPause();
@@ -358,21 +365,42 @@ switch (command) {
358
365
  // ── Restart MCP service ───────────────────────────────────────
359
366
  case 'restart': {
360
367
  const { execSync: _execSyncRestart } = await import('child_process');
361
- const os = (await import('os')).default;
362
- console.log('\n Restarting Troxy MCP service...');
368
+ const fsR = await import('fs');
369
+ const osR = (await import('os')).default;
370
+ console.log('\n Restarting Troxy service...');
363
371
  try {
364
- if (os.platform() === 'linux') {
372
+ if (osR.platform() === 'linux') {
373
+ // Migrate service file from old "troxy mcp" to "troxy daemon" if needed
374
+ const svcPath = '/etc/systemd/system/troxy-mcp.service';
375
+ if (fsR.existsSync(svcPath)) {
376
+ const svc = fsR.readFileSync(svcPath, 'utf8');
377
+ if (svc.includes('ExecStart=') && svc.includes(' mcp') && !svc.includes(' daemon')) {
378
+ const fixed = svc.replace(/(ExecStart=\S+) mcp/, '$1 daemon');
379
+ fsR.writeFileSync('/tmp/troxy-mcp.service', fixed);
380
+ _execSyncRestart('sudo mv /tmp/troxy-mcp.service /etc/systemd/system/troxy-mcp.service');
381
+ _execSyncRestart('sudo systemctl daemon-reload');
382
+ process.stdout.write(' Migrated service to daemon mode ✓\n');
383
+ }
384
+ }
365
385
  _execSyncRestart('sudo systemctl restart troxy-mcp', { stdio: 'inherit' });
366
- } else if (os.platform() === 'darwin') {
367
- const { homedir } = await import('os');
368
- _execSyncRestart(`launchctl unload ~/Library/LaunchAgents/io.troxy.mcp.plist 2>/dev/null; launchctl load ~/Library/LaunchAgents/io.troxy.mcp.plist`, { shell: true, stdio: 'inherit' });
386
+ } else if (osR.platform() === 'darwin') {
387
+ const plistPath = osR.homedir() + '/Library/LaunchAgents/ai.troxy.mcp.plist';
388
+ if (fsR.existsSync(plistPath)) {
389
+ const plist = fsR.readFileSync(plistPath, 'utf8');
390
+ if (plist.includes('<string>mcp</string>')) {
391
+ const fixed = plist.replace('<string>mcp</string>', '<string>daemon</string>');
392
+ fsR.writeFileSync(plistPath, fixed);
393
+ process.stdout.write(' Migrated service to daemon mode ✓\n');
394
+ }
395
+ }
396
+ _execSyncRestart(`launchctl unload ${plistPath} 2>/dev/null; launchctl load ${plistPath}`, { shell: true, stdio: 'inherit' });
369
397
  } else {
370
- console.error('\n Restart is only supported on Linux and macOS.\n On Windows, restart the Troxy MCP process manually.\n');
398
+ console.error('\n Restart is only supported on Linux and macOS.\n');
371
399
  process.exit(1);
372
400
  }
373
- console.log(' MCP service restarted ✓\n');
374
- } catch {
375
- 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');
401
+ console.log(' Service restarted ✓\n');
402
+ } catch (e) {
403
+ console.error('\n Could not restart service. Try manually:\n Linux: sudo systemctl restart troxy-mcp\n macOS: launchctl unload/load ~/Library/LaunchAgents/ai.troxy.mcp.plist\n');
376
404
  process.exit(1);
377
405
  }
378
406
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
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>