@phnx-labs/agents-cli 1.22.34 → 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 +672 -2
- package/README.md +82 -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 +85 -0
- package/dist/commands/share.js +379 -8
- 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/codex-policy.d.ts +9 -1
- package/dist/lib/codex-policy.js +17 -2
- 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 +220 -122
- 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-key.d.ts +17 -0
- package/dist/lib/project-key.js +26 -0
- 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/delete.d.ts +93 -0
- package/dist/lib/share/delete.js +127 -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 +136 -27
- 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 +14 -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
|
@@ -13,6 +13,21 @@ export const SESSION_AGENTS = ['claude', 'codex', 'gemini', 'antigravity', 'open
|
|
|
13
13
|
* of {@link SESSION_AGENTS}). The single predicate every session-index writer
|
|
14
14
|
* gates on, so "is this a trackable agent?" is decided in exactly one place.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* The tmux session name the CLI mints for an agent run: `ag-<agent>-<shortid>`.
|
|
18
|
+
*
|
|
19
|
+
* ONE definition. This shape was independently re-written as a regex literal in
|
|
20
|
+
* five places (focus's selector test, sessions-resume's direct-selector test,
|
|
21
|
+
* active's name parsers, actor-sidecar, and the deeplink parser); they drifted —
|
|
22
|
+
* focus accepted a 6+ hex suffix while the others required exactly 8 — which is
|
|
23
|
+
* how the same alias could be an "identity" to one code path and a keyword query
|
|
24
|
+
* to the next.
|
|
25
|
+
*/
|
|
26
|
+
export const AG_TMUX_NAME_RE = /^ag-([a-z][a-z0-9-]*?)-([0-9a-f]{8})$/i;
|
|
27
|
+
/** Whether `name` is an `ag-<agent>-<shortid>` tmux session name. */
|
|
28
|
+
export function isAgentTmuxAlias(name) {
|
|
29
|
+
return AG_TMUX_NAME_RE.test(name);
|
|
30
|
+
}
|
|
16
31
|
export function isSessionTrackedAgent(agent) {
|
|
17
32
|
return SESSION_AGENTS.includes(agent);
|
|
18
33
|
}
|
|
@@ -41,7 +41,7 @@ export interface ViewingIn {
|
|
|
41
41
|
* BOTH "no client attached" and "could not locate the pane", so the pane's resolved
|
|
42
42
|
* `tmuxTarget` is what separates them: without it there is no evidence of absence,
|
|
43
43
|
* only absence of evidence. Claiming `'detached'` there would invent an orphaned
|
|
44
|
-
* session — and a consumer acts on that claim (
|
|
44
|
+
* session — and a consumer acts on that claim (the ext's picker pre-ticks every
|
|
45
45
|
* detached row for rescue), so the wrong answer resumes a session nobody asked for.
|
|
46
46
|
*
|
|
47
47
|
* Shared by the `--active` row renderer and the `--json` serializer so a machine
|
|
@@ -40,7 +40,7 @@ const execFileAsync = promisify(execFile);
|
|
|
40
40
|
* BOTH "no client attached" and "could not locate the pane", so the pane's resolved
|
|
41
41
|
* `tmuxTarget` is what separates them: without it there is no evidence of absence,
|
|
42
42
|
* only absence of evidence. Claiming `'detached'` there would invent an orphaned
|
|
43
|
-
* session — and a consumer acts on that claim (
|
|
43
|
+
* session — and a consumer acts on that claim (the ext's picker pre-ticks every
|
|
44
44
|
* detached row for rescue), so the wrong answer resumes a session nobody asked for.
|
|
45
45
|
*
|
|
46
46
|
* Shared by the `--active` row renderer and the `--json` serializer so a machine
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ActiveSession } from './active.js';
|
|
2
|
+
import { readActiveSessionsCache } from './session-cache.js';
|
|
3
|
+
export declare const SESSION_WATCH_VERSION: 1;
|
|
4
|
+
export declare const SESSION_WATCH_HEARTBEAT_MS = 15000;
|
|
5
|
+
export type SessionWatchScopeStatus = 'available' | 'unavailable';
|
|
6
|
+
export type SessionWatchEnvelope = {
|
|
7
|
+
version: 1;
|
|
8
|
+
type: 'reset';
|
|
9
|
+
streamId: string;
|
|
10
|
+
sequence: number;
|
|
11
|
+
capturedAt: number;
|
|
12
|
+
scope: string;
|
|
13
|
+
rows: SessionWatchRow[];
|
|
14
|
+
} | {
|
|
15
|
+
version: 1;
|
|
16
|
+
type: 'upsert';
|
|
17
|
+
streamId: string;
|
|
18
|
+
sequence: number;
|
|
19
|
+
capturedAt: number;
|
|
20
|
+
scope: string;
|
|
21
|
+
rowKey: string;
|
|
22
|
+
row: SessionWatchRow;
|
|
23
|
+
} | {
|
|
24
|
+
version: 1;
|
|
25
|
+
type: 'remove';
|
|
26
|
+
streamId: string;
|
|
27
|
+
sequence: number;
|
|
28
|
+
capturedAt: number;
|
|
29
|
+
scope: string;
|
|
30
|
+
rowKey: string;
|
|
31
|
+
} | {
|
|
32
|
+
version: 1;
|
|
33
|
+
type: 'scope';
|
|
34
|
+
streamId: string;
|
|
35
|
+
sequence: number;
|
|
36
|
+
capturedAt: number;
|
|
37
|
+
scope: string;
|
|
38
|
+
status: SessionWatchScopeStatus;
|
|
39
|
+
reason?: string;
|
|
40
|
+
} | {
|
|
41
|
+
version: 1;
|
|
42
|
+
type: 'heartbeat';
|
|
43
|
+
streamId: string;
|
|
44
|
+
sequence: number;
|
|
45
|
+
scope: string;
|
|
46
|
+
capturedAt: number;
|
|
47
|
+
};
|
|
48
|
+
export interface SessionWatchRow extends Omit<ActiveSession, 'viewingIn'> {
|
|
49
|
+
rowKey: string;
|
|
50
|
+
sourceDevice: string;
|
|
51
|
+
resumable: boolean;
|
|
52
|
+
unwatched: boolean;
|
|
53
|
+
viewingIn: string | null;
|
|
54
|
+
recovery: {
|
|
55
|
+
command: 'agents';
|
|
56
|
+
args: string[];
|
|
57
|
+
cwd?: string;
|
|
58
|
+
} | null;
|
|
59
|
+
}
|
|
60
|
+
/** Stable, opaque identity for one row within one device scope. */
|
|
61
|
+
export declare function sessionWatchRowKey(scope: string, row: ActiveSession): string;
|
|
62
|
+
export declare function toSessionWatchRow(scope: string, row: ActiveSession): SessionWatchRow;
|
|
63
|
+
/** Stream-local sequencer and convergent row diff. */
|
|
64
|
+
export declare class SessionWatchState {
|
|
65
|
+
readonly streamId: string;
|
|
66
|
+
private sequence;
|
|
67
|
+
private readonly rows;
|
|
68
|
+
constructor(streamId?: string);
|
|
69
|
+
private base;
|
|
70
|
+
reset(scope: string, sourceRows: ActiveSession[]): SessionWatchEnvelope;
|
|
71
|
+
update(scope: string, sourceRows: ActiveSession[]): SessionWatchEnvelope[];
|
|
72
|
+
patch(scope: string, upserts: ActiveSession[], removes: string[]): SessionWatchEnvelope[];
|
|
73
|
+
scope(scope: string, status: SessionWatchScopeStatus, reason?: string): SessionWatchEnvelope;
|
|
74
|
+
heartbeat(scope: string, capturedAt?: number): SessionWatchEnvelope;
|
|
75
|
+
}
|
|
76
|
+
export interface WatchLocalOptions {
|
|
77
|
+
scope: string;
|
|
78
|
+
signal: AbortSignal;
|
|
79
|
+
emit: (event: SessionWatchEnvelope) => void;
|
|
80
|
+
refreshMs?: number;
|
|
81
|
+
heartbeatMs?: number;
|
|
82
|
+
readCache?: typeof readActiveSessionsCache;
|
|
83
|
+
journalPath?: string;
|
|
84
|
+
journalPollMs?: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Keep one local subscription alive. Startup reads one canonical reset snapshot;
|
|
88
|
+
* steady state tails the canonical writer journal and never invokes a gather.
|
|
89
|
+
*/
|
|
90
|
+
export declare function watchLocalSessions(options: WatchLocalOptions): Promise<void>;
|
|
91
|
+
export interface WatchFleetOptions {
|
|
92
|
+
signal: AbortSignal;
|
|
93
|
+
emit: (event: SessionWatchEnvelope) => void;
|
|
94
|
+
reconnectMs?: number;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Subscribe to every dialable compute device with one persistent SSH process.
|
|
98
|
+
* A peer disconnect emits `scope: unavailable` but no removes; reconnecting
|
|
99
|
+
* peer resets only its own scope. There is no recurring fleet list command.
|
|
100
|
+
*/
|
|
101
|
+
export declare function watchFleetSessions(options: WatchFleetOptions): Promise<void>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { loadDevices, isControlDevice, isDialableDevice } from '../devices/registry.js';
|
|
7
|
+
import { deviceIdentityArgs, sshTargetFor } from '../devices/connect.js';
|
|
8
|
+
import { machineId, normalizeHost } from '../machine-id.js';
|
|
9
|
+
import { SSH_OPTS, controlOpts, shellQuote } from '../ssh-exec.js';
|
|
10
|
+
import { buildWindowsAgentsCommand, remoteShellFor } from '../hosts/remote-cmd.js';
|
|
11
|
+
import { activeSessionsJournalPath, activeSessionJournalIdentity, readActiveSessionsCache, } from './session-cache.js';
|
|
12
|
+
export const SESSION_WATCH_VERSION = 1;
|
|
13
|
+
export const SESSION_WATCH_HEARTBEAT_MS = 15_000;
|
|
14
|
+
/** Stable, opaque identity for one row within one device scope. */
|
|
15
|
+
export function sessionWatchRowKey(scope, row) {
|
|
16
|
+
const identity = activeSessionJournalIdentity(row);
|
|
17
|
+
return createHash('sha256').update(`${scope}\0${identity}`).digest('base64url').slice(0, 22);
|
|
18
|
+
}
|
|
19
|
+
export function toSessionWatchRow(scope, row) {
|
|
20
|
+
const rowKey = sessionWatchRowKey(scope, row);
|
|
21
|
+
const resumable = Boolean(row.sessionId);
|
|
22
|
+
const viewingIn = row.viewingIn
|
|
23
|
+
? [row.viewingIn.app, row.viewingIn.tab ? `tab ${row.viewingIn.tab}` : undefined].filter(Boolean).join(' ')
|
|
24
|
+
: null;
|
|
25
|
+
return {
|
|
26
|
+
...row,
|
|
27
|
+
rowKey,
|
|
28
|
+
sourceDevice: scope,
|
|
29
|
+
resumable,
|
|
30
|
+
unwatched: !viewingIn,
|
|
31
|
+
viewingIn,
|
|
32
|
+
recovery: resumable
|
|
33
|
+
? { command: 'agents', args: ['sessions', 'resume', row.sessionId, '--device', scope], ...(row.cwd ? { cwd: row.cwd } : {}) }
|
|
34
|
+
: null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function sameRow(a, b) {
|
|
38
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
39
|
+
}
|
|
40
|
+
/** Stream-local sequencer and convergent row diff. */
|
|
41
|
+
export class SessionWatchState {
|
|
42
|
+
streamId;
|
|
43
|
+
sequence = 0;
|
|
44
|
+
rows = new Map();
|
|
45
|
+
constructor(streamId = randomUUID()) { this.streamId = streamId; }
|
|
46
|
+
base(type) {
|
|
47
|
+
return { version: SESSION_WATCH_VERSION, type, streamId: this.streamId, sequence: ++this.sequence, capturedAt: Date.now() };
|
|
48
|
+
}
|
|
49
|
+
reset(scope, sourceRows) {
|
|
50
|
+
const rows = sourceRows.map((row) => toSessionWatchRow(scope, row));
|
|
51
|
+
this.rows.set(scope, new Map(rows.map((row) => [row.rowKey, row])));
|
|
52
|
+
return { ...this.base('reset'), scope, rows };
|
|
53
|
+
}
|
|
54
|
+
update(scope, sourceRows) {
|
|
55
|
+
const previous = this.rows.get(scope) ?? new Map();
|
|
56
|
+
const nextRows = sourceRows.map((row) => toSessionWatchRow(scope, row));
|
|
57
|
+
const next = new Map(nextRows.map((row) => [row.rowKey, row]));
|
|
58
|
+
const events = [];
|
|
59
|
+
for (const [rowKey, row] of next) {
|
|
60
|
+
if (!previous.has(rowKey) || !sameRow(previous.get(rowKey), row)) {
|
|
61
|
+
events.push({ ...this.base('upsert'), scope, rowKey, row });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const rowKey of previous.keys()) {
|
|
65
|
+
if (!next.has(rowKey))
|
|
66
|
+
events.push({ ...this.base('remove'), scope, rowKey });
|
|
67
|
+
}
|
|
68
|
+
this.rows.set(scope, next);
|
|
69
|
+
return events;
|
|
70
|
+
}
|
|
71
|
+
patch(scope, upserts, removes) {
|
|
72
|
+
const current = new Map(this.rows.get(scope) ?? []);
|
|
73
|
+
const events = [];
|
|
74
|
+
for (const source of upserts) {
|
|
75
|
+
const row = toSessionWatchRow(scope, source);
|
|
76
|
+
if (!current.has(row.rowKey) || !sameRow(current.get(row.rowKey), row)) {
|
|
77
|
+
current.set(row.rowKey, row);
|
|
78
|
+
events.push({ ...this.base('upsert'), scope, rowKey: row.rowKey, row });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const identity of removes) {
|
|
82
|
+
const rowKey = createHash('sha256').update(`${scope}\0${identity}`).digest('base64url').slice(0, 22);
|
|
83
|
+
if (current.delete(rowKey))
|
|
84
|
+
events.push({ ...this.base('remove'), scope, rowKey });
|
|
85
|
+
}
|
|
86
|
+
this.rows.set(scope, current);
|
|
87
|
+
return events;
|
|
88
|
+
}
|
|
89
|
+
scope(scope, status, reason) {
|
|
90
|
+
// Deliberately retain rows while unavailable. A reconnecting scope replaces
|
|
91
|
+
// them with a reset; transient fleet loss must not look like session death.
|
|
92
|
+
return { ...this.base('scope'), scope, status, ...(reason ? { reason } : {}) };
|
|
93
|
+
}
|
|
94
|
+
heartbeat(scope, capturedAt = Date.now()) {
|
|
95
|
+
return { ...this.base('heartbeat'), scope, capturedAt };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Keep one local subscription alive. Startup reads one canonical reset snapshot;
|
|
100
|
+
* steady state tails the canonical writer journal and never invokes a gather.
|
|
101
|
+
*/
|
|
102
|
+
export async function watchLocalSessions(options) {
|
|
103
|
+
const state = new SessionWatchState();
|
|
104
|
+
const readCache = options.readCache ?? readActiveSessionsCache;
|
|
105
|
+
const journal = options.journalPath ?? activeSessionsJournalPath();
|
|
106
|
+
const heartbeatMs = options.heartbeatMs ?? SESSION_WATCH_HEARTBEAT_MS;
|
|
107
|
+
let offset = 0;
|
|
108
|
+
try {
|
|
109
|
+
offset = fs.statSync(journal).size;
|
|
110
|
+
}
|
|
111
|
+
catch { /* first publication */ }
|
|
112
|
+
const initial = readCache('local');
|
|
113
|
+
options.emit(state.reset(options.scope, initial?.sessions ?? []));
|
|
114
|
+
options.emit(state.scope(options.scope, initial ? 'available' : 'unavailable', initial ? undefined : 'awaiting publisher'));
|
|
115
|
+
if (options.signal.aborted)
|
|
116
|
+
return;
|
|
117
|
+
await new Promise((resolve) => {
|
|
118
|
+
let partial = '';
|
|
119
|
+
let reading = false;
|
|
120
|
+
const readAppended = () => {
|
|
121
|
+
if (reading || options.signal.aborted)
|
|
122
|
+
return;
|
|
123
|
+
reading = true;
|
|
124
|
+
try {
|
|
125
|
+
const size = fs.statSync(journal).size;
|
|
126
|
+
if (size < offset) {
|
|
127
|
+
offset = 0;
|
|
128
|
+
partial = '';
|
|
129
|
+
}
|
|
130
|
+
if (size === offset)
|
|
131
|
+
return;
|
|
132
|
+
const fd = fs.openSync(journal, 'r');
|
|
133
|
+
try {
|
|
134
|
+
const buffer = Buffer.alloc(size - offset);
|
|
135
|
+
fs.readSync(fd, buffer, 0, buffer.length, offset);
|
|
136
|
+
offset = size;
|
|
137
|
+
const lines = (partial + buffer.toString('utf8')).split('\n');
|
|
138
|
+
partial = lines.pop() ?? '';
|
|
139
|
+
for (const line of lines) {
|
|
140
|
+
if (!line)
|
|
141
|
+
continue;
|
|
142
|
+
try {
|
|
143
|
+
const record = JSON.parse(line);
|
|
144
|
+
if (record.version !== 1 || record.scope !== 'local' || !Array.isArray(record.upserts) || !Array.isArray(record.removes))
|
|
145
|
+
continue;
|
|
146
|
+
for (const event of state.patch(options.scope, record.upserts, record.removes))
|
|
147
|
+
options.emit(event);
|
|
148
|
+
options.emit(state.scope(options.scope, 'available'));
|
|
149
|
+
}
|
|
150
|
+
catch { /* partial/corrupt journal lines are not state */ }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
fs.closeSync(fd);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch { /* journal may not exist until the first publisher write */ }
|
|
158
|
+
finally {
|
|
159
|
+
reading = false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
fs.mkdirSync(path.dirname(journal), { recursive: true });
|
|
163
|
+
const journalListener = () => readAppended();
|
|
164
|
+
fs.watchFile(journal, { interval: options.journalPollMs ?? 250 }, journalListener);
|
|
165
|
+
const heartbeatTimer = setInterval(() => options.emit(state.heartbeat(options.scope)), heartbeatMs);
|
|
166
|
+
const stop = () => { fs.unwatchFile(journal, journalListener); clearInterval(heartbeatTimer); resolve(); };
|
|
167
|
+
options.signal.addEventListener('abort', stop, { once: true });
|
|
168
|
+
// Close the offset/read/watch handoff: a publisher may append after the
|
|
169
|
+
// startup offset is captured but before watchFile is registered.
|
|
170
|
+
readAppended();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function remoteWatchCommand(os) {
|
|
174
|
+
const args = ['sessions', 'watch', '--json', '--local'];
|
|
175
|
+
return remoteShellFor(os) === 'powershell'
|
|
176
|
+
? buildWindowsAgentsCommand({ args })
|
|
177
|
+
: `bash -lc ${shellQuote(`agents ${args.map(shellQuote).join(' ')}`)}`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Subscribe to every dialable compute device with one persistent SSH process.
|
|
181
|
+
* A peer disconnect emits `scope: unavailable` but no removes; reconnecting
|
|
182
|
+
* peer resets only its own scope. There is no recurring fleet list command.
|
|
183
|
+
*/
|
|
184
|
+
export async function watchFleetSessions(options) {
|
|
185
|
+
const local = watchLocalSessions({ scope: machineId(), signal: options.signal, emit: options.emit });
|
|
186
|
+
let devices;
|
|
187
|
+
try {
|
|
188
|
+
devices = await loadDevices();
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
await local;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const self = machineId();
|
|
195
|
+
const peers = Object.values(devices).filter((device) => isDialableDevice(device)
|
|
196
|
+
&& !isControlDevice(device)
|
|
197
|
+
&& normalizeHost(device.name) !== self
|
|
198
|
+
&& ['windows', 'linux', 'macos'].includes(device.platform));
|
|
199
|
+
const reconnectMs = options.reconnectMs ?? 2_000;
|
|
200
|
+
const peerTasks = peers.map(async (device) => {
|
|
201
|
+
const scope = normalizeHost(device.name);
|
|
202
|
+
const state = new SessionWatchState();
|
|
203
|
+
while (!options.signal.aborted) {
|
|
204
|
+
let target;
|
|
205
|
+
try {
|
|
206
|
+
target = sshTargetFor(device);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
options.emit(state.scope(scope, 'unavailable', error instanceof Error ? error.message : String(error)));
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
const child = spawn('ssh', [
|
|
213
|
+
...SSH_OPTS, ...controlOpts(), ...deviceIdentityArgs(device), target, remoteWatchCommand(device.platform),
|
|
214
|
+
], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
215
|
+
const stop = () => child.kill('SIGTERM');
|
|
216
|
+
options.signal.addEventListener('abort', stop, { once: true });
|
|
217
|
+
const reader = createInterface({ input: child.stdout });
|
|
218
|
+
reader.on('line', (line) => {
|
|
219
|
+
try {
|
|
220
|
+
const event = JSON.parse(line);
|
|
221
|
+
if (event.version === SESSION_WATCH_VERSION && typeof event.sequence === 'number')
|
|
222
|
+
options.emit(event);
|
|
223
|
+
}
|
|
224
|
+
catch { /* incomplete/non-protocol peer output is not state */ }
|
|
225
|
+
});
|
|
226
|
+
const code = await new Promise((resolve) => {
|
|
227
|
+
child.once('error', () => resolve(null));
|
|
228
|
+
child.once('close', resolve);
|
|
229
|
+
});
|
|
230
|
+
reader.close();
|
|
231
|
+
options.signal.removeEventListener('abort', stop);
|
|
232
|
+
if (options.signal.aborted)
|
|
233
|
+
break;
|
|
234
|
+
options.emit(state.scope(scope, 'unavailable', code === null ? 'ssh failed' : `ssh exited ${code}`));
|
|
235
|
+
await new Promise((resolve) => {
|
|
236
|
+
const timer = setTimeout(resolve, reconnectMs);
|
|
237
|
+
options.signal.addEventListener('abort', () => { clearTimeout(timer); resolve(); }, { once: true });
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
await Promise.all([local, ...peerTasks]);
|
|
242
|
+
}
|
|
@@ -8,6 +8,10 @@ export interface ShareConfig {
|
|
|
8
8
|
domain?: string;
|
|
9
9
|
/** Cloudflare Web Analytics token injected into published HTML pages. */
|
|
10
10
|
analyticsToken?: string;
|
|
11
|
+
/** sha256 of the Worker script deployed at the last provision/update. Absent
|
|
12
|
+
* on any config written before this field existed — treat that as "unknown",
|
|
13
|
+
* never as "outdated" (there is nothing to compare against). */
|
|
14
|
+
templateHash?: string;
|
|
11
15
|
}
|
|
12
16
|
export declare const SHARE_BUNDLE = "share";
|
|
13
17
|
export declare const SHARE_TOKEN_KEY = "WRITE_TOKEN";
|
package/dist/lib/share/config.js
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type ShareConfig } from './config.js';
|
|
2
|
+
/** DI seam for tests — override the real HTTP DELETE. */
|
|
3
|
+
export type DeleteFn = (url: string, headers: Record<string, string>) => Promise<{
|
|
4
|
+
ok: boolean;
|
|
5
|
+
status: number;
|
|
6
|
+
}>;
|
|
7
|
+
/** DI seam for tests — override the real HTTP existence check (HEAD). */
|
|
8
|
+
export type CheckFn = (url: string) => Promise<{
|
|
9
|
+
status: number;
|
|
10
|
+
}>;
|
|
11
|
+
export interface DeleteEndpoint {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
token: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolvedShareTarget {
|
|
16
|
+
/** R2 object key for the page, `<user>/<slug>`. */
|
|
17
|
+
key: string;
|
|
18
|
+
/** R2 object key for the sibling OG cover, `<user>/<slug>.png`. */
|
|
19
|
+
coverKey: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize any of the three accepted target forms to the R2 key that publish
|
|
23
|
+
* would have written:
|
|
24
|
+
* - a full share URL: `https://share.agents-cli.sh/<user>/<slug>`
|
|
25
|
+
* - `<user>/<slug>`
|
|
26
|
+
* - a bare `<slug>` — resolved against the caller's own namespace exactly as
|
|
27
|
+
* `publishToEndpoint` resolves it at publish time (resolveShareUsername +
|
|
28
|
+
* buildShareKey), so a bare slug always targets *your* published page.
|
|
29
|
+
*
|
|
30
|
+
* The URL and `<user>/<slug>` forms are taken as already the exact key a prior
|
|
31
|
+
* publish produced (no re-sanitizing) — only the bare-slug form runs through
|
|
32
|
+
* `buildShareKey`, because that's the one case where the slug hasn't already
|
|
33
|
+
* been normalized by a publish.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveDeleteTarget(target: string, opts?: {
|
|
36
|
+
githubUser?: string;
|
|
37
|
+
}): Promise<ResolvedShareTarget>;
|
|
38
|
+
export interface DeleteObjectResult {
|
|
39
|
+
key: string;
|
|
40
|
+
url: string;
|
|
41
|
+
/** Whether the object resolved (non-404) before the DELETE was issued. */
|
|
42
|
+
existedBefore: boolean;
|
|
43
|
+
/** Whether the Worker's DELETE call itself reported ok. */
|
|
44
|
+
deleted: boolean;
|
|
45
|
+
/** The postcondition: a follow-up check resolved 404 after the DELETE. */
|
|
46
|
+
verified404: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Delete one R2 object behind the share Worker and assert the postcondition.
|
|
50
|
+
* `{"ok":true}` from the Worker is not evidence a page came down — R2 delete is
|
|
51
|
+
* idempotent, so a DELETE on a key that was never there also returns `ok:true`.
|
|
52
|
+
* This checks existence before (so callers can tell "deleted" from "was never
|
|
53
|
+
* there") and re-checks after (so callers can tell "deleted" from "still public").
|
|
54
|
+
*/
|
|
55
|
+
export declare function deleteObject(endpoint: DeleteEndpoint, key: string, opts?: {
|
|
56
|
+
deleter?: DeleteFn;
|
|
57
|
+
checker?: CheckFn;
|
|
58
|
+
}): Promise<DeleteObjectResult>;
|
|
59
|
+
export interface DeleteShareOptions {
|
|
60
|
+
/** Skip deleting the sibling `<slug>.png` OG cover (default: delete it too). */
|
|
61
|
+
keepCover?: boolean;
|
|
62
|
+
/** Treat an already-missing target as a no-op success instead of an error
|
|
63
|
+
* (mirrors SQL's `DROP ... IF EXISTS`). Default: missing target is an error. */
|
|
64
|
+
ifExists?: boolean;
|
|
65
|
+
/** Override the GitHub username used to resolve a bare-slug target. */
|
|
66
|
+
githubUser?: string;
|
|
67
|
+
/** DI seam for tests — override the persisted share endpoint config. */
|
|
68
|
+
config?: ShareConfig;
|
|
69
|
+
/** DI seam for tests — override the keychain-backed write token. */
|
|
70
|
+
writeToken?: string;
|
|
71
|
+
/** DI seam for tests — override the real HTTP DELETE. */
|
|
72
|
+
deleter?: DeleteFn;
|
|
73
|
+
/** DI seam for tests — override the real HTTP existence check. */
|
|
74
|
+
checker?: CheckFn;
|
|
75
|
+
}
|
|
76
|
+
export interface DeleteShareResult {
|
|
77
|
+
key: string;
|
|
78
|
+
url: string;
|
|
79
|
+
existedBefore: boolean;
|
|
80
|
+
verified404: boolean;
|
|
81
|
+
/** True when `ifExists` was set and the target was already gone — nothing ran. */
|
|
82
|
+
skipped?: boolean;
|
|
83
|
+
cover?: {
|
|
84
|
+
key: string;
|
|
85
|
+
url: string;
|
|
86
|
+
existedBefore: boolean;
|
|
87
|
+
verified404: boolean;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Delete one share target (page + by default its OG cover) and verify both
|
|
91
|
+
* are gone. Throws on an unverified takedown — never reports success for an
|
|
92
|
+
* object that still resolves. */
|
|
93
|
+
export declare function deleteShare(target: string, opts?: DeleteShareOptions): Promise<DeleteShareResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The delete path for `agents share delete` / `agents unshare` — an authed DELETE
|
|
2
|
+
// to the Worker, which already implements it (worker-template.ts). Mirrors
|
|
3
|
+
// publish.ts: pure target-resolution logic is exported for tests, the network
|
|
4
|
+
// calls (a status check + a delete) sit behind an injectable DI seam.
|
|
5
|
+
//
|
|
6
|
+
// The Worker's R2 delete is idempotent — DELETE on a key that never existed still
|
|
7
|
+
// returns `{"ok":true}` — so `{"ok":true}` alone is never proof of a takedown.
|
|
8
|
+
// Every delete here is followed by a status check that must observe 404 before
|
|
9
|
+
// the operation is reported as successful.
|
|
10
|
+
import { readShareConfig, readWriteToken } from './config.js';
|
|
11
|
+
import { buildShareKey, resolveShareUsername } from './publish.js';
|
|
12
|
+
/**
|
|
13
|
+
* Normalize any of the three accepted target forms to the R2 key that publish
|
|
14
|
+
* would have written:
|
|
15
|
+
* - a full share URL: `https://share.agents-cli.sh/<user>/<slug>`
|
|
16
|
+
* - `<user>/<slug>`
|
|
17
|
+
* - a bare `<slug>` — resolved against the caller's own namespace exactly as
|
|
18
|
+
* `publishToEndpoint` resolves it at publish time (resolveShareUsername +
|
|
19
|
+
* buildShareKey), so a bare slug always targets *your* published page.
|
|
20
|
+
*
|
|
21
|
+
* The URL and `<user>/<slug>` forms are taken as already the exact key a prior
|
|
22
|
+
* publish produced (no re-sanitizing) — only the bare-slug form runs through
|
|
23
|
+
* `buildShareKey`, because that's the one case where the slug hasn't already
|
|
24
|
+
* been normalized by a publish.
|
|
25
|
+
*/
|
|
26
|
+
export async function resolveDeleteTarget(target, opts = {}) {
|
|
27
|
+
const trimmed = target.trim();
|
|
28
|
+
if (!trimmed)
|
|
29
|
+
throw new Error('Share target is empty.');
|
|
30
|
+
let key;
|
|
31
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
32
|
+
const url = new URL(trimmed);
|
|
33
|
+
const segments = url.pathname
|
|
34
|
+
.replace(/^\/+|\/+$/g, '')
|
|
35
|
+
.split('/')
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.map(decodeURIComponent);
|
|
38
|
+
if (segments.length < 2) {
|
|
39
|
+
throw new Error(`Not a share page URL (expected .../<user>/<slug>): ${trimmed}`);
|
|
40
|
+
}
|
|
41
|
+
key = segments.slice(0, 2).join('/');
|
|
42
|
+
}
|
|
43
|
+
else if (trimmed.includes('/')) {
|
|
44
|
+
const segments = trimmed.replace(/^\/+|\/+$/g, '').split('/').filter(Boolean);
|
|
45
|
+
if (segments.length !== 2) {
|
|
46
|
+
throw new Error(`Expected <user>/<slug>, got: ${trimmed}`);
|
|
47
|
+
}
|
|
48
|
+
key = segments.join('/');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const username = await resolveShareUsername(opts);
|
|
52
|
+
key = buildShareKey(username, trimmed);
|
|
53
|
+
}
|
|
54
|
+
return { key, coverKey: `${key}.png` };
|
|
55
|
+
}
|
|
56
|
+
async function defaultCheck(url) {
|
|
57
|
+
const res = await fetch(url, { method: 'HEAD' });
|
|
58
|
+
return { status: res.status };
|
|
59
|
+
}
|
|
60
|
+
async function defaultDelete(url, headers) {
|
|
61
|
+
const res = await fetch(url, { method: 'DELETE', headers });
|
|
62
|
+
return { ok: res.ok, status: res.status };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Delete one R2 object behind the share Worker and assert the postcondition.
|
|
66
|
+
* `{"ok":true}` from the Worker is not evidence a page came down — R2 delete is
|
|
67
|
+
* idempotent, so a DELETE on a key that was never there also returns `ok:true`.
|
|
68
|
+
* This checks existence before (so callers can tell "deleted" from "was never
|
|
69
|
+
* there") and re-checks after (so callers can tell "deleted" from "still public").
|
|
70
|
+
*/
|
|
71
|
+
export async function deleteObject(endpoint, key, opts = {}) {
|
|
72
|
+
const url = `${endpoint.baseUrl.replace(/\/+$/, '')}/${key}`;
|
|
73
|
+
const check = opts.checker ?? defaultCheck;
|
|
74
|
+
const del = opts.deleter ?? defaultDelete;
|
|
75
|
+
const before = await check(url);
|
|
76
|
+
const existedBefore = before.status !== 404;
|
|
77
|
+
const r = await del(url, { authorization: `Bearer ${endpoint.token}` });
|
|
78
|
+
if (!r.ok) {
|
|
79
|
+
throw new Error(`Delete failed (${r.status}) for ${url}. Check the write token, or that 'agents share setup' completed.`);
|
|
80
|
+
}
|
|
81
|
+
const after = await check(url);
|
|
82
|
+
const verified404 = after.status === 404;
|
|
83
|
+
return { key, url, existedBefore, deleted: r.ok, verified404 };
|
|
84
|
+
}
|
|
85
|
+
/** Delete one share target (page + by default its OG cover) and verify both
|
|
86
|
+
* are gone. Throws on an unverified takedown — never reports success for an
|
|
87
|
+
* object that still resolves. */
|
|
88
|
+
export async function deleteShare(target, opts = {}) {
|
|
89
|
+
const cfg = opts.config ?? readShareConfig();
|
|
90
|
+
if (!cfg) {
|
|
91
|
+
throw new Error("Not set up yet. Run 'agents share setup' (provision your own endpoint) or 'agents share join' (use an existing one).");
|
|
92
|
+
}
|
|
93
|
+
const token = opts.writeToken ?? readWriteToken();
|
|
94
|
+
const resolved = await resolveDeleteTarget(target, { githubUser: opts.githubUser });
|
|
95
|
+
const endpoint = { baseUrl: cfg.baseUrl, token };
|
|
96
|
+
const page = await deleteObject(endpoint, resolved.key, { deleter: opts.deleter, checker: opts.checker });
|
|
97
|
+
if (!page.existedBefore) {
|
|
98
|
+
if (opts.ifExists) {
|
|
99
|
+
return { key: page.key, url: page.url, existedBefore: false, verified404: page.verified404, skipped: true };
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Nothing to delete — ${page.url} was already not found. Pass --if-exists to treat this as a no-op instead of an error.`);
|
|
102
|
+
}
|
|
103
|
+
if (!page.verified404) {
|
|
104
|
+
throw new Error(`Delete reported success but ${page.url} still resolves — takedown NOT verified. Retry, or investigate the Worker/R2 directly.`);
|
|
105
|
+
}
|
|
106
|
+
const result = {
|
|
107
|
+
key: page.key,
|
|
108
|
+
url: page.url,
|
|
109
|
+
existedBefore: page.existedBefore,
|
|
110
|
+
verified404: page.verified404,
|
|
111
|
+
};
|
|
112
|
+
if (!opts.keepCover) {
|
|
113
|
+
const cover = await deleteObject(endpoint, resolved.coverKey, { deleter: opts.deleter, checker: opts.checker });
|
|
114
|
+
result.cover = {
|
|
115
|
+
key: cover.key,
|
|
116
|
+
url: cover.url,
|
|
117
|
+
existedBefore: cover.existedBefore,
|
|
118
|
+
verified404: cover.verified404,
|
|
119
|
+
};
|
|
120
|
+
// A missing cover is normal (non-HTML publishes, or --no-cover at publish
|
|
121
|
+
// time never made one) — only a cover that existed and is still up is a bug.
|
|
122
|
+
if (cover.existedBefore && !cover.verified404) {
|
|
123
|
+
throw new Error(`Cover delete reported success but ${cover.url} still resolves — takedown NOT verified. Retry, or pass --keep-cover and delete it manually.`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
@@ -51,6 +51,44 @@ export declare function mergeShareLifecycleRule(existing?: R2LifecycleRule[], ru
|
|
|
51
51
|
export declare function configureBucketLifecycle(apiToken: string, accountId: string, bucketName: string, opts?: ProvisionOptions): Promise<void>;
|
|
52
52
|
/** Upload the module Worker with an R2 binding (`BUCKET`). Secrets are set via the Workers Secrets API. */
|
|
53
53
|
export declare function deployWorker(apiToken: string, accountId: string, workerName: string, script: string, bucketName: string, opts?: ProvisionOptions): Promise<void>;
|
|
54
|
+
/** sha256 of the rendered Worker script, so a deployed endpoint can be compared
|
|
55
|
+
* against the current `worker-template.ts` without redeploying to find out. */
|
|
56
|
+
export declare function hashWorkerScript(script: string): string;
|
|
57
|
+
export interface UpdateWorkerResult {
|
|
58
|
+
/** sha256 of `script` (the hash the endpoint now matches, whether or not this
|
|
59
|
+
* call actually redeployed). */
|
|
60
|
+
templateHash: string;
|
|
61
|
+
/** True when the upload was skipped because `previousHash` already matched. */
|
|
62
|
+
skipped: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Re-deploy the Worker script against an ALREADY-provisioned endpoint (same
|
|
66
|
+
* account/worker/bucket `deployWorker` was first called with) and idempotently
|
|
67
|
+
* no-op when the template hasn't changed.
|
|
68
|
+
*
|
|
69
|
+
* Cloudflare's script-upload endpoint (`deployWorker`, above) replaces the
|
|
70
|
+
* Worker's bindings/secrets wholesale on every call — there is no documented,
|
|
71
|
+
* reliable way to tell it "keep the existing secret" (the `keep_bindings`
|
|
72
|
+
* metadata field some third-party guides mention is absent from Cloudflare's
|
|
73
|
+
* current Multipart upload metadata reference, and the community has reported
|
|
74
|
+
* it not preventing binding loss even when present:
|
|
75
|
+
* https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/,
|
|
76
|
+
* https://community.cloudflare.com/t/upload-worker-module-endpoint-removes-existing-bindings-despite-keep-bindings/766447).
|
|
77
|
+
* So instead this immediately re-applies WRITE_TOKEN via the documented Secrets
|
|
78
|
+
* API right after the upload — the same two-call sequence first-time
|
|
79
|
+
* provisioning already uses (`deployWorker` then `setWorkerSecret`), except
|
|
80
|
+
* `writeToken` here is the caller's EXISTING token, never a freshly generated
|
|
81
|
+
* one. Cloudflare's own docs describe exactly this "secrets survive a
|
|
82
|
+
* subsequent write" contract for the sibling `wrangler versions upload
|
|
83
|
+
* --secrets-file` flow ("Secrets not included in the file are preserved from
|
|
84
|
+
* the previous version" —
|
|
85
|
+
* https://developers.cloudflare.com/workers/configuration/secrets/#upload-secrets-alongside-code);
|
|
86
|
+
* re-setting the secret to its own value after every deploy gets the same
|
|
87
|
+
* outcome without depending on an unverified upload-time flag.
|
|
88
|
+
*/
|
|
89
|
+
export declare function updateWorker(apiToken: string, accountId: string, workerName: string, bucketName: string, script: string, writeToken: string, previousHash: string | undefined, opts?: ProvisionOptions & {
|
|
90
|
+
force?: boolean;
|
|
91
|
+
}): Promise<UpdateWorkerResult>;
|
|
54
92
|
/** Add/update the WRITE_TOKEN binding using Cloudflare's Workers Secrets API. */
|
|
55
93
|
export declare function setWorkerSecret(apiToken: string, accountId: string, workerName: string, writeToken: string, opts?: ProvisionOptions): Promise<void>;
|
|
56
94
|
/** Enable the free `*.workers.dev` route for the script, and return the account subdomain. */
|