@phnx-labs/agents-cli 1.20.65 → 1.20.66

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 (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +119 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/exec.d.ts +48 -0
  5. package/dist/commands/exec.js +65 -0
  6. package/dist/commands/monitors.js +8 -0
  7. package/dist/commands/plugins.js +28 -7
  8. package/dist/commands/sessions-browser.d.ts +82 -0
  9. package/dist/commands/sessions-browser.js +320 -0
  10. package/dist/commands/sessions.d.ts +1 -0
  11. package/dist/commands/sessions.js +121 -4
  12. package/dist/commands/share.d.ts +2 -0
  13. package/dist/commands/share.js +150 -0
  14. package/dist/commands/ssh.js +9 -1
  15. package/dist/index.js +2 -1
  16. package/dist/lib/agents.d.ts +28 -0
  17. package/dist/lib/agents.js +68 -0
  18. package/dist/lib/devices/connect.d.ts +18 -1
  19. package/dist/lib/devices/connect.js +10 -2
  20. package/dist/lib/devices/known-hosts.d.ts +62 -0
  21. package/dist/lib/devices/known-hosts.js +137 -0
  22. package/dist/lib/hosts/dispatch.js +20 -2
  23. package/dist/lib/hosts/remote-cmd.js +8 -4
  24. package/dist/lib/monitors/engine.js +4 -0
  25. package/dist/lib/monitors/sources/device.js +13 -3
  26. package/dist/lib/picker.d.ts +53 -0
  27. package/dist/lib/picker.js +214 -1
  28. package/dist/lib/plugins.d.ts +31 -1
  29. package/dist/lib/plugins.js +74 -13
  30. package/dist/lib/secrets/agent.d.ts +35 -0
  31. package/dist/lib/secrets/agent.js +114 -55
  32. package/dist/lib/secrets/filestore.d.ts +9 -0
  33. package/dist/lib/secrets/filestore.js +21 -8
  34. package/dist/lib/secrets/index.d.ts +7 -0
  35. package/dist/lib/secrets/index.js +26 -6
  36. package/dist/lib/share/capture.d.ts +29 -0
  37. package/dist/lib/share/capture.js +140 -0
  38. package/dist/lib/share/config.d.ts +35 -0
  39. package/dist/lib/share/config.js +100 -0
  40. package/dist/lib/share/og.d.ts +25 -0
  41. package/dist/lib/share/og.js +84 -0
  42. package/dist/lib/share/provision.d.ts +10 -0
  43. package/dist/lib/share/provision.js +91 -0
  44. package/dist/lib/share/publish.d.ts +57 -0
  45. package/dist/lib/share/publish.js +145 -0
  46. package/dist/lib/share/worker-template.d.ts +2 -0
  47. package/dist/lib/share/worker-template.js +82 -0
  48. package/dist/lib/shims.d.ts +13 -0
  49. package/dist/lib/shims.js +42 -2
  50. package/dist/lib/ssh-exec.d.ts +24 -0
  51. package/dist/lib/ssh-exec.js +15 -3
  52. package/dist/lib/startup/command-registry.d.ts +1 -0
  53. package/dist/lib/startup/command-registry.js +2 -0
  54. package/dist/lib/types.d.ts +10 -0
  55. package/package.json +1 -1
package/dist/lib/shims.js CHANGED
@@ -16,7 +16,7 @@ import { confirm, select } from '@inquirer/prompts';
16
16
  import { IS_WINDOWS, prependToWindowsUserPath } from './platform/index.js';
17
17
  import { getShimsDir, getVersionsDir, getBackupsDir, getHistoryDir, ensureAgentsDir } from './state.js';
18
18
  export { getShimsDir };
19
- import { AGENTS, agentConfigDirName } from './agents.js';
19
+ import { AGENTS, agentConfigDirName, readAuthAccountIdentity } from './agents.js';
20
20
  /**
21
21
  * Files and directories to always skip during conflict detection and migration.
22
22
  * These are never user config that should be migrated.
@@ -1143,6 +1143,21 @@ function detectMigrationConflicts(agent, version) {
1143
1143
  * preserved so the "freshest" comparison stays stable and switches don't
1144
1144
  * ping-pong. Best-effort: a failed copy just means the user re-logs in.
1145
1145
  */
1146
+ /**
1147
+ * Best-effort account identity for the credential *directory* of a file-auth
1148
+ * agent (droid / kimi / antigravity), or null when the directory holds no
1149
+ * decodable account claim. Delegates to readAuthAccountIdentity, which decrypts
1150
+ * / decodes each agent's REAL on-disk format (droid AES-256-GCM + WorkOS JWT,
1151
+ * kimi access-token JWT, antigravity refresh-token) — the earlier plaintext
1152
+ * top-level-key scan matched NO real credential file, so the guard below never
1153
+ * engaged. Two dirs for the SAME account compare equal; DIFFERENT accounts
1154
+ * compare distinct. Used by carryForwardAuthFiles to refuse overwriting one
1155
+ * account's login with a credential that belongs to a DIFFERENT account
1156
+ * (RUSH-1764).
1157
+ */
1158
+ export function readAuthFileIdentity(agent, configDir) {
1159
+ return readAuthAccountIdentity(agent, configDir);
1160
+ }
1146
1161
  export function carryForwardAuthFiles(agent, toConfigDir) {
1147
1162
  const authFiles = AGENTS[agent].authFiles;
1148
1163
  if (!authFiles || authFiles.length === 0)
@@ -1158,12 +1173,33 @@ export function carryForwardAuthFiles(agent, toConfigDir) {
1158
1173
  catch {
1159
1174
  return; // no installed versions to source from
1160
1175
  }
1176
+ // Account identity currently installed at the destination home (null when the
1177
+ // dest is empty or its credential can't be decoded). When it IS known, only a
1178
+ // source home whose credential decodes to the SAME account is eligible to
1179
+ // overwrite it — so a newer login for a DIFFERENT account sitting in another
1180
+ // version-home can't silently replace the account the user is signed into
1181
+ // (RUSH-1764). An empty destination still seeds from the freshest source (the
1182
+ // version-switch case). Identity is a per-DIR/account property, not per-file:
1183
+ // for droid it comes from decrypting auth.v2.file with auth.v2.key, so it must
1184
+ // gate BOTH files as a unit (never carry account B's key over account A's).
1185
+ const toResolved = path.resolve(toConfigDir);
1186
+ const destIdentity = readAuthFileIdentity(agent, toConfigDir);
1187
+ const identityCache = new Map();
1188
+ const dirIdentity = (dir) => {
1189
+ const key = path.resolve(dir);
1190
+ if (!identityCache.has(key))
1191
+ identityCache.set(key, readAuthFileIdentity(agent, dir));
1192
+ return identityCache.get(key) ?? null;
1193
+ };
1161
1194
  for (const rel of authFiles) {
1162
1195
  const dest = path.join(toConfigDir, rel);
1163
1196
  const destResolved = path.resolve(dest);
1164
- // Newest existing source copy across all version homes (excluding dest).
1197
+ // Newest existing source copy across all version homes (excluding dest),
1198
+ // constrained to the destination's account identity when it is known.
1165
1199
  let newest = null;
1166
1200
  for (const dir of sourceDirs) {
1201
+ if (path.resolve(dir) === toResolved)
1202
+ continue; // never source from self
1167
1203
  const src = path.join(dir, rel);
1168
1204
  if (path.resolve(src) === destResolved)
1169
1205
  continue;
@@ -1176,6 +1212,10 @@ export function carryForwardAuthFiles(agent, toConfigDir) {
1176
1212
  }
1177
1213
  if (!st.isFile())
1178
1214
  continue;
1215
+ // Account-identity guard: never carry a different account's credential over
1216
+ // an existing login. Only enforced when the destination's identity is known.
1217
+ if (destIdentity !== null && dirIdentity(dir) !== destIdentity)
1218
+ continue;
1179
1219
  if (!newest || st.mtimeMs > newest.mtimeMs)
1180
1220
  newest = { path: src, mtimeMs: st.mtimeMs };
1181
1221
  }
@@ -29,6 +29,16 @@ export declare function shellQuote(s: string): string;
29
29
  */
30
30
  export declare const SSH_OPTS: readonly string[];
31
31
  export declare function controlOpts(): string[];
32
+ /**
33
+ * Compose an ssh connection-option prefix.
34
+ *
35
+ * `hostKeyOpts` (a caller's host-key posture, e.g. a pinned
36
+ * `StrictHostKeyChecking=yes` + managed `UserKnownHostsFile`) go FIRST, ahead of
37
+ * the `SSH_OPTS` baseline: ssh honors the *first* value it sees for each option,
38
+ * so an override placed after the baseline's `accept-new` would be silently
39
+ * ignored. Pure so the ordering contract is unit-testable. See RUSH-1767.
40
+ */
41
+ export declare function sshConnectOpts(mux: string[], hostKeyOpts?: string[]): string[];
32
42
  export interface SshExecOptions {
33
43
  /** Piped to the remote command's stdin (never interpolated into the shell). */
34
44
  input?: string;
@@ -38,6 +48,13 @@ export interface SshExecOptions {
38
48
  extraSshArgs?: string[];
39
49
  /** Reuse a persistent control socket across calls (default true; see `controlOpts`). */
40
50
  multiplex?: boolean;
51
+ /**
52
+ * Host-key `-o` options that OVERRIDE the accept-new baseline — prepended so
53
+ * ssh's first-value-wins rule takes them (see {@link sshConnectOpts}). Used to
54
+ * force strict verification against the managed known_hosts store on the
55
+ * credential-copy path (RUSH-1767).
56
+ */
57
+ hostKeyOpts?: string[];
41
58
  }
42
59
  export interface SshExecResult {
43
60
  /** Remote exit status, or null if ssh itself failed / timed out. */
@@ -79,6 +96,13 @@ export interface SshStreamOptions {
79
96
  tty?: boolean;
80
97
  /** Reuse a persistent control socket across calls (default true; see `controlOpts`). */
81
98
  multiplex?: boolean;
99
+ /**
100
+ * Host-key `-o` options that OVERRIDE the accept-new baseline — prepended so
101
+ * ssh's first-value-wins rule takes them (see {@link sshConnectOpts}). Used to
102
+ * force strict verification against the managed known_hosts store on the
103
+ * interactive credential-copy path (RUSH-1767).
104
+ */
105
+ hostKeyOpts?: string[];
82
106
  }
83
107
  /**
84
108
  * Foreground counterpart to `sshExec`: run `remoteCmd` on `target` with the
@@ -88,6 +88,18 @@ export function controlOpts() {
88
88
  '-o', 'ControlPersist=60s',
89
89
  ];
90
90
  }
91
+ /**
92
+ * Compose an ssh connection-option prefix.
93
+ *
94
+ * `hostKeyOpts` (a caller's host-key posture, e.g. a pinned
95
+ * `StrictHostKeyChecking=yes` + managed `UserKnownHostsFile`) go FIRST, ahead of
96
+ * the `SSH_OPTS` baseline: ssh honors the *first* value it sees for each option,
97
+ * so an override placed after the baseline's `accept-new` would be silently
98
+ * ignored. Pure so the ordering contract is unit-testable. See RUSH-1767.
99
+ */
100
+ export function sshConnectOpts(mux, hostKeyOpts) {
101
+ return [...(hostKeyOpts ?? []), ...SSH_OPTS, ...mux];
102
+ }
91
103
  /**
92
104
  * Run `remoteCmd` on `target` over ssh and capture stdout/stderr/exit.
93
105
  *
@@ -97,7 +109,7 @@ export function controlOpts() {
97
109
  export function sshExec(target, remoteCmd, opts = {}) {
98
110
  assertValidSshTarget(target);
99
111
  const mux = opts.multiplex === false ? [] : controlOpts();
100
- const args = [...SSH_OPTS, ...mux, ...(opts.extraSshArgs ?? []), target, remoteCmd];
112
+ const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...(opts.extraSshArgs ?? []), target, remoteCmd];
101
113
  const res = spawnSync('ssh', args, {
102
114
  input: opts.input,
103
115
  encoding: 'utf-8',
@@ -123,7 +135,7 @@ export function sshExec(target, remoteCmd, opts = {}) {
123
135
  export function sshExecRaw(target, remoteCmd, opts = {}) {
124
136
  assertValidSshTarget(target);
125
137
  const mux = opts.multiplex === false ? [] : controlOpts();
126
- const args = [...SSH_OPTS, ...mux, ...(opts.extraSshArgs ?? []), target, remoteCmd];
138
+ const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...(opts.extraSshArgs ?? []), target, remoteCmd];
127
139
  const res = spawnSync('ssh', args, {
128
140
  input: opts.input,
129
141
  // No `encoding` → spawnSync returns Buffers.
@@ -154,7 +166,7 @@ export function sshStream(target, remoteCmd, opts = {}) {
154
166
  assertValidSshTarget(target);
155
167
  const mux = opts.multiplex === false ? [] : controlOpts();
156
168
  const tty = opts.tty ? ['-tt'] : [];
157
- const args = [...SSH_OPTS, ...mux, ...tty, target, remoteCmd];
169
+ const args = [...sshConnectOpts(mux, opts.hostKeyOpts), ...tty, target, remoteCmd];
158
170
  const res = spawnSync('ssh', args, { stdio: 'inherit' });
159
171
  if (typeof res.status === 'number')
160
172
  return res.status;
@@ -91,6 +91,7 @@ export declare const loadMessage: ModuleLoader;
91
91
  export declare const loadFeed: ModuleLoader;
92
92
  export declare const loadMailboxes: ModuleLoader;
93
93
  export declare const loadServe: ModuleLoader;
94
+ export declare const loadShare: ModuleLoader;
94
95
  export declare const loadAudit: ModuleLoader;
95
96
  export declare const loadWebhook: ModuleLoader;
96
97
  export declare const loadFunnel: ModuleLoader;
@@ -69,6 +69,7 @@ export const loadMessage = async () => (await import('../../commands/message.js'
69
69
  export const loadFeed = async () => (await import('../../commands/feed.js')).registerFeedCommand;
70
70
  export const loadMailboxes = async () => (await import('../../commands/mailboxes.js')).registerMailboxesCommand;
71
71
  export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
72
+ export const loadShare = async () => (await import('../../commands/share.js')).registerShareCommands;
72
73
  export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
73
74
  export const loadWebhook = async () => (await import('../../commands/webhook.js')).registerWebhookCommand;
74
75
  export const loadFunnel = async () => (await import('../../commands/funnel.js')).registerFunnelCommand;
@@ -179,6 +180,7 @@ export const COMMAND_LOADERS = {
179
180
  mailboxes: [loadMailboxes],
180
181
  mailbox: [loadMailboxes],
181
182
  serve: [loadServe],
183
+ share: [loadShare],
182
184
  audit: [loadAudit],
183
185
  webhook: [loadWebhook],
184
186
  funnel: [loadFunnel],
@@ -723,6 +723,16 @@ export interface Meta {
723
723
  * Full shape in `lib/fleet/types.ts` (FleetManifest).
724
724
  */
725
725
  fleet?: import('./fleet/types.js').FleetManifest;
726
+ /** `agents share` endpoint (Cloudflare R2 + Worker). Set by `agents share
727
+ * setup`/`join`; syncs fleet-wide via `agents repo push/pull`. The write token
728
+ * lives in the `share` secrets bundle, not here. */
729
+ share?: {
730
+ baseUrl?: string;
731
+ accountId?: string;
732
+ workerName?: string;
733
+ bucketName?: string;
734
+ domain?: string;
735
+ };
726
736
  }
727
737
  /** Persisted agent-host entry in agents.yaml (overlay or inline). */
728
738
  export interface HostEntry {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.65",
3
+ "version": "1.20.66",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",