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 +42 -17
- package/package.json +1 -1
- package/src/daemon.js +33 -0
- package/src/init.js +3 -3
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
|
|
95
|
-
|
|
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:
|
|
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 --
|
|
104
|
-
troxy rotate-key --name "My Agent Key" --revoke-old
|
|
102
|
+
troxy rotate-key --name "My Agent Key"
|
|
105
103
|
|
|
106
|
-
After rotating:
|
|
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
|
|
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 (
|
|
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
|
|
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
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
|
|
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}
|
|
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>
|
|
208
|
+
<string>daemon</string>
|
|
209
209
|
</array>
|
|
210
210
|
<key>EnvironmentVariables</key>
|
|
211
211
|
<dict>
|