@phnx-labs/agents-cli 1.22.39 → 1.22.41
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 +316 -0
- package/README.md +116 -70
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +45 -27
- package/dist/browser.js +2 -2
- package/dist/cli/command-registry.d.ts +135 -0
- package/dist/cli/command-registry.js +259 -0
- package/dist/commands/accounts.d.ts +20 -0
- package/dist/commands/accounts.js +186 -25
- package/dist/commands/apply.d.ts +3 -3
- package/dist/commands/apply.js +20 -63
- package/dist/commands/browser.d.ts +15 -0
- package/dist/commands/browser.js +318 -131
- package/dist/commands/budget.d.ts +7 -6
- package/dist/commands/budget.js +8 -7
- package/dist/commands/cloud.js +17 -20
- package/dist/commands/commands.js +1 -1
- package/dist/commands/computer-actions.d.ts +1 -35
- package/dist/commands/computer-actions.js +13 -181
- package/dist/commands/computer.d.ts +28 -3
- package/dist/commands/computer.js +93 -65
- package/dist/commands/config.js +16 -3
- package/dist/commands/cost.d.ts +2 -1
- package/dist/commands/cost.js +7 -6
- package/dist/commands/daemon.js +151 -4
- package/dist/commands/detach.js +1 -1
- package/dist/commands/doctor.js +19 -11
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.d.ts +2 -50
- package/dist/commands/exec.js +160 -215
- package/dist/commands/factory.js +1 -1
- package/dist/commands/feed.d.ts +2 -2
- package/dist/commands/feed.js +12 -23
- package/dist/commands/fleet-capture.js +25 -27
- package/dist/commands/focus.d.ts +2 -3
- package/dist/commands/focus.js +34 -9
- package/dist/commands/go.js +15 -5
- package/dist/commands/harness.d.ts +3 -3
- package/dist/commands/harness.js +53 -5
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +2 -2
- package/dist/commands/insights.d.ts +5 -5
- package/dist/commands/insights.js +15 -9
- package/dist/commands/inspect.d.ts +2 -3
- package/dist/commands/inspect.js +4 -16
- package/dist/commands/logs.d.ts +2 -2
- package/dist/commands/logs.js +9 -9
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/memory.js +1 -1
- package/dist/commands/message.d.ts +1 -1
- package/dist/commands/message.js +7 -7
- package/dist/commands/mine.d.ts +11 -8
- package/dist/commands/mine.js +11 -10
- package/dist/commands/models.js +1 -1
- package/dist/commands/modes.js +1 -1
- package/dist/commands/monitors.js +23 -6
- package/dist/commands/output.d.ts +7 -6
- package/dist/commands/output.js +11 -10
- package/dist/commands/packages.js +4 -4
- package/dist/commands/perf.js +1 -1
- package/dist/commands/permissions.js +49 -32
- package/dist/commands/plugins.d.ts +1 -1
- package/dist/commands/plugins.js +3 -3
- package/dist/commands/profiles.d.ts +6 -9
- package/dist/commands/profiles.js +8 -321
- package/dist/commands/projects.d.ts +9 -0
- package/dist/commands/projects.js +140 -8
- package/dist/commands/prune.js +5 -13
- package/dist/commands/refresh-rules.js +1 -1
- package/dist/commands/repo.d.ts +5 -7
- package/dist/commands/repo.js +34 -18
- package/dist/commands/resource-view.js +4 -0
- package/dist/commands/resume.d.ts +11 -7
- package/dist/commands/resume.js +82 -98
- package/dist/commands/route.d.ts +12 -0
- package/dist/commands/route.js +261 -0
- package/dist/commands/routines.d.ts +2 -3
- package/dist/commands/routines.js +120 -145
- package/dist/commands/rules.js +1 -1
- package/dist/commands/run-account-picker.d.ts +1 -1
- package/dist/commands/run-account-picker.js +2 -2
- package/dist/commands/run-broadcast.d.ts +21 -0
- package/dist/commands/run-broadcast.js +89 -0
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/secrets-vault.d.ts +10 -0
- package/dist/commands/{login.js → secrets-vault.js} +34 -14
- package/dist/commands/secrets.d.ts +4 -4
- package/dist/commands/secrets.js +158 -159
- package/dist/commands/sessions-backfill.js +1 -1
- package/dist/commands/sessions-browser.d.ts +1 -1
- package/dist/commands/sessions-browser.js +9 -5
- package/dist/commands/sessions-export.js +2 -2
- package/dist/commands/sessions-inject.js +4 -5
- package/dist/commands/sessions-migrate.js +16 -27
- package/dist/commands/sessions-picker.d.ts +14 -0
- package/dist/commands/sessions-picker.js +209 -13
- package/dist/commands/sessions-resume.d.ts +7 -3
- package/dist/commands/sessions-resume.js +81 -33
- package/dist/commands/sessions-share.d.ts +25 -0
- package/dist/commands/sessions-share.js +166 -0
- package/dist/commands/sessions-stats.js +2 -2
- package/dist/commands/sessions.d.ts +75 -68
- package/dist/commands/sessions.js +144 -180
- package/dist/commands/setup-browser.js +1 -1
- package/dist/commands/setup-computer.js +1 -1
- package/dist/commands/setup-fleet.js +3 -0
- package/dist/commands/setup-mine.d.ts +7 -3
- package/dist/commands/setup-mine.js +13 -8
- package/dist/commands/setup-preferences.js +2 -2
- package/dist/commands/setup-secrets.js +2 -2
- package/dist/commands/setup-watchdog.js +2 -2
- package/dist/commands/setup.js +7 -2
- package/dist/commands/share.d.ts +65 -0
- package/dist/commands/share.js +253 -20
- package/dist/commands/skills.js +3 -3
- package/dist/commands/snapshot.d.ts +2 -2
- package/dist/commands/snapshot.js +9 -9
- package/dist/commands/ssh.d.ts +1 -1
- package/dist/commands/ssh.js +186 -64
- package/dist/commands/subagents.js +1 -1
- package/dist/commands/sync.js +24 -9
- package/dist/commands/teams.d.ts +2 -2
- package/dist/commands/teams.js +9 -18
- package/dist/commands/tmux.js +5 -1
- package/dist/commands/trash.js +1 -1
- package/dist/commands/usage.d.ts +1 -1
- package/dist/commands/usage.js +2 -2
- package/dist/commands/utils.d.ts +4 -14
- package/dist/commands/utils.js +6 -27
- package/dist/commands/versions.js +2 -2
- package/dist/commands/view.d.ts +9 -77
- package/dist/commands/view.js +23 -8
- package/dist/commands/watchdog.js +1 -1
- package/dist/commands/webhook.js +25 -34
- package/dist/commands/workflows.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/account-capabilities.d.ts +39 -0
- package/dist/lib/account-capabilities.js +59 -0
- package/dist/lib/account-catalog.js +1 -1
- package/dist/lib/account-registry.d.ts +66 -0
- package/dist/lib/account-registry.js +151 -5
- package/dist/lib/{rotate.d.ts → accounting/rotate.d.ts} +4 -4
- package/dist/lib/{rotate.js → accounting/rotate.js} +8 -8
- package/dist/lib/{usage.d.ts → accounting/usage.d.ts} +4 -4
- package/dist/lib/{usage.js → accounting/usage.js} +11 -11
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +72 -4
- package/dist/lib/agents.js +231 -199
- package/dist/lib/answer-router.d.ts +2 -2
- package/dist/lib/ask-classifier.d.ts +1 -1
- package/dist/lib/ask-classifier.js +1 -1
- package/dist/lib/audit/log.js +1 -1
- package/dist/lib/auth-health.d.ts +1 -1
- package/dist/lib/auth-health.js +2 -2
- package/dist/lib/brand.js +1 -1
- package/dist/lib/browser/caller-identity.d.ts +28 -0
- package/dist/lib/browser/caller-identity.js +156 -0
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.js +12 -1
- package/dist/lib/browser/drivers/ssh.d.ts +1 -1
- package/dist/lib/browser/drivers/ssh.js +3 -2
- package/dist/lib/browser/hygiene.d.ts +10 -2
- package/dist/lib/browser/hygiene.js +7 -4
- package/dist/lib/browser/ipc.d.ts +22 -2
- package/dist/lib/browser/ipc.js +185 -21
- package/dist/lib/browser/profiles.d.ts +69 -5
- package/dist/lib/browser/profiles.js +135 -13
- package/dist/lib/browser/remote-control.d.ts +2 -2
- package/dist/lib/browser/remote-control.js +3 -3
- package/dist/lib/browser/runtime-state.d.ts +79 -0
- package/dist/lib/browser/runtime-state.js +119 -1
- package/dist/lib/browser/service.d.ts +137 -2
- package/dist/lib/browser/service.js +492 -54
- package/dist/lib/browser/sessions-list.d.ts +2 -1
- package/dist/lib/browser/sessions-list.js +4 -13
- package/dist/lib/browser/types.d.ts +19 -1
- package/dist/lib/budget/config.js +1 -1
- package/dist/lib/budget/ledger.js +1 -1
- package/dist/lib/byok-usage.js +1 -1
- package/dist/lib/capabilities.js +3 -15
- package/dist/lib/catchup.d.ts +1 -1
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/cloud/dispatch.js +1 -1
- package/dist/lib/cloud/host.d.ts +3 -3
- package/dist/lib/cloud/host.js +5 -5
- package/dist/lib/cloud/rush.d.ts +9 -21
- package/dist/lib/cloud/rush.js +33 -120
- package/dist/lib/cloud/types.d.ts +2 -2
- package/dist/lib/commands.js +3 -13
- package/dist/lib/computer/actions.d.ts +36 -0
- package/dist/lib/computer/actions.js +162 -0
- package/dist/lib/computer/dispatch.d.ts +2 -2
- package/dist/lib/computer/dispatch.js +2 -2
- package/dist/lib/computer/sessions-list.d.ts +3 -3
- package/dist/lib/computer/sessions-list.js +2 -2
- package/dist/lib/config-keys.d.ts +2 -1
- package/dist/lib/config-keys.js +8 -4
- package/dist/lib/config-machine-keys.d.ts +4 -2
- package/dist/lib/config-machine-keys.js +5 -2
- package/dist/lib/crabbox/lease.js +9 -1
- package/dist/lib/crabbox/runtimes.d.ts +32 -7
- package/dist/lib/crabbox/runtimes.js +72 -30
- package/dist/lib/{daemon.d.ts → daemon/daemon.d.ts} +59 -1
- package/dist/lib/{daemon.js → daemon/daemon.js} +237 -49
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/daemon-ticks.d.ts +25 -0
- package/dist/lib/daemon-ticks.js +31 -1
- package/dist/lib/daemon-webhooks.d.ts +104 -0
- package/dist/lib/daemon-webhooks.js +215 -0
- package/dist/lib/device-config.d.ts +85 -75
- package/dist/lib/device-config.js +280 -142
- package/dist/lib/devices/config-migration.d.ts +29 -34
- package/dist/lib/devices/config-migration.js +209 -130
- package/dist/lib/devices/connect.d.ts +2 -2
- package/dist/lib/devices/connect.js +2 -2
- package/dist/lib/devices/discovery-policy.d.ts +30 -0
- package/dist/lib/devices/discovery-policy.js +101 -0
- package/dist/lib/devices/fleet-inventory.js +8 -3
- package/dist/lib/devices/harness-inventory.d.ts +1 -1
- package/dist/lib/devices/harness-inventory.js +12 -3
- package/dist/lib/devices/known-hosts.js +1 -1
- package/dist/lib/devices/pool.d.ts +10 -5
- package/dist/lib/devices/pool.js +5 -7
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/resolve-profile.d.ts +2 -2
- package/dist/lib/devices/resolve-profile.js +2 -2
- package/dist/lib/devices/resolve-target.d.ts +1 -1
- package/dist/lib/devices/resolve-target.js +4 -4
- package/dist/lib/devices/self-host.d.ts +1 -1
- package/dist/lib/devices/self-host.js +3 -3
- package/dist/lib/devices/ssh-config.d.ts +1 -1
- package/dist/lib/devices/stats-cache.js +1 -1
- package/dist/lib/devices/sync.d.ts +3 -1
- package/dist/lib/devices/sync.js +3 -3
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/drift-sync.js +2 -2
- package/dist/lib/drift.js +2 -2
- package/dist/lib/event-families.d.ts +1 -1
- package/dist/lib/event-stream.d.ts +1 -1
- package/dist/lib/event-stream.js +2 -2
- package/dist/lib/events-ingest.js +2 -2
- package/dist/lib/exec.d.ts +4 -4
- package/dist/lib/exec.js +11 -11
- package/dist/lib/factory/snapshot.d.ts +1 -1
- package/dist/lib/factory/snapshot.js +1 -1
- package/dist/lib/{activity.d.ts → feed/activity.d.ts} +1 -1
- package/dist/lib/{activity.js → feed/activity.js} +7 -7
- package/dist/lib/{events.d.ts → feed/events.d.ts} +2 -2
- package/dist/lib/{events.js → feed/events.js} +5 -5
- package/dist/lib/{feed.js → feed/feed.js} +4 -4
- package/dist/lib/feed-outcome.d.ts +1 -1
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +1 -1
- package/dist/lib/feed-post.d.ts +1 -1
- package/dist/lib/feed-post.js +1 -1
- package/dist/lib/feed-ranking.d.ts +1 -1
- package/dist/lib/fleet/apply.d.ts +0 -6
- package/dist/lib/fleet/apply.js +18 -45
- package/dist/lib/fleet/auth-sync.d.ts +16 -35
- package/dist/lib/fleet/auth-sync.js +16 -75
- package/dist/lib/fleet/capture.js +8 -6
- package/dist/lib/fleet/manifest.js +23 -11
- package/dist/lib/fleet/types.d.ts +19 -7
- package/dist/lib/fleet-cache.d.ts +1 -1
- package/dist/lib/fleet-cache.js +1 -1
- package/dist/lib/format.d.ts +19 -0
- package/dist/lib/format.js +44 -1
- package/dist/lib/friction-heuristics.d.ts +1 -1
- package/dist/lib/git.d.ts +24 -1
- package/dist/lib/git.js +82 -38
- package/dist/lib/heal.js +3 -3
- package/dist/lib/hooks.d.ts +18 -0
- package/dist/lib/hooks.js +113 -1
- package/dist/lib/hosts/credentials.d.ts +30 -10
- package/dist/lib/hosts/credentials.js +38 -26
- package/dist/lib/hosts/dispatch.d.ts +3 -3
- package/dist/lib/hosts/dispatch.js +3 -3
- package/dist/lib/hosts/option.d.ts +3 -3
- package/dist/lib/hosts/option.js +7 -8
- package/dist/lib/hosts/passthrough.d.ts +17 -17
- package/dist/lib/hosts/passthrough.js +67 -50
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconcile.d.ts +1 -1
- package/dist/lib/hosts/reconcile.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +1 -1
- package/dist/lib/hosts/reconnect.js +2 -2
- package/dist/lib/hosts/registry.d.ts +5 -5
- package/dist/lib/hosts/registry.js +5 -5
- package/dist/lib/hosts/remote-cmd.d.ts +10 -10
- package/dist/lib/hosts/remote-cmd.js +25 -18
- package/dist/lib/hosts/remote-os.d.ts +4 -4
- package/dist/lib/hosts/remote-os.js +4 -4
- package/dist/lib/hosts/remote-session-id.d.ts +2 -2
- package/dist/lib/hosts/remote-session-id.js +2 -2
- package/dist/lib/hosts/routing-flag.d.ts +3 -3
- package/dist/lib/hosts/routing-flag.js +4 -5
- package/dist/lib/hosts/run-target.d.ts +4 -4
- package/dist/lib/hosts/run-target.js +4 -4
- package/dist/lib/hosts/session-index.d.ts +2 -2
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/hosts/session-marker.d.ts +1 -1
- package/dist/lib/hosts/session-marker.js +1 -1
- package/dist/lib/hosts/types.d.ts +2 -2
- package/dist/lib/hosts/types.js +2 -2
- package/dist/lib/import.js +1 -1
- package/dist/lib/{migrate.d.ts → installations/migrate.d.ts} +13 -1
- package/dist/lib/{migrate.js → installations/migrate.js} +160 -40
- package/dist/lib/installations/resolve.js +1 -1
- package/dist/lib/installations/strategies.js +1 -1
- package/dist/lib/installations/update.js +2 -2
- package/dist/lib/{versions.d.ts → installations/versions.d.ts} +12 -3
- package/dist/lib/{versions.js → installations/versions.js} +51 -31
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/linear-projects.d.ts +28 -0
- package/dist/lib/linear-projects.js +30 -0
- package/dist/lib/loop.js +1 -1
- package/dist/lib/mailbox-gc.js +1 -1
- package/dist/lib/mailbox.js +1 -1
- package/dist/lib/mcp-registry.d.ts +62 -0
- package/dist/lib/mcp-registry.js +177 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +124 -307
- 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/install-menubar.d.ts +7 -0
- package/dist/lib/menubar/install-menubar.js +24 -6
- package/dist/lib/menubar/snapshot.d.ts +7 -0
- package/dist/lib/menubar/snapshot.js +8 -6
- package/dist/lib/models.js +1 -1
- package/dist/lib/monitors/config.d.ts +12 -0
- package/dist/lib/monitors/config.js +3 -0
- package/dist/lib/monitors/dispatch.js +26 -5
- package/dist/lib/monitors/engine.js +10 -0
- package/dist/lib/monitors/sources/webhook.d.ts +1 -1
- package/dist/lib/monitors/state.d.ts +42 -0
- package/dist/lib/monitors/state.js +30 -0
- package/dist/lib/notify.d.ts +1 -1
- package/dist/lib/observe-aliases.d.ts +3 -3
- package/dist/lib/observe-aliases.js +3 -21
- package/dist/lib/output/git-output.js +2 -2
- package/dist/lib/overdue.d.ts +1 -1
- package/dist/lib/overdue.js +1 -1
- package/dist/lib/permissions-registry.d.ts +65 -0
- package/dist/lib/permissions-registry.js +599 -0
- package/dist/lib/permissions.d.ts +24 -13
- package/dist/lib/permissions.js +52 -271
- package/dist/lib/picker.d.ts +16 -0
- package/dist/lib/picker.js +33 -3
- package/dist/lib/placement.d.ts +1 -1
- package/dist/lib/placement.js +4 -4
- package/dist/lib/platform/paths.d.ts +8 -0
- package/dist/lib/platform/paths.js +11 -0
- package/dist/lib/platform/posixpath.js +3 -3
- package/dist/lib/{plugin-marketplace.d.ts → plugins/plugin-marketplace.d.ts} +1 -1
- package/dist/lib/{plugin-marketplace.js → plugins/plugin-marketplace.js} +2 -2
- package/dist/lib/{plugins.d.ts → plugins/plugins.d.ts} +1 -1
- package/dist/lib/{plugins.js → plugins/plugins.js} +7 -7
- package/dist/lib/{skills.d.ts → plugins/skills.d.ts} +1 -1
- package/dist/lib/{skills.js → plugins/skills.js} +7 -7
- package/dist/lib/pricing/cost.d.ts +1 -1
- package/dist/lib/pricing/cost.js +1 -1
- package/dist/lib/profiles-presets.d.ts +1 -1
- package/dist/lib/profiles-presets.js +1 -1
- package/dist/lib/profiles.js +2 -2
- package/dist/lib/project-import.js +4 -1
- package/dist/lib/project-key.js +7 -0
- package/dist/lib/project-launch.d.ts +1 -1
- package/dist/lib/project-launch.js +3 -3
- package/dist/lib/project-pull.d.ts +128 -0
- package/dist/lib/project-pull.js +384 -0
- package/dist/lib/project-root.d.ts +2 -2
- package/dist/lib/project-root.js +2 -2
- package/dist/lib/project-status.js +1 -1
- package/dist/lib/projects.d.ts +23 -0
- package/dist/lib/projects.js +29 -6
- package/dist/lib/refresh.d.ts +12 -1
- package/dist/lib/refresh.js +16 -9
- package/dist/lib/registry.d.ts +9 -2
- package/dist/lib/registry.js +11 -4
- package/dist/lib/resources.d.ts +2 -2
- package/dist/lib/resources.js +2 -2
- package/dist/lib/routers.d.ts +81 -0
- package/dist/lib/routers.js +153 -0
- package/dist/lib/routine-context.d.ts +1 -0
- package/dist/lib/routine-context.js +7 -2
- package/dist/lib/routine-notify-owner.d.ts +1 -1
- package/dist/lib/routine-notify.d.ts +1 -1
- package/dist/lib/routine-notify.js +1 -1
- package/dist/lib/routine-process-cleanup.d.ts +1 -1
- package/dist/lib/routine-readiness.d.ts +1 -1
- package/dist/lib/routine-readiness.js +11 -4
- package/dist/lib/routines-placement.d.ts +12 -2
- package/dist/lib/routines-placement.js +20 -3
- package/dist/lib/routines-project.d.ts +1 -1
- package/dist/lib/routines-project.js +1 -1
- package/dist/lib/rules/compile.d.ts +1 -0
- package/dist/lib/rules/compile.js +10 -2
- package/dist/lib/rules/rules.js +1 -1
- package/dist/lib/run-notify.js +1 -1
- package/dist/lib/runner.d.ts +80 -2
- package/dist/lib/runner.js +261 -67
- package/dist/lib/sandbox.d.ts +1 -1
- package/dist/lib/scheduler.d.ts +1 -1
- package/dist/lib/scheduler.js +1 -1
- package/dist/lib/{routines.d.ts → scheduling/routines.d.ts} +54 -9
- package/dist/lib/{routines.js → scheduling/routines.js} +173 -14
- 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.js +2 -2
- package/dist/lib/secrets/audit.js +1 -1
- package/dist/lib/secrets/bundles.d.ts +4 -4
- package/dist/lib/secrets/bundles.js +8 -8
- package/dist/lib/secrets/headless.d.ts +16 -0
- package/dist/lib/secrets/headless.js +21 -0
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.d.ts +1 -1
- package/dist/lib/secrets/list-filter.js +2 -2
- package/dist/lib/secrets/push.d.ts +1 -1
- package/dist/lib/secrets/push.js +10 -12
- package/dist/lib/secrets/remote.d.ts +21 -17
- package/dist/lib/secrets/remote.js +39 -23
- package/dist/lib/secrets/vault.js +2 -2
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/self-update.d.ts +39 -4
- package/dist/lib/self-update.js +78 -25
- package/dist/lib/serve/control.d.ts +1 -1
- package/dist/lib/serve/control.js +4 -4
- package/dist/lib/serve/data.d.ts +1 -1
- package/dist/lib/serve/data.js +1 -1
- package/dist/lib/serve/stream.js +1 -1
- package/dist/lib/service-manifest.d.ts +29 -0
- package/dist/lib/service-manifest.js +80 -0
- package/dist/lib/session/active.d.ts +59 -7
- package/dist/lib/session/active.js +158 -17
- package/dist/lib/session/bash-command.js +1 -1
- package/dist/lib/session/claude-accounts.d.ts +1 -1
- package/dist/lib/session/claude-accounts.js +1 -1
- package/dist/lib/session/db.d.ts +10 -2
- package/dist/lib/session/db.js +25 -6
- package/dist/lib/session/discover.d.ts +77 -0
- package/dist/lib/session/discover.js +130 -25
- package/dist/lib/session/fork.js +1 -1
- package/dist/lib/session/inject.d.ts +1 -1
- package/dist/lib/session/insights.d.ts +1 -1
- package/dist/lib/session/insights.js +1 -1
- package/dist/lib/session/live-metadata.d.ts +15 -0
- package/dist/lib/session/live-metadata.js +76 -0
- package/dist/lib/session/migrate-targets.d.ts +1 -1
- package/dist/lib/session/recovery.d.ts +1 -1
- package/dist/lib/session/recovery.js +2 -2
- package/dist/lib/session/remote-active.d.ts +10 -1
- package/dist/lib/session/remote-active.js +8 -3
- package/dist/lib/session/remote-bundle.d.ts +1 -1
- package/dist/lib/session/remote-bundle.js +2 -2
- package/dist/lib/session/remote-list.d.ts +15 -4
- package/dist/lib/session/remote-list.js +40 -3
- package/dist/lib/session/remote.d.ts +5 -5
- package/dist/lib/session/remote.js +13 -17
- package/dist/lib/session/resume-command.d.ts +3 -2
- package/dist/lib/session/resume-command.js +4 -3
- package/dist/lib/session/resume-owner.d.ts +3 -3
- package/dist/lib/session/resume-owner.js +3 -3
- package/dist/lib/session/session-cache.d.ts +12 -0
- package/dist/lib/session/session-cache.js +2 -0
- package/dist/lib/session/share-html.d.ts +55 -0
- package/dist/lib/session/share-html.js +319 -0
- package/dist/lib/session/shell-programs.js +1 -1
- package/dist/lib/session/state.js +5 -5
- package/dist/lib/session/team-filter.d.ts +1 -1
- package/dist/lib/session/team-filter.js +1 -1
- package/dist/lib/session/types.d.ts +3 -3
- package/dist/lib/session/width.d.ts +2 -2
- package/dist/lib/session/width.js +2 -2
- package/dist/lib/settings-manifest.d.ts +2 -0
- package/dist/lib/settings-manifest.js +81 -3
- package/dist/lib/share/delete.d.ts +21 -0
- package/dist/lib/share/delete.js +42 -0
- package/dist/lib/share/publish.d.ts +120 -0
- package/dist/lib/share/publish.js +243 -18
- package/dist/lib/share/worker-template.js +172 -9
- package/dist/lib/shims.js +35 -7
- package/dist/lib/smart-launch.d.ts +4 -4
- package/dist/lib/smart-launch.js +6 -4
- package/dist/lib/snapshot.d.ts +4 -4
- package/dist/lib/snapshot.js +2 -2
- package/dist/lib/ssh-exec.d.ts +1 -1
- package/dist/lib/ssh-exec.js +2 -2
- package/dist/lib/ssh-tunnel.d.ts +10 -10
- package/dist/lib/ssh-tunnel.js +12 -12
- package/dist/lib/staleness/checkers/hooks.d.ts +1 -1
- package/dist/lib/staleness/checkers/hooks.js +1 -1
- package/dist/lib/staleness/detectors/permissions.js +0 -23
- package/dist/lib/staleness/detectors/plugins.js +1 -1
- package/dist/lib/staleness/fingerprint.js +1 -1
- package/dist/lib/staleness/layers.d.ts +1 -1
- package/dist/lib/staleness/layers.js +1 -1
- package/dist/lib/staleness/writers/mcp.js +3 -1
- package/dist/lib/staleness/writers/plugins.js +1 -1
- package/dist/lib/staleness/writers/types.d.ts +8 -0
- package/dist/lib/startup/command-registry.d.ts +6 -147
- package/dist/lib/startup/command-registry.js +40 -303
- package/dist/lib/startup/dev-build.d.ts +0 -21
- package/dist/lib/startup/dev-build.js +29 -8
- package/dist/lib/state.d.ts +19 -1
- package/dist/lib/state.js +120 -51
- package/dist/lib/subagents.js +1 -1
- package/dist/lib/sync-status.js +1 -1
- package/dist/lib/sync-umbrella.d.ts +6 -1
- package/dist/lib/sync-umbrella.js +9 -4
- package/dist/lib/teams/agents.js +2 -2
- package/dist/lib/teams/registry.js +1 -1
- package/dist/lib/terminal/backends/vscodium-agent.js +1 -1
- package/dist/lib/terminal/engine.js +1 -1
- package/dist/lib/terminal/index.d.ts +4 -0
- package/dist/lib/terminal/index.js +3 -0
- package/dist/lib/terminal/inject.d.ts +2 -2
- package/dist/lib/terminal/inject.js +2 -2
- package/dist/lib/terminal/transport.js +1 -1
- package/dist/lib/tmux/index.d.ts +1 -1
- package/dist/lib/tmux/index.js +1 -1
- package/dist/lib/tmux/session.d.ts +21 -5
- package/dist/lib/tmux/session.js +76 -22
- package/dist/lib/triggers/handlers.d.ts +38 -3
- package/dist/lib/triggers/handlers.js +58 -7
- package/dist/lib/triggers/webhook.d.ts +113 -5
- package/dist/lib/triggers/webhook.js +223 -44
- package/dist/lib/types.d.ts +19 -7
- package/dist/lib/usage-fleet.d.ts +1 -1
- package/dist/lib/usage-fleet.js +1 -1
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +3 -3
- package/dist/lib/version-duplicates.js +2 -2
- package/dist/lib/view-types.d.ts +56 -0
- package/dist/lib/view-types.js +1 -0
- package/dist/lib/watchdog/rotate.d.ts +2 -2
- package/dist/lib/watchdog/rotate.js +3 -3
- package/dist/lib/watchdog/runner.d.ts +2 -3
- package/dist/lib/watchdog/runner.js +3 -4
- package/dist/lib/workflows.js +1 -1
- package/dist/lib/wrap.d.ts +33 -0
- package/dist/lib/wrap.js +70 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +2 -2
- package/dist/commands/bench.d.ts +0 -2
- package/dist/commands/bench.js +0 -101
- package/dist/commands/cp.d.ts +0 -84
- package/dist/commands/cp.js +0 -231
- package/dist/commands/login.d.ts +0 -2
- package/dist/lib/resources/commands.d.ts +0 -46
- package/dist/lib/resources/commands.js +0 -211
- package/dist/lib/resources/hooks.d.ts +0 -16
- package/dist/lib/resources/hooks.js +0 -153
- package/dist/lib/resources/mcp.d.ts +0 -34
- package/dist/lib/resources/mcp.js +0 -250
- package/dist/lib/resources/memory.d.ts +0 -15
- package/dist/lib/resources/memory.js +0 -46
- package/dist/lib/resources/permissions.d.ts +0 -13
- package/dist/lib/resources/permissions.js +0 -202
- package/dist/lib/resources/rules.d.ts +0 -46
- package/dist/lib/resources/rules.js +0 -179
- package/dist/lib/resources/skills.d.ts +0 -37
- package/dist/lib/resources/skills.js +0 -247
- package/dist/lib/resources/subagents.d.ts +0 -46
- package/dist/lib/resources/subagents.js +0 -202
- /package/dist/lib/{feed.d.ts → feed/feed.d.ts} +0 -0
|
@@ -9,11 +9,151 @@ import { readFileSync } from 'node:fs';
|
|
|
9
9
|
import { basename } from 'node:path';
|
|
10
10
|
import { execFileSync } from 'node:child_process';
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
|
+
import { hostname as osHostname } from 'node:os';
|
|
12
13
|
import { readShareConfig, readWriteToken } from './config.js';
|
|
13
14
|
import { resolveGitHubUsername } from '../git.js';
|
|
14
15
|
import { captureCover, OG_WIDTH, OG_HEIGHT, OG_SCALE } from './capture.js';
|
|
15
16
|
import { deriveMeta, injectOgMeta } from './og.js';
|
|
16
17
|
import { injectAnalyticsBeacon } from './analytics.js';
|
|
18
|
+
/**
|
|
19
|
+
* Reserved `customMetadata` keys the CLI sets automatically from the exec env,
|
|
20
|
+
* git, and the local clock (plus `label`/`label-source`) — a `--meta key=value`
|
|
21
|
+
* may not target any of these (see {@link parseMetaEntries}).
|
|
22
|
+
*/
|
|
23
|
+
export const RESERVED_META_KEYS = ['agent', 'session', 'host', 'repo', 'date', 'label', 'label-source'];
|
|
24
|
+
const META_KEY_RE = /^[a-z0-9-]{1,64}$/;
|
|
25
|
+
/**
|
|
26
|
+
* Auto-capture publish provenance from the exec env, git, and the local clock.
|
|
27
|
+
* Every field is present only when the environment genuinely carries it — a
|
|
28
|
+
* human running the command by hand outside a git repo yields `session`/`agent`/
|
|
29
|
+
* `repo` all undefined, never an invented value. `host` is always present
|
|
30
|
+
* (`os.hostname()` never fails to return something real about where the publish
|
|
31
|
+
* ran, so it isn't "invented" in the same sense).
|
|
32
|
+
*/
|
|
33
|
+
export function resolveShareProvenance(opts = {}) {
|
|
34
|
+
const env = opts.env ?? process.env;
|
|
35
|
+
return {
|
|
36
|
+
session: env.AGENTS_SESSION_ID || env.AGENT_SESSION_ID || undefined,
|
|
37
|
+
agent: env.AGENTS_AGENT_NAME || undefined,
|
|
38
|
+
host: opts.hostname ?? osHostname(),
|
|
39
|
+
repo: gitRepoName(opts.dir ?? process.cwd()),
|
|
40
|
+
date: (opts.now ?? new Date()).toISOString().slice(0, 10),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse repeated `--meta key=value` CLI args into a validated metadata record.
|
|
45
|
+
* Keys are lowercase `[a-z0-9-]`, up to 64 characters, and may not collide with
|
|
46
|
+
* {@link RESERVED_META_KEYS} (the provenance the CLI sets automatically, plus
|
|
47
|
+
* label/label-source) — throws naming the offending pair on any violation.
|
|
48
|
+
*/
|
|
49
|
+
export function parseMetaEntries(pairs) {
|
|
50
|
+
const meta = {};
|
|
51
|
+
for (const pair of pairs) {
|
|
52
|
+
const eq = pair.indexOf('=');
|
|
53
|
+
if (eq <= 0) {
|
|
54
|
+
throw new Error(`Bad --meta '${pair}'. Expected key=value.`);
|
|
55
|
+
}
|
|
56
|
+
const key = pair.slice(0, eq).trim();
|
|
57
|
+
const value = pair.slice(eq + 1);
|
|
58
|
+
if (!META_KEY_RE.test(key)) {
|
|
59
|
+
throw new Error(`Bad --meta key '${key}'. Keys are lowercase letters, digits, and hyphens, up to 64 characters.`);
|
|
60
|
+
}
|
|
61
|
+
if (RESERVED_META_KEYS.includes(key)) {
|
|
62
|
+
throw new Error(`--meta ${key}=… is reserved (set automatically from your session/git) — pass a different key.`);
|
|
63
|
+
}
|
|
64
|
+
meta[key] = value;
|
|
65
|
+
}
|
|
66
|
+
return meta;
|
|
67
|
+
}
|
|
68
|
+
/** S3's `x-amz-meta` convention caps user metadata around 2KB; R2 publishes no
|
|
69
|
+
* hard limit of its own, so stay under that ceiling to keep a share portable to
|
|
70
|
+
* an S3-compatible mirror. Checked over the FULL customMetadata payload
|
|
71
|
+
* (provenance + label + --meta combined), since that's what actually gets
|
|
72
|
+
* written to the object. */
|
|
73
|
+
const MAX_METADATA_BYTES = 2048;
|
|
74
|
+
/** Throws when the combined `customMetadata` payload would exceed {@link MAX_METADATA_BYTES}. */
|
|
75
|
+
export function assertMetadataSize(customMetadata) {
|
|
76
|
+
const bytes = Buffer.byteLength(JSON.stringify(customMetadata), 'utf8');
|
|
77
|
+
if (bytes > MAX_METADATA_BYTES) {
|
|
78
|
+
throw new Error(`Share metadata is ${bytes} bytes, over the ${MAX_METADATA_BYTES}-byte cap ` +
|
|
79
|
+
`(provenance + --label + --meta combined). Trim your --meta values.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Collapse a label to a single line before it goes into the `x-share-label`
|
|
84
|
+
* header or public customMetadata. `<title>[^<]{1,200}</title>` matches
|
|
85
|
+
* newlines (`[^<]` excludes only `<`), and `.trim()` only strips leading/
|
|
86
|
+
* trailing whitespace, not embedded newlines — a multi-line `<title>` (or an
|
|
87
|
+
* explicit `--label`/`--title` the caller typed with a literal newline) was
|
|
88
|
+
* previously passed straight into `Headers.set()` unsanitized, which throws
|
|
89
|
+
* an unhandled `TypeError: Invalid value` and crashes the publish outright.
|
|
90
|
+
*/
|
|
91
|
+
export function sanitizeLabel(text) {
|
|
92
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Typographic characters that reach a header constantly — a curly quote from a
|
|
96
|
+
* pasted prompt, an em dash from prose, the ellipsis a truncated title ends on —
|
|
97
|
+
* each mapped to the ASCII form a reader loses nothing by seeing.
|
|
98
|
+
*/
|
|
99
|
+
const HEADER_TRANSLITERATIONS = [
|
|
100
|
+
[/[‘’‚‛]/g, "'"],
|
|
101
|
+
[/[“”„‟]/g, '"'],
|
|
102
|
+
[/[–—―]/g, '-'],
|
|
103
|
+
[/…/g, '...'],
|
|
104
|
+
[/[ ]/g, ' '],
|
|
105
|
+
[/[•·]/g, '-'],
|
|
106
|
+
];
|
|
107
|
+
/**
|
|
108
|
+
* Make a free-text value safe to put in an HTTP header.
|
|
109
|
+
*
|
|
110
|
+
* `fetch` encodes header values as a **ByteString**, so any code point above 255
|
|
111
|
+
* throws `TypeError: Cannot convert argument to a ByteString` — an unhandled
|
|
112
|
+
* crash with a stack trace, mid-publish, after the body has already been read.
|
|
113
|
+
* Reproduced by publishing a session whose title ended in `…` (U+2026), and
|
|
114
|
+
* reachable by any emoji, curly quote, accented name, or CJK text in a `--label`,
|
|
115
|
+
* a `--meta` value, or a repo name.
|
|
116
|
+
*
|
|
117
|
+
* The transliterations above cover what actually shows up; anything else outside
|
|
118
|
+
* latin1 is dropped, and a value that transliterates to nothing at all (a title
|
|
119
|
+
* written entirely in a non-latin script) degrades to a marker rather than an
|
|
120
|
+
* empty header. Lossy on purpose: carrying full Unicode needs percent-encoding
|
|
121
|
+
* here AND a matching decode in the Worker, which every already-deployed Worker
|
|
122
|
+
* would render as `%E2%80%A6` until its operator ran `agents artifacts share
|
|
123
|
+
* update` — tracked as RUSH-2786.
|
|
124
|
+
*/
|
|
125
|
+
export function toHeaderValue(text) {
|
|
126
|
+
let safe = text;
|
|
127
|
+
for (const [pattern, replacement] of HEADER_TRANSLITERATIONS)
|
|
128
|
+
safe = safe.replace(pattern, replacement);
|
|
129
|
+
safe = safe.replace(/[^\x20-\x7E\xA0-\xFF]/g, '').replace(/\s+/g, ' ').trim();
|
|
130
|
+
// An input that was empty to begin with stays empty — `--meta note=` means an
|
|
131
|
+
// empty note, not an unnamed one. The marker is only for a value that HAD
|
|
132
|
+
// content and lost all of it to the latin1 fold.
|
|
133
|
+
if (safe)
|
|
134
|
+
return safe;
|
|
135
|
+
return text.trim() ? '(unnamed)' : '';
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Best-effort human title when `--label` is omitted: the HTML `<title>`, else a
|
|
139
|
+
* Markdown frontmatter `title:`, else the filename. Always returns something —
|
|
140
|
+
* a headless publish must never hang waiting on a prompt for one.
|
|
141
|
+
*/
|
|
142
|
+
export function deriveLabel(filePath, body) {
|
|
143
|
+
const text = body.toString('utf8');
|
|
144
|
+
const htmlTitle = /<title[^>]*>([^<]{1,200})<\/title>/i.exec(text);
|
|
145
|
+
if (htmlTitle?.[1]?.trim())
|
|
146
|
+
return sanitizeLabel(htmlTitle[1]);
|
|
147
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
148
|
+
if (frontmatter) {
|
|
149
|
+
const titleLine = /^title:\s*(.+)$/m.exec(frontmatter[1]);
|
|
150
|
+
const cleaned = titleLine?.[1]?.trim().replace(/^["']|["']$/g, '').trim();
|
|
151
|
+
if (cleaned)
|
|
152
|
+
return sanitizeLabel(cleaned);
|
|
153
|
+
}
|
|
154
|
+
const base = basename(filePath).replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').trim();
|
|
155
|
+
return sanitizeLabel(base || basename(filePath));
|
|
156
|
+
}
|
|
17
157
|
/** Default auto-expire for unflagged publishes — accidental links decay (RUSH-2443). */
|
|
18
158
|
export const DEFAULT_SHARE_EXPIRE = '30d';
|
|
19
159
|
const UNIT_MS = { s: 1e3, m: 6e4, h: 36e5, d: 864e5, w: 6048e5 };
|
|
@@ -44,6 +184,27 @@ export function resolveExpire(spec) {
|
|
|
44
184
|
}
|
|
45
185
|
/** Email addresses — the RUSH-2428 incident page carried seven of these. */
|
|
46
186
|
const EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
187
|
+
/**
|
|
188
|
+
* Mask every email address in `text`, so a page can be published without tripping
|
|
189
|
+
* {@link scanShareContent}.
|
|
190
|
+
*
|
|
191
|
+
* Lives here, beside the scanner, rather than in `lib/redact.ts`: it exists solely
|
|
192
|
+
* to satisfy this gate, and sharing {@link EMAIL_RE} is what makes the masking
|
|
193
|
+
* *sufficient* to clear it rather than merely reducing the hit count. Two copies of
|
|
194
|
+
* the pattern in two modules would drift, and the failure mode of that drift is a
|
|
195
|
+
* refused publish at runtime.
|
|
196
|
+
*
|
|
197
|
+
* Deliberately NOT part of `redactSecrets`: an email is not a credential, and a
|
|
198
|
+
* transcript rendered for a private gist or local review reads better with the real
|
|
199
|
+
* author addresses intact. It only becomes a leak once the text is PUBLISHED — a
|
|
200
|
+
* world-readable page carrying seven of them is the RUSH-2428 incident.
|
|
201
|
+
*
|
|
202
|
+
* The whole address goes, domain included — a personal domain identifies its owner
|
|
203
|
+
* as surely as the local part does, and keeping it would buy the reader nothing.
|
|
204
|
+
*/
|
|
205
|
+
export function redactEmails(text) {
|
|
206
|
+
return text.replace(EMAIL_RE, '[EMAIL]');
|
|
207
|
+
}
|
|
47
208
|
/**
|
|
48
209
|
* Credential-shaped strings that an agent routinely dumps into reports: GitHub
|
|
49
210
|
* PATs, OpenAI/Anthropic/etc. API keys, AWS access keys, Slack tokens, and a
|
|
@@ -118,7 +279,7 @@ export function formatSensitiveContentError(hits) {
|
|
|
118
279
|
: kinds[0] === 'email'
|
|
119
280
|
? 'email addresses'
|
|
120
281
|
: 'credential-shaped strings';
|
|
121
|
-
return (`Refusing to publish:
|
|
282
|
+
return (`Refusing to publish: found ${what} (${samples}) in the file, label, or metadata. ` +
|
|
122
283
|
`Shares are world-readable by URL — pass --force to publish anyway, ` +
|
|
123
284
|
`or --unlisted --expire 12h to bound the blast radius.`);
|
|
124
285
|
}
|
|
@@ -134,8 +295,9 @@ export function slugify(name) {
|
|
|
134
295
|
function sanitizeSlugPart(s) {
|
|
135
296
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
136
297
|
}
|
|
137
|
-
/** The
|
|
138
|
-
|
|
298
|
+
/** The current repo's name, or undefined outside a git checkout — never a
|
|
299
|
+
* fallback guess, since callers that want one (detectProject) supply it themselves. */
|
|
300
|
+
function gitRepoName(dir) {
|
|
139
301
|
try {
|
|
140
302
|
const top = execFileSync('git', ['-C', dir, 'rev-parse', '--show-toplevel'], {
|
|
141
303
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -143,12 +305,16 @@ export function detectProject(dir = process.cwd()) {
|
|
|
143
305
|
.toString()
|
|
144
306
|
.trim();
|
|
145
307
|
if (top)
|
|
146
|
-
return sanitizeSlugPart(basename(top)) ||
|
|
308
|
+
return sanitizeSlugPart(basename(top)) || undefined;
|
|
147
309
|
}
|
|
148
310
|
catch {
|
|
149
|
-
// not a git repo
|
|
311
|
+
// not a git repo
|
|
150
312
|
}
|
|
151
|
-
return
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
/** The project the file belongs to — git repo name, else the cwd's basename. */
|
|
316
|
+
export function detectProject(dir = process.cwd()) {
|
|
317
|
+
return gitRepoName(dir) ?? (sanitizeSlugPart(basename(dir)) || 'share');
|
|
152
318
|
}
|
|
153
319
|
/**
|
|
154
320
|
* Notion-style default slug: `<project>-<feature>-<16hex>`. Project scopes the link
|
|
@@ -273,31 +439,88 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
273
439
|
const expiresAt = resolveExpire(opts.expire);
|
|
274
440
|
const unlisted = opts.unlisted === true;
|
|
275
441
|
const pageUrl = `${endpoint.baseUrl.replace(/\/+$/, '')}/${key}`;
|
|
442
|
+
const provenance = opts.provenance ?? resolveShareProvenance();
|
|
443
|
+
const meta = opts.meta ?? {};
|
|
276
444
|
const put = opts.uploader ??
|
|
277
445
|
(async (u, b, h) => {
|
|
278
446
|
const res = await fetch(u, { method: 'PUT', headers: h, body: new Uint8Array(b) });
|
|
279
447
|
return { ok: res.ok, status: res.status, url: u };
|
|
280
448
|
});
|
|
281
|
-
const authHeaders = (contentType) => {
|
|
282
|
-
const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
|
|
283
|
-
if (expiresAt)
|
|
284
|
-
h['x-share-expires-at'] = expiresAt;
|
|
285
|
-
if (unlisted)
|
|
286
|
-
h['x-share-visibility'] = 'unlisted';
|
|
287
|
-
return h;
|
|
288
|
-
};
|
|
289
449
|
let body = readFileSync(filePath);
|
|
290
450
|
let coverUrl;
|
|
291
451
|
const isHtml = /\.html?$/i.test(filePath);
|
|
292
|
-
|
|
293
|
-
//
|
|
294
|
-
//
|
|
452
|
+
const explicitLabel = opts.label?.trim();
|
|
453
|
+
// sanitizeLabel here (not just inside deriveLabel) covers an explicit
|
|
454
|
+
// --label/--title the caller typed with an embedded newline — deriveLabel
|
|
455
|
+
// is only reached when --label is omitted.
|
|
456
|
+
const label = explicitLabel ? sanitizeLabel(explicitLabel) : deriveLabel(filePath, body);
|
|
457
|
+
const labelSource = explicitLabel ? 'explicit' : 'derived';
|
|
458
|
+
// Pre-publish scan (RUSH-2443/RUSH-2683): refuse emails / credential-shaped
|
|
459
|
+
// strings unless --force. Runs on the raw file body AND on every piece of
|
|
460
|
+
// free-text metadata that lands in public customMetadata — --label (explicit
|
|
461
|
+
// or derived) and every --meta value. Metadata is visible in the gallery,
|
|
462
|
+
// `share list --json`, and `share revisions` just like the page itself, so a
|
|
463
|
+
// credential smuggled in there is exactly as exposed as one in the body; it
|
|
464
|
+
// must not have a free pass around this gate. Runs before analytics/cover
|
|
465
|
+
// mutation so a beacon injection never triggers a false positive on the body
|
|
466
|
+
// scan. Binary media bodies are a no-op for the body scan.
|
|
295
467
|
if (opts.force !== true) {
|
|
296
|
-
const hits =
|
|
468
|
+
const hits = [
|
|
469
|
+
...scanShareContent(body),
|
|
470
|
+
...scanShareContent(label),
|
|
471
|
+
...Object.values(meta).flatMap((v) => scanShareContent(v)),
|
|
472
|
+
];
|
|
297
473
|
if (hits.length > 0) {
|
|
298
474
|
throw new Error(formatSensitiveContentError(hits));
|
|
299
475
|
}
|
|
300
476
|
}
|
|
477
|
+
// Validate the FULL customMetadata payload before any network call — fail
|
|
478
|
+
// fast, not mid-upload.
|
|
479
|
+
const metadataPreview = { ...meta, label, 'label-source': labelSource };
|
|
480
|
+
if (provenance.agent)
|
|
481
|
+
metadataPreview.agent = provenance.agent;
|
|
482
|
+
if (provenance.session)
|
|
483
|
+
metadataPreview.session = provenance.session;
|
|
484
|
+
if (provenance.host)
|
|
485
|
+
metadataPreview.host = provenance.host;
|
|
486
|
+
if (provenance.repo)
|
|
487
|
+
metadataPreview.repo = provenance.repo;
|
|
488
|
+
if (provenance.date)
|
|
489
|
+
metadataPreview.date = provenance.date;
|
|
490
|
+
assertMetadataSize(metadataPreview);
|
|
491
|
+
const authHeaders = (contentType) => {
|
|
492
|
+
const h = { authorization: `Bearer ${endpoint.token}`, 'content-type': contentType };
|
|
493
|
+
if (expiresAt)
|
|
494
|
+
h['x-share-expires-at'] = expiresAt;
|
|
495
|
+
if (unlisted)
|
|
496
|
+
h['x-share-visibility'] = 'unlisted';
|
|
497
|
+
// Every free-text header goes through toHeaderValue: a non-latin1 code point
|
|
498
|
+
// anywhere in a label, a repo name, or a --meta value throws inside fetch and
|
|
499
|
+
// crashes the publish outright.
|
|
500
|
+
if (provenance.agent)
|
|
501
|
+
h['x-share-agent'] = toHeaderValue(provenance.agent);
|
|
502
|
+
if (provenance.session)
|
|
503
|
+
h['x-share-session'] = toHeaderValue(provenance.session);
|
|
504
|
+
if (provenance.host)
|
|
505
|
+
h['x-share-host'] = toHeaderValue(provenance.host);
|
|
506
|
+
if (provenance.repo)
|
|
507
|
+
h['x-share-repo'] = toHeaderValue(provenance.repo);
|
|
508
|
+
if (provenance.date)
|
|
509
|
+
h['x-share-date'] = toHeaderValue(provenance.date);
|
|
510
|
+
h['x-share-label'] = toHeaderValue(label);
|
|
511
|
+
h['x-share-label-source'] = labelSource;
|
|
512
|
+
// Per VALUE, before JSON.stringify — folding the serialized form would rewrite
|
|
513
|
+
// a curly quote inside a value into a bare `"`, which is structural in JSON and
|
|
514
|
+
// makes the Worker's JSON.parse throw. It swallows that error, so every --meta
|
|
515
|
+
// key would silently vanish on a 200.
|
|
516
|
+
if (Object.keys(meta).length > 0) {
|
|
517
|
+
const headerMeta = Object.fromEntries(Object.entries(meta).map(([k, v]) => [toHeaderValue(k), toHeaderValue(v)]));
|
|
518
|
+
h['x-share-meta'] = JSON.stringify(headerMeta);
|
|
519
|
+
}
|
|
520
|
+
if (opts.noRevision)
|
|
521
|
+
h['x-share-no-revision'] = '1';
|
|
522
|
+
return h;
|
|
523
|
+
};
|
|
301
524
|
// Analytics: cookieless CF Web Analytics beacon, injected for HTML by default.
|
|
302
525
|
if (isHtml && opts.analytics !== false && opts.analyticsToken) {
|
|
303
526
|
body = Buffer.from(injectAnalyticsBeacon(body.toString('utf8'), opts.analyticsToken), 'utf8');
|
|
@@ -324,6 +547,8 @@ export async function publishToEndpoint(filePath, endpoint, opts = {}) {
|
|
|
324
547
|
url: r.url ?? pageUrl,
|
|
325
548
|
expiresAt,
|
|
326
549
|
coverUrl,
|
|
550
|
+
label,
|
|
551
|
+
labelSource,
|
|
327
552
|
...(unlisted ? { unlisted: true } : {}),
|
|
328
553
|
};
|
|
329
554
|
}
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
//
|
|
3
3
|
// One tiny Worker does both sides:
|
|
4
4
|
// - PUT /<username>/<slug> — bearer-gated (WRITE_TOKEN secret); writes the body
|
|
5
|
-
// to R2 via the BUCKET binding, storing an optional expires-at value
|
|
6
|
-
//
|
|
5
|
+
// to R2 via the BUCKET binding, storing an optional expires-at value plus
|
|
6
|
+
// provenance (agent/session/host/repo/date), a label, and any `--meta`
|
|
7
|
+
// entries in object metadata. Overwriting an existing slug first copies the
|
|
8
|
+
// current object to <slug>/rev-<ts>-<rand> (revision history) unless
|
|
9
|
+
// x-share-no-revision is set.
|
|
7
10
|
// - GET /<username>/<slug> — public; streams the object from R2, 410s (and lazily
|
|
8
11
|
// deletes) once its expiry has passed. A bucket lifecycle rule is the durable
|
|
9
12
|
// sweeper; this is the immediate gate.
|
|
13
|
+
// - GET /<username>/<slug>?revisions=json — machine-readable history of the
|
|
14
|
+
// retained prior versions under that slug, newest first.
|
|
10
15
|
// - GET /<username> — public gallery of that user's shares (HTML).
|
|
11
16
|
// - GET /<username>?format=json — public machine-readable listing of that user's
|
|
12
17
|
// ACTIVE shares (`agents artifacts share list`). Same single-segment path as the HTML
|
|
@@ -55,9 +60,71 @@ export default {
|
|
|
55
60
|
// keeping the direct URL world-readable (capability URL, not secret).
|
|
56
61
|
const visibility = (request.headers.get('x-share-visibility') || '').toLowerCase();
|
|
57
62
|
const contentType = request.headers.get('content-type') || 'text/html; charset=utf-8';
|
|
58
|
-
|
|
63
|
+
// Provenance (RUSH-2683): captured client-side from the exec env/git/clock,
|
|
64
|
+
// never invented here — a header is simply absent when the CLI had nothing
|
|
65
|
+
// to say. --meta entries ride one JSON header; reserved keys are stripped
|
|
66
|
+
// from that JSON UNCONDITIONALLY (not just overwritten when a provenance
|
|
67
|
+
// header happens to be present) so a same-named x-share-meta entry can
|
|
68
|
+
// never smuggle through on a publish that carries no agent/session/host/
|
|
69
|
+
// repo/date at all — e.g. a human publishing outside an agent session, or
|
|
70
|
+
// outside a git checkout.
|
|
71
|
+
const agent = request.headers.get('x-share-agent') || '';
|
|
72
|
+
const session = request.headers.get('x-share-session') || '';
|
|
73
|
+
const host = request.headers.get('x-share-host') || '';
|
|
74
|
+
const repo = request.headers.get('x-share-repo') || '';
|
|
75
|
+
const date = request.headers.get('x-share-date') || '';
|
|
76
|
+
const label = request.headers.get('x-share-label') || '';
|
|
77
|
+
const labelSource = request.headers.get('x-share-label-source') || '';
|
|
78
|
+
let extraMeta = {};
|
|
79
|
+
const metaHeader = request.headers.get('x-share-meta');
|
|
80
|
+
if (metaHeader) {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(metaHeader);
|
|
83
|
+
if (parsed && typeof parsed === 'object') extraMeta = parsed;
|
|
84
|
+
} catch {
|
|
85
|
+
// malformed --meta header — ignore rather than fail the whole publish
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const customMetadata = { ...extraMeta };
|
|
89
|
+
// Strip every reserved key UNCONDITIONALLY before re-applying the real
|
|
90
|
+
// provenance below — an if(value)-guarded overwrite alone leaves a
|
|
91
|
+
// same-named --meta entry in place whenever the real header is absent.
|
|
92
|
+
for (const reservedKey of RESERVED_METADATA_KEYS) {
|
|
93
|
+
delete customMetadata[reservedKey];
|
|
94
|
+
}
|
|
59
95
|
if (expiresAt) customMetadata['expires-at'] = expiresAt;
|
|
60
96
|
if (visibility === 'unlisted') customMetadata['visibility'] = 'unlisted';
|
|
97
|
+
if (agent) customMetadata['agent'] = agent;
|
|
98
|
+
if (session) customMetadata['session'] = session;
|
|
99
|
+
if (host) customMetadata['host'] = host;
|
|
100
|
+
if (repo) customMetadata['repo'] = repo;
|
|
101
|
+
if (date) customMetadata['date'] = date;
|
|
102
|
+
if (label) customMetadata['label'] = label;
|
|
103
|
+
if (labelSource) customMetadata['label-source'] = labelSource;
|
|
104
|
+
|
|
105
|
+
// Revision retention (RUSH-2683): R2 has no native object versioning, so a
|
|
106
|
+
// republish over an EXISTING key first copies the current object to
|
|
107
|
+
// <key>/rev-<ts>-<rand> before the canonical key is overwritten. Default
|
|
108
|
+
// keep-all; --no-revision (x-share-no-revision) skips it. The random
|
|
109
|
+
// suffix guards against two rapid overwrites of the same slug colliding on
|
|
110
|
+
// the millisecond, and against a slug whose canonical key already sits 3+
|
|
111
|
+
// segments deep (an unsupported shape outside the CLI) landing on the same
|
|
112
|
+
// literal revision key.
|
|
113
|
+
const noRevision = !!request.headers.get('x-share-no-revision');
|
|
114
|
+
if (!noRevision) {
|
|
115
|
+
const existing = await env.BUCKET.get(path);
|
|
116
|
+
if (existing) {
|
|
117
|
+
const existingHeaders = new Headers();
|
|
118
|
+
if (typeof existing.writeHttpMetadata === 'function') existing.writeHttpMetadata(existingHeaders);
|
|
119
|
+
const existingContentType = existingHeaders.get('content-type');
|
|
120
|
+
const revKey = path + '/rev-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);
|
|
121
|
+
await env.BUCKET.put(revKey, existing.body, {
|
|
122
|
+
httpMetadata: existingContentType ? { contentType: existingContentType } : undefined,
|
|
123
|
+
customMetadata: existing.customMetadata || {},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
61
128
|
await env.BUCKET.put(path, request.body, {
|
|
62
129
|
httpMetadata: { contentType },
|
|
63
130
|
customMetadata,
|
|
@@ -88,6 +155,13 @@ export default {
|
|
|
88
155
|
// signal rather than a missing route).
|
|
89
156
|
}
|
|
90
157
|
|
|
158
|
+
// Revision history for one canonical <user>/<slug> key (RUSH-2683). Checked
|
|
159
|
+
// before the plain object GET below so the query param routes even though
|
|
160
|
+
// the canonical key itself resolves to a real object.
|
|
161
|
+
if (segments.length === 2 && url.searchParams.get('revisions') === 'json') {
|
|
162
|
+
return renderRevisions(env.BUCKET, url.origin, path, request.method);
|
|
163
|
+
}
|
|
164
|
+
|
|
91
165
|
const obj = await env.BUCKET.get(path);
|
|
92
166
|
if (!obj) return new Response('not found', { status: 404, headers: { 'content-type': 'text/plain' } });
|
|
93
167
|
const expiresAt = obj.customMetadata && obj.customMetadata['expires-at'];
|
|
@@ -114,6 +188,16 @@ export default {
|
|
|
114
188
|
},
|
|
115
189
|
};
|
|
116
190
|
|
|
191
|
+
// A canonical share object's key is exactly 2 segments (<user>/<slug>, or the
|
|
192
|
+
// sibling <user>/<slug>.png OG cover). A retained revision lives a 3rd segment
|
|
193
|
+
// deeper (<user>/<slug>/rev-<ts>-<rand>) — history, not a page in its own
|
|
194
|
+
// right, so every gallery/listing view hides it. A raw external PUT to an
|
|
195
|
+
// already-3-segment-deep path is treated the same way for the same reason:
|
|
196
|
+
// there is no ambiguity to resolve, and the CLI never produces such a key.
|
|
197
|
+
function isRevisionKey(key) {
|
|
198
|
+
return key.split('/').length > 2;
|
|
199
|
+
}
|
|
200
|
+
|
|
117
201
|
async function renderGallery(bucket, origin, user, method) {
|
|
118
202
|
const objects = [];
|
|
119
203
|
let cursor;
|
|
@@ -124,6 +208,7 @@ async function renderGallery(bucket, origin, user, method) {
|
|
|
124
208
|
} while (cursor);
|
|
125
209
|
|
|
126
210
|
const activeObjects = objects.filter(o => {
|
|
211
|
+
if (isRevisionKey(o.key)) return false;
|
|
127
212
|
if (o.key.endsWith('.png')) return false;
|
|
128
213
|
// Unlisted pages are reachable by direct URL only — never on the gallery.
|
|
129
214
|
if (o.customMetadata && o.customMetadata.visibility === 'unlisted') return false;
|
|
@@ -133,7 +218,9 @@ async function renderGallery(bucket, origin, user, method) {
|
|
|
133
218
|
const items = activeObjects.map(o => {
|
|
134
219
|
const slug = o.key.slice(o.key.indexOf('/') + 1);
|
|
135
220
|
const url = origin + '/' + o.key;
|
|
136
|
-
|
|
221
|
+
const label = (o.customMetadata && o.customMetadata['label']) || '';
|
|
222
|
+
const agent = (o.customMetadata && o.customMetadata['agent']) || '';
|
|
223
|
+
return { slug, url, updated: o.uploaded, label, agent };
|
|
137
224
|
});
|
|
138
225
|
|
|
139
226
|
if (method === 'HEAD') {
|
|
@@ -148,14 +235,17 @@ async function renderGallery(bucket, origin, user, method) {
|
|
|
148
235
|
'body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:720px;margin:48px auto;padding:0 24px;color:#111;background:#fafafa}' +
|
|
149
236
|
'h1{font-size:28px;margin-bottom:8px}a{color:#0a0a0a;text-decoration:none;border-bottom:1px solid #999}' +
|
|
150
237
|
'a:hover{border-color:#111}ul{list-style:none;padding:0}li{margin:16px 0}' +
|
|
151
|
-
'.slug{font-weight:600}.meta{color:#666;font-size:14px}' +
|
|
238
|
+
'.slug{font-weight:600}.title{font-weight:600}.meta{color:#666;font-size:14px}' +
|
|
152
239
|
'</style></head><body>' +
|
|
153
240
|
'<h1>@' + escapeHtml(user) + '</h1>' +
|
|
154
241
|
'<p class="meta">' + items.length + ' shared ' + (items.length === 1 ? 'page' : 'pages') + '</p>' +
|
|
155
242
|
'<ul>' +
|
|
156
243
|
items.map(i =>
|
|
157
|
-
'<li
|
|
158
|
-
'<
|
|
244
|
+
'<li>' +
|
|
245
|
+
(i.label ? '<span class="title">' + escapeHtml(i.label) + '</span><br>' : '') +
|
|
246
|
+
'<a class="slug" href="' + escapeHtml(i.url) + '">' + escapeHtml(i.slug) + '</a>' +
|
|
247
|
+
'<br><span class="meta">' + new Date(i.updated).toISOString().slice(0, 10) +
|
|
248
|
+
(i.agent ? ' · ' + escapeHtml(i.agent) : '') + '</span></li>'
|
|
159
249
|
).join('') +
|
|
160
250
|
'</ul></body></html>';
|
|
161
251
|
return new Response(html, { status: 200, headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'public, max-age=60' } });
|
|
@@ -170,11 +260,21 @@ async function renderListing(bucket, origin, user, method) {
|
|
|
170
260
|
cursor = list.truncated ? list.cursor : undefined;
|
|
171
261
|
} while (cursor);
|
|
172
262
|
|
|
263
|
+
// Revisions never appear as their own listing row, but they count toward
|
|
264
|
+
// their canonical object's revisionCount.
|
|
265
|
+
const revisionCounts = {};
|
|
266
|
+
for (const o of objects) {
|
|
267
|
+
if (!isRevisionKey(o.key)) continue;
|
|
268
|
+
const canonicalKey = o.key.split('/').slice(0, 2).join('/');
|
|
269
|
+
revisionCounts[canonicalKey] = (revisionCounts[canonicalKey] || 0) + 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
173
272
|
const now = Date.now();
|
|
174
273
|
const items = objects
|
|
175
274
|
.filter(o => {
|
|
176
|
-
// Mirror the gallery: hide sibling .png OG covers, expired
|
|
177
|
-
// unlisted pages — the listing is the machine-readable public surface.
|
|
275
|
+
// Mirror the gallery: hide revisions, sibling .png OG covers, expired
|
|
276
|
+
// pages, and unlisted pages — the listing is the machine-readable public surface.
|
|
277
|
+
if (isRevisionKey(o.key)) return false;
|
|
178
278
|
if (o.key.endsWith('.png')) return false;
|
|
179
279
|
if (o.customMetadata && o.customMetadata.visibility === 'unlisted') return false;
|
|
180
280
|
const expiresAt = o.customMetadata && o.customMetadata['expires-at'];
|
|
@@ -187,6 +287,13 @@ async function renderListing(bucket, origin, user, method) {
|
|
|
187
287
|
contentType: (o.httpMetadata && o.httpMetadata.contentType) || null,
|
|
188
288
|
publishedAt: new Date(o.uploaded).toISOString(),
|
|
189
289
|
expiresAt: (o.customMetadata && o.customMetadata['expires-at']) || null,
|
|
290
|
+
label: (o.customMetadata && o.customMetadata['label']) || null,
|
|
291
|
+
agent: (o.customMetadata && o.customMetadata['agent']) || null,
|
|
292
|
+
session: (o.customMetadata && o.customMetadata['session']) || null,
|
|
293
|
+
host: (o.customMetadata && o.customMetadata['host']) || null,
|
|
294
|
+
repo: (o.customMetadata && o.customMetadata['repo']) || null,
|
|
295
|
+
revisionCount: revisionCounts[o.key] || 0,
|
|
296
|
+
meta: extraMetaOf(o.customMetadata),
|
|
190
297
|
}));
|
|
191
298
|
// Newest first, so the human table and any script reads the freshest share top.
|
|
192
299
|
items.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : a.publishedAt > b.publishedAt ? -1 : 0));
|
|
@@ -200,6 +307,62 @@ async function renderListing(bucket, origin, user, method) {
|
|
|
200
307
|
});
|
|
201
308
|
}
|
|
202
309
|
|
|
310
|
+
async function renderRevisions(bucket, origin, key, method) {
|
|
311
|
+
const objects = [];
|
|
312
|
+
let cursor;
|
|
313
|
+
do {
|
|
314
|
+
const list = await bucket.list({ prefix: key + '/rev-', limit: 1000, cursor, include: ['httpMetadata', 'customMetadata'] });
|
|
315
|
+
objects.push(...(list.objects || []));
|
|
316
|
+
cursor = list.truncated ? list.cursor : undefined;
|
|
317
|
+
} while (cursor);
|
|
318
|
+
|
|
319
|
+
const items = objects.map(o => ({
|
|
320
|
+
key: o.key,
|
|
321
|
+
url: origin + '/' + o.key,
|
|
322
|
+
size: typeof o.size === 'number' ? o.size : 0,
|
|
323
|
+
contentType: (o.httpMetadata && o.httpMetadata.contentType) || null,
|
|
324
|
+
uploadedAt: new Date(o.uploaded).toISOString(),
|
|
325
|
+
expiresAt: (o.customMetadata && o.customMetadata['expires-at']) || null,
|
|
326
|
+
label: (o.customMetadata && o.customMetadata['label']) || null,
|
|
327
|
+
agent: (o.customMetadata && o.customMetadata['agent']) || null,
|
|
328
|
+
session: (o.customMetadata && o.customMetadata['session']) || null,
|
|
329
|
+
host: (o.customMetadata && o.customMetadata['host']) || null,
|
|
330
|
+
repo: (o.customMetadata && o.customMetadata['repo']) || null,
|
|
331
|
+
meta: extraMetaOf(o.customMetadata),
|
|
332
|
+
}));
|
|
333
|
+
// Newest first — the most recently replaced version leads.
|
|
334
|
+
items.sort((a, b) => (a.uploadedAt < b.uploadedAt ? 1 : a.uploadedAt > b.uploadedAt ? -1 : 0));
|
|
335
|
+
|
|
336
|
+
if (method === 'HEAD') {
|
|
337
|
+
return new Response(null, { status: 200, headers: { 'content-type': 'application/json; charset=utf-8' } });
|
|
338
|
+
}
|
|
339
|
+
return new Response(JSON.stringify({ key, count: items.length, revisions: items }), {
|
|
340
|
+
status: 200,
|
|
341
|
+
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'public, max-age=30' },
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// The customMetadata keys the CLI sets automatically (provenance + label) —
|
|
346
|
+
// never a real \`--meta key=value\` entry (the CLI rejects a colliding key
|
|
347
|
+
// before it ever reaches this Worker; see RESERVED_META_KEYS in publish.ts).
|
|
348
|
+
// One list, reused both to strip a same-named --meta collision on write and
|
|
349
|
+
// to split arbitrary --meta entries back out on read.
|
|
350
|
+
var RESERVED_METADATA_KEYS = ['expires-at', 'visibility', 'agent', 'session', 'host', 'repo', 'date', 'label', 'label-source'];
|
|
351
|
+
|
|
352
|
+
// Everything in customMetadata that ISN'T one of the reserved provenance/label
|
|
353
|
+
// keys above — i.e. the caller's own \`--meta key=value\` entries. Surfaced on
|
|
354
|
+
// every read route (listing, revisions) so a value stored with \`--meta
|
|
355
|
+
// kind=plan --meta ticket=RUSH-2683\` is actually visible again, not just
|
|
356
|
+
// write-only (RUSH-2683 review fix).
|
|
357
|
+
function extraMetaOf(customMetadata) {
|
|
358
|
+
var out = {};
|
|
359
|
+
if (!customMetadata) return out;
|
|
360
|
+
for (var k in customMetadata) {
|
|
361
|
+
if (RESERVED_METADATA_KEYS.indexOf(k) === -1) out[k] = customMetadata[k];
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
|
|
203
366
|
function escapeHtml(s) {
|
|
204
367
|
return String(s)
|
|
205
368
|
.replace(/&/g, '&')
|
package/dist/lib/shims.js
CHANGED
|
@@ -504,6 +504,32 @@ parse_agents_default() {
|
|
|
504
504
|
' "$meta"
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
# Parse the pins JSON (~/.agents/.history/devices/pins-<machine>.json) for this
|
|
508
|
+
# AGENT's pinned version. The file is JSON.stringify(…, 2) output, so the
|
|
509
|
+
# "agents" map sits at 2-space indent and its entries at 4 — a stable shape the
|
|
510
|
+
# awk below scrapes without a JSON parser (shims must stay dependency-free).
|
|
511
|
+
parse_pins_default() {
|
|
512
|
+
local pins="$1"
|
|
513
|
+
[ -f "$pins" ] || return 0
|
|
514
|
+
# Scope carefully: enter ONLY on a line that opens the agents block
|
|
515
|
+
# ( "agents": {) — an inline-empty map ("agents": {}) must NOT enter,
|
|
516
|
+
# or the needle would leak into a following "isolatedAgents" block and an
|
|
517
|
+
# isolated pin would masquerade as the global default. Exit at the block's
|
|
518
|
+
# closing brace (a trailing comma is fine). Entries sit at exactly 4 spaces.
|
|
519
|
+
awk -v agent="$AGENT" '
|
|
520
|
+
/^ "agents": [{]$/ { in_agents=1; next }
|
|
521
|
+
in_agents && /^ }/ { exit }
|
|
522
|
+
in_agents && /^ "/ {
|
|
523
|
+
needle = "\\"" agent "\\":"
|
|
524
|
+
if (index($0, needle) > 0) {
|
|
525
|
+
line = substr($0, index($0, needle) + length(needle))
|
|
526
|
+
gsub(/[[:space:],"]/, "", line)
|
|
527
|
+
print line; exit
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
' "$pins"
|
|
531
|
+
}
|
|
532
|
+
|
|
507
533
|
# This machine's device id — mirrors machineId()/normalizeHost() in
|
|
508
534
|
# src/lib/machine-id.ts: first hostname label, lowercased, non-[a-z0-9_-] -> '-'.
|
|
509
535
|
# MUST stay in sync or the shim reads the wrong device folder.
|
|
@@ -514,15 +540,17 @@ machine_id() {
|
|
|
514
540
|
[ -n "$raw" ] && printf '%s' "$raw" || printf 'unknown'
|
|
515
541
|
}
|
|
516
542
|
|
|
517
|
-
# Resolve the default version. The agents: version pins are
|
|
518
|
-
# devices
|
|
519
|
-
#
|
|
520
|
-
#
|
|
521
|
-
#
|
|
522
|
-
#
|
|
543
|
+
# Resolve the default version. The agents: version pins are MACHINE-LOCAL runtime
|
|
544
|
+
# state at .history/devices/pins-<machine>.json (untracked — auto-written pins in
|
|
545
|
+
# the tracked device doc caused commit churn); read that first, then fall back to
|
|
546
|
+
# the tracked device doc (installs not yet migrated) and finally the central
|
|
547
|
+
# agents.yaml (pre-split installs). Must match readMeta()'s central+pins merge in
|
|
548
|
+
# state.ts -- reading only the central file (the old behavior) missed every device
|
|
549
|
+
# pin and made the shim re-prompt "no default set" on every launch.
|
|
523
550
|
resolve_default_version() {
|
|
524
551
|
local v
|
|
525
|
-
v=$(
|
|
552
|
+
v=$(parse_pins_default "$AGENTS_USER_DIR/.history/devices/pins-$(machine_id).json")
|
|
553
|
+
[ -n "$v" ] || v=$(parse_agents_default "$AGENTS_USER_DIR/devices/$(machine_id)/agents.yaml")
|
|
526
554
|
[ -n "$v" ] || v=$(parse_agents_default "$AGENTS_USER_DIR/agents.yaml")
|
|
527
555
|
printf '%s' "$v"
|
|
528
556
|
}
|