@phnx-labs/agents-cli 1.20.78 → 1.20.82

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 +487 -0
  2. package/README.md +25 -25
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +9 -0
  5. package/dist/commands/activity.js +153 -24
  6. package/dist/commands/apply.js +11 -4
  7. package/dist/commands/cli.js +1 -1
  8. package/dist/commands/commands.d.ts +1 -1
  9. package/dist/commands/commands.js +7 -6
  10. package/dist/commands/defaults.js +3 -11
  11. package/dist/commands/doctor.d.ts +65 -0
  12. package/dist/commands/doctor.js +575 -31
  13. package/dist/commands/events.js +1 -1
  14. package/dist/commands/exec.js +24 -5
  15. package/dist/commands/feed.d.ts +4 -0
  16. package/dist/commands/feed.js +87 -11
  17. package/dist/commands/harness.js +2 -2
  18. package/dist/commands/hooks.js +1 -1
  19. package/dist/commands/import.js +8 -4
  20. package/dist/commands/logs.js +6 -0
  21. package/dist/commands/models.js +1 -1
  22. package/dist/commands/packages.js +6 -6
  23. package/dist/commands/permissions.js +1 -1
  24. package/dist/commands/profiles.js +1 -1
  25. package/dist/commands/pull.d.ts +1 -1
  26. package/dist/commands/pull.js +4 -4
  27. package/dist/commands/repo.d.ts +78 -0
  28. package/dist/commands/repo.js +187 -11
  29. package/dist/commands/routines.js +12 -2
  30. package/dist/commands/rules.js +13 -12
  31. package/dist/commands/secrets.d.ts +3 -1
  32. package/dist/commands/secrets.js +100 -32
  33. package/dist/commands/sessions-inject.d.ts +7 -4
  34. package/dist/commands/sessions-inject.js +18 -18
  35. package/dist/commands/sessions-picker.js +32 -3
  36. package/dist/commands/sessions-resume.d.ts +16 -0
  37. package/dist/commands/sessions-resume.js +14 -13
  38. package/dist/commands/sessions.d.ts +31 -0
  39. package/dist/commands/sessions.js +141 -16
  40. package/dist/commands/set.d.ts +15 -0
  41. package/dist/commands/set.js +79 -0
  42. package/dist/commands/setup-fleet.d.ts +21 -0
  43. package/dist/commands/setup-fleet.js +201 -0
  44. package/dist/commands/setup-secrets.d.ts +20 -0
  45. package/dist/commands/setup-secrets.js +223 -0
  46. package/dist/commands/setup.js +17 -3
  47. package/dist/commands/skills.js +1 -1
  48. package/dist/commands/sync.js +7 -2
  49. package/dist/commands/teams-picker.js +0 -1
  50. package/dist/commands/teams.d.ts +33 -1
  51. package/dist/commands/teams.js +208 -44
  52. package/dist/commands/versions.js +10 -5
  53. package/dist/commands/view.d.ts +1 -0
  54. package/dist/commands/view.js +11 -0
  55. package/dist/index.js +5 -8
  56. package/dist/lib/acp/harnesses.js +0 -7
  57. package/dist/lib/activity.d.ts +156 -0
  58. package/dist/lib/activity.js +282 -0
  59. package/dist/lib/agents.d.ts +9 -2
  60. package/dist/lib/agents.js +72 -63
  61. package/dist/lib/browser/service.js +3 -0
  62. package/dist/lib/browser/types.d.ts +7 -0
  63. package/dist/lib/capabilities.js +6 -2
  64. package/dist/lib/crabbox/lease.js +2 -2
  65. package/dist/lib/crabbox/setup-copy.d.ts +4 -4
  66. package/dist/lib/crabbox/setup-copy.js +4 -4
  67. package/dist/lib/daemon.d.ts +18 -0
  68. package/dist/lib/daemon.js +47 -6
  69. package/dist/lib/devices/fleet.d.ts +1 -1
  70. package/dist/lib/devices/fleet.js +1 -1
  71. package/dist/lib/doctor-diff.d.ts +20 -0
  72. package/dist/lib/doctor-diff.js +6 -1
  73. package/dist/lib/drift.d.ts +31 -11
  74. package/dist/lib/drift.js +58 -7
  75. package/dist/lib/events.d.ts +8 -1
  76. package/dist/lib/events.js +10 -1
  77. package/dist/lib/exec.js +35 -8
  78. package/dist/lib/feed-post.d.ts +28 -1
  79. package/dist/lib/feed-post.js +110 -2
  80. package/dist/lib/fleet/apply.js +8 -1
  81. package/dist/lib/fleet/auth-sync.d.ts +18 -1
  82. package/dist/lib/fleet/auth-sync.js +25 -11
  83. package/dist/lib/fleet/remote-login.js +5 -0
  84. package/dist/lib/git.d.ts +14 -14
  85. package/dist/lib/git.js +41 -44
  86. package/dist/lib/hooks.d.ts +42 -0
  87. package/dist/lib/hooks.js +137 -93
  88. package/dist/lib/hosts/dispatch.d.ts +28 -1
  89. package/dist/lib/hosts/dispatch.js +33 -5
  90. package/dist/lib/hosts/option.js +2 -2
  91. package/dist/lib/hosts/passthrough.d.ts +22 -1
  92. package/dist/lib/hosts/passthrough.js +238 -13
  93. package/dist/lib/hosts/run-target.d.ts +2 -0
  94. package/dist/lib/hosts/run-target.js +1 -0
  95. package/dist/lib/hq/floor.d.ts +1 -1
  96. package/dist/lib/hq/floor.js +6 -1
  97. package/dist/lib/mcp.js +3 -71
  98. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  99. package/dist/lib/menubar/notify-desktop.d.ts +15 -0
  100. package/dist/lib/menubar/notify-desktop.js +41 -5
  101. package/dist/lib/merged-resources.d.ts +11 -0
  102. package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
  103. package/dist/lib/permissions.d.ts +0 -32
  104. package/dist/lib/permissions.js +4 -157
  105. package/dist/lib/picker.js +1 -1
  106. package/dist/lib/platform/process.d.ts +16 -5
  107. package/dist/lib/platform/process.js +54 -0
  108. package/dist/lib/plugins.d.ts +0 -8
  109. package/dist/lib/plugins.js +4 -110
  110. package/dist/lib/project-resources.js +4 -1
  111. package/dist/lib/refresh.d.ts +5 -3
  112. package/dist/lib/refresh.js +7 -5
  113. package/dist/lib/resources/commands.js +4 -1
  114. package/dist/lib/resources/mcp.js +0 -4
  115. package/dist/lib/resources/permissions.d.ts +1 -1
  116. package/dist/lib/resources/permissions.js +1 -5
  117. package/dist/lib/resources/rules.js +4 -1
  118. package/dist/lib/resources/skills.js +4 -1
  119. package/dist/lib/resources/subagents.js +4 -0
  120. package/dist/lib/resources/types.d.ts +1 -1
  121. package/dist/lib/routines.d.ts +20 -0
  122. package/dist/lib/routines.js +6 -0
  123. package/dist/lib/run-defaults.d.ts +1 -0
  124. package/dist/lib/run-defaults.js +9 -0
  125. package/dist/lib/runner.d.ts +0 -12
  126. package/dist/lib/runner.js +49 -10
  127. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  128. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  129. package/dist/lib/secrets/agent.d.ts +16 -0
  130. package/dist/lib/secrets/agent.js +105 -3
  131. package/dist/lib/secrets/audit.d.ts +46 -0
  132. package/dist/lib/secrets/audit.js +56 -0
  133. package/dist/lib/secrets/bundles.d.ts +11 -8
  134. package/dist/lib/secrets/bundles.js +31 -23
  135. package/dist/lib/secrets/index.d.ts +2 -1
  136. package/dist/lib/secrets/index.js +8 -2
  137. package/dist/lib/secrets/remote.d.ts +75 -2
  138. package/dist/lib/secrets/remote.js +139 -5
  139. package/dist/lib/secrets/sync.js +3 -3
  140. package/dist/lib/session/active.d.ts +110 -26
  141. package/dist/lib/session/active.js +161 -50
  142. package/dist/lib/session/actor-sidecar.d.ts +23 -0
  143. package/dist/lib/session/actor-sidecar.js +101 -0
  144. package/dist/lib/session/db.d.ts +4 -1
  145. package/dist/lib/session/db.js +65 -8
  146. package/dist/lib/session/discover.d.ts +4 -0
  147. package/dist/lib/session/discover.js +13 -0
  148. package/dist/lib/session/hook-sessions.d.ts +9 -0
  149. package/dist/lib/session/hook-sessions.js +57 -8
  150. package/dist/lib/session/pid-registry.d.ts +13 -0
  151. package/dist/lib/session/render.d.ts +2 -0
  152. package/dist/lib/session/render.js +1 -1
  153. package/dist/lib/session/types.d.ts +16 -0
  154. package/dist/lib/staleness/detectors/commands.js +2 -2
  155. package/dist/lib/staleness/detectors/permissions.js +1 -45
  156. package/dist/lib/staleness/writers/commands.js +4 -5
  157. package/dist/lib/staleness/writers/hooks.js +1 -1
  158. package/dist/lib/startup/command-registry.d.ts +1 -2
  159. package/dist/lib/startup/command-registry.js +2 -4
  160. package/dist/lib/subagents-registry.js +5 -12
  161. package/dist/lib/subagents.d.ts +0 -10
  162. package/dist/lib/subagents.js +0 -12
  163. package/dist/lib/teams/agents.d.ts +0 -2
  164. package/dist/lib/teams/agents.js +10 -27
  165. package/dist/lib/teams/index.d.ts +1 -1
  166. package/dist/lib/teams/index.js +1 -1
  167. package/dist/lib/types.d.ts +17 -9
  168. package/dist/lib/usage.d.ts +28 -5
  169. package/dist/lib/usage.js +271 -8
  170. package/dist/lib/versions.js +7 -1
  171. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  172. package/dist/lib/watchdog/watchdog.js +25 -13
  173. package/package.json +1 -2
  174. package/dist/commands/check.d.ts +0 -15
  175. package/dist/commands/check.js +0 -180
  176. package/dist/commands/resources.d.ts +0 -5
@@ -0,0 +1,201 @@
1
+ /**
2
+ * `agents setup fleet` — guided Tailscale device onboarding.
3
+ *
4
+ * This wizard is a front door over the existing fleet/device commands: sync
5
+ * Tailscale into the registry, choose auth, render SSH config, test devices, and
6
+ * optionally run the fleet updater without reimplementing those subcommands.
7
+ */
8
+ import chalk from 'chalk';
9
+ import { spawnSync } from 'node:child_process';
10
+ import { getCliLaunch } from '../lib/cli-entry.js';
11
+ import { loadDevices } from '../lib/devices/registry.js';
12
+ import { runDeviceSync } from '../lib/devices/sync.js';
13
+ import { tailscaleStatusJson } from '../lib/devices/tailscale.js';
14
+ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
15
+ function parseAuth(raw) {
16
+ const value = (raw ?? 'key').toLowerCase();
17
+ if (value === 'key' || value === 'password')
18
+ return value;
19
+ throw new Error(`Invalid --auth '${raw}'. Use key or password.`);
20
+ }
21
+ function runAgentsSubcommand(args, opts = {}) {
22
+ const launch = getCliLaunch(args);
23
+ const res = spawnSync(launch.command, launch.args, { stdio: 'inherit', env: process.env });
24
+ if ((res.status ?? 1) === 0)
25
+ return true;
26
+ if (opts.optional)
27
+ return false;
28
+ throw new Error(`agents ${args.join(' ')} failed with exit ${res.status ?? 1}.`);
29
+ }
30
+ function commandExists(command) {
31
+ const res = spawnSync(command, ['version'], { stdio: 'ignore', windowsHide: true });
32
+ if (!res.error)
33
+ return true;
34
+ const fallback = spawnSync(command, ['--version'], { stdio: 'ignore', windowsHide: true });
35
+ return !fallback.error;
36
+ }
37
+ function tailscaleStatusOk() {
38
+ try {
39
+ tailscaleStatusJson();
40
+ return true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ function printTailscaleInstallInstructions() {
47
+ console.error(chalk.red('Tailscale is not installed or not on PATH.'));
48
+ const platform = process.platform;
49
+ console.log(chalk.bold('\nInstall Tailscale, then re-run `agents setup fleet`:'));
50
+ if (platform === 'darwin') {
51
+ console.log(chalk.cyan(' brew install --cask tailscale'));
52
+ console.log(chalk.gray(' or install from https://tailscale.com/download/mac'));
53
+ }
54
+ else if (platform === 'linux') {
55
+ console.log(chalk.cyan(' curl -fsSL https://tailscale.com/install.sh | sh'));
56
+ console.log(chalk.gray(' then run: sudo tailscale up'));
57
+ }
58
+ else if (platform === 'win32') {
59
+ console.log(chalk.cyan(' winget install Tailscale.Tailscale'));
60
+ console.log(chalk.gray(' or install from https://tailscale.com/download/windows'));
61
+ }
62
+ else {
63
+ console.log(chalk.gray(' https://tailscale.com/download'));
64
+ }
65
+ }
66
+ async function resolveInteractiveChoices(opts) {
67
+ if (!isInteractiveTerminal()) {
68
+ return {
69
+ auth: parseAuth(opts.auth),
70
+ writeSshConfig: Boolean(opts.writeSshConfig),
71
+ testConnectivity: Boolean(opts.testConnectivity),
72
+ fleetUpdate: Boolean(opts.fleetUpdate),
73
+ };
74
+ }
75
+ const { confirm, select } = await import('@inquirer/prompts');
76
+ const auth = opts.auth ? parseAuth(opts.auth) : await select({
77
+ message: 'Default SSH auth for registered devices',
78
+ default: 'key',
79
+ choices: [
80
+ { name: 'key — use your SSH agent / on-disk public keys', value: 'key' },
81
+ { name: 'password — read the login password from an agents secrets bundle', value: 'password' },
82
+ ],
83
+ });
84
+ return {
85
+ auth,
86
+ writeSshConfig: opts.writeSshConfig ?? await confirm({
87
+ message: 'Write ~/.ssh/config.d/agents for plain ssh/scp/rsync?',
88
+ default: true,
89
+ }),
90
+ testConnectivity: opts.testConnectivity ?? await confirm({
91
+ message: 'Test connectivity with `agents ssh <device> uname`?',
92
+ default: true,
93
+ }),
94
+ fleetUpdate: opts.fleetUpdate ?? await confirm({
95
+ message: 'Run `agents fleet update` on online devices now?',
96
+ default: false,
97
+ }),
98
+ };
99
+ }
100
+ async function syncDevices(opts) {
101
+ if (isInteractiveTerminal() && !opts.yes) {
102
+ return runAgentsSubcommand(['devices', 'sync'], { optional: true });
103
+ }
104
+ const res = await runDeviceSync({ soft: true });
105
+ if (!res.ok) {
106
+ console.error(chalk.red(`Tailscale discovery failed: ${res.reason ?? 'unknown error'}`));
107
+ return false;
108
+ }
109
+ const extra = res.pending.length ? chalk.gray(` (${res.pending.length} new)`) : '';
110
+ console.log(chalk.green(`Synced ${res.synced} device${res.synced === 1 ? '' : 's'} from Tailscale${extra}.`));
111
+ return true;
112
+ }
113
+ async function applyAuth(auth, opts) {
114
+ const reg = await loadDevices();
115
+ const names = Object.keys(reg).sort();
116
+ if (names.length === 0) {
117
+ console.log(chalk.gray("No devices registered yet. Run 'agents setup fleet --yes' after joining Tailscale."));
118
+ return [];
119
+ }
120
+ if (auth === 'password' && !opts.bundle) {
121
+ throw new Error('--bundle is required with --auth password.');
122
+ }
123
+ for (const name of names) {
124
+ const args = ['devices', 'set', name, '--auth', auth];
125
+ if (opts.bundle)
126
+ args.push('--bundle', opts.bundle);
127
+ if (opts.bundleKey)
128
+ args.push('--bundle-key', opts.bundleKey);
129
+ runAgentsSubcommand(args);
130
+ }
131
+ return names;
132
+ }
133
+ function testConnectivity(names) {
134
+ if (names.length === 0)
135
+ return;
136
+ console.log(chalk.bold('\nTesting connectivity:'));
137
+ for (const name of names) {
138
+ const ok = runAgentsSubcommand(['ssh', name, 'uname'], { optional: true });
139
+ console.log(ok ? chalk.green(` ${name}: ok`) : chalk.yellow(` ${name}: failed`));
140
+ }
141
+ }
142
+ function printOnboardingSummary(names, auth) {
143
+ console.log(chalk.bold('\nFleet setup summary.'));
144
+ console.log(chalk.gray(`devices: ${names.length ? names.join(', ') : 'none registered'}`));
145
+ console.log(chalk.gray(`auth: ${auth}${auth === 'password' ? ' via secrets bundle' : ''}`));
146
+ console.log(chalk.bold('\nTry:'));
147
+ console.log(chalk.cyan(' agents devices list'));
148
+ console.log(chalk.cyan(' agents ssh <device> uname'));
149
+ console.log(chalk.cyan(' agents fleet run hostname'));
150
+ console.log(chalk.cyan(' agents fleet update'));
151
+ }
152
+ export async function runFleetSetupWizard(opts = {}) {
153
+ if (!commandExists('tailscale')) {
154
+ printTailscaleInstallInstructions();
155
+ return false;
156
+ }
157
+ if (!tailscaleStatusOk()) {
158
+ console.error(chalk.red('Tailscale is installed but not logged in or the daemon is unavailable.'));
159
+ console.log(chalk.gray('Run `tailscale login` (or `sudo tailscale up` on Linux), then re-run `agents setup fleet`.'));
160
+ return false;
161
+ }
162
+ const choices = await resolveInteractiveChoices(opts);
163
+ const synced = await syncDevices(opts);
164
+ if (!synced)
165
+ return false;
166
+ const names = await applyAuth(choices.auth, opts);
167
+ if (choices.writeSshConfig)
168
+ runAgentsSubcommand(['devices', 'render', '--write']);
169
+ if (choices.testConnectivity)
170
+ testConnectivity(names);
171
+ if (choices.fleetUpdate)
172
+ runAgentsSubcommand(['fleet', 'update']);
173
+ printOnboardingSummary(names, choices.auth);
174
+ return true;
175
+ }
176
+ /** Register `agents setup fleet` under the parent `setup` command. */
177
+ export function registerSetupFleetCommand(setupCmd) {
178
+ setupCmd
179
+ .command('fleet')
180
+ .description('Set up `agents fleet` — discover Tailscale devices, choose auth, render SSH config, and test connectivity.')
181
+ .option('--auth <method>', 'device auth method: key or password', 'key')
182
+ .option('--bundle <name>', 'secrets bundle holding the login password when --auth password is used')
183
+ .option('--bundle-key <key>', "key within the secrets bundle (default 'password')")
184
+ .option('--yes', 'non-interactive: register every discovered, non-ignored Tailscale node')
185
+ .option('--write-ssh-config', 'write ~/.ssh/config.d/agents after syncing')
186
+ .option('--test-connectivity', 'run `agents ssh <device> uname` for each registered device')
187
+ .option('--fleet-update', 'run `agents fleet update` after setup')
188
+ .action(async (options) => {
189
+ try {
190
+ await runFleetSetupWizard(options);
191
+ }
192
+ catch (err) {
193
+ if (isPromptCancelled(err)) {
194
+ console.log(chalk.yellow('\nCancelled'));
195
+ return;
196
+ }
197
+ console.error(chalk.red(err.message));
198
+ process.exitCode = 1;
199
+ }
200
+ });
201
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `agents setup secrets` — guided defaults for `agents secrets` onboarding.
3
+ *
4
+ * The wizard writes setup preferences and delegates imports to the existing
5
+ * `agents secrets import` command, keeping bundle storage as the source of truth.
6
+ */
7
+ import type { Command } from 'commander';
8
+ interface SetupSecretsOptions {
9
+ backend?: string;
10
+ policy?: string;
11
+ importFrom?: string;
12
+ bundle?: string;
13
+ vault?: string;
14
+ iUnderstand?: boolean;
15
+ force?: boolean;
16
+ }
17
+ export declare function runSecretsSetupWizard(opts?: SetupSecretsOptions): Promise<boolean>;
18
+ /** Register `agents setup secrets` under the parent `setup` command. */
19
+ export declare function registerSetupSecretsCommand(setupCmd: Command): void;
20
+ export {};
@@ -0,0 +1,223 @@
1
+ /**
2
+ * `agents setup secrets` — guided defaults for `agents secrets` onboarding.
3
+ *
4
+ * The wizard writes setup preferences and delegates imports to the existing
5
+ * `agents secrets import` command, keeping bundle storage as the source of truth.
6
+ */
7
+ import chalk from 'chalk';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { getCliLaunch } from '../lib/cli-entry.js';
12
+ import { getHistoryDir, updateMeta } from '../lib/state.js';
13
+ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
14
+ function defaultBackendForPlatform(platform = process.platform) {
15
+ return platform === 'darwin' ? 'keychain' : 'file';
16
+ }
17
+ function setupSecretsPrefsPath() {
18
+ return path.join(getHistoryDir(), 'setup', 'secrets.json');
19
+ }
20
+ function parseBackend(raw) {
21
+ const value = (raw ?? defaultBackendForPlatform()).toLowerCase();
22
+ if (value === 'keychain' || value === 'file' || value === 'vault')
23
+ return value;
24
+ throw new Error(`Invalid --backend '${raw}'. Use keychain, file, or vault.`);
25
+ }
26
+ function parsePolicy(raw) {
27
+ const value = (raw ?? 'daily').toLowerCase();
28
+ if (value === 'daily' || value === 'always' || value === 'never')
29
+ return value;
30
+ throw new Error(`Invalid --policy '${raw}'. Use daily, always, or never.`);
31
+ }
32
+ function parseImportSource(raw) {
33
+ const value = (raw ?? 'none').toLowerCase();
34
+ if (value === 'none' || value === 'env' || value === '1password' || value === 'icloud')
35
+ return value;
36
+ throw new Error(`Invalid --import-from '${raw}'. Use none, env, 1password, or icloud.`);
37
+ }
38
+ function hasCommand(command) {
39
+ const res = spawnSync(command, ['--version'], { stdio: 'ignore', windowsHide: true });
40
+ return !res.error;
41
+ }
42
+ function saveSetupSecretsPrefs(prefs) {
43
+ const file = setupSecretsPrefsPath();
44
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
45
+ fs.writeFileSync(file, JSON.stringify(prefs, null, 2) + '\n', { mode: 0o600 });
46
+ return file;
47
+ }
48
+ function applySecretsDefaults(backend, policy) {
49
+ updateMeta((meta) => ({
50
+ ...meta,
51
+ secrets: {
52
+ ...meta.secrets,
53
+ backend,
54
+ ...(policy === 'never' ? {} : { policy }),
55
+ },
56
+ }));
57
+ }
58
+ function backendArgs(backend) {
59
+ return backend === 'vault' ? ['--synced'] : ['--backend', backend];
60
+ }
61
+ function printBackendNotes(backend) {
62
+ if (backend === 'keychain') {
63
+ console.log(chalk.gray('backend: keychain — macOS reads may ask for Touch ID or the device password.'));
64
+ }
65
+ else if (backend === 'file') {
66
+ console.log(chalk.gray('backend: file — set AGENTS_SECRETS_PASSPHRASE for headless encrypted-file reads.'));
67
+ }
68
+ else {
69
+ console.log(chalk.gray('backend: vault — synced ~/.agents/vault.age storage; unlock it with agents login.'));
70
+ }
71
+ }
72
+ function runAgentsSubcommand(args) {
73
+ const launch = getCliLaunch(args);
74
+ const res = spawnSync(launch.command, launch.args, { stdio: 'inherit', env: process.env });
75
+ if ((res.status ?? 1) !== 0) {
76
+ throw new Error(`agents ${args.join(' ')} failed with exit ${res.status ?? 1}.`);
77
+ }
78
+ }
79
+ async function maybeImportSecrets(source, opts) {
80
+ if (source === 'none')
81
+ return;
82
+ let bundle = opts.bundle;
83
+ if (!bundle) {
84
+ if (!isInteractiveTerminal()) {
85
+ throw new Error(`--bundle is required with --import-from ${source} in a non-interactive shell.`);
86
+ }
87
+ const { input } = await import('@inquirer/prompts');
88
+ bundle = await input({
89
+ message: 'Import into which bundle?',
90
+ default: 'default',
91
+ validate: (v) => (v.trim().length > 0 ? true : 'Enter a bundle name.'),
92
+ });
93
+ }
94
+ const args = ['secrets', 'import', bundle, ...backendArgs(opts.backend)];
95
+ if (opts.force)
96
+ args.push('--force');
97
+ if (source === 'env') {
98
+ let envPath = opts.vault;
99
+ if (!envPath) {
100
+ if (!isInteractiveTerminal()) {
101
+ throw new Error('--vault must name the .env path when --import-from env is used non-interactively.');
102
+ }
103
+ const { input } = await import('@inquirer/prompts');
104
+ envPath = await input({
105
+ message: '.env file path',
106
+ default: '.env',
107
+ validate: (v) => (v.trim().length > 0 ? true : 'Enter a .env path.'),
108
+ });
109
+ }
110
+ args.push('--from', envPath);
111
+ }
112
+ else if (source === '1password') {
113
+ const vault = opts.vault;
114
+ if (!vault)
115
+ throw new Error('--vault <name> is required with --import-from 1password.');
116
+ args.push('--from', `1password:${vault}`);
117
+ }
118
+ else {
119
+ args.push('--from', 'icloud');
120
+ }
121
+ runAgentsSubcommand(args);
122
+ }
123
+ async function resolveInteractiveChoices(opts) {
124
+ if (!isInteractiveTerminal()) {
125
+ return {
126
+ backend: parseBackend(opts.backend),
127
+ policy: parsePolicy(opts.policy),
128
+ importSource: parseImportSource(opts.importFrom),
129
+ };
130
+ }
131
+ const { select } = await import('@inquirer/prompts');
132
+ const backend = opts.backend ? parseBackend(opts.backend) : await select({
133
+ message: 'Default secrets backend',
134
+ default: defaultBackendForPlatform(),
135
+ choices: [
136
+ { name: 'keychain — local OS keychain; reads may ask for Touch ID/device password', value: 'keychain' },
137
+ { name: 'file — passphrase-encrypted file store for headless machines', value: 'file' },
138
+ { name: 'vault — synced ~/.agents/vault.age file; requires agents login', value: 'vault' },
139
+ ],
140
+ });
141
+ const policy = opts.policy ? parsePolicy(opts.policy) : await select({
142
+ message: 'Default prompt policy',
143
+ default: 'daily',
144
+ choices: [
145
+ { name: 'daily — ask once, then hold for the configured window', value: 'daily' },
146
+ { name: 'always — ask on every read', value: 'always' },
147
+ { name: 'never — no biometry ACL; use only for automation-only bundles', value: 'never' },
148
+ ],
149
+ });
150
+ const importChoices = [
151
+ { name: 'skip import', value: 'none' },
152
+ { name: 'import from .env file', value: 'env' },
153
+ ...(hasCommand('op') ? [{ name: 'import from 1Password vault', value: '1password' }] : []),
154
+ { name: 'import legacy iCloud Keychain bundles', value: 'icloud' },
155
+ ];
156
+ const importSource = opts.importFrom ? parseImportSource(opts.importFrom) : await select({
157
+ message: 'Import existing secrets now?',
158
+ default: 'none',
159
+ choices: importChoices,
160
+ });
161
+ return { backend, policy, importSource };
162
+ }
163
+ function printOnboardingSummary(backend, policy, prefsFile) {
164
+ console.log(chalk.bold('\nSecrets setup saved.'));
165
+ console.log(chalk.gray(`preferences: ${prefsFile}`));
166
+ printBackendNotes(backend);
167
+ if (policy === 'never') {
168
+ console.log(chalk.yellow('policy: never is saved as a setup preference; create automation-only bundles with --policy never --i-understand.'));
169
+ }
170
+ else {
171
+ console.log(chalk.gray(`policy: ${policy} — used by bundles that do not set their own policy.`));
172
+ }
173
+ console.log(chalk.bold('\nTry:'));
174
+ console.log(chalk.cyan(` agents secrets create prod ${backendArgs(backend).join(' ')} --policy ${policy === 'never' ? 'daily' : policy}`));
175
+ console.log(chalk.cyan(' agents secrets add prod API_KEY'));
176
+ console.log(chalk.cyan(' agents run claude "deploy" --secrets prod'));
177
+ }
178
+ export async function runSecretsSetupWizard(opts = {}) {
179
+ const { backend, policy, importSource } = await resolveInteractiveChoices(opts);
180
+ if (policy === 'never' && !opts.iUnderstand && !isInteractiveTerminal()) {
181
+ throw new Error("Refusing to save policy 'never' non-interactively without --i-understand.");
182
+ }
183
+ applySecretsDefaults(backend, policy);
184
+ const prefsFile = saveSetupSecretsPrefs({
185
+ defaultBackend: backend,
186
+ defaultPolicy: policy,
187
+ updatedAt: new Date().toISOString(),
188
+ });
189
+ await maybeImportSecrets(importSource, {
190
+ bundle: opts.bundle,
191
+ vault: opts.vault,
192
+ backend,
193
+ force: opts.force,
194
+ });
195
+ printOnboardingSummary(backend, policy, prefsFile);
196
+ return true;
197
+ }
198
+ /** Register `agents setup secrets` under the parent `setup` command. */
199
+ export function registerSetupSecretsCommand(setupCmd) {
200
+ setupCmd
201
+ .command('secrets')
202
+ .description('Configure `agents secrets` defaults and optionally import existing secrets.')
203
+ .option('--backend <backend>', 'default backend: keychain, file, or vault')
204
+ .option('--policy <policy>', 'default prompt policy: daily, always, or never')
205
+ .option('--import-from <source>', 'optional import source: none, env, 1password, or icloud')
206
+ .option('--bundle <name>', 'bundle name for optional imports')
207
+ .option('--vault <name-or-path>', '1Password vault name, or .env path with --import-from env')
208
+ .option('--i-understand', 'allow saving the "never" policy preference without an interactive prompt')
209
+ .option('--force', 'pass --force through to optional imports')
210
+ .action(async (options) => {
211
+ try {
212
+ await runSecretsSetupWizard(options);
213
+ }
214
+ catch (err) {
215
+ if (isPromptCancelled(err)) {
216
+ console.log(chalk.yellow('\nCancelled'));
217
+ return;
218
+ }
219
+ console.error(chalk.red(err.message));
220
+ process.exitCode = 1;
221
+ }
222
+ });
223
+ }
@@ -22,6 +22,8 @@ import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.j
22
22
  import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
23
23
  import { registerSetupShareCommand, runShareWizard } from './setup-share.js';
24
24
  import { registerSetupMineCommand } from './setup-mine.js';
25
+ import { registerSetupSecretsCommand } from './setup-secrets.js';
26
+ import { registerSetupFleetCommand } from './setup-fleet.js';
25
27
  const HOME = os.homedir();
26
28
  /**
27
29
  * Import an existing unmanaged agent installation into agents-cli.
@@ -247,6 +249,8 @@ async function runSetupHub() {
247
249
  { name: 'browser — drive a real Chrome/Brave/Edge for web automation', value: 'browser' },
248
250
  { name: 'computer — control native macOS apps (screenshot, click, type)', value: 'computer' },
249
251
  { name: 'share — publish shareable links (Cloudflare R2 + Worker)', value: 'share' },
252
+ { name: 'secrets — choose storage defaults and import existing credentials', value: 'secrets' },
253
+ { name: 'fleet — discover Tailscale devices and configure SSH access', value: 'fleet' },
250
254
  ],
251
255
  });
252
256
  for (const pick of picks) {
@@ -257,6 +261,10 @@ async function runSetupHub() {
257
261
  await runComputerWizard();
258
262
  else if (pick === 'share')
259
263
  await runShareWizard();
264
+ else if (pick === 'secrets')
265
+ await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
266
+ else if (pick === 'fleet')
267
+ await import('./setup-fleet.js').then((m) => m.runFleetSetupWizard());
260
268
  }
261
269
  }
262
270
  catch (err) {
@@ -272,11 +280,13 @@ export function registerSetupCommand(program) {
272
280
  .description('First-time setup. Clones a config repo and installs agent CLIs.')
273
281
  .option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
274
282
  .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
275
- // Capability subcommands: `agents setup browser|computer|share|mine`.
283
+ // Capability subcommands: `agents setup browser|computer|share|mine|secrets|fleet`.
276
284
  registerSetupBrowserCommand(setupCmd);
277
285
  registerSetupComputerCommand(setupCmd);
278
286
  registerSetupShareCommand(setupCmd);
279
287
  registerSetupMineCommand(setupCmd);
288
+ registerSetupSecretsCommand(setupCmd);
289
+ registerSetupFleetCommand(setupCmd);
280
290
  setHelpSections(setupCmd, {
281
291
  examples: `
282
292
  # First-time setup (clones the system repo into ~/.agents/.system/)
@@ -289,20 +299,24 @@ export function registerSetupCommand(program) {
289
299
  agents setup browser
290
300
  agents setup computer
291
301
  agents setup share
302
+ agents setup secrets
303
+ agents setup fleet
292
304
  `,
293
305
  notes: `
294
306
  What it does:
295
307
  1. Clones the system repo into ~/.agents/.system/
296
308
  2. Imports any unmanaged agent installations it finds
297
- 3. On a TTY, offers to set up optional capabilities (browser/computer/share)
309
+ 3. On a TTY, offers to set up optional capabilities (browser/computer/share/secrets/fleet)
298
310
 
299
311
  Capability setup can also be run any time on its own:
300
312
  agents setup browser # detect a browser + create the default profile
301
313
  agents setup computer # install the signed macOS helper + grant permissions
302
314
  agents setup share # provision or join a Cloudflare share endpoint
315
+ agents setup secrets # choose secrets backend/policy defaults + import
316
+ agents setup fleet # discover Tailscale devices + configure SSH access
303
317
 
304
318
  To install CLIs from agents.yaml and sync resources into version homes:
305
- agents repo refresh -y
319
+ agents sync --local -y
306
320
  `,
307
321
  });
308
322
  setupCmd.action(async (options) => {
@@ -81,7 +81,7 @@ When to use:
81
81
  skillsCmd
82
82
  .command('add [source]')
83
83
  .description('Install skills from a source (GitHub, local) or pick from central storage')
84
- .option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or gemini@default')
84
+ .option('-a, --agents <list>', 'Targets: claude, codex@0.116.0, or antigravity@default')
85
85
  .option('--names <list>', 'Skill names from ~/.agents/skills/ (comma-separated)')
86
86
  .option('-y, --yes', 'Skip all prompts')
87
87
  .addHelpText('after', `
@@ -31,7 +31,7 @@
31
31
  */
32
32
  import * as path from 'path';
33
33
  import chalk from 'chalk';
34
- import { agentLabel, resolveAgentName, ALL_AGENT_IDS } from '../lib/agents.js';
34
+ import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
35
35
  import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildRepoScopedSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/versions.js';
36
36
  import { capableAgents } from '../lib/capabilities.js';
37
37
  import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
@@ -128,7 +128,7 @@ function repoChoiceLabel(repo) {
128
128
  async function runInteractiveReconcile(opts, outLog, errLog) {
129
129
  const { checkbox } = await import('@inquirer/prompts');
130
130
  const cwd = opts.cwd || process.cwd();
131
- const installedAgents = ALL_AGENT_IDS.filter((a) => listInstalledVersions(a).length > 0);
131
+ const installedAgents = MANAGED_AGENT_IDS.filter((a) => listInstalledVersions(a).length > 0);
132
132
  if (installedAgents.length === 0) {
133
133
  errLog(chalk.red('No agents installed. Install one: agents add claude@latest'));
134
134
  process.exitCode = 1;
@@ -311,6 +311,11 @@ async function runSync(agentSpec, repoArg, opts) {
311
311
  }
312
312
  agentId = resolved;
313
313
  }
314
+ if (agentId && isAgentHardDeprecated(agentId)) {
315
+ errLog(chalk.red(hardDeprecationError(agentId)));
316
+ process.exitCode = 1;
317
+ return;
318
+ }
314
319
  if (opts.agentVersion) {
315
320
  // Legacy flag and the launch-shim hot path (`--agent-version <concrete>`):
316
321
  // pass through verbatim. Selector aliases are a positional-spec feature.
@@ -12,7 +12,6 @@ import { itemPicker } from '../lib/picker.js';
12
12
  const AGENT_LABEL = {
13
13
  claude: 'Claude',
14
14
  codex: 'Codex',
15
- gemini: 'Gemini',
16
15
  cursor: 'Cursor',
17
16
  opencode: 'OpenCode',
18
17
  };
@@ -6,7 +6,33 @@
6
6
  * dependencies between teammates, and clean up when work is done.
7
7
  */
8
8
  import type { Command } from 'commander';
9
- import { AgentManager, AgentStatus } from '../lib/teams/agents.js';
9
+ import { AgentManager, AgentStatus, type TaskType } from '../lib/teams/agents.js';
10
+ import { type TaskInfo } from '../lib/teams/api.js';
11
+ import { type TeamRow } from './teams-picker.js';
12
+ type TeamRegistry = Record<string, {
13
+ created_at: string;
14
+ description?: string;
15
+ }>;
16
+ export interface TeamListAgentSnapshot {
17
+ agent_id: string;
18
+ task_name: string;
19
+ agent_type: string;
20
+ status: string;
21
+ prompt: string;
22
+ started_at: string;
23
+ completed_at: string | null;
24
+ workspace_dir: string | null;
25
+ version: string | null;
26
+ remote_session_id: string | null;
27
+ name: string | null;
28
+ after: string[];
29
+ task_type: TaskType | null;
30
+ host: string | null;
31
+ mode: string | null;
32
+ cloud_session_id: string | null;
33
+ cloud_provider: string | null;
34
+ pr_url: string | null;
35
+ }
10
36
  /** Where `teams message`/`teams resume` routes a follow-up, by teammate status. */
11
37
  export type TeamMessageRoute = {
12
38
  kind: 'steer';
@@ -32,6 +58,11 @@ export declare function decideTeamMessageRoute(status: AgentStatus, hasMessage:
32
58
  * the teammate itself so we don't need the original --cloud CLI args.
33
59
  */
34
60
  export declare function wireCloudDispatcher(mgr: AgentManager): void;
61
+ export declare function buildTeamRowsFromSnapshots(registry: TeamRegistry, agents: TeamListAgentSnapshot[]): {
62
+ rows: TeamRow[];
63
+ teams: TaskInfo[];
64
+ names: string[];
65
+ };
35
66
  /**
36
67
  * `teams add --remote-cwd` is a no-op trap. `--remote-cwd` comes from the shared
37
68
  * `--host` option family (option.ts) and is meaningful for commands that *route*
@@ -45,3 +76,4 @@ export declare function wireCloudDispatcher(mgr: AgentManager): void;
45
76
  export declare function remoteCwdOnAddError(team: string): string;
46
77
  /** Register the `agents teams` command tree (list, create, add, status, start, remove, disband, logs, doctor). */
47
78
  export declare function registerTeamsCommands(program: Command): void;
79
+ export {};