@phnx-labs/agents-cli 1.20.67 → 1.20.68

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 (47) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +3 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/check.js +96 -2
  5. package/dist/commands/doctor.js +37 -9
  6. package/dist/commands/exec.js +39 -1
  7. package/dist/commands/plugins.js +11 -2
  8. package/dist/commands/repo.js +47 -2
  9. package/dist/commands/ssh.js +103 -2
  10. package/dist/commands/teams.d.ts +11 -0
  11. package/dist/commands/teams.js +39 -1
  12. package/dist/commands/view.d.ts +1 -0
  13. package/dist/commands/view.js +12 -8
  14. package/dist/lib/agents.d.ts +11 -5
  15. package/dist/lib/agents.js +27 -24
  16. package/dist/lib/browser/profiles.js +4 -25
  17. package/dist/lib/cli-entry.d.ts +23 -0
  18. package/dist/lib/cli-entry.js +116 -0
  19. package/dist/lib/daemon.d.ts +2 -2
  20. package/dist/lib/daemon.js +8 -89
  21. package/dist/lib/devices/fleet.d.ts +23 -0
  22. package/dist/lib/devices/fleet.js +38 -0
  23. package/dist/lib/devices/health-report.d.ts +46 -0
  24. package/dist/lib/devices/health-report.js +159 -0
  25. package/dist/lib/devices/health.d.ts +14 -4
  26. package/dist/lib/devices/health.js +49 -8
  27. package/dist/lib/git.d.ts +10 -0
  28. package/dist/lib/git.js +23 -0
  29. package/dist/lib/hosts/option.js +1 -1
  30. package/dist/lib/hosts/ready.js +5 -3
  31. package/dist/lib/hosts/remote-cmd.d.ts +12 -0
  32. package/dist/lib/hosts/remote-cmd.js +17 -1
  33. package/dist/lib/mcp.d.ts +21 -0
  34. package/dist/lib/mcp.js +278 -13
  35. package/dist/lib/resources/mcp.js +20 -342
  36. package/dist/lib/routines.d.ts +13 -0
  37. package/dist/lib/routines.js +21 -0
  38. package/dist/lib/runner.js +18 -40
  39. package/dist/lib/secrets/agent.js +11 -15
  40. package/dist/lib/share/capture.js +21 -3
  41. package/dist/lib/signin-badge.d.ts +41 -0
  42. package/dist/lib/signin-badge.js +64 -0
  43. package/dist/lib/ssh-exec.d.ts +5 -0
  44. package/dist/lib/ssh-exec.js +55 -1
  45. package/dist/lib/usage.d.ts +17 -3
  46. package/dist/lib/usage.js +63 -16
  47. package/package.json +1 -1
package/dist/lib/mcp.js CHANGED
@@ -10,12 +10,13 @@
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import * as yaml from 'yaml';
13
+ import * as TOML from 'smol-toml';
13
14
  import { execFileSync } from 'child_process';
14
15
  import * as os from 'os';
15
16
  import { getMcpDir, getUserMcpDir, getProjectAgentsDir, getVersionsDir, getUserAgentsDir } from './state.js';
16
17
  import { getBinaryPath, getVersionHomePath } from './versions.js';
17
18
  import { IS_WINDOWS, execFileShellSpec } from './platform/index.js';
18
- import { AGENTS } from './agents.js';
19
+ import { AGENTS, getMcpConfigPathForHome, getProjectMcpConfigPath } from './agents.js';
19
20
  import { isCapable } from './capabilities.js';
20
21
  import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from './gemini-settings.js';
21
22
  /**
@@ -634,6 +635,225 @@ function installMcpToOpenCodeConfig(server, versionHome) {
634
635
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
635
636
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
636
637
  }
638
+ /**
639
+ * Agents whose config file format is implemented by `writeMcpConfig`.
640
+ * Others are intentionally skipped until their schema is added.
641
+ */
642
+ function writeMcpConfigSupportsAgent(agentId) {
643
+ switch (agentId) {
644
+ case 'claude':
645
+ case 'cursor':
646
+ case 'gemini':
647
+ case 'kimi':
648
+ case 'droid':
649
+ case 'forge':
650
+ case 'openclaw':
651
+ case 'codex':
652
+ case 'grok':
653
+ case 'opencode':
654
+ case 'hermes':
655
+ return true;
656
+ default:
657
+ return false;
658
+ }
659
+ }
660
+ /**
661
+ * Serialize MCP servers into an agent-specific config file.
662
+ *
663
+ * `mode: 'overwrite'` replaces the whole MCP section (used for tool-managed
664
+ * version-home configs). `mode: 'merge'` updates/adds the provided server
665
+ * entries while preserving existing entries (used for project-level configs
666
+ * that users may hand-edit or populate via agent CLI commands).
667
+ */
668
+ export function writeMcpConfig(agentId, configPath, servers, mode = 'overwrite') {
669
+ if (servers.length === 0) {
670
+ return;
671
+ }
672
+ switch (agentId) {
673
+ case 'claude':
674
+ case 'cursor':
675
+ case 'gemini':
676
+ case 'kimi':
677
+ case 'droid':
678
+ case 'forge': {
679
+ let config = {};
680
+ if (fs.existsSync(configPath)) {
681
+ try {
682
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
683
+ }
684
+ catch {
685
+ config = {};
686
+ }
687
+ }
688
+ const mcpServers = mode === 'merge' && config.mcpServers && typeof config.mcpServers === 'object'
689
+ ? { ...config.mcpServers }
690
+ : {};
691
+ for (const server of servers) {
692
+ if (server.transport === 'stdio') {
693
+ mcpServers[server.name] = {
694
+ command: server.command,
695
+ args: server.args || [],
696
+ env: server.env || {},
697
+ };
698
+ }
699
+ else {
700
+ mcpServers[server.name] = {
701
+ url: server.url,
702
+ ...(server.headers && { headers: server.headers }),
703
+ };
704
+ }
705
+ }
706
+ config.mcpServers = mcpServers;
707
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
708
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
709
+ break;
710
+ }
711
+ case 'openclaw': {
712
+ let config = {};
713
+ if (fs.existsSync(configPath)) {
714
+ try {
715
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
716
+ }
717
+ catch {
718
+ config = {};
719
+ }
720
+ }
721
+ if (!config.mcp || typeof config.mcp !== 'object') {
722
+ config.mcp = {};
723
+ }
724
+ const mcp = config.mcp;
725
+ const mcpServers = mode === 'merge' && mcp.servers && typeof mcp.servers === 'object'
726
+ ? { ...mcp.servers }
727
+ : {};
728
+ for (const server of servers) {
729
+ if (server.transport === 'stdio') {
730
+ mcpServers[server.name] = {
731
+ command: server.command,
732
+ args: server.args || [],
733
+ env: server.env || {},
734
+ };
735
+ }
736
+ else {
737
+ mcpServers[server.name] = {
738
+ url: server.url,
739
+ transport: server.transport,
740
+ ...(server.headers && { headers: server.headers }),
741
+ };
742
+ }
743
+ }
744
+ mcp.servers = mcpServers;
745
+ config.mcp = mcp;
746
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
747
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
748
+ break;
749
+ }
750
+ case 'codex':
751
+ case 'grok': {
752
+ let config = {};
753
+ if (fs.existsSync(configPath)) {
754
+ try {
755
+ config = TOML.parse(fs.readFileSync(configPath, 'utf-8'));
756
+ }
757
+ catch {
758
+ config = {};
759
+ }
760
+ }
761
+ const mcpServers = mode === 'merge' && config.mcp_servers && typeof config.mcp_servers === 'object'
762
+ ? { ...config.mcp_servers }
763
+ : {};
764
+ for (const server of servers) {
765
+ if (server.transport === 'stdio') {
766
+ mcpServers[server.name] = {
767
+ command: server.command,
768
+ args: server.args || [],
769
+ ...(server.env && { env: server.env }),
770
+ };
771
+ }
772
+ else {
773
+ mcpServers[server.name] = {
774
+ url: server.url,
775
+ ...(server.headers && { headers: server.headers }),
776
+ };
777
+ }
778
+ }
779
+ config.mcp_servers = mcpServers;
780
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
781
+ fs.writeFileSync(configPath, TOML.stringify(config), 'utf-8');
782
+ break;
783
+ }
784
+ case 'opencode': {
785
+ let config = {};
786
+ if (fs.existsSync(configPath)) {
787
+ try {
788
+ const content = fs.readFileSync(configPath, 'utf-8');
789
+ const jsonContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
790
+ config = JSON.parse(jsonContent);
791
+ }
792
+ catch {
793
+ config = {};
794
+ }
795
+ }
796
+ const mcp = mode === 'merge' && config.mcp && typeof config.mcp === 'object'
797
+ ? { ...config.mcp }
798
+ : {};
799
+ for (const server of servers) {
800
+ if (server.transport === 'stdio') {
801
+ const commandArray = [server.command, ...(server.args || [])];
802
+ mcp[server.name] = {
803
+ type: 'local',
804
+ command: commandArray,
805
+ ...(server.env && { env: server.env }),
806
+ };
807
+ }
808
+ else {
809
+ mcp[server.name] = {
810
+ type: 'remote',
811
+ url: server.url,
812
+ };
813
+ }
814
+ }
815
+ config.mcp = mcp;
816
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
817
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
818
+ break;
819
+ }
820
+ case 'hermes': {
821
+ let config = {};
822
+ if (fs.existsSync(configPath)) {
823
+ try {
824
+ const parsed = yaml.parse(fs.readFileSync(configPath, 'utf-8'));
825
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
826
+ config = parsed;
827
+ }
828
+ }
829
+ catch {
830
+ config = {};
831
+ }
832
+ }
833
+ const mcpServers = mode === 'merge' && config.mcp_servers && typeof config.mcp_servers === 'object' && !Array.isArray(config.mcp_servers)
834
+ ? { ...config.mcp_servers }
835
+ : {};
836
+ for (const server of servers) {
837
+ if (server.transport === 'stdio') {
838
+ mcpServers[server.name] = {
839
+ command: server.command,
840
+ args: server.args || [],
841
+ ...(server.env && { env: server.env }),
842
+ };
843
+ }
844
+ else {
845
+ mcpServers[server.name] = {
846
+ url: server.url,
847
+ };
848
+ }
849
+ }
850
+ config.mcp_servers = mcpServers;
851
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
852
+ fs.writeFileSync(configPath, yaml.stringify(config), 'utf-8');
853
+ break;
854
+ }
855
+ }
856
+ }
637
857
  /**
638
858
  * Install MCP servers to an agent.
639
859
  * For Claude/Codex: uses CLI commands (claude mcp add, codex mcp add)
@@ -658,47 +878,92 @@ export function installMcpServers(agentId, version, versionHome, mcpNames, optio
658
878
  binaryPath += '.cmd';
659
879
  }
660
880
  for (const server of servers) {
881
+ let handled = false;
661
882
  try {
662
883
  if (agentId === 'claude') {
663
884
  installMcpViaClaude(binaryPath, server, versionHome);
664
- applied.push(server.name);
885
+ handled = true;
665
886
  }
666
887
  else if (agentId === 'codex') {
667
888
  installMcpViaCodex(binaryPath, server, versionHome);
668
- applied.push(server.name);
889
+ handled = true;
669
890
  }
670
891
  else if (agentId === 'gemini') {
671
892
  installMcpToGeminiConfig(server, versionHome);
672
- applied.push(server.name);
893
+ handled = true;
673
894
  }
674
895
  else if (agentId === 'cursor') {
675
896
  installMcpToCursorConfig(server, versionHome);
676
- applied.push(server.name);
897
+ handled = true;
677
898
  }
678
899
  else if (agentId === 'opencode') {
679
900
  installMcpToOpenCodeConfig(server, versionHome);
680
- applied.push(server.name);
901
+ handled = true;
902
+ }
903
+ else if (agentId === 'openclaw') {
904
+ // OpenClaw has no install CLI; write the JSON config directly.
905
+ // Use merge because this loop runs once per server.
906
+ const userConfigPath = getMcpConfigPathForHome(agentId, versionHome);
907
+ const writableServer = {
908
+ name: server.config.name,
909
+ transport: server.config.transport,
910
+ command: server.config.command,
911
+ args: server.config.args,
912
+ env: server.config.env,
913
+ url: server.config.url,
914
+ };
915
+ writeMcpConfig(agentId, userConfigPath, [writableServer], 'merge');
916
+ handled = true;
681
917
  }
682
918
  else if (agentId === 'grok') {
683
- // Grok primarily uses [mcp_servers] in ~/.grok/config.toml (or project .grok/config.toml).
684
- // We have the path helper; full writer can be added (reuse codex toml pattern).
685
- // For now the general sync + toml editing via agents mcp works via the path helpers.
686
- applied.push(server.name);
919
+ // Grok has no working `grok mcp add` CLI, so write the TOML config
920
+ // directly into the version-home directory for all scopes.
921
+ // Use merge because this loop runs once per server.
922
+ const userConfigPath = getMcpConfigPathForHome(agentId, versionHome);
923
+ const writableServer = {
924
+ name: server.config.name,
925
+ transport: server.config.transport,
926
+ command: server.config.command,
927
+ args: server.config.args,
928
+ env: server.config.env,
929
+ url: server.config.url,
930
+ };
931
+ writeMcpConfig(agentId, userConfigPath, [writableServer], 'merge');
932
+ handled = true;
687
933
  }
688
934
  else if (agentId === 'kimi') {
689
935
  installMcpToKimiConfig(server, versionHome);
690
- applied.push(server.name);
936
+ handled = true;
691
937
  }
692
938
  else if (agentId === 'droid') {
693
939
  installMcpToFactoryConfig(server, versionHome);
694
- applied.push(server.name);
940
+ handled = true;
695
941
  }
696
942
  else if (agentId === 'hermes') {
697
943
  installMcpToHermesConfig(server, versionHome);
698
- applied.push(server.name);
944
+ handled = true;
699
945
  }
700
946
  else if (agentId === 'forge') {
701
947
  installMcpToForgeConfig(server, versionHome);
948
+ handled = true;
949
+ }
950
+ // Project-layer servers also get merged into the agent's project-level
951
+ // config (e.g., .mcp.json, .codex/config.toml) so the CLI discovers them
952
+ // when run inside the repo.
953
+ if (server.scope === 'project' && options.cwd && writeMcpConfigSupportsAgent(agentId)) {
954
+ const projectConfigPath = getProjectMcpConfigPath(agentId, options.cwd);
955
+ const writableServer = {
956
+ name: server.config.name,
957
+ transport: server.config.transport,
958
+ command: server.config.command,
959
+ args: server.config.args,
960
+ env: server.config.env,
961
+ url: server.config.url,
962
+ };
963
+ writeMcpConfig(agentId, projectConfigPath, [writableServer], 'merge');
964
+ handled = true;
965
+ }
966
+ if (handled) {
702
967
  applied.push(server.name);
703
968
  }
704
969
  }