@phnx-labs/agents-cli 1.20.67 → 1.20.69

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 (83) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +14 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/check.js +96 -2
  5. package/dist/commands/computer.d.ts +26 -0
  6. package/dist/commands/computer.js +173 -149
  7. package/dist/commands/doctor.js +37 -9
  8. package/dist/commands/exec.d.ts +18 -0
  9. package/dist/commands/exec.js +126 -6
  10. package/dist/commands/fork.d.ts +9 -0
  11. package/dist/commands/fork.js +67 -0
  12. package/dist/commands/plugins.js +11 -2
  13. package/dist/commands/repo.js +47 -2
  14. package/dist/commands/run-account-picker.d.ts +14 -0
  15. package/dist/commands/run-account-picker.js +131 -0
  16. package/dist/commands/setup-browser.d.ts +18 -0
  17. package/dist/commands/setup-browser.js +142 -0
  18. package/dist/commands/setup-computer.d.ts +19 -0
  19. package/dist/commands/setup-computer.js +133 -0
  20. package/dist/commands/setup-share.d.ts +17 -0
  21. package/dist/commands/setup-share.js +85 -0
  22. package/dist/commands/setup.d.ts +1 -1
  23. package/dist/commands/setup.js +58 -1
  24. package/dist/commands/share.d.ts +15 -0
  25. package/dist/commands/share.js +10 -4
  26. package/dist/commands/ssh.js +267 -2
  27. package/dist/commands/teams.d.ts +11 -0
  28. package/dist/commands/teams.js +39 -1
  29. package/dist/commands/view.d.ts +4 -14
  30. package/dist/commands/view.js +39 -36
  31. package/dist/index.js +2 -1
  32. package/dist/lib/agents.d.ts +21 -5
  33. package/dist/lib/agents.js +59 -24
  34. package/dist/lib/auth-health.d.ts +103 -0
  35. package/dist/lib/auth-health.js +232 -0
  36. package/dist/lib/browser/chrome.d.ts +9 -0
  37. package/dist/lib/browser/chrome.js +22 -0
  38. package/dist/lib/browser/profiles.js +4 -25
  39. package/dist/lib/cli-entry.d.ts +23 -0
  40. package/dist/lib/cli-entry.js +116 -0
  41. package/dist/lib/computer/download.d.ts +51 -0
  42. package/dist/lib/computer/download.js +145 -0
  43. package/dist/lib/computer-rpc.js +9 -3
  44. package/dist/lib/daemon.d.ts +2 -2
  45. package/dist/lib/daemon.js +8 -89
  46. package/dist/lib/devices/connect.d.ts +15 -0
  47. package/dist/lib/devices/connect.js +17 -5
  48. package/dist/lib/devices/fleet.d.ts +23 -0
  49. package/dist/lib/devices/fleet.js +38 -0
  50. package/dist/lib/devices/health-report.d.ts +46 -0
  51. package/dist/lib/devices/health-report.js +159 -0
  52. package/dist/lib/devices/health.d.ts +14 -4
  53. package/dist/lib/devices/health.js +49 -8
  54. package/dist/lib/devices/terminfo.d.ts +44 -0
  55. package/dist/lib/devices/terminfo.js +167 -0
  56. package/dist/lib/git.d.ts +10 -0
  57. package/dist/lib/git.js +23 -0
  58. package/dist/lib/hosts/option.js +1 -1
  59. package/dist/lib/hosts/ready.js +5 -3
  60. package/dist/lib/hosts/remote-cmd.d.ts +12 -0
  61. package/dist/lib/hosts/remote-cmd.js +17 -1
  62. package/dist/lib/mcp.d.ts +21 -0
  63. package/dist/lib/mcp.js +278 -13
  64. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  65. package/dist/lib/resources/mcp.js +20 -342
  66. package/dist/lib/rotate.d.ts +12 -8
  67. package/dist/lib/rotate.js +22 -23
  68. package/dist/lib/routines.d.ts +13 -0
  69. package/dist/lib/routines.js +21 -0
  70. package/dist/lib/runner.js +18 -40
  71. package/dist/lib/secrets/agent.js +11 -15
  72. package/dist/lib/session/fork.d.ts +32 -0
  73. package/dist/lib/session/fork.js +101 -0
  74. package/dist/lib/share/capture.js +21 -3
  75. package/dist/lib/signin-badge.d.ts +41 -0
  76. package/dist/lib/signin-badge.js +64 -0
  77. package/dist/lib/ssh-exec.d.ts +5 -0
  78. package/dist/lib/ssh-exec.js +55 -1
  79. package/dist/lib/startup/command-registry.d.ts +1 -0
  80. package/dist/lib/startup/command-registry.js +2 -0
  81. package/dist/lib/usage.d.ts +40 -3
  82. package/dist/lib/usage.js +147 -16
  83. package/package.json +1 -1
@@ -117,6 +117,18 @@ export interface WindowsAgentsCommand {
117
117
  * `& agents …` invokes the CLI from the machine PATH (Windows has no login
118
118
  * shell, so there is no `bash -lc` equivalent — the shim is simply on PATH).
119
119
  */
120
+ /**
121
+ * PowerShell prelude that silences the progress stream. PowerShell 5.1 serializes
122
+ * progress records ("Preparing modules for first use.") to CLIXML when stderr is a
123
+ * redirected pipe (an ssh capture, not a console), so a remote `agents …` result
124
+ * otherwise comes back wrapped in a raw `#< CLIXML <Objs …>` blob — unreadable to
125
+ * humans and to the JSON parsers that consume doctor / fleet-status output. Prepend
126
+ * this to EVERY Windows script that invokes `agents` (there is more than one builder
127
+ * in this file). Verified against a live win-mini (PowerShell 5.1): this clears it.
128
+ * (`-OutputFormat Text` does NOT — it governs success-stream object serialization,
129
+ * not the progress stream.)
130
+ */
131
+ export declare const POWERSHELL_PROGRESS_SILENCE = "$ProgressPreference = 'SilentlyContinue'";
120
132
  export declare function windowsAgentsScript(cmd: WindowsAgentsCommand): string;
121
133
  /**
122
134
  * Build the `ssh <target> <cmd>` string for one `agents …` invocation on a
@@ -110,6 +110,7 @@ export const RUN_OPTION_FORWARDING = {
110
110
  lease: 'local-only',
111
111
  keepBox: 'local-only',
112
112
  copyCreds: 'local-only', // copies creds TO the host before dispatch — local concern only
113
+ authCheck: 'local-only', // --no-auth-check gates the local interactive login preflight; --host runs skip that preflight entirely
113
114
  };
114
115
  /** Actionable messages for value-aware rejections, keyed by attribute name. */
115
116
  export const RUN_OPTION_REJECT_MESSAGES = {
@@ -190,9 +191,21 @@ export function decodePowershell(encoded) {
190
191
  * `& agents …` invokes the CLI from the machine PATH (Windows has no login
191
192
  * shell, so there is no `bash -lc` equivalent — the shim is simply on PATH).
192
193
  */
194
+ /**
195
+ * PowerShell prelude that silences the progress stream. PowerShell 5.1 serializes
196
+ * progress records ("Preparing modules for first use.") to CLIXML when stderr is a
197
+ * redirected pipe (an ssh capture, not a console), so a remote `agents …` result
198
+ * otherwise comes back wrapped in a raw `#< CLIXML <Objs …>` blob — unreadable to
199
+ * humans and to the JSON parsers that consume doctor / fleet-status output. Prepend
200
+ * this to EVERY Windows script that invokes `agents` (there is more than one builder
201
+ * in this file). Verified against a live win-mini (PowerShell 5.1): this clears it.
202
+ * (`-OutputFormat Text` does NOT — it governs success-stream object serialization,
203
+ * not the progress stream.)
204
+ */
205
+ export const POWERSHELL_PROGRESS_SILENCE = "$ProgressPreference = 'SilentlyContinue'";
193
206
  export function windowsAgentsScript(cmd) {
194
207
  const { args, env, cwd, propagateExit = true } = cmd;
195
- const parts = [];
208
+ const parts = [POWERSHELL_PROGRESS_SILENCE];
196
209
  if (env)
197
210
  for (const [k, v] of Object.entries(env))
198
211
  parts.push(`$env:${k} = ${powershellQuote(v)}`);
@@ -232,6 +245,9 @@ export function buildWindowsStdinImportCommand(bundle, opts = {}) {
232
245
  // the secret-bearing temp file would otherwise be left behind (RUSH-1764). $tmp
233
246
  // starts null so a GetTempFileName that itself throws leaves nothing to remove.
234
247
  const script = [
248
+ // Same CLIXML guard as windowsAgentsScript — this builder also runs `& agents …`
249
+ // and its raw stderr is printed to the user on failure (secrets export --host <win>).
250
+ POWERSHELL_PROGRESS_SILENCE,
235
251
  '$in = [Console]::In.ReadToEnd()',
236
252
  '$tmp = $null',
237
253
  `try { $tmp = [System.IO.Path]::GetTempFileName(); [System.IO.File]::WriteAllText($tmp, $in); ` +
package/dist/lib/mcp.d.ts CHANGED
@@ -108,6 +108,27 @@ export declare function registerMcpCommandToTargets(targets: {
108
108
  directAgents: AgentId[];
109
109
  versionSelections: Map<AgentId, string[]>;
110
110
  }, name: string, commandSpec: McpCommandSpec, scope?: 'user' | 'project', transport?: string): Promise<McpTargetOperationResult[]>;
111
+ /**
112
+ * MCP server shaped for direct config-file serialization.
113
+ */
114
+ export interface WritableMcpServer {
115
+ name: string;
116
+ transport: 'stdio' | 'http' | 'sse';
117
+ command?: string;
118
+ args?: string[];
119
+ env?: Record<string, string>;
120
+ url?: string;
121
+ headers?: Record<string, string>;
122
+ }
123
+ /**
124
+ * Serialize MCP servers into an agent-specific config file.
125
+ *
126
+ * `mode: 'overwrite'` replaces the whole MCP section (used for tool-managed
127
+ * version-home configs). `mode: 'merge'` updates/adds the provided server
128
+ * entries while preserving existing entries (used for project-level configs
129
+ * that users may hand-edit or populate via agent CLI commands).
130
+ */
131
+ export declare function writeMcpConfig(agentId: AgentId, configPath: string, servers: WritableMcpServer[], mode?: 'overwrite' | 'merge'): void;
111
132
  /**
112
133
  * Install MCP servers to an agent.
113
134
  * For Claude/Codex: uses CLI commands (claude mcp add, codex mcp add)
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
  }