@phnx-labs/agents-cli 1.22.25 → 1.22.26

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 (82) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/README.md +17 -2
  3. package/dist/bin/agents +0 -0
  4. package/dist/browser.js +14 -4
  5. package/dist/commands/apply.js +52 -8
  6. package/dist/commands/browser.js +35 -0
  7. package/dist/commands/doctor.js +8 -0
  8. package/dist/commands/insights.d.ts +25 -19
  9. package/dist/commands/insights.js +107 -33
  10. package/dist/commands/reconnect.d.ts +46 -0
  11. package/dist/commands/reconnect.js +109 -0
  12. package/dist/commands/routines.js +2 -2
  13. package/dist/commands/secrets.d.ts +2 -8
  14. package/dist/commands/secrets.js +29 -105
  15. package/dist/commands/sessions.js +4 -0
  16. package/dist/commands/setup-secrets.d.ts +1 -0
  17. package/dist/commands/setup-secrets.js +1 -1
  18. package/dist/commands/setup.d.ts +26 -3
  19. package/dist/commands/setup.js +105 -46
  20. package/dist/commands/teams.d.ts +6 -0
  21. package/dist/commands/teams.js +43 -0
  22. package/dist/commands/trends.d.ts +8 -0
  23. package/dist/commands/trends.js +10 -156
  24. package/dist/index.js +1 -1
  25. package/dist/lib/agents.d.ts +11 -0
  26. package/dist/lib/agents.js +29 -2
  27. package/dist/lib/analytics/dashboard.d.ts +10 -6
  28. package/dist/lib/analytics/dashboard.js +6 -4
  29. package/dist/lib/analytics/mix-commands.d.ts +53 -0
  30. package/dist/lib/analytics/mix-commands.js +229 -0
  31. package/dist/lib/analytics/recipes.d.ts +19 -14
  32. package/dist/lib/analytics/recipes.js +4 -2
  33. package/dist/lib/browser/ipc.d.ts +26 -0
  34. package/dist/lib/browser/ipc.js +139 -24
  35. package/dist/lib/browser/profiles.d.ts +11 -0
  36. package/dist/lib/browser/profiles.js +1 -1
  37. package/dist/lib/browser/stream.d.ts +14 -0
  38. package/dist/lib/browser/stream.js +71 -0
  39. package/dist/lib/channels/owner-sink.d.ts +27 -0
  40. package/dist/lib/channels/owner-sink.js +93 -0
  41. package/dist/lib/devices/doctor-findings.d.ts +7 -1
  42. package/dist/lib/devices/doctor-findings.js +33 -1
  43. package/dist/lib/fleet/apply.d.ts +59 -3
  44. package/dist/lib/fleet/apply.js +183 -6
  45. package/dist/lib/fleet/types.d.ts +21 -2
  46. package/dist/lib/hooks/cache.js +15 -0
  47. package/dist/lib/hosts/passthrough.d.ts +23 -0
  48. package/dist/lib/hosts/passthrough.js +45 -0
  49. package/dist/lib/hosts/ready.d.ts +2 -0
  50. package/dist/lib/hosts/ready.js +10 -1
  51. package/dist/lib/hosts/reconnect.d.ts +14 -12
  52. package/dist/lib/hosts/reconnect.js +41 -40
  53. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  54. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  55. package/dist/lib/routines.js +14 -2
  56. package/dist/lib/runner.d.ts +0 -3
  57. package/dist/lib/runner.js +1 -14
  58. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  59. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  60. package/dist/lib/secrets/push.d.ts +94 -0
  61. package/dist/lib/secrets/push.js +145 -0
  62. package/dist/lib/secrets/reaper.d.ts +15 -1
  63. package/dist/lib/secrets/reaper.js +30 -3
  64. package/dist/lib/session/db.d.ts +21 -3
  65. package/dist/lib/session/db.js +221 -13
  66. package/dist/lib/session/discover.d.ts +1 -0
  67. package/dist/lib/session/discover.js +115 -19
  68. package/dist/lib/session/insights.d.ts +18 -0
  69. package/dist/lib/session/insights.js +143 -1
  70. package/dist/lib/session/tool-index.js +133 -22
  71. package/dist/lib/session/tool-store.d.ts +26 -2
  72. package/dist/lib/session/tool-store.js +36 -17
  73. package/dist/lib/ssh-exec.js +8 -2
  74. package/dist/lib/startup/command-registry.d.ts +1 -0
  75. package/dist/lib/startup/command-registry.js +4 -0
  76. package/dist/lib/teams/agents.d.ts +13 -0
  77. package/dist/lib/teams/agents.js +75 -7
  78. package/dist/lib/teams/placement-probe.d.ts +21 -0
  79. package/dist/lib/teams/placement-probe.js +135 -0
  80. package/dist/lib/teams/scheduler.d.ts +74 -1
  81. package/dist/lib/teams/scheduler.js +187 -10
  82. package/package.json +1 -1
@@ -57,6 +57,7 @@ import { registerSessionsFavoriteCommand } from './sessions-favorite.js';
57
57
  import { isFavorite, listFavorites } from '../lib/session/favorites.js';
58
58
  import { registerGoCommand } from './go.js';
59
59
  import { registerFocusCommand } from './focus.js';
60
+ import { registerReconnectCommand } from './reconnect.js';
60
61
  import { registerDetachCommand } from './detach.js';
61
62
  import { registerAttachCommand } from './attach.js';
62
63
  import { registerSessionsInjectCommand } from './sessions-inject.js';
@@ -66,6 +67,7 @@ import { registerSessionsImportCommand } from './sessions-import.js';
66
67
  import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
67
68
  import { registerSessionsBackfillCommand } from './sessions-backfill.js';
68
69
  import { registerSessionsStatsCommand } from './sessions-stats.js';
70
+ import { registerSessionsInsightsCommand } from './insights.js';
69
71
  import { registerSessionsOptimizeCommand } from './sessions-optimize.js';
70
72
  import { runBrowserSessions } from '../lib/browser/sessions-list.js';
71
73
  import { countToolProgramOccurrences, parseToolProgramCountClause, readToolIndexCoverage, searchToolCalls, TOOL_QUERY_MAX_CLAUSE_BYTES, TOOL_QUERY_MAX_CLAUSES, TOOL_QUERY_MAX_RESULT_SESSIONS, serializeToolSearchEnvelope, toolSearchRemoteReceiveBudget, } from '../lib/session/tool-index.js';
@@ -4082,6 +4084,7 @@ export function registerSessionsCommands(program) {
4082
4084
  registerSessionsFavoriteCommand(sessionsCmd);
4083
4085
  registerGoCommand(sessionsCmd);
4084
4086
  registerFocusCommand(sessionsCmd);
4087
+ registerReconnectCommand(sessionsCmd);
4085
4088
  registerDetachCommand(sessionsCmd);
4086
4089
  registerAttachCommand(sessionsCmd);
4087
4090
  registerSessionsInjectCommand(sessionsCmd);
@@ -4092,6 +4095,7 @@ export function registerSessionsCommands(program) {
4092
4095
  registerSessionsMigrationsCommand(sessionsCmd);
4093
4096
  registerSessionsBackfillCommand(sessionsCmd);
4094
4097
  registerSessionsStatsCommand(sessionsCmd);
4098
+ registerSessionsInsightsCommand(sessionsCmd);
4095
4099
  registerSessionsOptimizeCommand(sessionsCmd);
4096
4100
  // Observe-umbrella alias (Phase 3): roster → sessions --active.
4097
4101
  registerSessionsObserveAliases(program);
@@ -15,6 +15,7 @@ interface SetupSecretsOptions {
15
15
  iUnderstand?: boolean;
16
16
  force?: boolean;
17
17
  }
18
+ export declare function setupSecretsPrefsPath(): string;
18
19
  /** The wizard's `--policy` shares the canonical parser with `agents secrets
19
20
  * policy`, so it accepts `hold` (and the `daily`/`session` aliases) rather than
20
21
  * carrying its own copy that knows only the retired vocabulary. `parsePolicyOpt`
@@ -15,7 +15,7 @@ import { parsePolicyOpt } from './secrets.js';
15
15
  function defaultBackendForPlatform(platform = process.platform) {
16
16
  return platform === 'darwin' ? 'keychain' : 'file';
17
17
  }
18
- function setupSecretsPrefsPath() {
18
+ export function setupSecretsPrefsPath() {
19
19
  return path.join(getHistoryDir(), 'setup', 'secrets.json');
20
20
  }
21
21
  function parseBackend(raw) {
@@ -5,12 +5,14 @@
5
5
  * ~/.agents/.system/ and installs agent CLIs with resource syncing.
6
6
  */
7
7
  import type { Command } from 'commander';
8
- /** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
9
- export declare function runSetup(program: Command, options?: {
8
+ interface RunSetupOptions {
10
9
  force?: boolean;
11
10
  suppressFooter?: boolean;
12
11
  systemRepo?: boolean;
13
- }): Promise<void>;
12
+ runHub?: () => Promise<void>;
13
+ }
14
+ /** First-run setup. Clones ~/.agents/.system/ from the system repo if needed. */
15
+ export declare function runSetup(program: Command, options?: RunSetupOptions): Promise<void>;
14
16
  /**
15
17
  * Ensure the system repo exists before running a command that needs it.
16
18
  * If ~/.agents/.system/ is not a git repo AND we're in an interactive TTY,
@@ -18,5 +20,26 @@ export declare function runSetup(program: Command, options?: {
18
20
  * error and exit.
19
21
  */
20
22
  export declare function ensureInitialized(program: Command): Promise<void>;
23
+ /**
24
+ * Interactive "what else do you want to set up?" menu shown after the bare
25
+ * `agents setup` finishes on a TTY. Each pick runs that capability's guided
26
+ * wizard. Never throws — a cancel or an optional wizard's error just skips the
27
+ * rest and lets core setup complete.
28
+ */
29
+ export type SetupPhase = 'browser' | 'computer' | 'share' | 'secrets' | 'fleet' | 'watchdog' | 'preferences';
30
+ export type SetupStatusState = 'ready' | 'missing' | 'n/a';
31
+ export interface SetupStatusRow {
32
+ phase: 'core' | SetupPhase;
33
+ state: SetupStatusState;
34
+ detail: string;
35
+ }
36
+ export declare function getSetupStatus(): Promise<SetupStatusRow[]>;
37
+ export declare function renderSetupStatus(rows: SetupStatusRow[]): void;
38
+ export declare function runSetupHub(deps?: {
39
+ interactive?: boolean;
40
+ selectPhase?: (rows: SetupStatusRow[]) => Promise<SetupPhase | 'exit'>;
41
+ runPhase?: (phase: SetupPhase) => Promise<void>;
42
+ }): Promise<void>;
21
43
  /** Register the `agents setup` command and its capability subcommands. */
22
44
  export declare function registerSetupCommand(program: Command): void;
45
+ export {};
@@ -26,6 +26,13 @@ import { registerSetupSecretsCommand } from './setup-secrets.js';
26
26
  import { registerSetupFleetCommand } from './setup-fleet.js';
27
27
  import { registerSetupWatchdogCommand, runWatchdogSetupWizard } from './setup-watchdog.js';
28
28
  import { runPreferencesStep } from './setup-preferences.js';
29
+ import { getConfiguredDefaultProfileName, getProfile, isProfileLaunchableHere, DEFAULT_BROWSER_PROFILE_NAME } from '../lib/browser/profiles.js';
30
+ import { listInstalledBrowsers } from '../lib/browser/chrome.js';
31
+ import { probeComputerTrust } from './computer.js';
32
+ import { readShareConfig } from '../lib/share/config.js';
33
+ import { loadDevices } from '../lib/devices/registry.js';
34
+ import { getConfigValue } from '../lib/device-config.js';
35
+ import { setupSecretsPrefsPath } from './setup-secrets.js';
29
36
  const HOME = os.homedir();
30
37
  /**
31
38
  * Import an existing unmanaged agent installation into agents-cli.
@@ -70,9 +77,9 @@ export async function runSetup(program, options = {}) {
70
77
  const agentsDir = getAgentsDir();
71
78
  const alreadyConfigured = isGitRepo(agentsDir);
72
79
  if (alreadyConfigured && !options.force) {
73
- console.log(chalk.gray('~/.agents/.system/ is already set up.'));
74
- console.log(chalk.gray('\nTo sync updates: agents repo pull system'));
75
- console.log(chalk.gray('To re-run setup: agents setup --force'));
80
+ if (options.suppressFooter)
81
+ return;
82
+ await (options.runHub ?? runSetupHub)();
76
83
  return;
77
84
  }
78
85
  // Detect existing installations BEFORE cloning (they won't exist after if we import)
@@ -193,11 +200,7 @@ export async function runSetup(program, options = {}) {
193
200
  // Fresh-machine hub: offer to set up the optional capabilities that need their
194
201
  // own guided flow. TTY-only and fully opt-in — a non-interactive `agents setup`
195
202
  // stops at the system-repo bootstrap above, unchanged.
196
- await runSetupHub();
197
- // Preferences step: the two questions that keep agents off the wrong machine —
198
- // which box you sit at (interactive host) and which browser agents drive here.
199
- // TTY-only, skippable, and writes the same keys as `agents devices …`.
200
- await runPreferencesStep();
203
+ await (options.runHub ?? runSetupHub)();
201
204
  console.log(chalk.bold('\nSetup complete. Try:'));
202
205
  console.log(chalk.cyan(' agents view ') + chalk.gray(' # see what\'s installed'));
203
206
  console.log(chalk.cyan(' agents run <agent> "hello" ') + chalk.gray(' # run an agent'));
@@ -229,42 +232,88 @@ export async function ensureInitialized(program) {
229
232
  }
230
233
  await runSetup(program, { suppressFooter: true });
231
234
  }
232
- /**
233
- * Interactive "what else do you want to set up?" menu shown after the bare
234
- * `agents setup` finishes on a TTY. Each pick runs that capability's guided
235
- * wizard. Never throws a cancel or an optional wizard's error just skips the
236
- * rest and lets core setup complete.
237
- */
238
- async function runSetupHub() {
239
- if (!isInteractiveTerminal())
235
+ export async function getSetupStatus() {
236
+ const configuredBrowserProfile = getConfiguredDefaultProfileName();
237
+ const browserProfile = await getProfile(configuredBrowserProfile ?? DEFAULT_BROWSER_PROFILE_NAME);
238
+ const browserReady = browserProfile !== null && isProfileLaunchableHere(browserProfile);
239
+ const installedBrowsers = listInstalledBrowsers();
240
+ const computerState = process.platform === 'darwin' ? (await probeComputerTrust() ? 'ready' : 'missing') : 'n/a';
241
+ const devices = await loadDevices();
242
+ const coreReady = isGitRepo(getAgentsDir());
243
+ const secretsReady = fs.existsSync(setupSecretsPrefsPath());
244
+ const shareConfig = readShareConfig();
245
+ const watchdogEnabled = getConfigValue('watchdog.enabled').value === true;
246
+ const interactiveHost = getConfigValue('interactive.host').value;
247
+ const defaultBrowser = getConfigValue('browser.profile').value;
248
+ return [
249
+ { phase: 'core', state: coreReady ? 'ready' : 'missing', detail: coreReady ? 'system repo ready' : 'system repo missing' },
250
+ { phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${browserProfile.name}` : browserProfile ? `profile ${browserProfile.name} cannot launch here` : installedBrowsers.length ? 'no default profile' : 'no supported browser found' },
251
+ { phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
252
+ { phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
253
+ { phase: 'fleet', state: Object.keys(devices).length ? 'ready' : 'missing', detail: Object.keys(devices).length ? `${Object.keys(devices).length} device${Object.keys(devices).length === 1 ? '' : 's'} registered` : 'no devices registered' },
254
+ { phase: 'share', state: shareConfig ? 'ready' : 'missing', detail: shareConfig?.baseUrl ?? 'endpoint not configured' },
255
+ { phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
256
+ { phase: 'preferences', state: interactiveHost || defaultBrowser ? 'ready' : 'missing', detail: [interactiveHost && `host ${interactiveHost}`, defaultBrowser && `browser ${defaultBrowser}`].filter(Boolean).join(' · ') || 'interactive host and browser unset' },
257
+ ];
258
+ }
259
+ export function renderSetupStatus(rows) {
260
+ console.log(chalk.bold('\nagents setup — onboarding\n'));
261
+ for (const row of rows) {
262
+ const marker = row.state === 'ready' ? chalk.green('[x]') : row.state === 'n/a' ? chalk.gray('[-]') : chalk.yellow('[ ]');
263
+ const label = row.phase[0].toUpperCase() + row.phase.slice(1);
264
+ console.log(` ${marker} ${label.padEnd(12)} ${chalk.gray(row.detail)}`);
265
+ }
266
+ }
267
+ async function runSetupPhase(phase) {
268
+ if (phase === 'browser')
269
+ await runBrowserWizard();
270
+ else if (phase === 'computer')
271
+ await runComputerWizard();
272
+ else if (phase === 'share')
273
+ await runShareWizard();
274
+ else if (phase === 'secrets')
275
+ await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
276
+ else if (phase === 'fleet')
277
+ await import('./setup-fleet.js').then((m) => m.runFleetSetupWizard());
278
+ else if (phase === 'watchdog')
279
+ await runWatchdogSetupWizard();
280
+ else
281
+ await runPreferencesStep();
282
+ }
283
+ export async function runSetupHub(deps = {}) {
284
+ const interactive = deps.interactive ?? isInteractiveTerminal();
285
+ if (!interactive) {
286
+ const rows = await getSetupStatus();
287
+ renderSetupStatus(rows);
288
+ if (rows.some((row) => row.state === 'missing'))
289
+ process.exitCode = 1;
240
290
  return;
291
+ }
241
292
  try {
242
- const { checkbox } = await import('@inquirer/prompts');
243
- const picks = await checkbox({
244
- message: 'Set up optional capabilities now? (space to select, enter to confirm)',
245
- choices: [
246
- { name: 'browser — drive a real Chrome/Brave/Edge for web automation', value: 'browser' },
247
- { name: 'computer — control native macOS apps (screenshot, click, type)', value: 'computer' },
248
- { name: 'share — publish shareable links (Cloudflare R2 + Worker)', value: 'share' },
249
- { name: 'secrets — choose storage defaults and import existing credentials', value: 'secrets' },
250
- { name: 'fleet — discover Tailscale devices and configure SSH access', value: 'fleet' },
251
- { name: 'watchdog — resume stalled agent sessions on selected devices', value: 'watchdog' },
252
- ],
253
- });
254
- for (const pick of picks) {
293
+ while (true) {
294
+ const rows = await getSetupStatus();
295
+ renderSetupStatus(rows);
296
+ let pick;
297
+ if (deps.selectPhase) {
298
+ pick = await deps.selectPhase(rows);
299
+ }
300
+ else {
301
+ const { select } = await import('@inquirer/prompts');
302
+ pick = await select({
303
+ message: 'Choose a phase to configure',
304
+ choices: [
305
+ ...rows.filter((row) => row.phase !== 'core' && row.state !== 'n/a').map((row) => ({
306
+ name: `${row.state === 'ready' ? '[x]' : '[ ]'} ${row.phase.padEnd(12)} ${row.detail}`,
307
+ value: row.phase,
308
+ })),
309
+ { name: 'Exit setup', value: 'exit' },
310
+ ],
311
+ });
312
+ }
313
+ if (pick === 'exit')
314
+ return;
255
315
  console.log();
256
- if (pick === 'browser')
257
- await runBrowserWizard();
258
- else if (pick === 'computer')
259
- await runComputerWizard();
260
- else if (pick === 'share')
261
- await runShareWizard();
262
- else if (pick === 'secrets')
263
- await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
264
- else if (pick === 'fleet')
265
- await import('./setup-fleet.js').then((m) => m.runFleetSetupWizard());
266
- else if (pick === 'watchdog')
267
- await runWatchdogSetupWizard();
316
+ await (deps.runPhase ?? runSetupPhase)(pick);
268
317
  }
269
318
  }
270
319
  catch (err) {
@@ -277,7 +326,7 @@ async function runSetupHub() {
277
326
  export function registerSetupCommand(program) {
278
327
  const setupCmd = program
279
328
  .command('setup')
280
- .description('First-time setup. Clones a config repo and installs agent CLIs.')
329
+ .description('Set up agents-cli, or re-open the capability onboarding hub.')
281
330
  .option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
282
331
  .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
283
332
  // Capability subcommands: `agents setup browser|computer|share|mine|secrets|fleet`.
@@ -288,6 +337,18 @@ export function registerSetupCommand(program) {
288
337
  registerSetupSecretsCommand(setupCmd);
289
338
  registerSetupFleetCommand(setupCmd);
290
339
  registerSetupWatchdogCommand(setupCmd);
340
+ setupCmd.command('status')
341
+ .description('Show setup readiness for core, browser, computer, secrets, fleet, share, watchdog, and preferences.')
342
+ .option('--json', 'print machine-readable JSON')
343
+ .action(async (options) => {
344
+ const rows = await getSetupStatus();
345
+ if (options.json)
346
+ console.log(JSON.stringify(rows, null, 2));
347
+ else
348
+ renderSetupStatus(rows);
349
+ if (rows.some((row) => row.state === 'missing'))
350
+ process.exitCode = 1;
351
+ });
291
352
  setHelpSections(setupCmd, {
292
353
  examples: `
293
354
  # First-time setup (clones the system repo into ~/.agents/.system/)
@@ -308,10 +369,8 @@ export function registerSetupCommand(program) {
308
369
  What it does:
309
370
  1. Clones the system repo into ~/.agents/.system/
310
371
  2. Imports any unmanaged agent installations it finds
311
- 3. On a TTY, offers to set up optional capabilities (browser/computer/share/secrets/fleet/watchdog)
312
- 4. On a TTY, asks preferences: which machine you sit at (interactive host)
313
- and which browser agents drive here — both skippable, both the same
314
- keys 'agents devices set-interactive' / 'browser profiles set-default' write
372
+ 3. Opens a re-runnable capability menu with live ready/missing status
373
+ 4. Delegates each selection to its existing setup wizard
315
374
 
316
375
  Capability setup can also be run any time on its own:
317
376
  agents setup browser # detect a browser + create the default profile
@@ -34,6 +34,12 @@ export interface TeamListAgentSnapshot {
34
34
  cloud_provider: string | null;
35
35
  pr_url: string | null;
36
36
  }
37
+ /**
38
+ * One-line operator nudge after `teams start`: teammates are briefed to post
39
+ * IMPORTANT milestones to the feed (RUSH-2250), and this is how to watch them.
40
+ * Print-only — no engine behavior.
41
+ */
42
+ export declare function printFeedHint(team: string): void;
37
43
  /** Where `teams message`/`teams resume` routes a follow-up, by teammate status. */
38
44
  export type TeamMessageRoute = {
39
45
  kind: 'steer';
@@ -153,6 +153,18 @@ function fullName(type, version) {
153
153
  const name = AGENT_NAMES[type];
154
154
  return version ? `${name} ${version}` : name;
155
155
  }
156
+ /**
157
+ * One-line operator nudge after `teams start`: teammates are briefed to post
158
+ * IMPORTANT milestones to the feed (RUSH-2250), and this is how to watch them.
159
+ * Print-only — no engine behavior.
160
+ */
161
+ export function printFeedHint(team) {
162
+ console.log(chalk.gray('Tip: teammates post IMPORTANT milestones to the feed (') +
163
+ chalk.cyan('agents feed timeline') +
164
+ chalk.gray('); watch team progress with ') +
165
+ chalk.cyan(`agents teams status ${team}`) +
166
+ chalk.gray('.'));
167
+ }
156
168
  /**
157
169
  * Resolve a teammate spec to its execution target.
158
170
  *
@@ -365,6 +377,8 @@ async function runOneWave(mgr, team, json) {
365
377
  console.log(` ${chalk.blue(h)} ${chalk.gray('after')} ${a.after.join(', ')}`);
366
378
  }
367
379
  }
380
+ if (launched.length > 0)
381
+ printFeedHint(team);
368
382
  }
369
383
  /**
370
384
  * Path to the pr-watch dedupe-state file for a team. Persists the set of
@@ -1766,11 +1780,40 @@ export function registerTeamsCommands(program) {
1766
1780
  await warnUnsignedTeammates(mgr, team);
1767
1781
  await warnThrottledTeammates(mgr, team);
1768
1782
  }
1783
+ // Health-/harness-aware pre-flight (RUSH-2002): if the team declares a
1784
+ // device pool and no device in it can run a pending teammate's agent, fail
1785
+ // loud here rather than launching a wave that strands teammates pending.
1786
+ // --force downgrades it to a warning so the user can still try.
1787
+ const placementError = await mgr.preflightPlacement(team);
1788
+ if (placementError) {
1789
+ if (isJsonMode(opts)) {
1790
+ console.log(JSON.stringify({
1791
+ team,
1792
+ error: 'no-viable-device',
1793
+ message: placementError.message,
1794
+ excluded: placementError.excluded,
1795
+ }));
1796
+ if (!opts.force) {
1797
+ process.exitCode = 1;
1798
+ return;
1799
+ }
1800
+ }
1801
+ else if (opts.force) {
1802
+ console.error(chalk.yellow(`⚠ ${placementError.message}\n Continuing anyway (--force).`));
1803
+ }
1804
+ else {
1805
+ console.error(chalk.red(placementError.message));
1806
+ process.exitCode = 1;
1807
+ return;
1808
+ }
1809
+ }
1769
1810
  emit('teams.start', { module: 'teams', team, watch: Boolean(opts.watch) });
1770
1811
  if (!opts.watch) {
1771
1812
  await runOneWave(mgr, team, Boolean(opts.json));
1772
1813
  return;
1773
1814
  }
1815
+ if (!isJsonMode(opts))
1816
+ printFeedHint(team);
1774
1817
  const intervalMs = Math.max(1000, Number.parseInt(opts.interval, 10) * 1000 || 8000);
1775
1818
  const maxWaves = Math.max(1, Number.parseInt(opts.maxWaves, 10) || 1000);
1776
1819
  const json = isJsonMode(opts);
@@ -1,2 +1,10 @@
1
+ /**
2
+ * Deprecated top-level spelling of the counter-mix tree.
3
+ *
4
+ * Implementation lives under `agents insights mix` (see
5
+ * `lib/analytics/mix-commands.ts`). This file only registers the thin alias so
6
+ * old scripts and agent briefs keep working while printing one deprecation line
7
+ * — no second recipe implementation.
8
+ */
1
9
  import type { Command } from 'commander';
2
10
  export declare function registerTrendsCommand(program: Command): void;
@@ -1,158 +1,12 @@
1
- import chalk from 'chalk';
2
- import { setHelpSections } from '../lib/help.js';
3
- import { buildTrendsDashboard, trendsWindow, runRecipe, RECIPE_IDS } from '../lib/analytics/dashboard.js';
4
- import { listRecipes } from '../lib/analytics/recipes.js';
5
- import { queryUsage, usageDbPath, USAGE_KINDS } from '../lib/analytics/usage-db.js';
6
- function parseDays(raw) {
7
- const n = parseInt(raw ?? '7', 10);
8
- return Number.isFinite(n) && n > 0 ? n : 7;
9
- }
10
- function parseLimit(raw, fallback) {
11
- const n = parseInt(raw ?? String(fallback), 10);
12
- return Number.isFinite(n) && n > 0 ? n : fallback;
13
- }
14
- function printSection(section) {
15
- console.log(chalk.bold(section.title));
16
- if (section.rows.length === 0) {
17
- console.log(chalk.gray(' (no data)'));
18
- console.log();
19
- return;
20
- }
21
- const keys = Object.keys(section.rows[0]);
22
- const widths = keys.map((k) => Math.max(k.length, ...section.rows.map((r) => String(r[k] ?? '').length), 4));
23
- const pad = (s, w) => (s.length >= w ? s.slice(0, w) : s + ' '.repeat(w - s.length));
24
- console.log(chalk.gray(keys.map((k, i) => pad(k.toUpperCase(), widths[i])).join(' ')));
25
- for (const row of section.rows) {
26
- console.log(keys.map((k, i) => pad(String(row[k] ?? ''), widths[i])).join(' '));
27
- }
28
- console.log();
29
- }
30
- function renderDashboard(days, asJson) {
31
- const dash = buildTrendsDashboard({ days });
32
- if (asJson) {
33
- console.log(JSON.stringify(dash, null, 2));
34
- return;
35
- }
36
- console.log(chalk.bold(`agents trends — last ${dash.window.days} days`));
37
- console.log(chalk.gray(`compute ${dash.durationMs}ms · usage ${usageDbPath()}`));
38
- console.log();
39
- if (dash.sections.length === 0) {
40
- console.log(chalk.gray('No session or usage data in this window yet.'));
41
- return;
42
- }
43
- for (const section of dash.sections)
44
- printSection(section);
45
- }
1
+ /**
2
+ * Deprecated top-level spelling of the counter-mix tree.
3
+ *
4
+ * Implementation lives under `agents insights mix` (see
5
+ * `lib/analytics/mix-commands.ts`). This file only registers the thin alias so
6
+ * old scripts and agent briefs keep working while printing one deprecation line
7
+ * no second recipe implementation.
8
+ */
9
+ import { registerDeprecatedTrendsAlias } from '../lib/analytics/mix-commands.js';
46
10
  export function registerTrendsCommand(program) {
47
- const trends = program
48
- .command('trends')
49
- .description('Usage analytics — harness/model mix, token ratios, resource frequency')
50
- .option('--days <n>', 'Days of history to include', '7')
51
- .option('--json', 'Emit JSON instead of tables')
52
- .action(function summary() {
53
- const opts = this.opts();
54
- renderDashboard(parseDays(opts.days), Boolean(opts.json));
55
- });
56
- setHelpSections(trends, {
57
- examples: `
58
- # Auto recipe dashboard (7d)
59
- agents trends
60
-
61
- # Last 30 days
62
- agents trends --days 30
63
-
64
- # One recipe as JSON
65
- agents trends harness-mix --json
66
-
67
- # Raw usage events
68
- agents trends query --kind secret --days 7
69
-
70
- # List baked recipe ids
71
- agents trends recipes
72
- `,
73
- notes: `
74
- Session recipes read sessions.db; resource recipes read ~/.agents/.history/analytics/usage.db.
75
- Empty recipes are skipped on the default dashboard.
76
- Quota / rate-limits remain on \`agents usage\`; latency on \`agents perf\`.
77
- `,
78
- });
79
- trends.command('recipes')
80
- .description('List baked recipe ids')
81
- .option('--json', 'Emit JSON')
82
- .action((opts) => {
83
- const list = listRecipes();
84
- if (opts.json) {
85
- console.log(JSON.stringify(list, null, 2));
86
- return;
87
- }
88
- for (const r of list) {
89
- console.log(`${r.id.padEnd(22)} ${r.store.padEnd(10)} ${r.title}`);
90
- }
91
- });
92
- trends.command('query')
93
- .description('Raw usage-event query')
94
- .option('--kind <kind>', `One of: ${USAGE_KINDS.join(', ')}`)
95
- .option('--name <name>', 'Resource name filter')
96
- .option('--event <event>', 'Event name filter')
97
- .option('--days <n>', 'Days of history', '7')
98
- .option('--limit <n>', 'Max rows', '40')
99
- .option('--json', 'Emit JSON')
100
- .action((opts) => {
101
- const win = trendsWindow(parseDays(opts.days));
102
- const kind = opts.kind && USAGE_KINDS.includes(opts.kind)
103
- ? opts.kind
104
- : undefined;
105
- if (opts.kind && !kind) {
106
- console.error(`Unknown kind '${opts.kind}'. Expected: ${USAGE_KINDS.join(', ')}`);
107
- process.exitCode = 1;
108
- return;
109
- }
110
- const rows = queryUsage({
111
- kind,
112
- name: opts.name,
113
- event: opts.event,
114
- sinceIso: win.sinceIso,
115
- limit: parseLimit(opts.limit, 40),
116
- });
117
- if (opts.json) {
118
- console.log(JSON.stringify({ window: win, rows }, null, 2));
119
- return;
120
- }
121
- if (rows.length === 0) {
122
- console.log(chalk.gray('No usage events.'));
123
- return;
124
- }
125
- printSection({
126
- id: 'query',
127
- title: 'Usage events',
128
- rows: rows.map((r) => ({
129
- ts: r.ts,
130
- kind: r.kind,
131
- name: r.name,
132
- event: r.event,
133
- agent: r.agent,
134
- })),
135
- });
136
- });
137
- for (const id of RECIPE_IDS) {
138
- trends.command(id)
139
- .description(`Recipe: ${id}`)
140
- .option('--days <n>', 'Days of history', '7')
141
- .option('--json', 'Emit JSON')
142
- .action(function recipeAction() {
143
- const parent = this.parent?.opts?.();
144
- const opts = { ...parent, ...this.opts() };
145
- const win = trendsWindow(parseDays(opts.days));
146
- const section = runRecipe(id, win);
147
- if (opts.json) {
148
- console.log(JSON.stringify({ window: win, section }, null, 2));
149
- return;
150
- }
151
- if (section.empty) {
152
- console.log(chalk.gray(`No data for recipe '${id}' in the last ${win.days} days.`));
153
- return;
154
- }
155
- printSection(section);
156
- });
157
- }
11
+ registerDeprecatedTrendsAlias(program);
158
12
  }
package/dist/index.js CHANGED
@@ -327,7 +327,7 @@ Credentials and profiles:
327
327
  Diagnostics:
328
328
  doctor [agent[@version]] Diagnose CLI availability, sync status, and resource divergence; --check for the CI drift gate
329
329
  usage [agent] Show rate-limit and quota usage per agent
330
- insights How you work — tools, friction, rhythm, split by Claude account
330
+ insights How work looks behaviour (default) or counter mix (insights mix)
331
331
  perf Latency rollups (hooks, commands, runs) from the disposable perf warehouse
332
332
 
333
333
  Config sync:
@@ -34,6 +34,17 @@ export declare function findInPath(command: string): string | null;
34
34
  export declare const AGENTS: Record<AgentId, AgentConfig>;
35
35
  /** All current and legacy agent IDs derived from the AGENTS registry. */
36
36
  export declare const ALL_AGENT_IDS: AgentId[];
37
+ /**
38
+ * CLI command templates per agent for daemon-fired routine jobs, with
39
+ * {prompt} as a placeholder. Lives here (not runner.ts) so routines.ts can
40
+ * import ROUTINE_AGENT_IDS for schedule-time validation without a circular
41
+ * import (runner.ts already imports from routines.ts).
42
+ */
43
+ export declare const ROUTINE_AGENT_COMMANDS: Record<string, string[]>;
44
+ /** Agents the routine daemon can actually run when firing locally, derived
45
+ * from the command table above so the `--agent` help and validateJob's
46
+ * schedule-time check can never drift from it. */
47
+ export declare const ROUTINE_AGENT_IDS: readonly string[];
37
48
  /** Agents retained only for legacy reads, not install/import/sync targets. */
38
49
  export declare const HARD_DEPRECATED_AGENT_IDS: AgentId[];
39
50
  /** Agents that can receive managed installs, imports, and resource sync writes. */
@@ -384,14 +384,22 @@ export const AGENTS = {
384
384
  instructionsFile: 'workspace/AGENTS.md', // Primary memory file (also has SOUL.md, IDENTITY.md, etc.)
385
385
  format: 'markdown',
386
386
  variableSyntax: '{{ARGUMENTS}}',
387
- supportsHooks: true,
387
+ // hooks: NOT supported. OpenClaw only exposes a fixed set of internal,
388
+ // named hooks (e.g. `boot-md`, which runs BOOT.md on gateway restart) —
389
+ // there is no general event->shell-command registration surface (no
390
+ // PreToolUse/PostToolUse/UserPromptSubmit equivalent an agents-cli
391
+ // hooks.yaml manifest could target). registerHooksToSettings has no
392
+ // `openclaw` case and silently no-ops (RUSH-2122: capability claimed
393
+ // hooks:true with zero hooks ever installed). Flip back to `true` only
394
+ // alongside a real registerHooksForOpenClaw implementation.
395
+ supportsHooks: false,
388
396
  // allowlist: maps blanket (whole-tool) rules to ~/.openclaw/openclaw.json
389
397
  // tools.alsoAllow (allow) / tools.deny (deny). OpenClaw gates at tool
390
398
  // granularity only, so sub-command/path/domain patterns are skipped.
391
399
  // OpenClaw is self-updating (no pinned since), so `true` is correct.
392
400
  // Workflows sync as Lobster `.lobster` files under `.openclaw/workflows/`;
393
401
  // the Lobster tool runs them by receiving the file path as `pipeline`.
394
- capabilities: { hooks: true, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'skip'], interactiveRepl: true },
402
+ capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: true, skills: true, commands: false, plugins: true, subagents: true, rules: { file: 'workspace/AGENTS.md' }, workflows: true, memory: true, modes: ['plan', 'edit', 'skip'], interactiveRepl: true },
395
403
  },
396
404
  copilot: {
397
405
  id: 'copilot',
@@ -884,6 +892,25 @@ export const AGENTS = {
884
892
  };
885
893
  /** All current and legacy agent IDs derived from the AGENTS registry. */
886
894
  export const ALL_AGENT_IDS = Object.keys(AGENTS);
895
+ /**
896
+ * CLI command templates per agent for daemon-fired routine jobs, with
897
+ * {prompt} as a placeholder. Lives here (not runner.ts) so routines.ts can
898
+ * import ROUTINE_AGENT_IDS for schedule-time validation without a circular
899
+ * import (runner.ts already imports from routines.ts).
900
+ */
901
+ export const ROUTINE_AGENT_COMMANDS = {
902
+ claude: ['claude', '-p', '--verbose', '{prompt}', '--output-format', 'stream-json', '--permission-mode', 'plan'],
903
+ codex: ['codex', 'exec', '{prompt}', '--json'],
904
+ gemini: ['gemini', '{prompt}', '--output-format', 'stream-json'],
905
+ cursor: ['cursor-agent', '-p', '{prompt}', '--output-format', 'stream-json'],
906
+ kimi: ['kimi', '--prompt', '{prompt}', '--output-format', 'stream-json'],
907
+ droid: ['droid', 'exec', '{prompt}', '-o', 'stream-json'],
908
+ muse: ['muse', 'exec', '{prompt}', '--json'],
909
+ };
910
+ /** Agents the routine daemon can actually run when firing locally, derived
911
+ * from the command table above so the `--agent` help and validateJob's
912
+ * schedule-time check can never drift from it. */
913
+ export const ROUTINE_AGENT_IDS = Object.freeze(Object.keys(ROUTINE_AGENT_COMMANDS));
887
914
  /** Agents retained only for legacy reads, not install/import/sync targets. */
888
915
  export const HARD_DEPRECATED_AGENT_IDS = ALL_AGENT_IDS.filter((id) => AGENTS[id].deprecated?.hard);
889
916
  /** Agents that can receive managed installs, imports, and resource sync writes. */