@phnx-labs/agents-cli 1.22.35 → 1.22.37
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.
- package/CHANGELOG.md +666 -2
- package/README.md +75 -35
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.d.ts +15 -0
- package/dist/bootstrap.js +1211 -0
- package/dist/commands/accounts.d.ts +4 -11
- package/dist/commands/accounts.js +159 -27
- package/dist/commands/attach.js +17 -4
- package/dist/commands/audit.d.ts +8 -8
- package/dist/commands/audit.js +57 -47
- package/dist/commands/browser.js +15 -11
- package/dist/commands/cloud.js +1 -1
- package/dist/commands/commands.js +0 -11
- package/dist/commands/computer-actions.js +33 -0
- package/dist/commands/computer-sessions-picker.d.ts +21 -0
- package/dist/commands/computer-sessions-picker.js +141 -0
- package/dist/commands/computer.d.ts +16 -0
- package/dist/commands/computer.js +37 -0
- package/dist/commands/config.d.ts +17 -0
- package/dist/commands/config.js +401 -0
- package/dist/commands/cp.d.ts +84 -0
- package/dist/commands/cp.js +231 -0
- package/dist/commands/daemon.js +243 -14
- package/dist/commands/detach-core.d.ts +1 -1
- package/dist/commands/detach-core.js +12 -2
- package/dist/commands/detach.js +1 -1
- package/dist/commands/doctor.js +64 -5
- package/dist/commands/events.d.ts +24 -12
- package/dist/commands/events.js +144 -28
- package/dist/commands/exec.js +85 -41
- package/dist/commands/feed.d.ts +1 -1
- package/dist/commands/feed.js +16 -8
- package/dist/commands/focus.d.ts +33 -1
- package/dist/commands/focus.js +177 -17
- package/dist/commands/funnel.d.ts +1 -1
- package/dist/commands/go.js +9 -8
- package/dist/commands/harness-wizard.d.ts +1 -0
- package/dist/commands/harness-wizard.js +18 -20
- package/dist/commands/harness.d.ts +2 -0
- package/dist/commands/harness.js +59 -26
- package/dist/commands/hooks.js +0 -11
- package/dist/commands/inspect.d.ts +94 -1
- package/dist/commands/inspect.js +866 -40
- package/dist/commands/lease.d.ts +4 -4
- package/dist/commands/lease.js +6 -6
- package/dist/commands/login.js +0 -13
- package/dist/commands/logs.d.ts +2 -2
- package/dist/commands/logs.js +2 -2
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/memory.js +7 -2
- package/dist/commands/message.d.ts +1 -1
- package/dist/commands/message.js +37 -6
- package/dist/commands/models.js +4 -0
- package/dist/commands/monitors.d.ts +0 -1
- package/dist/commands/monitors.js +199 -16
- package/dist/commands/open.d.ts +18 -0
- package/dist/commands/open.js +57 -0
- package/dist/commands/packages.js +2 -107
- package/dist/commands/plugins.js +6 -3
- package/dist/commands/profiles.d.ts +1 -0
- package/dist/commands/profiles.js +16 -160
- package/dist/commands/projects.d.ts +28 -0
- package/dist/commands/projects.js +125 -5
- package/dist/commands/reconnect.js +9 -3
- package/dist/commands/repo.js +42 -1
- package/dist/commands/resource-view.d.ts +17 -0
- package/dist/commands/resource-view.js +36 -14
- package/dist/commands/resume.js +9 -2
- package/dist/commands/routines.d.ts +0 -2
- package/dist/commands/routines.js +473 -289
- package/dist/commands/secrets.js +52 -9
- package/dist/commands/sessions-export.d.ts +47 -0
- package/dist/commands/sessions-export.js +116 -6
- package/dist/commands/sessions-import.d.ts +17 -0
- package/dist/commands/sessions-import.js +114 -6
- package/dist/commands/sessions-migrate.d.ts +27 -0
- package/dist/commands/sessions-migrate.js +50 -9
- package/dist/commands/sessions-picker.js +23 -4
- package/dist/commands/sessions-resume.d.ts +5 -3
- package/dist/commands/sessions-resume.js +30 -8
- package/dist/commands/sessions-watch.d.ts +2 -0
- package/dist/commands/sessions-watch.js +53 -0
- package/dist/commands/sessions.d.ts +66 -3
- package/dist/commands/sessions.js +298 -47
- package/dist/commands/set.d.ts +1 -1
- package/dist/commands/set.js +2 -2
- package/dist/commands/setup-browser.js +2 -2
- package/dist/commands/setup-mine.js +0 -1
- package/dist/commands/setup-preferences.js +1 -1
- package/dist/commands/setup-share.js +34 -4
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.js +26 -0
- package/dist/commands/share.d.ts +67 -0
- package/dist/commands/share.js +275 -12
- package/dist/commands/skills.js +0 -11
- package/dist/commands/snapshot.js +1 -1
- package/dist/commands/ssh.d.ts +18 -0
- package/dist/commands/ssh.js +549 -210
- package/dist/commands/sync.d.ts +5 -0
- package/dist/commands/sync.js +212 -32
- package/dist/commands/teams.d.ts +13 -0
- package/dist/commands/teams.js +187 -30
- package/dist/commands/tickets.d.ts +2 -0
- package/dist/commands/tickets.js +43 -0
- package/dist/commands/update.js +2 -4
- package/dist/commands/usage.js +5 -3
- package/dist/commands/versions.js +11 -5
- package/dist/commands/view.js +15 -7
- package/dist/commands/webhook.d.ts +2 -2
- package/dist/commands/webhook.js +7 -7
- package/dist/index.d.ts +18 -2
- package/dist/index.js +53 -1264
- package/dist/lib/account-catalog.d.ts +18 -0
- package/dist/lib/account-catalog.js +38 -0
- package/dist/lib/account-provider-registry.d.ts +18 -0
- package/dist/lib/account-provider-registry.js +59 -0
- package/dist/lib/account-registry.d.ts +40 -0
- package/dist/lib/account-registry.js +239 -0
- package/dist/lib/account-schema.d.ts +44 -0
- package/dist/lib/account-schema.js +91 -0
- package/dist/lib/account-state-service.d.ts +21 -0
- package/dist/lib/account-state-service.js +60 -0
- package/dist/lib/activity.d.ts +1 -1
- package/dist/lib/activity.js +8 -1
- package/dist/lib/add-dir.d.ts +80 -0
- package/dist/lib/add-dir.js +241 -0
- package/dist/lib/agent-cli-commands.d.ts +11 -0
- package/dist/lib/agent-cli-commands.js +30 -0
- package/dist/lib/agents.js +44 -36
- package/dist/lib/answer-router.js +1 -1
- package/dist/lib/audit/log.d.ts +10 -27
- package/dist/lib/audit/log.js +20 -33
- package/dist/lib/auth-health.d.ts +9 -2
- package/dist/lib/auth-health.js +42 -9
- package/dist/lib/auto-pull-worker.js +7 -3
- package/dist/lib/auto-pull.d.ts +44 -3
- package/dist/lib/auto-pull.js +88 -5
- package/dist/lib/binary-shadow.d.ts +28 -0
- package/dist/lib/binary-shadow.js +121 -0
- package/dist/lib/brand.js +9 -3
- package/dist/lib/browser/ipc.d.ts +30 -0
- package/dist/lib/browser/ipc.js +81 -4
- package/dist/lib/browser/profiles.d.ts +3 -2
- package/dist/lib/browser/profiles.js +65 -22
- package/dist/lib/browser/service.d.ts +4 -0
- package/dist/lib/browser/service.js +30 -3
- package/dist/lib/browser/sessions-list.d.ts +22 -2
- package/dist/lib/browser/sessions-list.js +54 -6
- package/dist/lib/browser/stream.d.ts +2 -0
- package/dist/lib/browser/stream.js +1 -0
- package/dist/lib/browser/types.d.ts +19 -0
- package/dist/lib/byok-usage.d.ts +9 -2
- package/dist/lib/byok-usage.js +133 -31
- package/dist/lib/cli-entry.d.ts +11 -0
- package/dist/lib/cli-entry.js +46 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +2 -2
- package/dist/lib/cloud/registry.js +1 -1
- package/dist/lib/cloud/types.d.ts +1 -1
- package/dist/lib/commands.d.ts +2 -0
- package/dist/lib/commands.js +2 -0
- package/dist/lib/computer/sessions-list.d.ts +150 -0
- package/dist/lib/computer/sessions-list.js +363 -0
- package/dist/lib/config-keys.d.ts +86 -0
- package/dist/lib/config-keys.js +193 -0
- package/dist/lib/config-machine-keys.d.ts +12 -0
- package/dist/lib/config-machine-keys.js +17 -0
- package/dist/lib/config-transfer.js +3 -4
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/cli.js +3 -3
- package/dist/lib/daemon-health.d.ts +22 -0
- package/dist/lib/daemon-health.js +33 -3
- package/dist/lib/daemon-services.d.ts +50 -0
- package/dist/lib/daemon-services.js +147 -0
- package/dist/lib/daemon-ticks.d.ts +35 -66
- package/dist/lib/daemon-ticks.js +77 -159
- package/dist/lib/daemon.d.ts +72 -4
- package/dist/lib/daemon.js +792 -202
- package/dist/lib/deeplink/register.d.ts +49 -0
- package/dist/lib/deeplink/register.js +253 -0
- package/dist/lib/deeplink/url.d.ts +34 -0
- package/dist/lib/deeplink/url.js +78 -0
- package/dist/lib/device-config.d.ts +124 -29
- package/dist/lib/device-config.js +346 -141
- package/dist/lib/devices/config-migration.d.ts +42 -0
- package/dist/lib/devices/config-migration.js +203 -0
- package/dist/lib/devices/connect.js +14 -7
- package/dist/lib/devices/doctor-findings.d.ts +4 -1
- package/dist/lib/devices/doctor-findings.js +20 -1
- package/dist/lib/devices/harness-inventory.d.ts +11 -2
- package/dist/lib/devices/harness-inventory.js +0 -0
- package/dist/lib/devices/health.d.ts +1 -1
- package/dist/lib/devices/health.js +1 -1
- package/dist/lib/devices/pending.d.ts +22 -1
- package/dist/lib/devices/pending.js +67 -2
- package/dist/lib/devices/registry.d.ts +0 -25
- package/dist/lib/devices/registry.js +2 -83
- package/dist/lib/devices/resolve-profile.d.ts +20 -0
- package/dist/lib/devices/resolve-profile.js +44 -0
- package/dist/lib/devices/rollout-verify.d.ts +101 -0
- package/dist/lib/devices/rollout-verify.js +193 -0
- package/dist/lib/devices/ssh-config.js +10 -6
- package/dist/lib/event-families.d.ts +28 -0
- package/dist/lib/event-families.js +159 -0
- package/dist/lib/event-stream.d.ts +9 -1
- package/dist/lib/event-stream.js +26 -9
- package/dist/lib/events.d.ts +9 -1
- package/dist/lib/events.js +21 -6
- package/dist/lib/exec.d.ts +30 -0
- package/dist/lib/exec.js +219 -121
- package/dist/lib/feed-broadcast.d.ts +25 -0
- package/dist/lib/feed-broadcast.js +33 -0
- package/dist/lib/feed.d.ts +1 -1
- package/dist/lib/feed.js +12 -2
- package/dist/lib/fleet/capture.js +15 -0
- package/dist/lib/fleet/manifest.js +9 -0
- package/dist/lib/fleet/types.d.ts +9 -0
- package/dist/lib/fleet-status.js +10 -8
- package/dist/lib/git.d.ts +20 -6
- package/dist/lib/git.js +121 -17
- package/dist/lib/hosts/dispatch.d.ts +8 -3
- package/dist/lib/hosts/dispatch.js +29 -3
- package/dist/lib/hosts/logs.d.ts +2 -2
- package/dist/lib/hosts/logs.js +2 -2
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.d.ts +2 -3
- package/dist/lib/hosts/passthrough.js +10 -24
- package/dist/lib/hosts/progress.js +2 -2
- package/dist/lib/hosts/providers/devices.d.ts +3 -3
- package/dist/lib/hosts/providers/devices.js +18 -12
- package/dist/lib/hosts/ready.d.ts +5 -1
- package/dist/lib/hosts/ready.js +9 -3
- package/dist/lib/hosts/reconcile.d.ts +2 -2
- package/dist/lib/hosts/reconcile.js +2 -2
- package/dist/lib/hosts/registry.js +14 -9
- package/dist/lib/hosts/remote-cmd.js +1 -1
- package/dist/lib/hosts/remote-os.d.ts +9 -6
- package/dist/lib/hosts/remote-os.js +13 -6
- package/dist/lib/hosts/routing-flag.d.ts +23 -0
- package/dist/lib/hosts/routing-flag.js +41 -0
- package/dist/lib/hosts/run-target.js +1 -1
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/hosts/tasks.d.ts +6 -6
- package/dist/lib/hosts/tasks.js +4 -4
- package/dist/lib/hosts/types.js +1 -2
- package/dist/lib/installations/resolve.js +2 -8
- package/dist/lib/isolation-boundary-report.js +0 -1
- package/dist/lib/linear-cache.d.ts +7 -0
- package/dist/lib/linear-cache.js +31 -4
- package/dist/lib/linear-project-counts.d.ts +1 -1
- package/dist/lib/linear-project-counts.js +2 -3
- package/dist/lib/linear-projects.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/mailbox-target.d.ts +1 -1
- package/dist/lib/mailbox-target.js +1 -1
- package/dist/lib/manifest.js +7 -4
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/snapshot.d.ts +3 -2
- package/dist/lib/menubar/snapshot.js +6 -2
- package/dist/lib/migrate-fold.d.ts +33 -0
- package/dist/lib/migrate-fold.js +130 -0
- package/dist/lib/migrate.d.ts +34 -34
- package/dist/lib/migrate.js +106 -128
- package/dist/lib/model-tier-overrides.d.ts +1 -1
- package/dist/lib/model-tier-overrides.js +15 -2
- package/dist/lib/monitors/config.d.ts +17 -3
- package/dist/lib/monitors/config.js +52 -18
- package/dist/lib/monitors/engine.d.ts +32 -1
- package/dist/lib/monitors/engine.js +94 -11
- package/dist/lib/monitors/fingerprint.d.ts +46 -0
- package/dist/lib/monitors/fingerprint.js +100 -0
- package/dist/lib/monitors/remote.d.ts +47 -0
- package/dist/lib/monitors/remote.js +81 -0
- package/dist/lib/monitors/state.d.ts +39 -0
- package/dist/lib/monitors/state.js +56 -0
- package/dist/lib/onepassword.d.ts +17 -0
- package/dist/lib/onepassword.js +52 -22
- package/dist/lib/open-url.js +1 -1
- package/dist/lib/picker.d.ts +14 -2
- package/dist/lib/picker.js +35 -13
- package/dist/lib/placement.d.ts +1 -1
- package/dist/lib/placement.js +1 -1
- package/dist/lib/pr-land-detach.d.ts +32 -0
- package/dist/lib/pr-land-detach.js +79 -0
- package/dist/lib/profiles-presets.js +13 -0
- package/dist/lib/profiles.d.ts +2 -0
- package/dist/lib/profiles.js +44 -1
- package/dist/lib/project-focus.d.ts +1 -1
- package/dist/lib/project-focus.js +1 -1
- package/dist/lib/project-probe.d.ts +4 -1
- package/dist/lib/project-probe.js +5 -4
- package/dist/lib/project-resources.js +2 -18
- package/dist/lib/project-root.d.ts +16 -0
- package/dist/lib/project-root.js +20 -2
- package/dist/lib/projects.d.ts +20 -1
- package/dist/lib/projects.js +78 -1
- package/dist/lib/refresh-coordinator.d.ts +24 -0
- package/dist/lib/refresh-coordinator.js +52 -0
- package/dist/lib/registry.d.ts +2 -2
- package/dist/lib/registry.js +1 -1
- package/dist/lib/resource-aliases.d.ts +13 -0
- package/dist/lib/resource-aliases.js +26 -0
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +16 -0
- package/dist/lib/resources.js +66 -10
- package/dist/lib/rotate.d.ts +3 -4
- package/dist/lib/rotate.js +4 -5
- package/dist/lib/routine-activation.d.ts +22 -2
- package/dist/lib/routine-activation.js +63 -16
- package/dist/lib/routine-context.d.ts +9 -2
- package/dist/lib/routine-context.js +21 -6
- package/dist/lib/routines-project.d.ts +58 -48
- package/dist/lib/routines-project.js +190 -159
- package/dist/lib/routines.d.ts +41 -6
- package/dist/lib/routines.js +92 -34
- package/dist/lib/run-defaults.d.ts +5 -0
- package/dist/lib/run-defaults.js +11 -1
- package/dist/lib/runner.d.ts +10 -8
- package/dist/lib/runner.js +122 -39
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +10 -3
- package/dist/lib/secrets/agent.js +43 -8
- package/dist/lib/secrets/bundles.d.ts +1 -0
- package/dist/lib/secrets/bundles.js +92 -80
- package/dist/lib/secrets/index.js +32 -9
- package/dist/lib/secrets/install-helper.d.ts +4 -4
- package/dist/lib/secrets/install-helper.js +4 -4
- package/dist/lib/secrets/push.d.ts +40 -6
- package/dist/lib/secrets/push.js +58 -11
- package/dist/lib/secrets/read-backoff.d.ts +1 -1
- package/dist/lib/secrets/read-backoff.js +1 -1
- package/dist/lib/secrets/remote.d.ts +24 -0
- package/dist/lib/secrets/remote.js +54 -4
- package/dist/lib/self-update.d.ts +108 -0
- package/dist/lib/self-update.js +276 -1
- package/dist/lib/session/active.d.ts +89 -5
- package/dist/lib/session/active.js +165 -10
- package/dist/lib/session/actor-sidecar.d.ts +1 -1
- package/dist/lib/session/actor-sidecar.js +2 -1
- package/dist/lib/session/db.d.ts +176 -2
- package/dist/lib/session/db.js +484 -17
- package/dist/lib/session/discover.js +27 -4
- package/dist/lib/session/hook-sessions.js +2 -2
- package/dist/lib/session/host-link.d.ts +2 -2
- package/dist/lib/session/host-link.js +2 -2
- package/dist/lib/session/pid-registry.d.ts +21 -0
- package/dist/lib/session/pid-registry.js +57 -0
- package/dist/lib/session/remote-active.js +15 -1
- package/dist/lib/session/remote.js +2 -2
- package/dist/lib/session/session-cache.d.ts +14 -7
- package/dist/lib/session/session-cache.js +51 -9
- package/dist/lib/session/state.d.ts +2 -2
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/sync/config.d.ts +10 -10
- package/dist/lib/session/sync/config.js +10 -10
- package/dist/lib/session/sync/r2.d.ts +34 -0
- package/dist/lib/session/sync/r2.js +123 -0
- package/dist/lib/session/throughput.d.ts +1 -1
- package/dist/lib/session/throughput.js +1 -1
- package/dist/lib/session/types.d.ts +23 -1
- package/dist/lib/session/types.js +15 -0
- package/dist/lib/session/viewing-in.d.ts +1 -1
- package/dist/lib/session/viewing-in.js +1 -1
- package/dist/lib/session/watch.d.ts +101 -0
- package/dist/lib/session/watch.js +242 -0
- package/dist/lib/share/config.d.ts +4 -0
- package/dist/lib/share/config.js +1 -0
- package/dist/lib/share/provision.d.ts +38 -0
- package/dist/lib/share/provision.js +50 -0
- package/dist/lib/share/publish.d.ts +40 -1
- package/dist/lib/share/publish.js +116 -3
- package/dist/lib/share/worker-template.js +71 -6
- package/dist/lib/shims.d.ts +17 -3
- package/dist/lib/shims.js +98 -26
- package/dist/lib/signin-badge.js +3 -2
- package/dist/lib/skills.js +2 -0
- package/dist/lib/smart-launch.d.ts +8 -8
- package/dist/lib/smart-launch.js +80 -59
- package/dist/lib/ssh-exec.d.ts +1 -1
- package/dist/lib/ssh-exec.js +1 -1
- package/dist/lib/staleness/prune.d.ts +88 -0
- package/dist/lib/staleness/prune.js +69 -0
- package/dist/lib/staleness/registry.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.js +34 -2
- package/dist/lib/staleness/writers/hooks.js +16 -0
- package/dist/lib/staleness/writers/kinds.d.ts +6 -0
- package/dist/lib/staleness/writers/skills.js +19 -0
- package/dist/lib/staleness/writers/subagents.d.ts +2 -3
- package/dist/lib/staleness/writers/subagents.js +0 -4
- package/dist/lib/staleness/writers/types.d.ts +32 -0
- package/dist/lib/startup/command-registry.d.ts +6 -12
- package/dist/lib/startup/command-registry.js +11 -31
- package/dist/lib/startup/dev-build.d.ts +10 -0
- package/dist/lib/startup/dev-build.js +13 -1
- package/dist/lib/startup/spellcheck.d.ts +18 -0
- package/dist/lib/startup/spellcheck.js +45 -0
- package/dist/lib/state.d.ts +36 -9
- package/dist/lib/state.js +96 -46
- package/dist/lib/subagents-registry.d.ts +0 -7
- package/dist/lib/subagents-registry.js +38 -65
- package/dist/lib/subagents.d.ts +0 -33
- package/dist/lib/subagents.js +0 -75
- package/dist/lib/sync-umbrella.js +1 -1
- package/dist/lib/teams/agents.d.ts +41 -1
- package/dist/lib/teams/agents.js +187 -9
- package/dist/lib/teams/api.d.ts +11 -1
- package/dist/lib/teams/api.js +11 -2
- package/dist/lib/teams/delivery.d.ts +41 -0
- package/dist/lib/teams/delivery.js +60 -0
- package/dist/lib/teams/registry.d.ts +14 -0
- package/dist/lib/teams/registry.js +51 -1
- package/dist/lib/teams/remoteWorktree.d.ts +19 -0
- package/dist/lib/teams/remoteWorktree.js +29 -0
- package/dist/lib/teams/scheduler.d.ts +1 -1
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/teams/worktree.d.ts +24 -0
- package/dist/lib/teams/worktree.js +42 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +10 -1
- package/dist/lib/terminal/backends/vscodium-agent.js +16 -5
- package/dist/lib/terminal/engine.d.ts +4 -0
- package/dist/lib/terminal/engine.js +8 -2
- package/dist/lib/terminal/types.d.ts +21 -2
- package/dist/lib/testdata/refresh-coordinator-worker.d.ts +1 -0
- package/dist/lib/testdata/refresh-coordinator-worker.js +23 -0
- package/dist/lib/tickets/list.d.ts +53 -0
- package/dist/lib/tickets/list.js +153 -0
- package/dist/lib/tmux/orphan-reap.d.ts +320 -0
- package/dist/lib/tmux/orphan-reap.js +644 -0
- package/dist/lib/tmux/session.d.ts +68 -3
- package/dist/lib/tmux/session.js +95 -5
- package/dist/lib/triggers/handlers.d.ts +20 -0
- package/dist/lib/triggers/handlers.js +6 -1
- package/dist/lib/types.d.ts +46 -34
- package/dist/lib/usage-fleet.d.ts +32 -0
- package/dist/lib/usage-fleet.js +125 -0
- package/dist/lib/usage-refresh.d.ts +3 -3
- package/dist/lib/usage-refresh.js +19 -16
- package/dist/lib/usage.d.ts +51 -47
- package/dist/lib/usage.js +145 -153
- package/dist/lib/version-duplicates.js +2 -2
- package/dist/lib/versions.d.ts +56 -13
- package/dist/lib/versions.js +195 -45
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +3 -3
- package/dist/lib/watchdog/rotate.d.ts +6 -6
- package/dist/lib/watchdog/rotate.js +6 -6
- package/dist/lib/workflows.d.ts +1 -1
- package/dist/lib/yaml-io.d.ts +47 -0
- package/dist/lib/yaml-io.js +55 -0
- package/package.json +2 -1
- package/scripts/install-helper.js +2 -2
- package/scripts/postinstall.js +23 -20
- package/dist/commands/defaults.d.ts +0 -7
- package/dist/commands/defaults.js +0 -107
- package/dist/commands/export.d.ts +0 -11
- package/dist/commands/export.js +0 -215
- package/dist/commands/helper.d.ts +0 -12
- package/dist/commands/helper.js +0 -87
- package/dist/commands/hosts.d.ts +0 -11
- package/dist/commands/hosts.js +0 -330
- package/dist/commands/lock.d.ts +0 -12
- package/dist/commands/lock.js +0 -70
- package/dist/commands/pull.d.ts +0 -17
- package/dist/commands/pull.js +0 -39
- package/dist/commands/push.d.ts +0 -14
- package/dist/commands/push.js +0 -30
- package/dist/commands/wallet.d.ts +0 -20
- package/dist/commands/wallet.js +0 -216
- package/dist/commands/worktree.d.ts +0 -19
- package/dist/commands/worktree.js +0 -272
- package/dist/lib/account-labels.d.ts +0 -24
- package/dist/lib/account-labels.js +0 -72
- package/dist/lib/auto-dispatch-linear.d.ts +0 -18
- package/dist/lib/auto-dispatch-linear.js +0 -107
- package/dist/lib/auto-dispatch-provider.d.ts +0 -10
- package/dist/lib/auto-dispatch-provider.js +0 -30
- package/dist/lib/auto-dispatch.d.ts +0 -93
- package/dist/lib/auto-dispatch.js +0 -128
- package/dist/lib/export.d.ts +0 -72
- package/dist/lib/export.js +0 -269
- package/dist/lib/lock.d.ts +0 -93
- package/dist/lib/lock.js +0 -207
- package/dist/lib/wallet/index.d.ts +0 -78
- package/dist/lib/wallet/index.js +0 -253
package/dist/commands/inspect.js
CHANGED
|
@@ -32,11 +32,16 @@ import { listHookEntriesFromDir } from '../lib/hooks.js';
|
|
|
32
32
|
import { getResourceInventory } from '../lib/resource-inventory.js';
|
|
33
33
|
import { listMcpServerConfigs, discoverMcpConfigsFromRepo } from '../lib/mcp.js';
|
|
34
34
|
import { discoverPlugins, discoverPluginsInDir, pluginResourceGroups, inspectPluginCapabilities, pluginCapabilityLabels } from '../lib/plugins.js';
|
|
35
|
+
import { showResourceList } from './resource-view.js';
|
|
35
36
|
import { PLUGIN_GROUP_COLORS } from './plugins.js';
|
|
37
|
+
import { isInteractiveTerminal } from './utils.js';
|
|
36
38
|
import { countSessionsInScope } from '../lib/session/discover.js';
|
|
37
39
|
import { isSessionTrackedAgent } from '../lib/session/types.js';
|
|
38
40
|
import { damerauLevenshtein } from '../lib/fuzzy.js';
|
|
39
41
|
import { terminalWidth, truncateToWidth, stringWidth, stripAnsi } from '../lib/session/width.js';
|
|
42
|
+
import { readJobFileResult, getJobRunsDir, readRunMeta, isOneShotRoutine, isPastOneShotRoutine, listJobs, } from '../lib/routines.js';
|
|
43
|
+
import { routineDeviceIndex, currentRoutineDevice, routineEnabledOnThisDevice, } from '../lib/routine-activation.js';
|
|
44
|
+
import { humanizeCron } from '../lib/routines-format.js';
|
|
40
45
|
/** Resource kinds the inspect command can drill into. */
|
|
41
46
|
const DRILLABLE_KINDS = [
|
|
42
47
|
'commands',
|
|
@@ -47,15 +52,19 @@ const DRILLABLE_KINDS = [
|
|
|
47
52
|
'plugins',
|
|
48
53
|
'workflows',
|
|
49
54
|
'subagents',
|
|
55
|
+
'routines',
|
|
50
56
|
];
|
|
51
57
|
/**
|
|
52
58
|
* Summary-view partition. SIMPLE kinds render as a one-line count + name preview;
|
|
53
|
-
* RICH kinds (hooks/plugins/mcp) get their own expanded section showing
|
|
54
|
-
* item's key detail (events/predicates, bundle contents, transport/url
|
|
55
|
-
* they cover every
|
|
59
|
+
* RICH kinds (hooks/plugins/mcp/routines) get their own expanded section showing
|
|
60
|
+
* each item's key detail (events/predicates, bundle contents, transport/url,
|
|
61
|
+
* schedule + where a routine actually fires). Together they cover every
|
|
62
|
+
* DrillableKind — the type below makes that claim load-bearing.
|
|
56
63
|
*/
|
|
57
64
|
const SIMPLE_KINDS = ['commands', 'skills', 'rules', 'subagents', 'workflows'];
|
|
58
|
-
const RICH_KINDS = ['hooks', 'plugins', 'mcp'];
|
|
65
|
+
const RICH_KINDS = ['hooks', 'plugins', 'mcp', 'routines'];
|
|
66
|
+
const _kindsPartitioned = true;
|
|
67
|
+
void _kindsPartitioned;
|
|
59
68
|
/**
|
|
60
69
|
* Singular aliases for the plural drill-down flags. `--plugin code` reads as
|
|
61
70
|
* "show the one plugin named code" — a required-value flag that always lands in
|
|
@@ -70,6 +79,7 @@ const SINGULAR_DRILL_ALIASES = {
|
|
|
70
79
|
plugin: 'plugins',
|
|
71
80
|
workflow: 'workflows',
|
|
72
81
|
subagent: 'subagents',
|
|
82
|
+
routine: 'routines',
|
|
73
83
|
};
|
|
74
84
|
const CAPABILITY_NAMES = [
|
|
75
85
|
'hooks', 'mcp', 'skills', 'commands', 'subagents', 'plugins', 'workflows', 'rules', 'allowlist',
|
|
@@ -262,6 +272,13 @@ function isDotAgentsRoot(dir) {
|
|
|
262
272
|
return true;
|
|
263
273
|
}
|
|
264
274
|
for (const kind of DRILLABLE_KINDS) {
|
|
275
|
+
// `routines/` alone is NOT a marker. Shipping example routine YAML for
|
|
276
|
+
// `agents routines add` is a documented pattern (this repo's own
|
|
277
|
+
// apps/cli/routines/), and that directory is the ONLY kind dir there — so
|
|
278
|
+
// counting it would make `agents inspect apps/cli` resolve a source tree as
|
|
279
|
+
// a DotAgents repo. Same reasoning as the nested-`.agents/` case above.
|
|
280
|
+
if (kind === 'routines')
|
|
281
|
+
continue;
|
|
265
282
|
if (safeStat(path.join(dir, kind))?.isDirectory())
|
|
266
283
|
return true;
|
|
267
284
|
}
|
|
@@ -271,9 +288,29 @@ export async function inspectRepo(repo, options) {
|
|
|
271
288
|
const drill = pickDrillKind(options);
|
|
272
289
|
const jsonHead = { repo: repo.label, root: repo.root };
|
|
273
290
|
if (drill) {
|
|
274
|
-
|
|
291
|
+
// Routines carry live state, and an unreadable device document silently
|
|
292
|
+
// removes that device from every `devices` cell — say so on this path too,
|
|
293
|
+
// not only in the overview.
|
|
294
|
+
let items;
|
|
295
|
+
if (drill.kind === 'routines') {
|
|
296
|
+
const provider = defaultRoutineLive();
|
|
297
|
+
items = collectRepoRoutines(repo, provider.live);
|
|
298
|
+
if (!options.json) {
|
|
299
|
+
for (const err of provider.errors) {
|
|
300
|
+
console.error(chalk.yellow(`device config unreadable — ${err}`));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
items = collectRepoKind(repo, drill.kind);
|
|
306
|
+
}
|
|
275
307
|
if (drill.query === true || drill.query === undefined) {
|
|
276
|
-
|
|
308
|
+
// A repo's hooks are wired by its OWN agents.yaml, not by whatever this
|
|
309
|
+
// machine has installed centrally — the same manifest the repo overview
|
|
310
|
+
// reads at collectRepoKind.
|
|
311
|
+
await renderItemList(repo.label, jsonHead, drill.kind, items, options, drill.kind === 'hooks'
|
|
312
|
+
? hookManifestByScript(hookManifestFromFile(path.join(repo.root, 'agents.yaml')))
|
|
313
|
+
: undefined);
|
|
277
314
|
}
|
|
278
315
|
else {
|
|
279
316
|
renderItemDetail(repo.label, jsonHead, drill.kind, String(drill.query), items, options);
|
|
@@ -314,8 +351,331 @@ export function collectRepoKind(repo, kind) {
|
|
|
314
351
|
if (fs.existsSync(subrulesDir))
|
|
315
352
|
return readResourceDir(subrulesDir, kind, repo.label);
|
|
316
353
|
}
|
|
354
|
+
// Routines are YAML job definitions whose real state (does it fire, where, how
|
|
355
|
+
// did it last run) lives outside the file. Their directory also holds
|
|
356
|
+
// `<name>/home/` sandbox overlays, which a flat readdir would count as
|
|
357
|
+
// routines — on this author's box that turned 24 routines into 49.
|
|
358
|
+
if (kind === 'routines')
|
|
359
|
+
return collectRepoRoutines(repo);
|
|
317
360
|
return readResourceDir(path.join(repo.root, kind), kind, repo.label);
|
|
318
361
|
}
|
|
362
|
+
const NO_LIVE_STATE = {
|
|
363
|
+
devices: [], materialized: false, thisDevice: '', enabledHere: null, lastStatus: null, lastAt: null,
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Whether the routine actually runs on this machine.
|
|
367
|
+
*
|
|
368
|
+
* Exactly `applyDeviceActivation` (`lib/routines.ts`): this device's own
|
|
369
|
+
* allowlist answer wins when it exists, and the file's `enabled:` governs only
|
|
370
|
+
* while that answer is `null`. Measured at the same scope as the daemon's, so
|
|
371
|
+
* `agents inspect --routines` and `agents routines list` cannot disagree.
|
|
372
|
+
*/
|
|
373
|
+
function routineEnabledHere(config, live) {
|
|
374
|
+
return live.enabledHere === null ? config.enabled : live.enabledHere;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The last run we can read, without paying for the whole history.
|
|
378
|
+
*
|
|
379
|
+
* `getLatestRun` goes through `listRuns`, which parses EVERY meta.json under the
|
|
380
|
+
* routine — one firing every 3 minutes accumulates hundreds. Run ids are ISO-ish
|
|
381
|
+
* and sort lexicographically, so walking from the newest and stopping at the
|
|
382
|
+
* first readable record gives the same answer for a bounded number of reads.
|
|
383
|
+
*/
|
|
384
|
+
function latestRunOf(name, maxProbes = 20) {
|
|
385
|
+
let runIds;
|
|
386
|
+
try {
|
|
387
|
+
runIds = fs.readdirSync(getJobRunsDir(name), { withFileTypes: true })
|
|
388
|
+
.filter(e => e.isDirectory())
|
|
389
|
+
.map(e => e.name)
|
|
390
|
+
.sort()
|
|
391
|
+
.reverse();
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
for (const runId of runIds.slice(0, maxProbes)) {
|
|
397
|
+
const meta = readRunMeta(name, runId);
|
|
398
|
+
if (meta)
|
|
399
|
+
return meta;
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Default live provider: one fleet index per collect, one bounded run read per
|
|
405
|
+
* routine, and this device's own activation answer per routine.
|
|
406
|
+
*
|
|
407
|
+
* Returns the collected `errors` alongside so the caller can surface an
|
|
408
|
+
* unreadable device document instead of letting the affected device silently
|
|
409
|
+
* vanish from every `devices` cell.
|
|
410
|
+
*/
|
|
411
|
+
function defaultRoutineLive() {
|
|
412
|
+
let index;
|
|
413
|
+
try {
|
|
414
|
+
index = routineDeviceIndex();
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
return { live: () => NO_LIVE_STATE, errors: [err.message] };
|
|
418
|
+
}
|
|
419
|
+
const thisDevice = (() => { try {
|
|
420
|
+
return currentRoutineDevice();
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return '';
|
|
424
|
+
} })();
|
|
425
|
+
const errors = [...index.errors];
|
|
426
|
+
// This device's own doc is read through readMeta, which THROWS on a corrupt
|
|
427
|
+
// one. Falling back to the file's `enabled:` silently would be the same
|
|
428
|
+
// silent-failure class the index's error collection just removed, so record it
|
|
429
|
+
// once — the answer is identical for every routine on this machine.
|
|
430
|
+
let ownDocError = null;
|
|
431
|
+
const enabledOnThisDevice = (name) => {
|
|
432
|
+
try {
|
|
433
|
+
return routineEnabledOnThisDevice(name);
|
|
434
|
+
}
|
|
435
|
+
catch (err) {
|
|
436
|
+
if (!ownDocError) {
|
|
437
|
+
ownDocError = `this device's own config is unreadable — ${err.message}`;
|
|
438
|
+
errors.push(ownDocError);
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const live = (name) => {
|
|
444
|
+
const run = latestRunOf(name);
|
|
445
|
+
return {
|
|
446
|
+
devices: index.byRoutine.get(name) ?? [],
|
|
447
|
+
materialized: index.materialized,
|
|
448
|
+
thisDevice,
|
|
449
|
+
// The same call applyDeviceActivation makes — per-machine, not fleet-wide.
|
|
450
|
+
enabledHere: enabledOnThisDevice(name),
|
|
451
|
+
lastStatus: run?.status ?? null,
|
|
452
|
+
lastAt: run?.startedAt ?? null,
|
|
453
|
+
};
|
|
454
|
+
};
|
|
455
|
+
return { live, errors };
|
|
456
|
+
}
|
|
457
|
+
/** `2h` / `3d` / `just now` — compact enough for a 10-column cell. */
|
|
458
|
+
export function compactAge(iso, now = new Date()) {
|
|
459
|
+
if (!iso)
|
|
460
|
+
return '';
|
|
461
|
+
const then = Date.parse(iso);
|
|
462
|
+
if (!Number.isFinite(then))
|
|
463
|
+
return '';
|
|
464
|
+
const mins = Math.floor((now.getTime() - then) / 60000);
|
|
465
|
+
if (mins < 1)
|
|
466
|
+
return 'now';
|
|
467
|
+
if (mins < 60)
|
|
468
|
+
return `${mins}m`;
|
|
469
|
+
const hours = Math.floor(mins / 60);
|
|
470
|
+
if (hours < 24)
|
|
471
|
+
return `${hours}h`;
|
|
472
|
+
const days = Math.floor(hours / 24);
|
|
473
|
+
if (days < 365)
|
|
474
|
+
return `${days}d`;
|
|
475
|
+
return `${Math.floor(days / 365)}y`;
|
|
476
|
+
}
|
|
477
|
+
/** What makes this routine fire: a humanized cron, an event trigger, or nothing. */
|
|
478
|
+
export function routineFireLabel(job, now = new Date()) {
|
|
479
|
+
const parts = [];
|
|
480
|
+
if (job.schedule)
|
|
481
|
+
parts.push(humanizeCron(job.schedule, job.timezone));
|
|
482
|
+
if (job.trigger) {
|
|
483
|
+
const t = job.trigger;
|
|
484
|
+
const event = manifestText(t.event) || manifestText(t.type) || 'event';
|
|
485
|
+
const qualifier = [manifestText(t.action), manifestText(t.label)].filter(Boolean).join(' ');
|
|
486
|
+
parts.push(qualifier ? `on ${event} (${qualifier})` : `on ${event}`);
|
|
487
|
+
}
|
|
488
|
+
if (parts.length === 0)
|
|
489
|
+
return 'no schedule or trigger';
|
|
490
|
+
if (isOneShotRoutine(job)) {
|
|
491
|
+
parts.push(isPastOneShotRoutine(job, now) ? '(one-shot, expired)' : '(one-shot)');
|
|
492
|
+
}
|
|
493
|
+
return parts.join(' · ');
|
|
494
|
+
}
|
|
495
|
+
/** What the routine actually executes. Exactly one of agent/workflow/command is set. */
|
|
496
|
+
export function routineTargetLabel(job) {
|
|
497
|
+
const agent = manifestText(job.agent);
|
|
498
|
+
if (agent)
|
|
499
|
+
return `agent ${agent}`;
|
|
500
|
+
const workflow = manifestText(job.workflow);
|
|
501
|
+
if (workflow)
|
|
502
|
+
return `workflow ${workflow}`;
|
|
503
|
+
if (manifestText(job.command))
|
|
504
|
+
return 'command (shell)';
|
|
505
|
+
return 'nothing configured';
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* A routine's one-line intent.
|
|
509
|
+
*
|
|
510
|
+
* Routine YAML has no `description:` key, so the generic first-prose-line reader
|
|
511
|
+
* returns `name: growth-metrics` for one file and a real sentence for the next.
|
|
512
|
+
* Prefer the prompt's first sentence, then the file's leading comment block, then
|
|
513
|
+
* the first meaningful line of a shell command.
|
|
514
|
+
*/
|
|
515
|
+
export function routineDescription(job, raw) {
|
|
516
|
+
const prompt = job ? manifestText(job.prompt).trim() : '';
|
|
517
|
+
if (prompt)
|
|
518
|
+
return summaryLine(truncate(prompt.replace(/\s+/g, ' '), 400));
|
|
519
|
+
const lines = raw.split('\n');
|
|
520
|
+
for (const line of lines) {
|
|
521
|
+
const comment = line.match(/^\s*#\s?(.+)$/);
|
|
522
|
+
if (!comment) {
|
|
523
|
+
if (line.trim())
|
|
524
|
+
break;
|
|
525
|
+
else
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const text = comment[1].trim().replace(/^Routine:\s*/i, '');
|
|
529
|
+
// Skip the shebang-ish and install-instruction noise that opens many files.
|
|
530
|
+
if (!text || /^(yaml-language-server|Install:|Usage:)/i.test(text))
|
|
531
|
+
continue;
|
|
532
|
+
return summaryLine(text);
|
|
533
|
+
}
|
|
534
|
+
const command = job ? manifestText(job.command) : '';
|
|
535
|
+
for (const line of command.split('\n')) {
|
|
536
|
+
const text = line.trim();
|
|
537
|
+
if (!text || text.startsWith('#') || /^set\s+-/.test(text))
|
|
538
|
+
continue;
|
|
539
|
+
return truncate(text, 120);
|
|
540
|
+
}
|
|
541
|
+
return '';
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Read every routine one DotAgents repo declares.
|
|
545
|
+
*
|
|
546
|
+
* Reads exactly `<root>/routines/`, never the layered `listJobs` — `collectRepoKind`
|
|
547
|
+
* is a single-root view, and returning `~/.agents` routines while inspecting a
|
|
548
|
+
* project repo would be a lie. `live` is injectable because `getUserAgentsDir()`
|
|
549
|
+
* and `getRunsDir()` have no env override, so tests cannot otherwise keep this
|
|
550
|
+
* off the developer's real fleet state.
|
|
551
|
+
*/
|
|
552
|
+
export function collectRepoRoutines(repo, live = defaultRoutineLive().live, now = new Date()) {
|
|
553
|
+
const dir = path.join(repo.root, 'routines');
|
|
554
|
+
let entries;
|
|
555
|
+
try {
|
|
556
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return [];
|
|
560
|
+
}
|
|
561
|
+
const items = [];
|
|
562
|
+
const seen = new Map();
|
|
563
|
+
// Files only: `routines/<name>/home/` is a sandbox overlay HOME, not a routine.
|
|
564
|
+
//
|
|
565
|
+
// `.yml` before `.yaml` for the same basename, because that is the order
|
|
566
|
+
// `readJobFromDir` tries — so when both exist we describe the file the daemon
|
|
567
|
+
// actually loads. A plain alphabetical sort put `.yaml` first and reported the
|
|
568
|
+
// wrong schedule and command for a routine that fires from its sibling.
|
|
569
|
+
const files = entries
|
|
570
|
+
.filter(e => e.isFile() && /\.ya?ml$/.test(e.name) && !e.name.startsWith('.'))
|
|
571
|
+
.map(e => e.name)
|
|
572
|
+
.sort((a, b) => {
|
|
573
|
+
const an = a.replace(/\.ya?ml$/, ''), bn = b.replace(/\.ya?ml$/, '');
|
|
574
|
+
if (an !== bn)
|
|
575
|
+
return an.localeCompare(bn);
|
|
576
|
+
return (a.endsWith('.yml') ? 0 : 1) - (b.endsWith('.yml') ? 0 : 1);
|
|
577
|
+
});
|
|
578
|
+
for (const file of files) {
|
|
579
|
+
const filePath = path.join(dir, file);
|
|
580
|
+
const { config, problem } = readJobFileResult(filePath);
|
|
581
|
+
const basename = file.replace(/\.ya?ml$/, '');
|
|
582
|
+
const name = config?.name || basename;
|
|
583
|
+
const existing = seen.get(name);
|
|
584
|
+
if (existing) {
|
|
585
|
+
// `writeJob` refuses to edit a routine with both extensions. Keep the item
|
|
586
|
+
// built from the file that loads, and flag the ambiguity on it — in `extra`
|
|
587
|
+
// for the panes AND in `json` so a --json consumer sees it too.
|
|
588
|
+
const collision = `both .yml and .yaml exist — ${file} is ignored; resolve before editing`;
|
|
589
|
+
existing.extra = [...(existing.extra ?? []), ['problem', collision]];
|
|
590
|
+
if (existing.json)
|
|
591
|
+
existing.json.problem = collision;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
const raw = (() => { try {
|
|
595
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
return '';
|
|
599
|
+
} })();
|
|
600
|
+
const state = config ? live(name) : NO_LIVE_STATE;
|
|
601
|
+
const extra = [];
|
|
602
|
+
if (config) {
|
|
603
|
+
extra.push(['fires', routineFireLabel(config, now)]);
|
|
604
|
+
extra.push(['runs', routineTargetLabel(config)]);
|
|
605
|
+
extra.push(['devices', state.devices.length > 0
|
|
606
|
+
? state.devices.join(', ')
|
|
607
|
+
: state.materialized ? 'no device — will not fire' : 'no device allowlist yet']);
|
|
608
|
+
extra.push(['last', state.lastStatus
|
|
609
|
+
? `${state.lastStatus}${state.lastAt ? ` · ${compactAge(state.lastAt, now)} ago` : ''}`
|
|
610
|
+
: 'never run']);
|
|
611
|
+
extra.push(['enabled', routineEnabledHere(config, state)
|
|
612
|
+
? `yes${state.thisDevice ? ` (on ${state.thisDevice})` : ''}`
|
|
613
|
+
: `no${state.thisDevice ? ` (on ${state.thisDevice})` : ''}`]);
|
|
614
|
+
const repoRef = manifestText(config.repo);
|
|
615
|
+
if (repoRef)
|
|
616
|
+
extra.push(['repo', repoRef]);
|
|
617
|
+
const tz = manifestText(config.timezone);
|
|
618
|
+
if (tz)
|
|
619
|
+
extra.push(['timezone', tz]);
|
|
620
|
+
if (config.catchup === false)
|
|
621
|
+
extra.push(['catchup', 'off — a missed fire is lost']);
|
|
622
|
+
const host = manifestText(config.host);
|
|
623
|
+
if (host)
|
|
624
|
+
extra.push(['host', host]);
|
|
625
|
+
const pinned = manifestList(config.devices);
|
|
626
|
+
if (pinned.length > 0)
|
|
627
|
+
extra.push(['pinned', pinned.join(', ')]);
|
|
628
|
+
if (name !== basename)
|
|
629
|
+
extra.push(['file', file]);
|
|
630
|
+
}
|
|
631
|
+
else if (problem) {
|
|
632
|
+
extra.push(['problem', problem]);
|
|
633
|
+
}
|
|
634
|
+
const item = {
|
|
635
|
+
name,
|
|
636
|
+
source: repo.label,
|
|
637
|
+
path: filePath,
|
|
638
|
+
linkTarget: filePath,
|
|
639
|
+
description: routineDescription(config, raw),
|
|
640
|
+
extra,
|
|
641
|
+
// Field names match `agents routines list --json` where they overlap, so
|
|
642
|
+
// one script can consume both. `devices` is what the YAML pins;
|
|
643
|
+
// `enabledDevices` is what the fleet actually allows — the diagnostic pair.
|
|
644
|
+
json: {
|
|
645
|
+
path: filePath,
|
|
646
|
+
schedule: config?.schedule ?? null,
|
|
647
|
+
scheduleHuman: config ? routineFireLabel(config, now) : null,
|
|
648
|
+
trigger: config?.trigger ?? null,
|
|
649
|
+
runs: config ? routineTargetLabel(config) : null,
|
|
650
|
+
agent: config?.agent ?? null,
|
|
651
|
+
workflow: config?.workflow ?? null,
|
|
652
|
+
command: config?.command ?? null,
|
|
653
|
+
timezone: config?.timezone ?? null,
|
|
654
|
+
repo: config?.repo ?? null,
|
|
655
|
+
// Effective on THIS machine, matching `agents routines list`.
|
|
656
|
+
enabled: config ? routineEnabledHere(config, state) : false,
|
|
657
|
+
// What the file itself declares, before device activation overrides it.
|
|
658
|
+
enabledInFile: config?.enabled ?? false,
|
|
659
|
+
catchup: config ? config.catchup !== false : null,
|
|
660
|
+
devices: config ? manifestList(config.devices) : [],
|
|
661
|
+
enabledDevices: state.devices,
|
|
662
|
+
activationMaterialized: state.materialized,
|
|
663
|
+
oneShot: config ? isOneShotRoutine(config) : false,
|
|
664
|
+
expired: config ? isPastOneShotRoutine(config, now) : false,
|
|
665
|
+
lastStatus: state.lastStatus,
|
|
666
|
+
lastRunAt: state.lastAt,
|
|
667
|
+
problem,
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
seen.set(name, item);
|
|
671
|
+
items.push(item);
|
|
672
|
+
}
|
|
673
|
+
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
674
|
+
}
|
|
675
|
+
/** Read one `extra` row back out — the renderers' only accessor. */
|
|
676
|
+
function extraOf(item, key) {
|
|
677
|
+
return item.extra?.find(([k]) => k === key)?.[1] ?? '';
|
|
678
|
+
}
|
|
319
679
|
/**
|
|
320
680
|
* Enumerate one directory of resources of `kind`, skipping dotfiles, build caches,
|
|
321
681
|
* and directory docs. Shared so the rules `subrules/` branch and the default
|
|
@@ -391,6 +751,19 @@ export function pathSize(p) {
|
|
|
391
751
|
}
|
|
392
752
|
return { bytes, files };
|
|
393
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Weigh only the entries actually listed, for a kind whose directory holds more
|
|
756
|
+
* than its resources (routines share theirs with sandbox overlay HOMEs).
|
|
757
|
+
*/
|
|
758
|
+
function itemsSize(items) {
|
|
759
|
+
let bytes = 0, files = 0;
|
|
760
|
+
for (const item of items) {
|
|
761
|
+
const sub = pathSize(item.path);
|
|
762
|
+
bytes += sub.bytes;
|
|
763
|
+
files += sub.files;
|
|
764
|
+
}
|
|
765
|
+
return { bytes, files };
|
|
766
|
+
}
|
|
394
767
|
/** Human byte size: "84 KB", "3.1 MB". */
|
|
395
768
|
export function formatBytes(n) {
|
|
396
769
|
if (n < 1024)
|
|
@@ -442,10 +815,24 @@ function renderRepoSummary(repo, options) {
|
|
|
442
815
|
let totalBytes = 0, totalFiles = 0;
|
|
443
816
|
let repoHookByScript = new Map();
|
|
444
817
|
let repoMcpConfigs = new Map();
|
|
818
|
+
let routineActivationErrors = [];
|
|
445
819
|
if (!options.brief) {
|
|
446
820
|
for (const kind of DRILLABLE_KINDS) {
|
|
447
|
-
|
|
448
|
-
|
|
821
|
+
// Routines share one live provider across the collect so the fleet index is
|
|
822
|
+
// read once, and so its errors survive to be reported below.
|
|
823
|
+
let items;
|
|
824
|
+
if (kind === 'routines') {
|
|
825
|
+
const provider = defaultRoutineLive();
|
|
826
|
+
routineActivationErrors = provider.errors;
|
|
827
|
+
items = collectRepoRoutines(repo, provider.live);
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
items = collectRepoKind(repo, kind);
|
|
831
|
+
}
|
|
832
|
+
// `routines/` also holds `<name>/home/` sandbox overlays — often far larger
|
|
833
|
+
// than the definitions and not resources at all, so weigh the files listed
|
|
834
|
+
// rather than the directory tree.
|
|
835
|
+
const size = kind === 'routines' ? itemsSize(items) : pathSize(path.join(repo.root, kind));
|
|
449
836
|
kindData[kind] = { items, size };
|
|
450
837
|
totalBytes += size.bytes;
|
|
451
838
|
totalFiles += size.files;
|
|
@@ -486,6 +873,12 @@ function renderRepoSummary(repo, options) {
|
|
|
486
873
|
version: i.extra?.find(([k]) => k === 'version')?.[1],
|
|
487
874
|
groups: Object.fromEntries((i.groups ?? []).map(g => [g.label, g.items.length])),
|
|
488
875
|
})) }];
|
|
876
|
+
if (kind === 'routines')
|
|
877
|
+
return [kind, { ...base, items: items.map(i => ({
|
|
878
|
+
name: i.name,
|
|
879
|
+
description: i.description,
|
|
880
|
+
...(i.json ?? {}),
|
|
881
|
+
})) }];
|
|
489
882
|
return [kind, base];
|
|
490
883
|
})),
|
|
491
884
|
}, null, 2));
|
|
@@ -529,18 +922,35 @@ function renderRepoSummary(repo, options) {
|
|
|
529
922
|
}
|
|
530
923
|
}
|
|
531
924
|
if (!options.brief) {
|
|
532
|
-
console.log(` ${'size'.padEnd(10)} ${formatBytes(totalBytes)} ${chalk.gray('·')} ${totalFiles} files`);
|
|
925
|
+
console.log(` ${'size'.padEnd(10)} ${formatBytes(totalBytes)} ${chalk.gray('·')} ${totalFiles} ${totalFiles === 1 ? 'file' : 'files'}`);
|
|
533
926
|
console.log('\n' + chalk.bold('Resources'));
|
|
534
927
|
for (const kind of SIMPLE_KINDS) {
|
|
535
928
|
const { items, size } = kindData[kind];
|
|
536
929
|
const count = String(items.length).padStart(4);
|
|
537
930
|
const sz = items.length > 0 ? formatBytes(size.bytes).padStart(8) : ''.padEnd(8);
|
|
538
|
-
|
|
539
|
-
|
|
931
|
+
// Width-aware, not a fixed 60: the old cap cut its own "…(+16)" tail off
|
|
932
|
+
// the rules row identically at 80, 100 and 160 columns.
|
|
933
|
+
const prefix = ` ${kind.padEnd(10)} ${count} ${sz} `;
|
|
934
|
+
const preview = items.length > 0
|
|
935
|
+
? chalk.gray(truncateToWidth(previewNames(items, 4), Math.max(12, terminalWidth() - stringWidth(prefix))))
|
|
936
|
+
: '';
|
|
937
|
+
console.log(`${prefix}${preview}`.trimEnd());
|
|
540
938
|
}
|
|
541
939
|
printExpandedSection('Hooks', hookRows(kindData.hooks.items, repoHookByScript));
|
|
542
940
|
printExpandedSection('Plugins', pluginRows(kindData.plugins.items));
|
|
543
941
|
printExpandedSection('MCP', mcpRows(kindData.mcp.items, repoMcpConfigs));
|
|
942
|
+
printExpandedSection('Routines', routineRows(kindData.routines.items));
|
|
943
|
+
const dark = routineDarkWarning(kindData.routines.items);
|
|
944
|
+
if (dark) {
|
|
945
|
+
console.log('');
|
|
946
|
+
console.log(` ${chalk.yellow('⚠')} ${dark}`);
|
|
947
|
+
console.log(chalk.gray(` Fix: agents routines devices <name> --set <device>`));
|
|
948
|
+
}
|
|
949
|
+
// An unreadable device document silently removes that device from every
|
|
950
|
+
// `devices` cell, which reads as "not enabled there" — say so instead.
|
|
951
|
+
for (const err of routineActivationErrors) {
|
|
952
|
+
console.log(chalk.gray(` device config unreadable — ${err}`));
|
|
953
|
+
}
|
|
544
954
|
}
|
|
545
955
|
console.log('');
|
|
546
956
|
console.log(chalk.gray(`Drill in: agents inspect ${repo.label} --skills <query>`));
|
|
@@ -685,6 +1095,7 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
685
1095
|
printExpandedSection('Hook files', hookRows(itemsByKind.hooks, hookByScript));
|
|
686
1096
|
printExpandedSection('Plugins', pluginRows(itemsByKind.plugins));
|
|
687
1097
|
printExpandedSection('MCP', mcpRows(itemsByKind.mcp, mcpConfigs));
|
|
1098
|
+
printExpandedSection('Routines', agentRoutineRows(itemsByKind.routines));
|
|
688
1099
|
}
|
|
689
1100
|
if (sessions) {
|
|
690
1101
|
console.log('\n' + chalk.bold('Sessions'));
|
|
@@ -698,15 +1109,22 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
698
1109
|
// ─── List mode ───────────────────────────────────────────────────────────────
|
|
699
1110
|
async function renderList(agent, version, versionHome, kind, options) {
|
|
700
1111
|
const items = collectKind(agent, versionHome, kind);
|
|
701
|
-
renderItemList(`${agent}@${version}`, { agent, version }, kind, items, options);
|
|
1112
|
+
await renderItemList(`${agent}@${version}`, { agent, version }, kind, items, options);
|
|
702
1113
|
}
|
|
703
|
-
function renderItemList(header, jsonHead, kind, items, options) {
|
|
1114
|
+
async function renderItemList(header, jsonHead, kind, items, options, hookManifest) {
|
|
704
1115
|
if (options.json) {
|
|
705
1116
|
console.log(JSON.stringify({
|
|
706
1117
|
...jsonHead,
|
|
707
1118
|
kind,
|
|
708
1119
|
count: items.length,
|
|
709
|
-
items: items.map(i => ({
|
|
1120
|
+
items: items.map(i => ({
|
|
1121
|
+
name: i.name,
|
|
1122
|
+
source: i.source,
|
|
1123
|
+
path: i.path,
|
|
1124
|
+
description: i.description,
|
|
1125
|
+
...(i.groups ? { groups: i.groups } : {}),
|
|
1126
|
+
...(i.json ?? {}),
|
|
1127
|
+
})),
|
|
710
1128
|
}, null, 2));
|
|
711
1129
|
return;
|
|
712
1130
|
}
|
|
@@ -716,29 +1134,129 @@ function renderItemList(header, jsonHead, kind, items, options) {
|
|
|
716
1134
|
console.log('');
|
|
717
1135
|
return;
|
|
718
1136
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1137
|
+
// Route through the shared resource view: an interactive picker with a live
|
|
1138
|
+
// preview in a TTY, a plain aligned table when piped. This is the same
|
|
1139
|
+
// renderer `agents skills list`, `agents commands` and four others already
|
|
1140
|
+
// use — inspect was the last drill-down printing its own two-line-per-entry
|
|
1141
|
+
// dump with a `[source]` tag repeated on every row.
|
|
1142
|
+
//
|
|
1143
|
+
// Sync targets are deliberately off: for a repo the resources ARE the source,
|
|
1144
|
+
// so the column would read "no installed versions" for every row.
|
|
1145
|
+
const sources = new Set(items.map(i => i.source));
|
|
1146
|
+
// A hook has no prose description — it is a script. Leaving the column blank
|
|
1147
|
+
// is honest but useless, so it carries what the Hooks view is actually for:
|
|
1148
|
+
// the events that fire it. Same string the overview prints.
|
|
1149
|
+
const hookEvents = kind !== 'hooks' ? null : (hookManifest ?? hookManifestByScript(loadCentralHookManifest()));
|
|
1150
|
+
// A routine's byte size says nothing; whether it last ran, and where it is
|
|
1151
|
+
// allowed to run, is the whole question. Devices needs the room, so it only
|
|
1152
|
+
// takes a column on a wide terminal — below that it folds into the description
|
|
1153
|
+
// rather than being dropped.
|
|
1154
|
+
const isRoutines = kind === 'routines';
|
|
1155
|
+
const routineDevicesColumn = isRoutines && terminalWidth() >= 100;
|
|
1156
|
+
await showResourceList({
|
|
1157
|
+
resourcePlural: kind,
|
|
1158
|
+
resourceSingular: kind.replace(/s$/, ''),
|
|
1159
|
+
extraLabel: isRoutines ? 'Last' : 'Size',
|
|
1160
|
+
// Only carry a source column when the rows actually differ; a uniform
|
|
1161
|
+
// `[.system]` on every row is 13 columns spent on one bit of information.
|
|
1162
|
+
extra2Label: routineDevicesColumn ? 'Devices' : (isRoutines ? undefined : (sources.size > 1 ? 'Source' : undefined)),
|
|
1163
|
+
showSync: false,
|
|
1164
|
+
// inspect's names run long — hook scripts (`00-agent-verify-work-complete`
|
|
1165
|
+
// and its `_test` sibling) and namespaced plugin entries (`/swarm:orchestrate`)
|
|
1166
|
+
// both blow past the 22-char default and truncate to the same string.
|
|
1167
|
+
//
|
|
1168
|
+
// Gate on terminal width, NOT on "do any rows have a description": that
|
|
1169
|
+
// proxy depended on which path built the items — the repo path hardcodes an
|
|
1170
|
+
// empty description so it widened, while the agent path filled one from the
|
|
1171
|
+
// script's shebang so it never did. Same command, two behaviours.
|
|
1172
|
+
nameCap: terminalWidth() >= 120 ? 40 : undefined,
|
|
1173
|
+
// Keep names clickable — the pre-picker list wrapped every name in an OSC-8
|
|
1174
|
+
// link to its file, and losing that would be a silent capability regression.
|
|
1175
|
+
linkFor: row => items.find(i => i.name === row.name)?.linkTarget,
|
|
1176
|
+
emptyMessage: ` (none installed)`,
|
|
1177
|
+
rows: items.map(item => ({
|
|
1178
|
+
name: item.name,
|
|
1179
|
+
description: (() => {
|
|
1180
|
+
const hook = hookEvents?.get(item.name);
|
|
1181
|
+
if (hook)
|
|
1182
|
+
return summarizeHook(hook);
|
|
1183
|
+
if (isRoutines) {
|
|
1184
|
+
const problem = extraOf(item, 'problem');
|
|
1185
|
+
if (problem)
|
|
1186
|
+
return problem;
|
|
1187
|
+
// Compose at the row, leaving item.description pure prose for JSON.
|
|
1188
|
+
// Also makes "daily" and "zion" searchable in the picker's filter.
|
|
1189
|
+
return [
|
|
1190
|
+
extraOf(item, 'fires'),
|
|
1191
|
+
routineDevicesColumn ? '' : compactDeviceCell(extraOf(item, 'devices')),
|
|
1192
|
+
summaryLine(item.description),
|
|
1193
|
+
].filter(Boolean).join(' · ');
|
|
1194
|
+
}
|
|
1195
|
+
return summaryLine(item.description);
|
|
1196
|
+
})(),
|
|
1197
|
+
extra: isRoutines ? routineLastCell(item) : itemSizeLabel(item.path),
|
|
1198
|
+
extra2: routineDevicesColumn
|
|
1199
|
+
? compactDeviceCell(extraOf(item, 'devices')) || 'none'
|
|
1200
|
+
: (isRoutines ? undefined : (sources.size > 1 ? item.source : undefined)),
|
|
1201
|
+
targets: [],
|
|
1202
|
+
// Pass the resolved manifest: the picker rebuilds this on every arrow
|
|
1203
|
+
// key, so re-reading ~11 KB of YAML per keystroke is wasted work on top
|
|
1204
|
+
// of being the wrong manifest for a repo target.
|
|
1205
|
+
buildDetail: () => previewFor(kind, item, hookEvents ?? new Map()),
|
|
1206
|
+
})),
|
|
1207
|
+
});
|
|
1208
|
+
// On a TTY the picker's preview pane carries each plugin's bundled skills and
|
|
1209
|
+
// commands. Piped, there is no pane — and the pre-picker output printed those
|
|
1210
|
+
// lines under every row, so a table alone would silently drop what a plugin
|
|
1211
|
+
// actually ships from `agents inspect . --plugins | grep`.
|
|
1212
|
+
if (!isInteractiveTerminal() && items.some(i => i.groups?.length)) {
|
|
1213
|
+
for (const item of items) {
|
|
1214
|
+
if (!item.groups?.length)
|
|
1215
|
+
continue;
|
|
1216
|
+
console.log('\n' + chalk.cyan(item.name));
|
|
1217
|
+
const width = Math.max(...item.groups.map(g => g.label.length));
|
|
1218
|
+
for (const g of item.groups) {
|
|
1219
|
+
const colorFn = PLUGIN_GROUP_COLORS[g.label] ?? chalk.white;
|
|
1220
|
+
// Wrapped, not truncated: routing these through a truncating helper is
|
|
1221
|
+
// what once showed 4 of a 10-command plugin at 80 columns.
|
|
1222
|
+
printWrappedJoined(` ${chalk.gray(g.label.padEnd(width))} `, g.items.map(s => colorFn(s)), ', ');
|
|
1223
|
+
}
|
|
725
1224
|
}
|
|
726
|
-
|
|
727
|
-
printGroupRows(item.groups);
|
|
1225
|
+
console.log('');
|
|
728
1226
|
}
|
|
729
|
-
console.log('');
|
|
730
1227
|
}
|
|
731
|
-
/**
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1228
|
+
/**
|
|
1229
|
+
* The scannable half of a description: everything before the trigger clause, and
|
|
1230
|
+
* only the first sentence of that. 15 of 20 skills in .system lead with their
|
|
1231
|
+
* purpose and then append "Triggers on: …" — in a one-line row that boilerplate
|
|
1232
|
+
* is what survives truncation, so the row says nothing. The full text still
|
|
1233
|
+
* renders in the preview pane.
|
|
1234
|
+
*/
|
|
1235
|
+
export function summaryLine(description) {
|
|
1236
|
+
if (!description)
|
|
1237
|
+
return '';
|
|
1238
|
+
const cutAtTrigger = description.split(/\s*(?:Triggers on|Use this skill when|Invoke when)\b/i)[0];
|
|
1239
|
+
// A description that OPENS with the trigger clause splits to an empty head.
|
|
1240
|
+
// Returning that would blank the row while --json still carried the text —
|
|
1241
|
+
// showing less than we have is worse than showing boilerplate.
|
|
1242
|
+
const trimmed = cutAtTrigger.trim() || description.trim();
|
|
1243
|
+
// First sentence, but only when the split leaves something substantial —
|
|
1244
|
+
// "e.g." and friends would otherwise chop a description to a fragment.
|
|
1245
|
+
const firstSentence = trimmed.match(/^.*?[.!?](?=\s+[A-Z(`])/)?.[0];
|
|
1246
|
+
const candidate = firstSentence && firstSentence.length >= 40 ? firstSentence : trimmed;
|
|
1247
|
+
return candidate.replace(/\s+/g, ' ').trim();
|
|
1248
|
+
}
|
|
1249
|
+
/** Compact size for a list row: `18 KB` for a bundle, `413 B` for a single file. */
|
|
1250
|
+
function itemSizeLabel(p) {
|
|
1251
|
+
if (!p)
|
|
1252
|
+
return '';
|
|
1253
|
+
const stat = safeStat(p);
|
|
1254
|
+
if (!stat)
|
|
1255
|
+
return '';
|
|
1256
|
+
if (!stat.isDirectory())
|
|
1257
|
+
return formatBytes(stat.size);
|
|
1258
|
+
const { bytes } = pathSize(p);
|
|
1259
|
+
return formatBytes(bytes);
|
|
742
1260
|
}
|
|
743
1261
|
// ─── Detail mode (fuzzy) ─────────────────────────────────────────────────────
|
|
744
1262
|
async function renderDetail(agent, version, versionHome, kind, query, options) {
|
|
@@ -868,6 +1386,13 @@ function summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs, hookInvento
|
|
|
868
1386
|
groups: Object.fromEntries((i.groups ?? []).map(g => [g.label, g.items.length])),
|
|
869
1387
|
})) };
|
|
870
1388
|
}
|
|
1389
|
+
else if (kind === 'routines') {
|
|
1390
|
+
out[kind] = { ...base, items: items.map(i => ({
|
|
1391
|
+
name: i.name,
|
|
1392
|
+
source: i.source,
|
|
1393
|
+
...Object.fromEntries(i.extra ?? []),
|
|
1394
|
+
})) };
|
|
1395
|
+
}
|
|
871
1396
|
else {
|
|
872
1397
|
out[kind] = { ...base, names: items.map(i => i.name) };
|
|
873
1398
|
}
|
|
@@ -900,8 +1425,54 @@ function collectKind(agent, versionHome, kind) {
|
|
|
900
1425
|
}));
|
|
901
1426
|
case 'plugins':
|
|
902
1427
|
return pluginItems();
|
|
1428
|
+
case 'routines':
|
|
1429
|
+
return agentRoutineItems(agent);
|
|
903
1430
|
}
|
|
904
1431
|
}
|
|
1432
|
+
/**
|
|
1433
|
+
* The routines that dispatch this agent.
|
|
1434
|
+
*
|
|
1435
|
+
* Version-invariant, like the `rules`/`subagents` cases above which also ignore
|
|
1436
|
+
* `versionHome`: a routine names an agent, never an agent@version. `listJobs()`
|
|
1437
|
+
* with no cwd is deliberate — it is the daemon's own view (user + system), so an
|
|
1438
|
+
* unsynced project routine that can never fire is not listed as if it could.
|
|
1439
|
+
*/
|
|
1440
|
+
function agentRoutineItems(agent) {
|
|
1441
|
+
const { live } = defaultRoutineLive();
|
|
1442
|
+
const now = new Date();
|
|
1443
|
+
let jobs;
|
|
1444
|
+
try {
|
|
1445
|
+
jobs = listJobs();
|
|
1446
|
+
}
|
|
1447
|
+
catch {
|
|
1448
|
+
return [];
|
|
1449
|
+
}
|
|
1450
|
+
return jobs
|
|
1451
|
+
.filter(job => job.agent === agent)
|
|
1452
|
+
.map(job => {
|
|
1453
|
+
const state = live(job.name);
|
|
1454
|
+
return {
|
|
1455
|
+
name: job.name,
|
|
1456
|
+
source: 'user',
|
|
1457
|
+
path: '',
|
|
1458
|
+
linkTarget: '',
|
|
1459
|
+
description: routineDescription(job, ''),
|
|
1460
|
+
extra: [
|
|
1461
|
+
['fires', routineFireLabel(job, now)],
|
|
1462
|
+
// listJobs already applied device activation, so job.enabled is the
|
|
1463
|
+
// effective answer here — no need to re-derive it.
|
|
1464
|
+
['enabled', job.enabled ? 'yes' : 'no'],
|
|
1465
|
+
['devices', state.devices.length > 0
|
|
1466
|
+
? state.devices.join(', ')
|
|
1467
|
+
: state.materialized ? 'no device — will not fire' : 'no device allowlist yet'],
|
|
1468
|
+
['last', state.lastStatus
|
|
1469
|
+
? `${state.lastStatus}${state.lastAt ? ` · ${compactAge(state.lastAt, now)} ago` : ''}`
|
|
1470
|
+
: 'never run'],
|
|
1471
|
+
],
|
|
1472
|
+
};
|
|
1473
|
+
})
|
|
1474
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1475
|
+
}
|
|
905
1476
|
function pluginItems() {
|
|
906
1477
|
return discoverPlugins().map(p => pluginToItem(p, 'user'));
|
|
907
1478
|
}
|
|
@@ -985,7 +1556,13 @@ function entriesFromAgentResources(agent, versionHome, kind) {
|
|
|
985
1556
|
source: e.scope,
|
|
986
1557
|
path: e.path,
|
|
987
1558
|
linkTarget: linkTarget(e.path),
|
|
988
|
-
description
|
|
1559
|
+
// A hook is a shell/Python script with no human description, so the prose
|
|
1560
|
+
// fallback returned code: all 53 under an agent home read "!/usr/bin/env
|
|
1561
|
+
// bash" (the `#` strip eating the shebang), and skipping that line only
|
|
1562
|
+
// promoted `set -euo pipefail`. repoHookItems already hardcodes '' for the
|
|
1563
|
+
// repo path; this makes the agent path agree instead of guessing. The
|
|
1564
|
+
// Hooks view shows firing events in that column.
|
|
1565
|
+
description: kind === 'hooks' ? '' : readDescription(e.path),
|
|
989
1566
|
}));
|
|
990
1567
|
}
|
|
991
1568
|
function skillsFromAgentResources(agent, versionHome) {
|
|
@@ -1021,6 +1598,96 @@ function buildDetail(item, kind) {
|
|
|
1021
1598
|
out[k] = v;
|
|
1022
1599
|
return out;
|
|
1023
1600
|
}
|
|
1601
|
+
/**
|
|
1602
|
+
* The preview pane for one row, refreshed as the selection moves.
|
|
1603
|
+
*
|
|
1604
|
+
* Adaptive on purpose — the sessions picker sets the precedent: a Cursor
|
|
1605
|
+
* session shows Dirs/Repos/Artifacts, a Codex one shows a different set, and
|
|
1606
|
+
* empty fields simply do not render. A hook's useful metadata (what fires it,
|
|
1607
|
+
* whether it is wired) has nothing in common with a skill's (what invokes it,
|
|
1608
|
+
* where it is synced), so each kind contributes its own rows and blanks drop out.
|
|
1609
|
+
*/
|
|
1610
|
+
export function previewFor(kind, item, hookManifest) {
|
|
1611
|
+
const out = [];
|
|
1612
|
+
const label = (k, v) => ` ${chalk.gray(k.padEnd(11))}${v}`;
|
|
1613
|
+
out.push(chalk.bold.cyan(item.name) + ' ' + chalk.gray(`${kind.replace(/s$/, '')} · ${item.source}`));
|
|
1614
|
+
if (item.description) {
|
|
1615
|
+
out.push('');
|
|
1616
|
+
for (const line of wrapJoined(' ', item.description.split(/\s+/), ' ', terminalWidth())) {
|
|
1617
|
+
out.push(chalk.white(line));
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
const rows = [];
|
|
1621
|
+
// A hook's identity is when it fires — the summary view has always shown this
|
|
1622
|
+
// while the drill-down showed only a size.
|
|
1623
|
+
if (kind === 'hooks') {
|
|
1624
|
+
// Use the manifest the caller already resolved. Re-resolving the CENTRAL
|
|
1625
|
+
// one here made the preview contradict its own row: a repo hook wired by
|
|
1626
|
+
// that repo's agents.yaml showed `PreToolUse(Bash)` in the table and
|
|
1627
|
+
// "not registered" in the pane below it, and the mirror case credited a
|
|
1628
|
+
// central registration to the repo. No `?? loadCentralHookManifest()`
|
|
1629
|
+
// fallback: it would be dead in production and would silently reinstate
|
|
1630
|
+
// exactly that bug the next time a caller forgot the argument.
|
|
1631
|
+
const hook = hookManifest.get(item.name);
|
|
1632
|
+
if (hook) {
|
|
1633
|
+
rows.push(['fires', chalk.yellow(summarizeHook(hook))]);
|
|
1634
|
+
rows.push(['wired', chalk.green('yes') + chalk.gray(' · agents.yaml')]);
|
|
1635
|
+
}
|
|
1636
|
+
else {
|
|
1637
|
+
rows.push(['wired', chalk.gray('no — on disk but not registered')]);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (kind === 'skills' || kind === 'commands' || kind === 'subagents') {
|
|
1641
|
+
const fm = readFrontmatter(item.path);
|
|
1642
|
+
if (fm) {
|
|
1643
|
+
const triggers = manifestList(fm.triggers).join(', ');
|
|
1644
|
+
if (triggers)
|
|
1645
|
+
rows.push(['triggers', triggers]);
|
|
1646
|
+
const model = manifestText(fm.model);
|
|
1647
|
+
if (model)
|
|
1648
|
+
rows.push(['model', model]);
|
|
1649
|
+
const tools = manifestList(fm.tools).join(', ');
|
|
1650
|
+
if (tools)
|
|
1651
|
+
rows.push(['tools', tools]);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
if (item.extra)
|
|
1655
|
+
rows.push(...item.extra);
|
|
1656
|
+
const size = itemSizeDetail(item.path);
|
|
1657
|
+
if (size)
|
|
1658
|
+
rows.push(['size', size]);
|
|
1659
|
+
if (item.path)
|
|
1660
|
+
rows.push(['path', chalk.blue(item.path)]);
|
|
1661
|
+
if (rows.length > 0) {
|
|
1662
|
+
out.push('');
|
|
1663
|
+
for (const [k, v] of rows)
|
|
1664
|
+
out.push(label(k, v));
|
|
1665
|
+
}
|
|
1666
|
+
// A plugin's bundled resources, each on its own line rather than a comma run.
|
|
1667
|
+
if (item.groups?.length) {
|
|
1668
|
+
for (const g of item.groups) {
|
|
1669
|
+
out.push('');
|
|
1670
|
+
out.push(' ' + chalk.bold(`${g.label} (${g.items.length})`));
|
|
1671
|
+
for (const entry of g.items.slice(0, 8))
|
|
1672
|
+
out.push(' ' + chalk.green(entry));
|
|
1673
|
+
if (g.items.length > 8)
|
|
1674
|
+
out.push(' ' + chalk.gray(`…${g.items.length - 8} more`));
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return out.join('\n');
|
|
1678
|
+
}
|
|
1679
|
+
/** `18 KB · 3 files` for a bundle, `413 B` for a single file. */
|
|
1680
|
+
function itemSizeDetail(p) {
|
|
1681
|
+
if (!p)
|
|
1682
|
+
return '';
|
|
1683
|
+
const stat = safeStat(p);
|
|
1684
|
+
if (!stat)
|
|
1685
|
+
return '';
|
|
1686
|
+
if (!stat.isDirectory())
|
|
1687
|
+
return formatBytes(stat.size);
|
|
1688
|
+
const { bytes, files } = pathSize(p);
|
|
1689
|
+
return `${formatBytes(bytes)} · ${files} ${files === 1 ? 'file' : 'files'}`;
|
|
1690
|
+
}
|
|
1024
1691
|
function buildDetailRows(item, kind) {
|
|
1025
1692
|
const rows = [];
|
|
1026
1693
|
if (item.path && kind !== 'mcp') {
|
|
@@ -1030,7 +1697,7 @@ function buildDetailRows(item, kind) {
|
|
|
1030
1697
|
if (stat) {
|
|
1031
1698
|
if (stat.isDirectory()) {
|
|
1032
1699
|
const { bytes, files } = pathSize(item.path);
|
|
1033
|
-
rows.push(['size', `${formatBytes(bytes)} · ${files} files`]);
|
|
1700
|
+
rows.push(['size', `${formatBytes(bytes)} · ${files} ${files === 1 ? 'file' : 'files'}`]);
|
|
1034
1701
|
}
|
|
1035
1702
|
else {
|
|
1036
1703
|
rows.push(['size', formatBytes(stat.size)]);
|
|
@@ -1065,9 +1732,13 @@ function buildDetailRows(item, kind) {
|
|
|
1065
1732
|
if (item.groups)
|
|
1066
1733
|
for (const g of item.groups)
|
|
1067
1734
|
rows.push([g.label, g.items.join(', ')]);
|
|
1068
|
-
if (item.extra)
|
|
1069
|
-
rows.push(...item.extra);
|
|
1070
1735
|
}
|
|
1736
|
+
// Every kind's scalar rows. previewFor already renders `extra` unconditionally,
|
|
1737
|
+
// so gating it on plugins here made the detail view and its own preview pane
|
|
1738
|
+
// disagree about the same item — and dropped a routine's schedule/status from
|
|
1739
|
+
// `--routine <name> --json` entirely.
|
|
1740
|
+
if (item.extra)
|
|
1741
|
+
rows.push(...item.extra);
|
|
1071
1742
|
return rows;
|
|
1072
1743
|
}
|
|
1073
1744
|
/** `system` → `sys`; everything else unchanged. Keeps the tag column narrow. */
|
|
@@ -1191,7 +1862,16 @@ export function hookManifestByScript(manifest) {
|
|
|
1191
1862
|
}
|
|
1192
1863
|
/** Build hook rows by enriching the installed hook items with manifest events/predicates. */
|
|
1193
1864
|
function hookRows(items, byScript) {
|
|
1194
|
-
|
|
1865
|
+
// The section shows only the first handful before a `…(+N)` tail, so order
|
|
1866
|
+
// matters: wired hooks first. Alphabetically `00-…_test` sorts next to the
|
|
1867
|
+
// hook it tests, so half the visible rows were test scaffolding with a blank
|
|
1868
|
+
// event column while real registered hooks hid behind the tail.
|
|
1869
|
+
const ordered = [...items].sort((a, b) => {
|
|
1870
|
+
const aw = byScript.has(a.name) ? 0 : 1;
|
|
1871
|
+
const bw = byScript.has(b.name) ? 0 : 1;
|
|
1872
|
+
return aw !== bw ? aw - bw : a.name.localeCompare(b.name);
|
|
1873
|
+
});
|
|
1874
|
+
return ordered.map(item => {
|
|
1195
1875
|
const hook = byScript.get(item.name);
|
|
1196
1876
|
return {
|
|
1197
1877
|
source: item.source,
|
|
@@ -1212,6 +1892,152 @@ function pluginRows(items) {
|
|
|
1212
1892
|
return { source: item.source, name: item.name, detail, linkTarget: item.linkTarget };
|
|
1213
1893
|
});
|
|
1214
1894
|
}
|
|
1895
|
+
/** Statuses that mean the last run did not do its job. */
|
|
1896
|
+
const UNHEALTHY_RUN = new Set(['failed', 'timeout', 'missed', 'blocked']);
|
|
1897
|
+
/**
|
|
1898
|
+
* Health tier for the section ordering. Lower sorts first.
|
|
1899
|
+
*
|
|
1900
|
+
* `printExpandedSection` shows only the first handful before a `…(+N)` tail, so
|
|
1901
|
+
* for a repo with 24 routines the sort IS the section. Same reasoning as
|
|
1902
|
+
* `hookRows` putting wired hooks first: a routine that fires nowhere is exactly
|
|
1903
|
+
* what the reader opened this for, and must never hide behind the tail.
|
|
1904
|
+
*/
|
|
1905
|
+
export function routineHealthTier(item) {
|
|
1906
|
+
if (extraOf(item, 'problem'))
|
|
1907
|
+
return 0;
|
|
1908
|
+
const fires = extraOf(item, 'fires');
|
|
1909
|
+
if (fires.includes('expired'))
|
|
1910
|
+
return 5;
|
|
1911
|
+
const devices = extraOf(item, 'devices');
|
|
1912
|
+
if (devices.startsWith('no device —'))
|
|
1913
|
+
return 1;
|
|
1914
|
+
if (extraOf(item, 'enabled').startsWith('no'))
|
|
1915
|
+
return 2;
|
|
1916
|
+
const last = extraOf(item, 'last').split(' ')[0];
|
|
1917
|
+
if (UNHEALTHY_RUN.has(last))
|
|
1918
|
+
return 3;
|
|
1919
|
+
return 4;
|
|
1920
|
+
}
|
|
1921
|
+
/** Build routine rows: `daily at 9:00 AM zion completed · 2h ago`. Broken first. */
|
|
1922
|
+
function routineRows(items) {
|
|
1923
|
+
const ordered = [...items].sort((a, b) => {
|
|
1924
|
+
const at = routineHealthTier(a), bt = routineHealthTier(b);
|
|
1925
|
+
return at !== bt ? at - bt : a.name.localeCompare(b.name);
|
|
1926
|
+
});
|
|
1927
|
+
const nameW = ordered.length > 0 ? Math.max(...ordered.slice(0, 6).map(r => r.name.length)) : 0;
|
|
1928
|
+
// Budget the three cells against what printExpandedSection leaves after the
|
|
1929
|
+
// `[source]` tag and the name column, so the status never truncates to `fai…`.
|
|
1930
|
+
const budget = Math.max(30, terminalWidth() - nameW - 14);
|
|
1931
|
+
const firesW = Math.min(21, Math.max(12, Math.floor(budget * 0.42)));
|
|
1932
|
+
const devicesW = Math.min(14, Math.max(7, Math.floor(budget * 0.22)));
|
|
1933
|
+
return ordered.map(item => {
|
|
1934
|
+
const problem = extraOf(item, 'problem');
|
|
1935
|
+
const fires = extraOf(item, 'fires');
|
|
1936
|
+
// A routine with no `fires` never parsed — the problem IS the whole row. One
|
|
1937
|
+
// that parsed but carries a problem (a duplicate-extension sibling) keeps its
|
|
1938
|
+
// schedule and status, with the problem appended rather than replacing them.
|
|
1939
|
+
if (problem && !fires) {
|
|
1940
|
+
return { source: item.source, name: item.name, linkTarget: item.linkTarget, detail: chalk.red(problem) };
|
|
1941
|
+
}
|
|
1942
|
+
const devices = compactDeviceCell(extraOf(item, 'devices'));
|
|
1943
|
+
const last = extraOf(item, 'last');
|
|
1944
|
+
// Pad the PLAIN strings, then colour. Padding a chalk-wrapped string counts
|
|
1945
|
+
// the escape codes as width and silently steals columns from the next cell.
|
|
1946
|
+
// The trailing problem gets what the three sized cells leave, or it would
|
|
1947
|
+
// blow the row past the terminal and break the alignment this budget exists
|
|
1948
|
+
// to hold — a 147-column row at COLUMNS=100 in testing.
|
|
1949
|
+
const problemW = Math.max(12, budget - firesW - devicesW - stringWidth(last) - 8);
|
|
1950
|
+
const detail = [
|
|
1951
|
+
chalk.gray(truncateToWidth(fires, firesW).padEnd(firesW)),
|
|
1952
|
+
(devices === 'none ⚠' ? chalk.yellow : chalk.gray)(truncateToWidth(devices, devicesW).padEnd(devicesW)),
|
|
1953
|
+
colorLast(last),
|
|
1954
|
+
problem ? chalk.red(truncateToWidth(` ⚠ ${problem}`, problemW)) : '',
|
|
1955
|
+
].join(' ').trimEnd();
|
|
1956
|
+
return {
|
|
1957
|
+
source: item.source,
|
|
1958
|
+
name: item.name,
|
|
1959
|
+
linkTarget: item.linkTarget,
|
|
1960
|
+
detail,
|
|
1961
|
+
};
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Routine rows for an agent target: `daily at 9:00 AM completed · 2h ago`.
|
|
1966
|
+
* No devices column — an agent view is inherently about this machine.
|
|
1967
|
+
*/
|
|
1968
|
+
function agentRoutineRows(items) {
|
|
1969
|
+
const ordered = [...items].sort((a, b) => {
|
|
1970
|
+
const at = routineHealthTier(a), bt = routineHealthTier(b);
|
|
1971
|
+
return at !== bt ? at - bt : a.name.localeCompare(b.name);
|
|
1972
|
+
});
|
|
1973
|
+
return ordered.map(item => ({
|
|
1974
|
+
source: item.source,
|
|
1975
|
+
name: item.name,
|
|
1976
|
+
detail: `${extraOf(item, 'fires').padEnd(20)} ${colorLast(extraOf(item, 'last'))}`,
|
|
1977
|
+
}));
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* `ok 2h` / `fail 3d` / `never` — the Last column, clamped to the 10-wide cell.
|
|
1981
|
+
*
|
|
1982
|
+
* The picker path pads without truncating, so an 11-char `blocked 3d` would push
|
|
1983
|
+
* the next column; the abbreviations cover only the two commonest statuses, so
|
|
1984
|
+
* the clamp is what actually holds the invariant.
|
|
1985
|
+
*/
|
|
1986
|
+
function routineLastCell(item, width = 10) {
|
|
1987
|
+
const last = extraOf(item, 'last');
|
|
1988
|
+
if (!last || last === 'never run')
|
|
1989
|
+
return 'never';
|
|
1990
|
+
const [status, , age] = last.split(' ');
|
|
1991
|
+
const word = status === 'completed' ? 'ok' : status === 'failed' ? 'fail' : status;
|
|
1992
|
+
return truncateToWidth(age ? `${word} ${age}` : word, width);
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Trim the long-form `devices` phrasing down to a cell.
|
|
1996
|
+
*
|
|
1997
|
+
* The extra row is written for the detail pane ("no device — will not fire");
|
|
1998
|
+
* a 16-column cell needs the same fact in two words.
|
|
1999
|
+
*/
|
|
2000
|
+
function compactDeviceCell(devices) {
|
|
2001
|
+
if (devices.startsWith('no device —'))
|
|
2002
|
+
return 'none ⚠';
|
|
2003
|
+
if (devices.startsWith('no device allowlist yet'))
|
|
2004
|
+
return 'unset';
|
|
2005
|
+
const list = devices.split(', ').filter(Boolean);
|
|
2006
|
+
return list.length > 1 ? `${list[0]} +${list.length - 1}` : (list[0] ?? '');
|
|
2007
|
+
}
|
|
2008
|
+
function colorLast(last) {
|
|
2009
|
+
if (!last || last === 'never run')
|
|
2010
|
+
return chalk.gray('never run');
|
|
2011
|
+
const word = last.split(' ')[0];
|
|
2012
|
+
if (word === 'completed')
|
|
2013
|
+
return chalk.green(last);
|
|
2014
|
+
if (UNHEALTHY_RUN.has(word))
|
|
2015
|
+
return chalk.red(last);
|
|
2016
|
+
return chalk.gray(last);
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* The one-line warning under the Routines section, or null when it would mislead.
|
|
2020
|
+
*
|
|
2021
|
+
* Silent until some device has materialized a `routines:` list — before that,
|
|
2022
|
+
* every routine is legitimately unpinned and "will not fire" would be noise.
|
|
2023
|
+
*/
|
|
2024
|
+
export function routineDarkWarning(items) {
|
|
2025
|
+
const materialized = items.some(i => {
|
|
2026
|
+
const d = extraOf(i, 'devices');
|
|
2027
|
+
return d !== '' && !d.startsWith('no device allowlist yet');
|
|
2028
|
+
});
|
|
2029
|
+
if (!materialized)
|
|
2030
|
+
return null;
|
|
2031
|
+
const dark = items.filter(i => extraOf(i, 'devices').startsWith('no device —')).length;
|
|
2032
|
+
if (dark === 0)
|
|
2033
|
+
return null;
|
|
2034
|
+
// The noun pluralizes on the total ("1 of 2 routines"), the verb on the count
|
|
2035
|
+
// that is actually dark ("... is on no device's allowlist").
|
|
2036
|
+
const noun = items.length === 1 ? 'routine' : 'routines';
|
|
2037
|
+
const verb = dark === 1 ? 'is' : 'are';
|
|
2038
|
+
const pronoun = dark === 1 ? 'it' : 'they';
|
|
2039
|
+
return `${dark} of ${items.length} ${noun} ${verb} on no device's allowlist — ${pronoun} will not fire.`;
|
|
2040
|
+
}
|
|
1215
2041
|
/** Build MCP rows by joining the installed mcp items with their full configs (transport/url/command). */
|
|
1216
2042
|
function mcpRows(items, configs) {
|
|
1217
2043
|
return items.map(item => {
|
|
@@ -1220,7 +2046,7 @@ function mcpRows(items, configs) {
|
|
|
1220
2046
|
});
|
|
1221
2047
|
}
|
|
1222
2048
|
/** Read a repo/agents.yaml `hooks:` section into a name→ManifestHook map (best-effort). */
|
|
1223
|
-
function hookManifestFromFile(agentsYamlPath) {
|
|
2049
|
+
export function hookManifestFromFile(agentsYamlPath) {
|
|
1224
2050
|
try {
|
|
1225
2051
|
const meta = yaml.parse(fs.readFileSync(agentsYamlPath, 'utf-8'));
|
|
1226
2052
|
return meta?.hooks ?? {};
|