@phnx-labs/agents-cli 1.22.114 → 1.22.115

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 (176) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +22 -102
  3. package/dist/browser.js +0 -0
  4. package/dist/cli/command-registry.js +0 -7
  5. package/dist/commands/accounts.d.ts +3 -77
  6. package/dist/commands/accounts.js +13 -366
  7. package/dist/commands/audit.js +1 -1
  8. package/dist/commands/auth.js +0 -2
  9. package/dist/commands/browser.d.ts +53 -0
  10. package/dist/commands/browser.js +199 -9
  11. package/dist/commands/doctor.d.ts +2 -3
  12. package/dist/commands/doctor.js +0 -11
  13. package/dist/commands/events.d.ts +1 -3
  14. package/dist/commands/events.js +4 -7
  15. package/dist/commands/exec.d.ts +0 -1
  16. package/dist/commands/exec.js +2 -7
  17. package/dist/commands/feed-watch.js +52 -8
  18. package/dist/commands/focus.js +1 -1
  19. package/dist/commands/go.d.ts +0 -17
  20. package/dist/commands/go.js +2 -19
  21. package/dist/commands/logs.js +1 -1
  22. package/dist/commands/mcp.js +8 -83
  23. package/dist/commands/memory.js +4 -47
  24. package/dist/commands/message.js +4 -4
  25. package/dist/commands/plugins.js +1 -93
  26. package/dist/commands/repo.js +0 -44
  27. package/dist/commands/resume.js +4 -1
  28. package/dist/commands/secrets-passthrough.js +2 -2
  29. package/dist/commands/send.d.ts +4 -4
  30. package/dist/commands/send.js +6 -51
  31. package/dist/commands/sessions-backup-setup.js +1 -1
  32. package/dist/commands/sessions-resume.js +0 -1
  33. package/dist/commands/sessions-share.d.ts +5 -7
  34. package/dist/commands/sessions-share.js +98 -49
  35. package/dist/commands/sessions.js +1 -8
  36. package/dist/commands/setup-browser.js +18 -2
  37. package/dist/commands/setup-computer.js +20 -6
  38. package/dist/commands/setup-secrets.js +24 -4
  39. package/dist/commands/setup-terminal.d.ts +3 -0
  40. package/dist/commands/setup-terminal.js +22 -0
  41. package/dist/commands/setup.d.ts +1 -1
  42. package/dist/commands/setup.js +20 -10
  43. package/dist/commands/skills.js +0 -8
  44. package/dist/commands/ssh.d.ts +6 -0
  45. package/dist/commands/ssh.js +92 -233
  46. package/dist/commands/sync.js +14 -5
  47. package/dist/commands/teams.js +1 -1
  48. package/dist/commands/traces.js +1 -1
  49. package/dist/lib/accounts/add.d.ts +0 -5
  50. package/dist/lib/accounts/add.js +1 -7
  51. package/dist/lib/artifacts-client.d.ts +20 -0
  52. package/dist/lib/artifacts-client.js +46 -0
  53. package/dist/lib/auth-mint.js +2 -2
  54. package/dist/lib/browser/runtime-state.d.ts +55 -0
  55. package/dist/lib/browser/runtime-state.js +99 -18
  56. package/dist/lib/browser/service.js +21 -1
  57. package/dist/lib/cli-resources.js +3 -1
  58. package/dist/lib/cloud/dispatch.js +1 -1
  59. package/dist/lib/cloudflare/creds.d.ts +10 -0
  60. package/dist/lib/cloudflare/creds.js +46 -0
  61. package/dist/lib/cloudflare/provision.d.ts +35 -0
  62. package/dist/lib/cloudflare/provision.js +144 -0
  63. package/dist/lib/computer/sessions-list.d.ts +55 -0
  64. package/dist/lib/computer/sessions-list.js +168 -1
  65. package/dist/lib/daemon/daemon.js +8 -1
  66. package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
  67. package/dist/lib/daemon/feed-stream-service.js +40 -0
  68. package/dist/lib/daemon-services.d.ts +1 -1
  69. package/dist/lib/daemon-services.js +5 -0
  70. package/dist/lib/devices/connect.d.ts +49 -5
  71. package/dist/lib/devices/connect.js +169 -21
  72. package/dist/lib/feed/envelope.d.ts +79 -0
  73. package/dist/lib/feed/envelope.js +23 -0
  74. package/dist/lib/feed/events.d.ts +6 -0
  75. package/dist/lib/feed/events.js +8 -0
  76. package/dist/lib/feed/hub-server.d.ts +86 -0
  77. package/dist/lib/feed/hub-server.js +334 -0
  78. package/dist/lib/feed/hub.d.ts +95 -0
  79. package/dist/lib/feed/hub.js +255 -0
  80. package/dist/lib/feed/tool-activity.d.ts +108 -0
  81. package/dist/lib/feed/tool-activity.js +313 -0
  82. package/dist/lib/feed/tools.d.ts +198 -0
  83. package/dist/lib/feed/tools.js +265 -0
  84. package/dist/lib/feed/watch.d.ts +50 -50
  85. package/dist/lib/feed/watch.js +147 -16
  86. package/dist/lib/format.d.ts +1 -1
  87. package/dist/lib/format.js +1 -1
  88. package/dist/lib/git.d.ts +0 -16
  89. package/dist/lib/git.js +0 -58
  90. package/dist/lib/helper-versions.js +1 -1
  91. package/dist/lib/hosts/remote-cmd.d.ts +51 -1
  92. package/dist/lib/hosts/remote-cmd.js +125 -8
  93. package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
  94. package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
  95. package/dist/lib/mcp.js +17 -11
  96. package/dist/lib/probe.d.ts +4 -1
  97. package/dist/lib/probe.js +5 -2
  98. package/dist/lib/pwsh.d.ts +33 -0
  99. package/dist/lib/pwsh.js +56 -0
  100. package/dist/lib/redact.d.ts +8 -0
  101. package/dist/lib/redact.js +11 -0
  102. package/dist/lib/refresh.d.ts +6 -2
  103. package/dist/lib/refresh.js +92 -72
  104. package/dist/lib/secrets-cli.d.ts +11 -0
  105. package/dist/lib/secrets-cli.js +30 -0
  106. package/dist/lib/secrets-client.js +3 -2
  107. package/dist/lib/session/detached.d.ts +7 -0
  108. package/dist/lib/session/detached.js +29 -0
  109. package/dist/lib/session/remote/peer-stream.d.ts +24 -2
  110. package/dist/lib/session/remote/peer-stream.js +33 -6
  111. package/dist/lib/session/sync/backend.d.ts +3 -3
  112. package/dist/lib/session/sync/backend.js +3 -3
  113. package/dist/lib/session/sync/provision.d.ts +1 -1
  114. package/dist/lib/session/sync/provision.js +2 -2
  115. package/dist/lib/sessions-client.js +0 -3
  116. package/dist/lib/setup-tool-install.d.ts +3 -0
  117. package/dist/lib/setup-tool-install.js +26 -0
  118. package/dist/lib/setup-tool-status.d.ts +22 -0
  119. package/dist/lib/setup-tool-status.js +215 -0
  120. package/dist/lib/share-runtime.d.ts +11 -0
  121. package/dist/lib/share-runtime.js +63 -0
  122. package/dist/lib/smart-launch.d.ts +1 -5
  123. package/dist/lib/smart-launch.js +3 -11
  124. package/dist/lib/ssh-exec.d.ts +44 -0
  125. package/dist/lib/ssh-exec.js +119 -0
  126. package/dist/lib/startup/command-registry.d.ts +6 -4
  127. package/dist/lib/startup/command-registry.js +10 -7
  128. package/dist/lib/state.js +2 -2
  129. package/dist/lib/storage/selection.d.ts +2 -2
  130. package/dist/lib/storage/selection.js +2 -2
  131. package/dist/lib/sync-umbrella.d.ts +5 -0
  132. package/dist/lib/sync-umbrella.js +18 -10
  133. package/dist/lib/traces/backend.d.ts +1 -2
  134. package/dist/lib/traces/backend.js +1 -2
  135. package/dist/lib/traces/provision.d.ts +1 -1
  136. package/dist/lib/traces/provision.js +2 -2
  137. package/dist/lib/types.d.ts +8 -6
  138. package/package.json +2 -3
  139. package/dist/commands/artifacts-setup.d.ts +0 -53
  140. package/dist/commands/artifacts-setup.js +0 -161
  141. package/dist/commands/artifacts.d.ts +0 -18
  142. package/dist/commands/artifacts.js +0 -58
  143. package/dist/commands/attach.d.ts +0 -12
  144. package/dist/commands/attach.js +0 -86
  145. package/dist/commands/auth-mint.d.ts +0 -12
  146. package/dist/commands/auth-mint.js +0 -108
  147. package/dist/commands/reconnect.d.ts +0 -46
  148. package/dist/commands/reconnect.js +0 -115
  149. package/dist/commands/share.d.ts +0 -293
  150. package/dist/commands/share.js +0 -1424
  151. package/dist/lib/share/analytics.d.ts +0 -13
  152. package/dist/lib/share/analytics.js +0 -45
  153. package/dist/lib/share/backend.d.ts +0 -120
  154. package/dist/lib/share/backend.js +0 -176
  155. package/dist/lib/share/capture.d.ts +0 -31
  156. package/dist/lib/share/capture.js +0 -174
  157. package/dist/lib/share/config.d.ts +0 -72
  158. package/dist/lib/share/config.js +0 -211
  159. package/dist/lib/share/delete.d.ts +0 -123
  160. package/dist/lib/share/delete.js +0 -173
  161. package/dist/lib/share/html.d.ts +0 -20
  162. package/dist/lib/share/html.js +0 -88
  163. package/dist/lib/share/http-error.d.ts +0 -53
  164. package/dist/lib/share/http-error.js +0 -65
  165. package/dist/lib/share/og.d.ts +0 -26
  166. package/dist/lib/share/og.js +0 -84
  167. package/dist/lib/share/provision.d.ts +0 -127
  168. package/dist/lib/share/provision.js +0 -285
  169. package/dist/lib/share/publish.d.ts +0 -379
  170. package/dist/lib/share/publish.js +0 -818
  171. package/dist/lib/share/worker-template.d.ts +0 -27
  172. package/dist/lib/share/worker-template.js +0 -2424
  173. package/dist/lib/storage/index.d.ts +0 -14
  174. package/dist/lib/storage/index.js +0 -14
  175. package/dist/lib/storage/visibility.d.ts +0 -82
  176. package/dist/lib/storage/visibility.js +0 -99
@@ -1122,7 +1122,8 @@ function registerDevicesCommands(program) {
1122
1122
  agents devices config mac-mini notes "runs the releases"
1123
1123
  agents devices config win-mini ssh.auth password
1124
1124
  agents devices config worker ssh.identity-file ~/.ssh/worker_ed25519
1125
- agents devices config mac-mini auto-launch.enabled off
1125
+ agents devices disable mac-mini # leave the auto-placement pool (auto-launch.enabled off)
1126
+ agents devices enable mac-mini # rejoin it
1126
1127
  agents devices describe mark-1 "gpu box — cuda 12.4" # one-line purpose, shown in the list
1127
1128
  agents devices config mac-mini interactive.host zion # where agents show YOU artifacts
1128
1129
  agents devices render --write # write ~/.ssh/config.d/agents include
@@ -1286,11 +1287,9 @@ function registerDevicesCommands(program) {
1286
1287
  // ─── devices config (unified settings surface) ────────────────────────────
1287
1288
  //
1288
1289
  // ONE command for every per-device setting: `agents devices config <name>
1289
- // [key] [value] [--unset] [--json]`. The retired subcommands (configure,
1290
- // note, set-interactive, set, enable/disable/prefer/unprefer) are hidden
1291
- // tombstones below — each prints a deprecation notice on STDERR (so a --json
1292
- // consumer's stdout stays parseable) and delegates to this same engine,
1293
- // preserving its old output shape and exit codes.
1290
+ // [key] [value] [--unset] [--json]`. configure, note, set-interactive, set,
1291
+ // prefer, and unprefer are deleted (PHNX-4051) — use this command directly.
1292
+ // enable/disable stay as first-class sugar over auto-launch.enabled.
1294
1293
  /** Parse a raw CLI string into a config key's typed value (bool/int pass validation, strings verbatim). */
1295
1294
  const parseConfigValueInput = (spec, raw) => {
1296
1295
  switch (spec.type) {
@@ -1685,8 +1684,10 @@ function registerDevicesCommands(program) {
1685
1684
  the device itself; --fleet still writes a fleet-wide default those boxes
1686
1685
  inherit until they override.
1687
1686
 
1688
- The retired subcommands still work and forward here: configure, note,
1689
- set, set-interactive, enable, disable, prefer, unprefer.
1687
+ configure, note, set, set-interactive, prefer, and unprefer are deleted;
1688
+ use this command directly (auto-launch.preferred, interactive.host, or
1689
+ any other key). enable/disable stay as first-class sugar over
1690
+ auto-launch.enabled.
1690
1691
  `,
1691
1692
  });
1692
1693
  const roleCmd = devicesCmd
@@ -1765,182 +1766,6 @@ function registerDevicesCommands(program) {
1765
1766
  long-form scratch use 'agents devices config <name> notes "…"'.
1766
1767
  `,
1767
1768
  });
1768
- /** Deprecation notice for a retired subcommand — STDERR only, so a --json consumer's stdout stays parseable. */
1769
- const configTombstoneNotice = (retired, replacement) => {
1770
- console.error(chalk.yellow(`Deprecated: "agents devices ${retired}" is now "agents devices ${replacement}". Running that for you.\n`));
1771
- };
1772
- devicesCmd
1773
- .command('enable <name>', { hidden: true })
1774
- .action(async (name) => {
1775
- try {
1776
- configTombstoneNotice('enable <name>', 'config <name> auto-launch.enabled on');
1777
- await mustGetDevice(name);
1778
- // Back to the default (enabled) = remove the key.
1779
- await runDevicesConfig(name, 'auto-launch.enabled', [], { unset: true, quiet: true });
1780
- console.log(chalk.green(`Enabled '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
1781
- }
1782
- catch (err) {
1783
- console.error(chalk.red(err.message));
1784
- process.exit(1);
1785
- }
1786
- });
1787
- devicesCmd
1788
- .command('disable <name>', { hidden: true })
1789
- .action(async (name) => {
1790
- try {
1791
- configTombstoneNotice('disable <name>', 'config <name> auto-launch.enabled off');
1792
- await mustGetDevice(name);
1793
- await runDevicesConfig(name, 'auto-launch.enabled', ['off'], { quiet: true });
1794
- console.log(chalk.green(`Disabled '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
1795
- }
1796
- catch (err) {
1797
- console.error(chalk.red(err.message));
1798
- process.exit(1);
1799
- }
1800
- });
1801
- devicesCmd
1802
- .command('prefer <name>', { hidden: true })
1803
- .action(async (name) => {
1804
- try {
1805
- configTombstoneNotice('prefer <name>', 'config <name> auto-launch.preferred on');
1806
- await mustGetDevice(name);
1807
- await runDevicesConfig(name, 'auto-launch.preferred', ['on'], { quiet: true });
1808
- console.log(chalk.green(`Preferred '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
1809
- }
1810
- catch (err) {
1811
- console.error(chalk.red(err.message));
1812
- process.exit(1);
1813
- }
1814
- });
1815
- devicesCmd
1816
- .command('unprefer <name>', { hidden: true })
1817
- .action(async (name) => {
1818
- try {
1819
- configTombstoneNotice('unprefer <name>', 'config <name> auto-launch.preferred off');
1820
- await mustGetDevice(name);
1821
- // Back to the default (not preferred) = remove the key.
1822
- await runDevicesConfig(name, 'auto-launch.preferred', [], { unset: true, quiet: true });
1823
- console.log(chalk.green(`No longer preferring '${name}'`) + chalk.gray(' for AGI EXT auto-launch.'));
1824
- }
1825
- catch (err) {
1826
- console.error(chalk.red(err.message));
1827
- process.exit(1);
1828
- }
1829
- });
1830
- devicesCmd
1831
- .command('set-interactive [name]', { hidden: true })
1832
- .option('--unset', 'clear the interactive host')
1833
- .option('--json', 'output machine-readable JSON')
1834
- .action(async (name, opts) => {
1835
- try {
1836
- configTombstoneNotice('set-interactive [name]', 'config <name> interactive.host <name>');
1837
- if (opts.unset) {
1838
- unsetConfigValue('interactive.host');
1839
- if (opts.json)
1840
- writeJson({ interactiveHost: null });
1841
- else
1842
- console.log(chalk.green('Cleared the interactive host.'));
1843
- return;
1844
- }
1845
- if (name) {
1846
- await mustGetDevice(name);
1847
- setConfigValue('interactive.host', name);
1848
- if (opts.json)
1849
- writeJson({ interactiveHost: name });
1850
- else
1851
- console.log(chalk.green(`Interactive host: '${name}'`) + chalk.gray(' — agents show you artifacts there. Clear with --unset.'));
1852
- return;
1853
- }
1854
- const current = getConfigValue('interactive.host').value;
1855
- if (opts.json) {
1856
- writeJson({ interactiveHost: current ?? null });
1857
- }
1858
- else if (current) {
1859
- console.log(`${chalk.bold('Interactive host:')} ${chalk.cyan(current)}`);
1860
- }
1861
- else {
1862
- console.log(chalk.gray("No interactive host set. Set one with 'agents devices config <name> interactive.host <name>'."));
1863
- }
1864
- }
1865
- catch (err) {
1866
- console.error(chalk.red(err.message));
1867
- process.exit(1);
1868
- }
1869
- });
1870
- devicesCmd
1871
- .command('configure <name>', { hidden: true })
1872
- .option('--max-agents <n>', 'cap concurrent agents')
1873
- .option('--scheduler <on|off>', 'allow the routines scheduler (daemon) to fire on this device')
1874
- .option('--json', 'output machine-readable JSON')
1875
- .action(async (name, opts) => {
1876
- try {
1877
- configTombstoneNotice('configure <name> [--max-agents N] [--scheduler on|off]', 'config <name> <key> <value>');
1878
- await mustGetDevice(name);
1879
- const writes = [];
1880
- if (opts.maxAgents !== undefined)
1881
- writes.push(['agents.max-concurrent', opts.maxAgents]);
1882
- if (opts.scheduler !== undefined) {
1883
- if (opts.scheduler !== 'on' && opts.scheduler !== 'off') {
1884
- throw new Error(`--scheduler expects 'on' or 'off', got '${opts.scheduler}'.`);
1885
- }
1886
- writes.push(['scheduler.enabled', opts.scheduler]);
1887
- }
1888
- for (const [key, value] of writes) {
1889
- await runDevicesConfig(name, key, [value], { quiet: Boolean(opts.json) });
1890
- }
1891
- if (opts.json)
1892
- printDevicesConfig(name, true);
1893
- else if (writes.length === 0)
1894
- printDevicesConfig(name, false);
1895
- }
1896
- catch (err) {
1897
- console.error(chalk.red(err.message));
1898
- process.exit(1);
1899
- }
1900
- });
1901
- devicesCmd
1902
- .command('note <name> [text...]', { hidden: true })
1903
- .option('--clear', 'remove all notes from the device')
1904
- .option('--json', 'output machine-readable JSON')
1905
- .action(async (name, text, opts) => {
1906
- try {
1907
- configTombstoneNotice('note <name> [text...]', 'config <name> notes <text>');
1908
- await mustGetDevice(name);
1909
- if (opts.clear) {
1910
- await runDevicesConfig(name, 'notes', [], { unset: true, quiet: true });
1911
- if (opts.json)
1912
- writeJson({ device: name, notes: [] });
1913
- else
1914
- console.log(chalk.green(`Cleared notes on '${name}'.`));
1915
- return;
1916
- }
1917
- if (text.length > 0) {
1918
- await runDevicesConfig(name, 'notes', text, { quiet: true });
1919
- const notes = getConfigValue('notes', { device: name }).value ?? [];
1920
- if (opts.json)
1921
- writeJson({ device: name, notes });
1922
- else
1923
- console.log(chalk.green(`Noted on '${name}':`) + ` ${text.join(' ')}`);
1924
- return;
1925
- }
1926
- const notes = getConfigValue('notes', { device: name }).value ?? [];
1927
- if (opts.json) {
1928
- writeJson({ device: name, notes });
1929
- }
1930
- else if (notes.length > 0) {
1931
- console.log(chalk.bold(`Notes for '${name}'`));
1932
- for (const n of notes)
1933
- console.log(` ${chalk.gray('•')} ${n}`);
1934
- }
1935
- else {
1936
- console.log(chalk.gray(`No notes on '${name}'. Add one with 'agents devices config ${name} notes "..."'.`));
1937
- }
1938
- }
1939
- catch (err) {
1940
- console.error(chalk.red(err.message));
1941
- process.exit(1);
1942
- }
1943
- });
1944
1769
  const runList = async (opts = {}) => {
1945
1770
  const reg = await loadDevices();
1946
1771
  const names = Object.keys(reg).sort();
@@ -2027,6 +1852,34 @@ function registerDevicesCommands(program) {
2027
1852
  }
2028
1853
  };
2029
1854
  devicesCmd.action(runList);
1855
+ devicesCmd
1856
+ .command('enable <name>')
1857
+ .description('Put a device back in the automatic-placement pool (clears auto-launch.enabled)')
1858
+ .action(async (name) => {
1859
+ try {
1860
+ await mustGetDevice(name);
1861
+ await runDevicesConfig(name, 'auto-launch.enabled', [], { unset: true, quiet: true });
1862
+ console.log(chalk.green(`Enabled '${name}'`) + chalk.gray(' for automatic placement.'));
1863
+ }
1864
+ catch (err) {
1865
+ console.error(chalk.red(err.message));
1866
+ process.exit(1);
1867
+ }
1868
+ });
1869
+ devicesCmd
1870
+ .command('disable <name>')
1871
+ .description('Drop a device from every automatic-placement path (sets auto-launch.enabled off)')
1872
+ .action(async (name) => {
1873
+ try {
1874
+ await mustGetDevice(name);
1875
+ await runDevicesConfig(name, 'auto-launch.enabled', ['off'], { quiet: true });
1876
+ console.log(chalk.green(`Disabled '${name}'`) + chalk.gray(' for automatic placement.'));
1877
+ }
1878
+ catch (err) {
1879
+ console.error(chalk.red(err.message));
1880
+ process.exit(1);
1881
+ }
1882
+ });
2030
1883
  devicesCmd
2031
1884
  .command('list')
2032
1885
  .alias('ls')
@@ -2214,53 +2067,6 @@ email) into a single row. Use \`agents devices harnesses\` for the per-install v
2214
2067
  process.exit(1);
2215
2068
  }
2216
2069
  });
2217
- devicesCmd
2218
- .command('set <name>', { hidden: true })
2219
- .option('--platform <platform>', 'windows | linux | macos')
2220
- .option('--user <user>', 'login user')
2221
- .option('--auth <method>', 'key | password')
2222
- .option('--bundle <bundle>', 'secrets bundle holding the password (for --auth password)')
2223
- .option('--bundle-key <key>', "key within the bundle (default 'password')")
2224
- .option('--identity-file <path>', 'private-key path for --auth key')
2225
- .option('--clear-identity-file', 'return key auth to ssh-agent/default-key discovery')
2226
- .action(async (name, opts) => {
2227
- try {
2228
- configTombstoneNotice('set <name> [--platform|--user|--auth|--bundle|--bundle-key|--identity-file …]', 'config <name> <ssh.*|platform> <value>');
2229
- const existing = await mustGetDevice(name);
2230
- const nextMethod = opts.auth ?? resolveDeviceProfile(existing).auth.method;
2231
- if (opts.identityFile && nextMethod !== 'key') {
2232
- throw new Error('--identity-file requires key auth; pass --auth key in the same command.');
2233
- }
2234
- const writes = [];
2235
- if (opts.platform)
2236
- writes.push({ key: 'platform', value: opts.platform });
2237
- if (opts.user)
2238
- writes.push({ key: 'ssh.user', value: opts.user });
2239
- if (opts.auth)
2240
- writes.push({ key: 'ssh.auth', value: opts.auth });
2241
- if (opts.bundle)
2242
- writes.push({ key: 'ssh.bundle', value: opts.bundle });
2243
- if (opts.bundleKey)
2244
- writes.push({ key: 'ssh.bundle-key', value: opts.bundleKey });
2245
- if (opts.identityFile)
2246
- writes.push({ key: 'ssh.identity-file', value: opts.identityFile });
2247
- if (opts.clearIdentityFile)
2248
- writes.push({ key: 'ssh.identity-file' }); // unset
2249
- if (writes.length === 0) {
2250
- printDevicesConfig(name, false);
2251
- return;
2252
- }
2253
- for (const w of writes) {
2254
- await runDevicesConfig(name, w.key, w.value !== undefined ? [w.value] : [], { unset: w.value === undefined, quiet: true });
2255
- }
2256
- const d = resolveDeviceProfile((await mustGetDevice(name)));
2257
- console.log(chalk.green(`Updated device '${name}'`) + chalk.gray(` (auth: ${d.auth.method}${d.auth.bundle ? ` via ${d.auth.bundle}` : ''})`));
2258
- }
2259
- catch (err) {
2260
- console.error(chalk.red(err.message));
2261
- process.exit(1);
2262
- }
2263
- });
2264
2070
  devicesCmd
2265
2071
  .command('remove <name>')
2266
2072
  .alias('rm')
@@ -2633,12 +2439,40 @@ async function doDeviceTaskStop(ref) {
2633
2439
  process.exitCode = 1;
2634
2440
  }
2635
2441
  }
2442
+ /**
2443
+ * Parse `--argv`'s JSON array, or report exactly what was wrong and return
2444
+ * undefined. Fails loud rather than coercing: a caller that meant to pass argv
2445
+ * and typo'd the JSON must not silently get shell-string semantics instead.
2446
+ */
2447
+ export function parseArgvJson(raw, log = (line) => console.error(line)) {
2448
+ let parsed;
2449
+ try {
2450
+ parsed = JSON.parse(raw);
2451
+ }
2452
+ catch (error) {
2453
+ log(`--argv is not valid JSON: ${error.message}`);
2454
+ log(`Expected a JSON array of strings, e.g. --argv '["uptime","-p"]'`);
2455
+ return undefined;
2456
+ }
2457
+ if (!Array.isArray(parsed)) {
2458
+ log('--argv must be a JSON ARRAY of strings.');
2459
+ return undefined;
2460
+ }
2461
+ const bad = parsed.findIndex((token) => typeof token !== 'string');
2462
+ if (bad >= 0) {
2463
+ log(`--argv element ${bad} is ${typeof parsed[bad]}, not a string.`);
2464
+ log('Every element is one argv token; numbers and objects have no argv meaning.');
2465
+ return undefined;
2466
+ }
2467
+ return parsed;
2468
+ }
2636
2469
  /** Register the `agents ssh` smart wrapper. */
2637
2470
  function registerSshWrapper(program) {
2638
2471
  const sshCmd = program
2639
2472
  .command('ssh <name> [cmd...]')
2640
2473
  .description('Connect to a registered device. Preflights reachability, picks the right shell, and authenticates (key or password-from-bundle).')
2641
2474
  .allowUnknownOption()
2475
+ .option('--argv <json>', 'run a JSON array of argv tokens with exact per-token fidelity (no shell splitting)')
2642
2476
  .addHelpText('after', `
2643
2477
  Examples:
2644
2478
  agents ssh yosemite-s0 # interactive login (mirrors your project dir)
@@ -2646,6 +2480,8 @@ Examples:
2646
2480
  agents ssh win-mini hostname # run a command (PowerShell on Windows)
2647
2481
  agents ssh yosemite-s0 uptime # run a command (POSIX)
2648
2482
  agents ssh auto # affinity-pick a device (same engine as 'agents run --device auto')
2483
+ agents ssh box --argv '["agents","feed","post","--title","two words","a & b"]'
2484
+ # exact tokens: spaces and metacharacters survive
2649
2485
 
2650
2486
  Devices come from 'agents devices'. Password auth pulls the secret from a
2651
2487
  secrets bundle via an askpass shim — the password never touches argv.
@@ -2660,8 +2496,31 @@ the target when it exists, else the remote home. Same portable-cwd rule as
2660
2496
  An 'agents browser …', 'ag browser …', or standalone 'browser …' command is
2661
2497
  stamped AGENTS_FLEET_REMOTE so the target's browser.remote-control consent
2662
2498
  gate applies, same as 'agents browser <verb> --device <name>'.
2499
+
2500
+ --argv takes a JSON array of strings and delivers each element as exactly ONE
2501
+ token on the peer, so a token containing a space, '&', '|', '$' or a quote
2502
+ arrives intact. The positional form keeps its existing semantics — the remote
2503
+ shell parses it — so the two are mutually exclusive rather than interchangeable.
2663
2504
  `)
2664
- .action(async (name, cmd) => {
2505
+ .action(async (name, cmd, opts) => {
2506
+ // `--argv` is a DIFFERENT delivery contract, not a spelling of the
2507
+ // positional form, so accepting both would have to silently pick one.
2508
+ if (opts.argv !== undefined && cmd.length > 0) {
2509
+ console.error(chalk.red('Pass either --argv <json> or a positional command, not both.'));
2510
+ console.error(chalk.gray('--argv delivers exact tokens; the positional form is parsed by the remote shell.'));
2511
+ process.exit(1);
2512
+ }
2513
+ let argvTokens;
2514
+ if (opts.argv !== undefined) {
2515
+ argvTokens = parseArgvJson(opts.argv);
2516
+ if (!argvTokens)
2517
+ process.exit(1);
2518
+ if (argvTokens.length === 0) {
2519
+ console.error(chalk.red('--argv needs at least one token (the program to run).'));
2520
+ process.exit(1);
2521
+ }
2522
+ cmd = argvTokens;
2523
+ }
2665
2524
  // Hidden askpass bridge: ssh execs the shim, which re-invokes us here.
2666
2525
  if (name === '__askpass') {
2667
2526
  await runAskpass();
@@ -2734,7 +2593,7 @@ gate applies, same as 'agents browser <verb> --device <name>'.
2734
2593
  // `agents run --device` (deriveMirroredCwd). Best-effort — a missing dir
2735
2594
  // falls back to the remote home. An explicit `cmd` keeps its cwd (RUSH-2412).
2736
2595
  const mirrorCwd = cmd.length === 0 ? deriveMirroredCwd(process.cwd()) : undefined;
2737
- const { args, env } = buildSshInvocation(device, cmd, shim, { pinned }, { interactiveCwd: mirrorCwd });
2596
+ const { args, env } = buildSshInvocation(device, cmd, shim, { pinned }, { interactiveCwd: mirrorCwd, ...(argvTokens ? { argv: true } : {}) });
2738
2597
  // Interactive login: make the local terminal's terminfo (e.g.
2739
2598
  // xterm-ghostty) available on the remote so backspace/colors/clear work.
2740
2599
  // Best-effort + cached per host — never blocks the login (see terminfo.ts).
@@ -514,7 +514,8 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
514
514
  // into. Any explicit flag, --yes, or --json keeps the non-interactive path.
515
515
  // --json is a machine consumer (and the fleet fan-out injects it), so never
516
516
  // open a picker under it.
517
- const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local);
517
+ const kindSelection = parseKindSelection(opts);
518
+ const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local || kindSelection);
518
519
  if (!quiet && !json && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
519
520
  await runInteractiveReconcile(opts, outLog, errLog);
520
521
  return;
@@ -524,7 +525,10 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
524
525
  repos: opts.repos,
525
526
  secrets: opts.secrets,
526
527
  cloud: opts.cloud,
527
- local: opts.local,
528
+ // A resource selector is a local reconcile unless the caller explicitly
529
+ // asks to fetch repos/secrets first. This keeps replacements such as
530
+ // `sync --mcp demo` scoped like the retired resource verb.
531
+ local: opts.local || (!!kindSelection && !opts.repos && !opts.secrets && !opts.cloud),
528
532
  };
529
533
  // Same chokepoint as `agents secrets push/pull` — prefers AGENTS_SYNC_PASSPHRASE,
530
534
  // falls back to the deprecated master-key name with a single warning.
@@ -541,6 +545,8 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
541
545
  passphrase,
542
546
  // quiet under --json so refresh() cannot pollute the JSON stdout the fleet parses.
543
547
  quiet: quiet || json,
548
+ selection: kindSelection,
549
+ allowExecSurfaces: !!opts.allowExecSurfaces,
544
550
  log: (msg) => { if (!quiet && !json)
545
551
  outLog(chalk.gray(` ${msg}`)); },
546
552
  });
@@ -548,20 +554,22 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
548
554
  // we act on central as converged by this sync rather than a stale pre-pull
549
555
  // copy that could re-claim a profile a peer already migrated. Skipped under
550
556
  // --cloud (fetch-only, no local reconcile).
551
- if (!opts.cloud)
557
+ if (!opts.cloud && !kindSelection)
552
558
  evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog);
553
559
  // Post-reconcile verification (PHNX-3186): re-read every version the reconcile
554
560
  // wrote into and confirm it now matches source. Without this the umbrella
555
561
  // printed `✓ sync: reconciled` unconditionally, the exact false-success the
556
562
  // ticket reports. Residual drift downgrades the line and sets a non-zero exit.
557
- const residual = result.reconciled
563
+ const residual = result.reconciled && !kindSelection
558
564
  ? verifyReconciled(result.reconciledVersions.map((r) => ({ agent: r.agent, version: r.version })), cwd)
559
565
  : [];
560
566
  // Post-reconcile repair, machine-wide: no agent scope → heal every installed
561
567
  // agent, re-wire hooks the diff left behind, and repair managed hook runtime
562
568
  // shims. The stale-CLI purge runs ONLY with `--prune-clis` (never automatic).
563
569
  // Skipped under --cloud (fetch-only: nothing was reconciled to repair).
564
- const repair = opts.cloud ? null : await repairAfterSync({ cwd, pruneClis: !!opts.pruneClis });
570
+ const repair = opts.cloud || kindSelection
571
+ ? null
572
+ : await repairAfterSync({ cwd, pruneClis: !!opts.pruneClis });
565
573
  // A repair that left something for a human (an unresolvable shim, a failed
566
574
  // rewire/purge) is a non-zero outcome, same as the deleted `doctor --fix`.
567
575
  const repairFailed = repair !== null && repairHadFailures(repair);
@@ -578,6 +586,7 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
578
586
  secrets: result.secrets,
579
587
  devices: result.devices,
580
588
  reconciled: result.reconciled,
589
+ selection: kindSelection ?? null,
581
590
  declined: result.declined,
582
591
  residualDrift: residual,
583
592
  repair: repair ? repairAfterSyncJson(repair) : null,
@@ -8,7 +8,7 @@ import { mailboxDir, enqueue } from '../lib/mailbox.js';
8
8
  import { resolveProvider } from '../lib/cloud/registry.js';
9
9
  import { emit } from '../lib/feed/events.js';
10
10
  import { maybeShowStarNudge } from '../lib/star-nudge.js';
11
- import { shareRuntimeEnv } from '../lib/share/config.js';
11
+ import { shareRuntimeEnv } from '../lib/share-runtime.js';
12
12
  import { runSupervisor } from '../lib/teams/supervisor.js';
13
13
  import { debug } from '../lib/teams/debug.js';
14
14
  import { runPrWatch, DEFAULT_MAX_WAVES, } from '../lib/teams/pr-watch.js';
@@ -3,7 +3,7 @@ import { setHelpSections } from '../lib/help.js';
3
3
  import { readSyncLedger, syncTraces } from '../lib/traces/sync.js';
4
4
  import { managedTracesBaseUrl, resolveTracesBackend } from '../lib/traces/backend.js';
5
5
  import { showUrl } from '../lib/open-url.js';
6
- import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/share/config.js';
6
+ import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/cloudflare/creds.js';
7
7
  import { PHOENIX_ID_BASE } from '../lib/identity/client.js';
8
8
  import { DEFAULT_BUCKET_NAME, DEFAULT_WORKER_NAME } from '../lib/traces/config.js';
9
9
  import { DEFAULT_TRACES_DOMAIN } from '../lib/traces/backend.js';
@@ -131,8 +131,3 @@ export declare function runAdd(agent: AgentId, name: string | undefined, opts: A
131
131
  * that IS how such a box logs in.
132
132
  */
133
133
  export declare function runLogin(agent: AgentId, name: string, opts: AddOptions, runners?: AddRunners): Promise<AddResult>;
134
- /** Back-compat alias for the retired `connect` module surface (one release). */
135
- export declare const runConnect: typeof runAdd;
136
- export declare const connectSupported: typeof addSupported;
137
- export declare const connectRefusal: typeof addRefusal;
138
- export declare const connectWorkerRefusal: typeof addWorkerRefusal;
@@ -15,8 +15,7 @@ import { ensureSlot, readSlots, recordSlot, slotDir } from './slots.js';
15
15
  export { LOGIN_INVOCATIONS };
16
16
  /**
17
17
  * `agents accounts add <harness> [name]` / `agents accounts login <harness>#<name>`
18
- * — the account onboarding front door (PHNX-3940, track T4; successor of the
19
- * `connect` module, kept as the `runConnect` alias for one release).
18
+ * — the account onboarding front door (PHNX-3940, track T4).
20
19
  *
21
20
  * An account is a credential SLOT, not an installation: the harness has ONE
22
21
  * managed installation (`ensureHarnessInstallation`, label `main`); every
@@ -542,11 +541,6 @@ async function _runLoginLocked(agent, name, opts, lock, runners) {
542
541
  warnings,
543
542
  };
544
543
  }
545
- /** Back-compat alias for the retired `connect` module surface (one release). */
546
- export const runConnect = runAdd;
547
- export const connectSupported = addSupported;
548
- export const connectRefusal = addRefusal;
549
- export const connectWorkerRefusal = addWorkerRefusal;
550
544
  /** Real runners: the install engine, an inherited-stdio login spawn, identity read, and the mint drive. */
551
545
  async function defaultAddRunners() {
552
546
  const [store, agentsMod, execMod] = await Promise.all([
@@ -0,0 +1,20 @@
1
+ export declare const ARTIFACTS_INSTALL_HINT = "npm i -g @phnx-labs/artifacts-cli";
2
+ export declare class ArtifactsClientError extends Error {
3
+ readonly code: 'ARTIFACTS_BIN_MISSING';
4
+ constructor(code: 'ARTIFACTS_BIN_MISSING', message: string);
5
+ }
6
+ /**
7
+ * Resolve the standalone `artifacts` executable. `ARTIFACTS_BIN` wins so a dev
8
+ * build can be driven without touching PATH.
9
+ *
10
+ * Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — the same
11
+ * load-bearing skip the secrets/computer clients rely on, so a leftover alias
12
+ * shim that execs back into this CLI can never be picked as the engine.
13
+ */
14
+ export declare function resolveArtifactsBin(): string;
15
+ /** How to invoke the resolved binary: a `.js` entrypoint runs through this
16
+ * runtime, a real executable is spawned directly. */
17
+ export declare function invocation(bin: string): {
18
+ command: string;
19
+ prefix: string[];
20
+ };
@@ -0,0 +1,46 @@
1
+ // Thin client for the standalone `artifacts` CLI (`@phnx-labs/artifacts-cli`).
2
+ //
3
+ // The artifact render + share ENGINE lives in artifacts-cli, not this repo
4
+ // (PHNX-3992, mirroring the secrets/computer extractions). agents-cli calls it
5
+ // only where a `sessions` convenience wants to publish (`agents sessions share`).
6
+ // A missing executable fails loud with install guidance — there is no fallback
7
+ // to a retired in-repo engine.
8
+ import { findInPath } from './agent-spec/agents.js';
9
+ export const ARTIFACTS_INSTALL_HINT = 'npm i -g @phnx-labs/artifacts-cli';
10
+ export class ArtifactsClientError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'ArtifactsClientError';
16
+ }
17
+ }
18
+ let cachedBin = null;
19
+ /**
20
+ * Resolve the standalone `artifacts` executable. `ARTIFACTS_BIN` wins so a dev
21
+ * build can be driven without touching PATH.
22
+ *
23
+ * Resolution uses `findInPath`, which skips `~/.agents/.cache/shims` — the same
24
+ * load-bearing skip the secrets/computer clients rely on, so a leftover alias
25
+ * shim that execs back into this CLI can never be picked as the engine.
26
+ */
27
+ export function resolveArtifactsBin() {
28
+ if (cachedBin)
29
+ return cachedBin;
30
+ const explicit = process.env.ARTIFACTS_BIN?.trim();
31
+ const resolved = explicit && explicit.length > 0 ? explicit : findInPath('artifacts');
32
+ if (!resolved) {
33
+ throw new ArtifactsClientError('ARTIFACTS_BIN_MISSING', 'The standalone `artifacts` CLI was not found. Install it with:\n' +
34
+ ` ${ARTIFACTS_INSTALL_HINT}\n` +
35
+ 'or point $ARTIFACTS_BIN at its executable.');
36
+ }
37
+ cachedBin = resolved;
38
+ return resolved;
39
+ }
40
+ /** How to invoke the resolved binary: a `.js` entrypoint runs through this
41
+ * runtime, a real executable is spawned directly. */
42
+ export function invocation(bin) {
43
+ if (/\.[mc]?js$/.test(bin))
44
+ return { command: process.execPath, prefix: [bin] };
45
+ return { command: bin, prefix: [] };
46
+ }
@@ -534,7 +534,7 @@ async function syncMintedBundles(accountName, device) {
534
534
  const accountPush = await pushBundleToHost(accountName, device, {
535
535
  remoteBackend,
536
536
  force: true,
537
- operation: 'accounts mint --fleet',
537
+ operation: 'accounts login --fleet',
538
538
  policyNever: true,
539
539
  agentOnly: false,
540
540
  literalValues: {
@@ -550,7 +550,7 @@ async function syncMintedBundles(accountName, device) {
550
550
  const authPush = await pushBundleToHost(AUTH_BUNDLE, device, {
551
551
  remoteBackend: 'file',
552
552
  force: true,
553
- operation: 'accounts mint --fleet',
553
+ operation: 'accounts login --fleet',
554
554
  policyNever: true,
555
555
  agentOnly: true,
556
556
  });