@relayplane/proxy 1.5.46 → 1.7.2

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.
Files changed (78) hide show
  1. package/README.md +297 -20
  2. package/assets/relayplane-proxy.service +20 -0
  3. package/dist/alerts.d.ts +72 -0
  4. package/dist/alerts.d.ts.map +1 -0
  5. package/dist/alerts.js +290 -0
  6. package/dist/alerts.js.map +1 -0
  7. package/dist/anomaly.d.ts +65 -0
  8. package/dist/anomaly.d.ts.map +1 -0
  9. package/dist/anomaly.js +193 -0
  10. package/dist/anomaly.js.map +1 -0
  11. package/dist/budget.d.ts +98 -0
  12. package/dist/budget.d.ts.map +1 -0
  13. package/dist/budget.js +356 -0
  14. package/dist/budget.js.map +1 -0
  15. package/dist/cli.js +512 -93
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config.d.ts +28 -2
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +122 -24
  20. package/dist/config.js.map +1 -1
  21. package/dist/downgrade.d.ts +37 -0
  22. package/dist/downgrade.d.ts.map +1 -0
  23. package/dist/downgrade.js +79 -0
  24. package/dist/downgrade.js.map +1 -0
  25. package/dist/mesh/capture.d.ts +11 -0
  26. package/dist/mesh/capture.d.ts.map +1 -0
  27. package/dist/mesh/capture.js +43 -0
  28. package/dist/mesh/capture.js.map +1 -0
  29. package/dist/mesh/fitness.d.ts +14 -0
  30. package/dist/mesh/fitness.d.ts.map +1 -0
  31. package/dist/mesh/fitness.js +40 -0
  32. package/dist/mesh/fitness.js.map +1 -0
  33. package/dist/mesh/index.d.ts +39 -0
  34. package/dist/mesh/index.d.ts.map +1 -0
  35. package/dist/mesh/index.js +118 -0
  36. package/dist/mesh/index.js.map +1 -0
  37. package/dist/mesh/store.d.ts +30 -0
  38. package/dist/mesh/store.d.ts.map +1 -0
  39. package/dist/mesh/store.js +174 -0
  40. package/dist/mesh/store.js.map +1 -0
  41. package/dist/mesh/sync.d.ts +37 -0
  42. package/dist/mesh/sync.d.ts.map +1 -0
  43. package/dist/mesh/sync.js +154 -0
  44. package/dist/mesh/sync.js.map +1 -0
  45. package/dist/mesh/types.d.ts +57 -0
  46. package/dist/mesh/types.d.ts.map +1 -0
  47. package/dist/mesh/types.js +7 -0
  48. package/dist/mesh/types.js.map +1 -0
  49. package/dist/rate-limiter.d.ts +64 -0
  50. package/dist/rate-limiter.d.ts.map +1 -0
  51. package/dist/rate-limiter.js +159 -0
  52. package/dist/rate-limiter.js.map +1 -0
  53. package/dist/relay-config.d.ts +9 -0
  54. package/dist/relay-config.d.ts.map +1 -1
  55. package/dist/relay-config.js +2 -0
  56. package/dist/relay-config.js.map +1 -1
  57. package/dist/response-cache.d.ts +139 -0
  58. package/dist/response-cache.d.ts.map +1 -0
  59. package/dist/response-cache.js +515 -0
  60. package/dist/response-cache.js.map +1 -0
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +5 -1
  63. package/dist/server.js.map +1 -1
  64. package/dist/standalone-proxy.d.ts +2 -1
  65. package/dist/standalone-proxy.d.ts.map +1 -1
  66. package/dist/standalone-proxy.js +736 -50
  67. package/dist/standalone-proxy.js.map +1 -1
  68. package/dist/telemetry.d.ts.map +1 -1
  69. package/dist/telemetry.js +21 -5
  70. package/dist/telemetry.js.map +1 -1
  71. package/dist/utils/model-suggestions.d.ts.map +1 -1
  72. package/dist/utils/model-suggestions.js +19 -2
  73. package/dist/utils/model-suggestions.js.map +1 -1
  74. package/dist/utils/version-status.d.ts +9 -0
  75. package/dist/utils/version-status.d.ts.map +1 -0
  76. package/dist/utils/version-status.js +28 -0
  77. package/dist/utils/version-status.js.map +1 -0
  78. package/package.json +7 -3
package/dist/cli.js CHANGED
@@ -43,6 +43,10 @@ const telemetry_js_1 = require("./telemetry.js");
43
43
  const fs_1 = require("fs");
44
44
  const path_1 = require("path");
45
45
  const os_1 = require("os");
46
+ const response_cache_js_1 = require("./response-cache.js");
47
+ const budget_js_1 = require("./budget.js");
48
+ const alerts_js_1 = require("./alerts.js");
49
+ // __dirname is available natively in CJS
46
50
  let VERSION = '0.0.0';
47
51
  try {
48
52
  const pkgPath = (0, path_1.join)(__dirname, '..', 'package.json');
@@ -389,6 +393,7 @@ Usage:
389
393
 
390
394
  Commands:
391
395
  (default) Start the proxy server
396
+ init Initialize config files
392
397
  login Log in to RelayPlane (opens browser)
393
398
  logout Clear stored credentials
394
399
  status Show proxy status, plan, and cloud sync
@@ -398,8 +403,13 @@ Commands:
398
403
  telemetry [on|off|status] Manage telemetry settings
399
404
  stats Show usage statistics
400
405
  config Show configuration
401
- autostart [on|off|status] Manage autostart on boot (systemd)
402
- mesh [status|sync|tips|contribute] Mesh learning layer management
406
+ config set-key <key> Set RelayPlane API key
407
+ budget [status|set|reset] Manage spend budgets and limits
408
+ alerts [list|counts] View cost alerts and anomaly history
409
+ cache [on|off|status|clear|stats] Manage response cache
410
+ service [install|uninstall|status] Manage system service (systemd/launchd)
411
+ autostart [on|off|status] Manage autostart on boot (systemd, legacy)
412
+ mesh [status|on|off|sync|contribute] Mesh learning layer management
403
413
 
404
414
  Options:
405
415
  --port <number> Port to listen on (default: 4100)
@@ -431,8 +441,8 @@ Example:
431
441
  npx @relayplane/proxy telemetry off
432
442
 
433
443
  # Point your agent at the proxy:
434
- # ANTHROPIC_BASE_URL=http://localhost:4801 your-agent
435
- # OPENAI_BASE_URL=http://localhost:4801/v1 your-agent
444
+ # ANTHROPIC_BASE_URL=http://localhost:4100 your-agent
445
+ # OPENAI_BASE_URL=http://localhost:4100/v1 your-agent
436
446
 
437
447
  Learn more: https://relayplane.com/docs
438
448
  `);
@@ -731,56 +741,61 @@ WantedBy=multi-user.target
731
741
  console.log('');
732
742
  }
733
743
  async function handleMeshCommand(args) {
734
- const { resolveMeshConfig } = await import('./relay-config.js');
735
- const config = resolveMeshConfig();
744
+ const meshCfg = (0, config_js_1.getMeshConfig)();
736
745
  const sub = args[0] ?? 'status';
737
746
  if (sub === 'status') {
738
- // Try hitting the running proxy's mesh status endpoint
747
+ // Try hitting the running proxy's mesh stats endpoint
739
748
  try {
740
749
  const controller = new AbortController();
741
750
  const timeout = setTimeout(() => controller.abort(), 2000);
742
- const res = await fetch('http://127.0.0.1:4100/v1/mesh/status', { signal: controller.signal });
751
+ const res = await fetch('http://127.0.0.1:4100/v1/mesh/stats', { signal: controller.signal });
743
752
  clearTimeout(timeout);
744
753
  if (res.ok) {
745
754
  const data = await res.json();
746
- const m = data.mesh;
747
755
  console.log('');
748
756
  console.log('🧠 Mesh Learning Layer');
749
757
  console.log('══════════════════════');
750
- console.log(` Available: ${m.available ? '✅' : '❌'}`);
751
- console.log(` Enabled: ${m.enabled ? '✅' : '❌'}`);
752
- console.log(` Atoms: ${m.atomCount}`);
753
- console.log(` Contributing: ${m.contributing ? '✅ Yes (sharing with mesh)' : '❌ No (local only)'}`);
754
- console.log(` Mesh URL: ${m.meshUrl}`);
755
- console.log(` Data dir: ${m.dataDir}`);
758
+ console.log(` Enabled: ${data.enabled ? '✅' : '❌'}`);
759
+ console.log(` Atoms (local): ${data.atoms_local}`);
760
+ console.log(` Atoms (synced): ${data.atoms_synced}`);
761
+ console.log(` Last sync: ${data.last_sync ?? 'never'}`);
762
+ console.log(` Endpoint: ${data.endpoint}`);
756
763
  console.log('');
757
764
  return;
758
765
  }
759
766
  }
760
767
  catch {
761
- // Proxy not running, show config
768
+ // Proxy not running
762
769
  }
763
770
  console.log('');
764
771
  console.log('🧠 Mesh Learning Layer (proxy not running)');
765
772
  console.log('══════════════════════════════════════════');
766
- console.log(` Enabled: ${config.enabled ? '✅' : '❌'}`);
767
- console.log(` Contribute: ${config.contribute ? '✅' : '❌'}`);
768
- console.log(` Mesh URL: ${config.meshUrl}`);
769
- console.log(` Data dir: ${config.dataDir}`);
770
- console.log(` Sync interval: ${config.syncIntervalMs / 1000}s`);
771
- console.log(` Inject interval: ${config.injectIntervalMs / 1000}s`);
773
+ console.log(` Enabled: ${meshCfg.enabled ? '✅' : '❌'}`);
774
+ console.log(` Contribute: ${meshCfg.contribute ? '✅' : '❌'}`);
775
+ console.log(` Endpoint: ${meshCfg.endpoint}`);
776
+ console.log(` Sync interval: ${meshCfg.sync_interval_ms / 1000}s`);
772
777
  console.log('');
773
778
  console.log(' Start the proxy to see live status.');
774
779
  console.log('');
775
780
  return;
776
781
  }
782
+ if (sub === 'on') {
783
+ (0, config_js_1.updateMeshConfig)({ enabled: true });
784
+ console.log(' ✅ Mesh enabled. Restart the proxy for changes to take effect.');
785
+ return;
786
+ }
787
+ if (sub === 'off') {
788
+ (0, config_js_1.updateMeshConfig)({ enabled: false });
789
+ console.log(' ❌ Mesh disabled. Restart the proxy for changes to take effect.');
790
+ return;
791
+ }
777
792
  if (sub === 'sync') {
778
793
  try {
779
794
  const res = await fetch('http://127.0.0.1:4100/v1/mesh/sync', { method: 'POST' });
780
795
  if (res.ok) {
781
796
  const data = await res.json();
782
- if (data.sync.error) {
783
- console.log(`âš ī¸ ${data.sync.error}`);
797
+ if (data.sync.errors && data.sync.errors.length > 0) {
798
+ console.log(`âš ī¸ Sync errors: ${data.sync.errors.join('; ')}`);
784
799
  }
785
800
  else {
786
801
  console.log(`✅ Synced: pushed ${data.sync.pushed ?? 0}, pulled ${data.sync.pulled ?? 0}`);
@@ -795,93 +810,308 @@ async function handleMeshCommand(args) {
795
810
  }
796
811
  return;
797
812
  }
798
- if (sub === 'tips') {
799
- try {
800
- const res = await fetch('http://127.0.0.1:4100/v1/mesh/tips');
801
- if (res.ok) {
802
- const data = await res.json();
803
- if (data.tips.length === 0) {
804
- console.log('No tips yet. Use the proxy to build knowledge.');
805
- return;
813
+ if (sub === 'contribute') {
814
+ const value = args[1]?.toLowerCase();
815
+ if (!value || value === 'status') {
816
+ console.log(`\n Mesh contribution: ${meshCfg.contribute ? '✅ Enabled' : '❌ Disabled'}`);
817
+ console.log('');
818
+ return;
819
+ }
820
+ if (value === 'on') {
821
+ (0, config_js_1.updateMeshConfig)({ contribute: true });
822
+ console.log('\n ✅ Mesh contribution enabled. Restart proxy for changes.\n');
823
+ return;
824
+ }
825
+ if (value === 'off') {
826
+ (0, config_js_1.updateMeshConfig)({ contribute: false });
827
+ console.log('\n ❌ Mesh contribution disabled. Restart proxy for changes.\n');
828
+ return;
829
+ }
830
+ console.log('Usage: relayplane mesh contribute [on|off|status]');
831
+ return;
832
+ }
833
+ console.log('Unknown mesh subcommand. Available: status, on, off, sync, contribute');
834
+ }
835
+ // ============================================
836
+ // SERVICE INSTALL/UNINSTALL/STATUS COMMAND
837
+ // ============================================
838
+ function getServiceAssetPath() {
839
+ return (0, path_1.join)(__dirname, '..', 'assets', 'relayplane-proxy.service');
840
+ }
841
+ function generateLaunchdPlist(binPath) {
842
+ const envKeys = ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'GEMINI_API_KEY', 'XAI_API_KEY'];
843
+ const envDict = envKeys
844
+ .filter(k => process.env[k])
845
+ .map(k => ` <key>${k}</key>\n <string>${process.env[k]}</string>`)
846
+ .join('\n');
847
+ return `<?xml version="1.0" encoding="UTF-8"?>
848
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
849
+ <plist version="1.0">
850
+ <dict>
851
+ <key>Label</key>
852
+ <string>com.relayplane.proxy</string>
853
+ <key>ProgramArguments</key>
854
+ <array>
855
+ <string>${binPath}</string>
856
+ </array>
857
+ <key>RunAtLoad</key>
858
+ <true/>
859
+ <key>KeepAlive</key>
860
+ <true/>
861
+ <key>StandardOutPath</key>
862
+ <string>${(0, os_1.homedir)()}/Library/Logs/relayplane-proxy.log</string>
863
+ <key>StandardErrorPath</key>
864
+ <string>${(0, os_1.homedir)()}/Library/Logs/relayplane-proxy.error.log</string>
865
+ <key>EnvironmentVariables</key>
866
+ <dict>
867
+ <key>HOME</key>
868
+ <string>${(0, os_1.homedir)()}</string>
869
+ <key>PATH</key>
870
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
871
+ <key>NODE_ENV</key>
872
+ <string>production</string>
873
+ ${envDict}
874
+ </dict>
875
+ </dict>
876
+ </plist>
877
+ `;
878
+ }
879
+ async function handleServiceCommand(args) {
880
+ const sub = args[0] ?? 'status';
881
+ const dryRun = args.includes('--dry-run');
882
+ const isMac = process.platform === 'darwin';
883
+ const isLinux = process.platform === 'linux';
884
+ if (!isMac && !isLinux) {
885
+ console.log(' âš ī¸ Service management is only supported on Linux (systemd) and macOS (launchd).');
886
+ return;
887
+ }
888
+ const { execSync } = require('child_process');
889
+ // Detect binary path
890
+ let binPath;
891
+ try {
892
+ binPath = execSync('which relayplane', { encoding: 'utf8' }).trim();
893
+ }
894
+ catch {
895
+ binPath = process.argv[0] ?? 'relayplane';
896
+ }
897
+ if (sub === 'install') {
898
+ if (isLinux) {
899
+ if (!hasSystemd()) {
900
+ console.log(' âš ī¸ systemd not found on this system.');
901
+ return;
902
+ }
903
+ if (!isRoot() && !dryRun) {
904
+ console.log(' âš ī¸ Service install requires root. Try: sudo relayplane service install');
905
+ return;
906
+ }
907
+ // Read the shipped service template and patch ExecStart
908
+ const assetPath = getServiceAssetPath();
909
+ let serviceContent;
910
+ if ((0, fs_1.existsSync)(assetPath)) {
911
+ serviceContent = (0, fs_1.readFileSync)(assetPath, 'utf8');
912
+ serviceContent = serviceContent.replace(/^ExecStart=.*$/m, `ExecStart=${binPath}`);
913
+ }
914
+ else {
915
+ // Fallback: generate inline
916
+ const envKeys = ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'GEMINI_API_KEY', 'XAI_API_KEY'];
917
+ const envLines = envKeys
918
+ .filter(k => process.env[k])
919
+ .map(k => `Environment=${k}=${process.env[k]}`)
920
+ .join('\n');
921
+ serviceContent = `[Unit]
922
+ Description=RelayPlane Proxy - Intelligent AI Model Routing
923
+ After=network.target
924
+ StartLimitIntervalSec=300
925
+ StartLimitBurst=5
926
+
927
+ [Service]
928
+ Type=notify
929
+ User=root
930
+ ExecStart=${binPath}
931
+ Restart=always
932
+ RestartSec=5
933
+ WatchdogSec=30
934
+ StandardOutput=journal
935
+ StandardError=journal
936
+ Environment=HOME=/root
937
+ Environment=NODE_ENV=production
938
+ ${envLines}
939
+
940
+ [Install]
941
+ WantedBy=multi-user.target
942
+ `;
943
+ }
944
+ // Append current env API keys not already in template
945
+ const envKeys = ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'GEMINI_API_KEY', 'XAI_API_KEY'];
946
+ for (const key of envKeys) {
947
+ if (process.env[key] && !serviceContent.includes(`Environment=${key}=`)) {
948
+ serviceContent = serviceContent.replace(/\[Install\]/, `Environment=${key}=${process.env[key]}\n\n[Install]`);
806
949
  }
950
+ }
951
+ if (dryRun) {
807
952
  console.log('');
808
- console.log('🧠 Current Tips');
809
- console.log('═══════════════');
810
- for (const tip of data.tips) {
811
- const icon = tip.type === 'tool' ? '🔧' : tip.type === 'negative' ? 'đŸšĢ' : '💡';
812
- console.log(` ${icon} [${tip.fitness.toFixed(2)}] ${tip.observation}`);
813
- }
953
+ console.log(' [DRY RUN] Would write to /etc/systemd/system/relayplane-proxy.service:');
954
+ console.log(' ─────────────────────────────────────────');
955
+ console.log(serviceContent);
956
+ console.log(' ─────────────────────────────────────────');
957
+ console.log(' [DRY RUN] Would run: systemctl daemon-reload && systemctl enable --now relayplane-proxy');
814
958
  console.log('');
959
+ return;
815
960
  }
816
- else {
817
- console.log('❌ Cannot fetch tips — is the proxy running?');
818
- }
961
+ (0, fs_1.writeFileSync)(SERVICE_PATH, serviceContent);
962
+ execSync('systemctl daemon-reload && systemctl enable --now relayplane-proxy', { stdio: 'inherit' });
963
+ const config = loadRelayplaneConfig();
964
+ config.autostart = true;
965
+ saveRelayplaneConfig(config);
966
+ console.log('');
967
+ console.log(' ✅ Service installed and started.');
968
+ console.log(' RelayPlane will start on boot and restart on crash.');
969
+ console.log(' Run `relayplane service status` to verify.');
970
+ console.log('');
819
971
  }
820
- catch {
821
- console.log('❌ Cannot connect to proxy. Start it first.');
972
+ else if (isMac) {
973
+ const plistPath = (0, path_1.join)((0, os_1.homedir)(), 'Library', 'LaunchAgents', 'com.relayplane.proxy.plist');
974
+ const plistContent = generateLaunchdPlist(binPath);
975
+ if (dryRun) {
976
+ console.log('');
977
+ console.log(` [DRY RUN] Would write to ${plistPath}:`);
978
+ console.log(' ─────────────────────────────────────────');
979
+ console.log(plistContent);
980
+ console.log(' ─────────────────────────────────────────');
981
+ console.log(` [DRY RUN] Would run: launchctl load ${plistPath}`);
982
+ console.log('');
983
+ return;
984
+ }
985
+ const dir = (0, path_1.dirname)(plistPath);
986
+ if (!(0, fs_1.existsSync)(dir))
987
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
988
+ (0, fs_1.writeFileSync)(plistPath, plistContent);
989
+ execSync(`launchctl load "${plistPath}"`, { stdio: 'inherit' });
990
+ console.log('');
991
+ console.log(' ✅ Service installed and loaded via launchd.');
992
+ console.log(' RelayPlane will start on login and restart on crash.');
993
+ console.log('');
822
994
  }
823
995
  return;
824
996
  }
825
- if (sub === 'contribute') {
826
- const value = args[1]?.toLowerCase();
827
- const configPath = (0, path_1.join)((0, os_1.homedir)(), '.relayplane', 'config.json');
828
- const configDir = (0, path_1.join)((0, os_1.homedir)(), '.relayplane');
829
- // Load or create config
830
- let config = {};
831
- try {
832
- if ((0, fs_1.existsSync)(configPath)) {
833
- config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
997
+ if (sub === 'uninstall') {
998
+ if (isLinux) {
999
+ if (!isRoot() && !dryRun) {
1000
+ console.log(' âš ī¸ Service uninstall requires root. Try: sudo relayplane service uninstall');
1001
+ return;
834
1002
  }
835
- }
836
- catch { /* fresh config */ }
837
- if (!value || value === 'status') {
838
- const enabled = config.mesh?.contribute === true;
839
- console.log(`\n Mesh contribution: ${enabled ? '✅ Enabled' : '❌ Disabled'}`);
840
- console.log('');
841
- if (enabled) {
842
- console.log(' You are sharing anonymized routing data with the collective mesh.');
843
- console.log(' This improves routing for everyone on the network.');
844
- console.log(' To disable: relayplane mesh contribute off');
1003
+ if (dryRun) {
1004
+ console.log('');
1005
+ console.log(' [DRY RUN] Would run: systemctl stop relayplane-proxy && systemctl disable relayplane-proxy');
1006
+ console.log(' [DRY RUN] Would remove /etc/systemd/system/relayplane-proxy.service');
1007
+ console.log(' [DRY RUN] Would run: systemctl daemon-reload');
1008
+ console.log('');
1009
+ return;
845
1010
  }
846
- else {
847
- console.log(' You are NOT sharing data with the mesh.');
848
- console.log(' Your routing is local-only.');
849
- console.log(' To enable: relayplane mesh contribute on');
1011
+ try {
1012
+ execSync('systemctl stop relayplane-proxy && systemctl disable relayplane-proxy', { stdio: 'inherit' });
1013
+ }
1014
+ catch { /* may not exist */ }
1015
+ try {
1016
+ if ((0, fs_1.existsSync)(SERVICE_PATH)) {
1017
+ const { unlinkSync } = require('fs');
1018
+ unlinkSync(SERVICE_PATH);
1019
+ execSync('systemctl daemon-reload', { stdio: 'inherit' });
1020
+ }
850
1021
  }
1022
+ catch { }
1023
+ const config = loadRelayplaneConfig();
1024
+ config.autostart = false;
1025
+ saveRelayplaneConfig(config);
851
1026
  console.log('');
852
- console.log(' What gets shared (anonymized):');
853
- console.log(' â€ĸ Task type (code_review, file_read, etc.)');
854
- console.log(' â€ĸ Model used and whether it succeeded');
855
- console.log(' â€ĸ Token count and latency');
856
- console.log(' â€ĸ Cost estimate');
1027
+ console.log(' ✅ Service uninstalled.');
857
1028
  console.log('');
858
- console.log(' Never shared: prompts, responses, file paths, API keys');
1029
+ }
1030
+ else if (isMac) {
1031
+ const plistPath = (0, path_1.join)((0, os_1.homedir)(), 'Library', 'LaunchAgents', 'com.relayplane.proxy.plist');
1032
+ if (dryRun) {
1033
+ console.log('');
1034
+ console.log(` [DRY RUN] Would run: launchctl unload ${plistPath}`);
1035
+ console.log(` [DRY RUN] Would remove ${plistPath}`);
1036
+ console.log('');
1037
+ return;
1038
+ }
1039
+ try {
1040
+ execSync(`launchctl unload "${plistPath}"`, { stdio: 'inherit' });
1041
+ }
1042
+ catch { }
1043
+ try {
1044
+ if ((0, fs_1.existsSync)(plistPath)) {
1045
+ const { unlinkSync } = require('fs');
1046
+ unlinkSync(plistPath);
1047
+ }
1048
+ }
1049
+ catch { }
1050
+ console.log('');
1051
+ console.log(' ✅ Service uninstalled from launchd.');
859
1052
  console.log('');
860
- return;
861
1053
  }
862
- if (value === 'on') {
863
- if (!(0, fs_1.existsSync)(configDir))
864
- (0, fs_1.mkdirSync)(configDir, { recursive: true });
865
- config.mesh = { ...(config.mesh || {}), contribute: true };
866
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
867
- console.log('\n ✅ Mesh contribution enabled');
868
- console.log(' Anonymized routing data will be shared with the collective mesh.');
869
- console.log(' Restart the proxy for changes to take effect.\n');
870
- return;
1054
+ return;
1055
+ }
1056
+ // status (default)
1057
+ if (isLinux && hasSystemd()) {
1058
+ let isEnabled = false;
1059
+ let isActive = false;
1060
+ let statusOutput = '';
1061
+ try {
1062
+ const enabled = execSync(`systemctl is-enabled ${SERVICE_NAME} 2>/dev/null`, { encoding: 'utf8' }).trim();
1063
+ isEnabled = enabled === 'enabled';
871
1064
  }
872
- if (value === 'off') {
873
- if (!(0, fs_1.existsSync)(configDir))
874
- (0, fs_1.mkdirSync)(configDir, { recursive: true });
875
- config.mesh = { ...(config.mesh || {}), contribute: false };
876
- (0, fs_1.writeFileSync)(configPath, JSON.stringify(config, null, 2));
877
- console.log('\n ❌ Mesh contribution disabled');
878
- console.log(' Your data stays local. Restart the proxy for changes to take effect.\n');
879
- return;
1065
+ catch { }
1066
+ try {
1067
+ const active = execSync(`systemctl is-active ${SERVICE_NAME} 2>/dev/null`, { encoding: 'utf8' }).trim();
1068
+ isActive = active === 'active';
880
1069
  }
881
- console.log('Usage: relayplane mesh contribute [on|off|status]');
882
- return;
1070
+ catch { }
1071
+ try {
1072
+ statusOutput = execSync(`systemctl status ${SERVICE_NAME} 2>&1 || true`, { encoding: 'utf8' });
1073
+ }
1074
+ catch { }
1075
+ console.log('');
1076
+ console.log(' 🔧 Service Status (systemd)');
1077
+ console.log(' ═══════════════════════════');
1078
+ console.log(` Enabled: ${isEnabled ? '✅ Yes' : '❌ No'}`);
1079
+ console.log(` Running: ${isActive ? 'đŸŸĸ Active' : '🔴 Inactive'}`);
1080
+ console.log('');
1081
+ if (statusOutput) {
1082
+ console.log(statusOutput.split('\n').map(l => ' ' + l).join('\n'));
1083
+ }
1084
+ if (!isEnabled) {
1085
+ console.log(' To install: sudo relayplane service install');
1086
+ }
1087
+ else {
1088
+ console.log(' To uninstall: sudo relayplane service uninstall');
1089
+ }
1090
+ console.log('');
1091
+ }
1092
+ else if (isMac) {
1093
+ const plistPath = (0, path_1.join)((0, os_1.homedir)(), 'Library', 'LaunchAgents', 'com.relayplane.proxy.plist');
1094
+ const installed = (0, fs_1.existsSync)(plistPath);
1095
+ let isLoaded = false;
1096
+ try {
1097
+ const output = execSync('launchctl list com.relayplane.proxy 2>&1', { encoding: 'utf8' });
1098
+ isLoaded = !output.includes('Could not find');
1099
+ }
1100
+ catch { }
1101
+ console.log('');
1102
+ console.log(' 🔧 Service Status (launchd)');
1103
+ console.log(' ═══════════════════════════');
1104
+ console.log(` Installed: ${installed ? '✅ Yes' : '❌ No'}`);
1105
+ console.log(` Loaded: ${isLoaded ? 'đŸŸĸ Yes' : '🔴 No'}`);
1106
+ console.log('');
1107
+ if (!installed) {
1108
+ console.log(' To install: relayplane service install');
1109
+ }
1110
+ else {
1111
+ console.log(' To uninstall: relayplane service uninstall');
1112
+ }
1113
+ console.log('');
883
1114
  }
884
- console.log('Unknown mesh subcommand. Available: status, sync, tips, contribute');
885
1115
  }
886
1116
  async function main() {
887
1117
  const args = process.argv.slice(2);
@@ -922,6 +1152,15 @@ async function main() {
922
1152
  // "relayplane start" just falls through to start the server
923
1153
  args.shift();
924
1154
  }
1155
+ const knownCommands = new Set([
1156
+ 'init', 'start', 'telemetry', 'stats', 'config', 'login', 'logout', 'upgrade',
1157
+ 'status', 'autostart', 'service', 'mesh', 'cache', 'budget', 'alerts', 'enable', 'disable',
1158
+ ]);
1159
+ if (command && !command.startsWith('-') && !knownCommands.has(command)) {
1160
+ console.error(`Unknown command: ${command}`);
1161
+ console.error('Run relayplane --help to see available commands.');
1162
+ process.exit(1);
1163
+ }
925
1164
  if (command === 'telemetry') {
926
1165
  handleTelemetryCommand(args.slice(1));
927
1166
  process.exit(0);
@@ -954,10 +1193,26 @@ async function main() {
954
1193
  await handleAutostartCommand(args.slice(1));
955
1194
  process.exit(0);
956
1195
  }
1196
+ if (command === 'service') {
1197
+ await handleServiceCommand(args.slice(1));
1198
+ process.exit(0);
1199
+ }
957
1200
  if (command === 'mesh') {
958
1201
  await handleMeshCommand(args.slice(1));
959
1202
  process.exit(0);
960
1203
  }
1204
+ if (command === 'cache') {
1205
+ handleCacheCommand(args.slice(1));
1206
+ process.exit(0);
1207
+ }
1208
+ if (command === 'budget') {
1209
+ handleBudgetCommand(args.slice(1));
1210
+ process.exit(0);
1211
+ }
1212
+ if (command === 'alerts') {
1213
+ handleAlertsCommand(args.slice(1));
1214
+ process.exit(0);
1215
+ }
961
1216
  if (command === 'enable') {
962
1217
  handleEnableDisableCommand(true);
963
1218
  process.exit(0);
@@ -1086,5 +1341,169 @@ async function main() {
1086
1341
  process.exit(1);
1087
1342
  }
1088
1343
  }
1344
+ function handleAlertsCommand(args) {
1345
+ const sub = args[0] ?? 'list';
1346
+ const alertMgr = (0, alerts_js_1.getAlertManager)({ enabled: true });
1347
+ try {
1348
+ alertMgr.init();
1349
+ }
1350
+ catch { /* ok */ }
1351
+ if (sub === 'list' || sub === 'recent') {
1352
+ const limit = parseInt(args[1] ?? '20', 10);
1353
+ const recent = alertMgr.getRecent(limit);
1354
+ console.log('');
1355
+ console.log('🔔 Recent Alerts');
1356
+ console.log('═════════════════');
1357
+ if (recent.length === 0) {
1358
+ console.log(' No alerts yet.');
1359
+ }
1360
+ else {
1361
+ for (const a of recent) {
1362
+ const icon = a.severity === 'critical' ? '🔴' : a.severity === 'warning' ? '🟡' : 'â„šī¸';
1363
+ const time = new Date(a.timestamp).toISOString().slice(0, 19);
1364
+ console.log(` ${icon} [${time}] ${a.type}: ${a.message}`);
1365
+ }
1366
+ }
1367
+ console.log('');
1368
+ alertMgr.close();
1369
+ return;
1370
+ }
1371
+ if (sub === 'counts') {
1372
+ const counts = alertMgr.getCounts();
1373
+ console.log('');
1374
+ console.log('🔔 Alert Counts');
1375
+ console.log(` Threshold: ${counts.threshold}`);
1376
+ console.log(` Anomaly: ${counts.anomaly}`);
1377
+ console.log(` Breach: ${counts.breach}`);
1378
+ console.log('');
1379
+ alertMgr.close();
1380
+ return;
1381
+ }
1382
+ console.log('Usage: relayplane alerts [list|counts]');
1383
+ alertMgr.close();
1384
+ }
1385
+ function handleBudgetCommand(args) {
1386
+ const sub = args[0] ?? 'status';
1387
+ const budget = (0, budget_js_1.getBudgetManager)();
1388
+ if (sub === 'status') {
1389
+ try {
1390
+ budget.init();
1391
+ }
1392
+ catch { /* ok */ }
1393
+ const status = budget.getStatus();
1394
+ const config = budget.getConfig();
1395
+ console.log('');
1396
+ console.log('💰 Budget Status');
1397
+ console.log(` Enabled: ${config.enabled ? '✅' : '❌'}`);
1398
+ console.log(` Daily: $${status.dailySpend.toFixed(4)} / $${status.dailyLimit} (${status.dailyPercent.toFixed(1)}%)`);
1399
+ console.log(` Hourly: $${status.hourlySpend.toFixed(4)} / $${status.hourlyLimit} (${status.hourlyPercent.toFixed(1)}%)`);
1400
+ console.log(` Per-request: max $${config.perRequestUsd}`);
1401
+ console.log(` On breach: ${config.onBreach}`);
1402
+ if (status.breached) {
1403
+ console.log(` âš ī¸ BREACHED: ${status.breachType}`);
1404
+ }
1405
+ console.log('');
1406
+ budget.close();
1407
+ return;
1408
+ }
1409
+ if (sub === 'set') {
1410
+ const daily = args.find((a, i) => args[i - 1] === '--daily');
1411
+ const hourly = args.find((a, i) => args[i - 1] === '--hourly');
1412
+ const perReq = args.find((a, i) => args[i - 1] === '--per-request');
1413
+ if (daily)
1414
+ budget.setLimits({ dailyUsd: parseFloat(daily) });
1415
+ if (hourly)
1416
+ budget.setLimits({ hourlyUsd: parseFloat(hourly) });
1417
+ if (perReq)
1418
+ budget.setLimits({ perRequestUsd: parseFloat(perReq) });
1419
+ console.log('✅ Budget limits updated');
1420
+ budget.close();
1421
+ return;
1422
+ }
1423
+ if (sub === 'reset') {
1424
+ try {
1425
+ budget.init();
1426
+ }
1427
+ catch { /* ok */ }
1428
+ budget.reset();
1429
+ console.log('✅ Budget spend reset for current window');
1430
+ budget.close();
1431
+ return;
1432
+ }
1433
+ console.log('Usage: relayplane budget [status|set|reset]');
1434
+ console.log(' set --daily <usd> --hourly <usd> --per-request <usd>');
1435
+ budget.close();
1436
+ }
1437
+ function handleCacheCommand(args) {
1438
+ const sub = args[0] ?? 'status';
1439
+ const cache = (0, response_cache_js_1.getResponseCache)();
1440
+ if (sub === 'status') {
1441
+ try {
1442
+ cache.init();
1443
+ }
1444
+ catch { /* ok */ }
1445
+ const status = cache.getStatus();
1446
+ console.log('');
1447
+ console.log('đŸ“Ļ Response Cache Status');
1448
+ console.log(` Enabled: ${status.enabled ? '✅' : '❌'}`);
1449
+ console.log(` Entries: ${status.entries}`);
1450
+ console.log(` Size: ${status.sizeMb} MB`);
1451
+ console.log(` Hit rate: ${status.hitRate}`);
1452
+ console.log(` Saved: $${status.savedCostUsd}`);
1453
+ console.log('');
1454
+ return;
1455
+ }
1456
+ if (sub === 'clear') {
1457
+ try {
1458
+ cache.init();
1459
+ }
1460
+ catch { /* ok */ }
1461
+ cache.clear();
1462
+ console.log('✅ Cache cleared');
1463
+ return;
1464
+ }
1465
+ if (sub === 'stats') {
1466
+ try {
1467
+ cache.init();
1468
+ }
1469
+ catch { /* ok */ }
1470
+ const stats = cache.getStats();
1471
+ console.log('');
1472
+ console.log('📊 Cache Statistics');
1473
+ console.log(` Total entries: ${stats.totalEntries}`);
1474
+ console.log(` Total size: ${(stats.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`);
1475
+ console.log(` Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
1476
+ console.log(` Hits: ${stats.hits} Misses: ${stats.misses} Bypasses: ${stats.bypasses}`);
1477
+ console.log(` Saved: $${stats.savedCostUsd.toFixed(4)} across ${stats.savedRequests} requests`);
1478
+ console.log('');
1479
+ const models = Object.entries(stats.byModel);
1480
+ if (models.length > 0) {
1481
+ console.log(' By Model:');
1482
+ for (const [model, m] of models) {
1483
+ console.log(` ${model}: ${m.entries} entries, ${m.hits} hits, $${m.savedCostUsd.toFixed(4)} saved`);
1484
+ }
1485
+ }
1486
+ const tasks = Object.entries(stats.byTaskType);
1487
+ if (tasks.length > 0) {
1488
+ console.log(' By Task Type:');
1489
+ for (const [task, t] of tasks) {
1490
+ console.log(` ${task}: ${t.entries} entries, ${t.hits} hits, $${t.savedCostUsd.toFixed(4)} saved`);
1491
+ }
1492
+ }
1493
+ console.log('');
1494
+ return;
1495
+ }
1496
+ if (sub === 'on') {
1497
+ cache.setEnabled(true);
1498
+ console.log('✅ Cache enabled');
1499
+ return;
1500
+ }
1501
+ if (sub === 'off') {
1502
+ cache.setEnabled(false);
1503
+ console.log('✅ Cache disabled');
1504
+ return;
1505
+ }
1506
+ console.log('Usage: relayplane cache [status|clear|stats|on|off]');
1507
+ }
1089
1508
  main();
1090
1509
  //# sourceMappingURL=cli.js.map