@phnx-labs/agents-cli 1.22.46 → 1.22.49
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 +663 -0
- package/LICENSE +102 -182
- package/README.md +96 -49
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +243 -25
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +711 -62
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +26 -24
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +107 -53
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +48 -5
- package/dist/commands/go.d.ts +3 -0
- package/dist/commands/go.js +16 -3
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +7 -6
- package/dist/commands/mailboxes.js +4 -3
- package/dist/commands/menubar.js +11 -4
- package/dist/commands/projects.d.ts +16 -1
- package/dist/commands/projects.js +58 -64
- package/dist/commands/resume.js +1 -1
- package/dist/commands/route.js +4 -4
- package/dist/commands/routines.js +4 -3
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +5 -0
- package/dist/commands/setup-secrets.js +23 -0
- package/dist/commands/share.d.ts +20 -5
- package/dist/commands/share.js +152 -52
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +243 -18
- package/dist/commands/teams.js +34 -2
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +32 -62
- package/dist/commands/watchdog.js +9 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +84 -6
- package/dist/lib/accounting/usage.js +293 -75
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +25 -15
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +40 -9
- package/dist/lib/browser/profiles.d.ts +104 -65
- package/dist/lib/browser/profiles.js +277 -165
- package/dist/lib/browser/registry.d.ts +39 -0
- package/dist/lib/browser/registry.js +149 -0
- package/dist/lib/browser/remote-control.d.ts +28 -2
- package/dist/lib/browser/remote-control.js +35 -3
- package/dist/lib/browser/resolve-target.d.ts +68 -0
- package/dist/lib/browser/resolve-target.js +250 -0
- package/dist/lib/browser/runtime-state.d.ts +44 -11
- package/dist/lib/browser/runtime-state.js +98 -15
- package/dist/lib/browser/service.d.ts +65 -9
- package/dist/lib/browser/service.js +196 -61
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/byok-usage.js +2 -2
- package/dist/lib/claude-statusline.d.ts +29 -0
- package/dist/lib/claude-statusline.js +139 -0
- package/dist/lib/cloud/host.d.ts +1 -1
- package/dist/lib/cloud/host.js +1 -1
- package/dist/lib/cloud/rush.js +1 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/computer/computer-rpc.js +2 -2
- package/dist/lib/computer/download.d.ts +15 -15
- package/dist/lib/computer/download.js +35 -98
- package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
- package/dist/lib/computer/ssh-tunnel.js +12 -22
- package/dist/lib/config-keys.d.ts +6 -11
- package/dist/lib/config-keys.js +18 -19
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +2 -2
- package/dist/lib/crabbox/lease.d.ts +9 -0
- package/dist/lib/crabbox/lease.js +70 -15
- package/dist/lib/crabbox/runtimes.js +1 -1
- package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
- package/dist/lib/daemon/account-state-daemon-service.js +26 -0
- package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
- package/dist/lib/daemon/browser-ipc-service.js +46 -0
- package/dist/lib/daemon/daemon.d.ts +3 -0
- package/dist/lib/daemon/daemon.js +212 -444
- package/dist/lib/daemon/device-probe-service.d.ts +26 -0
- package/dist/lib/daemon/device-probe-service.js +47 -0
- package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
- package/dist/lib/daemon/keychain-reap-service.js +32 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
- package/dist/lib/daemon/monitor-engine-service.js +29 -0
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
- package/dist/lib/daemon/secrets-broker-service.js +66 -0
- package/dist/lib/daemon/self-heal-service.d.ts +27 -0
- package/dist/lib/daemon/self-heal-service.js +48 -0
- package/dist/lib/daemon/service.d.ts +102 -0
- package/dist/lib/daemon/service.js +98 -0
- package/dist/lib/daemon/session-index-service.d.ts +22 -0
- package/dist/lib/daemon/session-index-service.js +40 -0
- package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
- package/dist/lib/daemon/state-dir-check-service.js +63 -0
- package/dist/lib/daemon/supervisor.d.ts +87 -0
- package/dist/lib/daemon/supervisor.js +298 -0
- package/dist/lib/daemon/watchdog-service.d.ts +19 -0
- package/dist/lib/daemon/watchdog-service.js +40 -0
- package/dist/lib/daemon-health.d.ts +15 -0
- package/dist/lib/daemon-health.js +29 -5
- package/dist/lib/daemon-services.d.ts +8 -1
- package/dist/lib/daemon-services.js +55 -0
- package/dist/lib/daemon-ticks.d.ts +13 -2
- package/dist/lib/daemon-ticks.js +22 -20
- package/dist/lib/device-config.d.ts +25 -10
- package/dist/lib/device-config.js +90 -39
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +26 -0
- package/dist/lib/devices/stats-cache.js +59 -7
- package/dist/lib/devices/worker-pick.d.ts +48 -0
- package/dist/lib/devices/worker-pick.js +113 -0
- package/dist/lib/exec.d.ts +70 -14
- package/dist/lib/exec.js +110 -38
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/harness/adapters/cursor.js +8 -11
- package/dist/lib/helper-download.d.ts +108 -0
- package/dist/lib/helper-download.js +194 -0
- package/dist/lib/helper-versions.d.ts +52 -0
- package/dist/lib/helper-versions.js +58 -0
- package/dist/lib/hooks/cache.js +19 -0
- package/dist/lib/hooks/install.js +2 -2
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/credentials.d.ts +1 -1
- package/dist/lib/hosts/credentials.js +1 -1
- package/dist/lib/hosts/dispatch.d.ts +7 -1
- package/dist/lib/hosts/dispatch.js +24 -5
- package/dist/lib/hosts/passthrough.d.ts +8 -15
- package/dist/lib/hosts/passthrough.js +35 -16
- package/dist/lib/hosts/providers/devices.d.ts +1 -1
- package/dist/lib/hosts/providers/devices.js +1 -1
- package/dist/lib/hosts/reconnect.d.ts +179 -21
- package/dist/lib/hosts/reconnect.js +266 -45
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/installations/migrate.d.ts +4 -5
- package/dist/lib/installations/migrate.js +6 -7
- package/dist/lib/installations/shims.d.ts +1 -1
- package/dist/lib/installations/shims.js +23 -13
- package/dist/lib/installations/versions.js +25 -2
- package/dist/lib/linear-autoclose.d.ts +1 -1
- package/dist/lib/linear-autoclose.js +1 -1
- package/dist/lib/loop.d.ts +2 -2
- package/dist/lib/loop.js +1 -1
- package/dist/lib/menubar/download-menubar.d.ts +47 -0
- package/dist/lib/menubar/download-menubar.js +60 -0
- package/dist/lib/menubar/install-menubar.d.ts +44 -7
- package/dist/lib/menubar/install-menubar.js +105 -23
- package/dist/lib/menubar/notify-desktop.d.ts +3 -3
- package/dist/lib/menubar/notify-desktop.js +6 -6
- package/dist/lib/model-tiers.d.ts +1 -1
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/project-probe.d.ts +1 -1
- package/dist/lib/project-probe.js +1 -1
- package/dist/lib/project-pull.d.ts +1 -1
- package/dist/lib/project-pull.js +2 -2
- package/dist/lib/projects.d.ts +1 -1
- package/dist/lib/projects.js +1 -1
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- package/dist/lib/sandbox.js +6 -7
- package/dist/lib/secrets/download-keychain.d.ts +47 -0
- package/dist/lib/secrets/download-keychain.js +70 -0
- package/dist/lib/secrets/install-helper.d.ts +20 -1
- package/dist/lib/secrets/install-helper.js +44 -3
- package/dist/lib/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +86 -2
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +142 -8
- package/dist/lib/session/discover.d.ts +23 -1
- package/dist/lib/session/discover.js +44 -10
- package/dist/lib/session/highlights.d.ts +49 -1
- package/dist/lib/session/highlights.js +95 -0
- package/dist/lib/session/host-link.d.ts +16 -2
- package/dist/lib/session/host-link.js +7 -1
- package/dist/lib/session/parse.js +33 -1
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +17 -3
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +93 -1
- package/dist/lib/session/state.js +1 -1
- package/dist/lib/session/tool-calls.d.ts +2 -0
- package/dist/lib/session/tool-calls.js +1 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +19 -0
- package/dist/lib/session/trajectory-html.js +770 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +284 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +384 -0
- package/dist/lib/session/types.d.ts +15 -0
- package/dist/lib/sha256-asset.d.ts +26 -0
- package/dist/lib/sha256-asset.js +41 -0
- package/dist/lib/share/backend.d.ts +95 -0
- package/dist/lib/share/backend.js +139 -0
- package/dist/lib/share/delete.d.ts +6 -2
- package/dist/lib/share/delete.js +8 -9
- package/dist/lib/share/html.d.ts +22 -0
- package/dist/lib/share/html.js +88 -0
- package/dist/lib/share/provision.d.ts +18 -4
- package/dist/lib/share/provision.js +38 -4
- package/dist/lib/share/publish.d.ts +39 -15
- package/dist/lib/share/publish.js +55 -26
- package/dist/lib/share/worker-template.js +171 -11
- package/dist/lib/sqlite.js +16 -3
- package/dist/lib/ssh-exec.d.ts +65 -0
- package/dist/lib/ssh-exec.js +120 -4
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +6 -3
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +2 -2
- package/dist/lib/state.js +11 -5
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/index.d.ts +1 -1
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/terminal/types.js +1 -1
- package/dist/lib/tmux/orphan-reap.js +1 -1
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +204 -27
- package/dist/lib/traces/backend.d.ts +29 -0
- package/dist/lib/traces/backend.js +46 -0
- package/dist/lib/traces/classify.d.ts +52 -0
- package/dist/lib/traces/classify.js +72 -0
- package/dist/lib/traces/config.d.ts +10 -0
- package/dist/lib/traces/config.js +2 -0
- package/dist/lib/traces/provision.d.ts +19 -0
- package/dist/lib/traces/provision.js +30 -0
- package/dist/lib/traces/sync.d.ts +150 -0
- package/dist/lib/traces/sync.js +447 -0
- package/dist/lib/traces/worker-template.d.ts +4 -0
- package/dist/lib/traces/worker-template.js +159 -0
- package/dist/lib/types.d.ts +34 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +18 -4
- package/dist/lib/usage-refresh.js +68 -19
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +5 -7
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
- package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
- package/dist/lib/usage-fleet.d.ts +0 -32
- package/dist/lib/usage-fleet.js +0 -125
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
2
3
|
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, updateMeta, } from '../state.js';
|
|
3
4
|
import { getConfigValue } from '../device-config.js';
|
|
5
|
+
import { machineId } from '../machine-id.js';
|
|
6
|
+
import { declaringDevices, profileRegistry, } from './registry.js';
|
|
4
7
|
import { findBrowserPath, findFirstInstalledBrowser, isPortInUse } from './chrome.js';
|
|
5
8
|
import { DEFAULT_VIEWPORT } from './devices.js';
|
|
9
|
+
export { declaringDevices, profileKind, profileRegistry, } from './registry.js';
|
|
6
10
|
/**
|
|
7
11
|
* Name of the profile `ensureDefaultBrowserProfile` auto-detects and pins.
|
|
8
12
|
*
|
|
@@ -28,25 +32,6 @@ export const LEGACY_DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
|
28
32
|
* wins, so no existing config breaks.
|
|
29
33
|
*/
|
|
30
34
|
export const DEFAULT_PROFILE_ALIAS = 'default';
|
|
31
|
-
/**
|
|
32
|
-
* True for a profile whose contents only make sense on THIS machine, so it must
|
|
33
|
-
* live in the per-machine doc rather than the fleet-synced agents.yaml.
|
|
34
|
-
*
|
|
35
|
-
* The auto-detected `default` always qualifies: it is generated by probing local
|
|
36
|
-
* browser installs and a free local port, so its `binary:` is an absolute path
|
|
37
|
-
* for one OS. Shared fleet-wide it caused a permanent rewrite loop — a macOS box
|
|
38
|
-
* wrote `/Applications/...`, a Linux box rewrote `/usr/bin/chromium-browser`,
|
|
39
|
-
* each on every `agents browser start`.
|
|
40
|
-
*
|
|
41
|
-
* As of RUSH-2716 it is not the only one: EVERY newly created profile is
|
|
42
|
-
* machine-local unless the caller explicitly asks for the fleet-synced store
|
|
43
|
-
* (`createProfile(p, { fleet: true })` / `--fleet`). See {@link createProfile}.
|
|
44
|
-
* This predicate is what keeps an existing `default` that an older version wrote
|
|
45
|
-
* into central from being written back there by {@link updateProfile}.
|
|
46
|
-
*/
|
|
47
|
-
function isMachineLocalProfile(name) {
|
|
48
|
-
return name === DEFAULT_BROWSER_PROFILE_NAME || name === LEGACY_DEFAULT_BROWSER_PROFILE_NAME;
|
|
49
|
-
}
|
|
50
35
|
/**
|
|
51
36
|
* The device-local configured default profile name (set via
|
|
52
37
|
* `agents browser use`), or undefined when unset. When set, it
|
|
@@ -81,7 +66,7 @@ export function getProfileDownloadsDir(name) {
|
|
|
81
66
|
export function getProfileSessionsDir(name, task) {
|
|
82
67
|
return path.join(getProfileRuntimeDir(name), 'sessions', task);
|
|
83
68
|
}
|
|
84
|
-
function configToProfile(name, config) {
|
|
69
|
+
function configToProfile(name, config, devices = []) {
|
|
85
70
|
validateRemoteBrowserBinaries(config);
|
|
86
71
|
return {
|
|
87
72
|
name,
|
|
@@ -97,6 +82,7 @@ function configToProfile(name, config) {
|
|
|
97
82
|
viewport: config.viewport,
|
|
98
83
|
logDir: config.logDir,
|
|
99
84
|
logHost: config.logHost,
|
|
85
|
+
devices,
|
|
100
86
|
};
|
|
101
87
|
}
|
|
102
88
|
function profileToConfig(profile) {
|
|
@@ -127,38 +113,24 @@ function profileToConfig(profile) {
|
|
|
127
113
|
config.logHost = profile.logHost;
|
|
128
114
|
return config;
|
|
129
115
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* machine's own. A machine-local entry WINS on a name collision — it was written
|
|
133
|
-
* by this box for this box, whereas the shared copy may carry another OS's
|
|
134
|
-
* binary path (that mismatch is exactly why `default` moved local).
|
|
135
|
-
*/
|
|
136
|
-
function allProfileConfigs() {
|
|
137
|
-
const meta = readMeta();
|
|
138
|
-
return { ...meta.browser, ...meta.deviceBrowser };
|
|
116
|
+
function selectedDeclaration(declarations) {
|
|
117
|
+
return declarations.find((declaration) => declaration.device === machineId()) ?? declarations[0];
|
|
139
118
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return Object.entries(configs).map(([name, config]) => configToProfile(name, config));
|
|
119
|
+
function localDeclaration(name) {
|
|
120
|
+
return profileRegistry().get(name)?.find((declaration) => declaration.device === machineId());
|
|
143
121
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
export async function listProfilesWithScope() {
|
|
150
|
-
const meta = readMeta();
|
|
151
|
-
const local = meta.deviceBrowser ?? {};
|
|
152
|
-
return Object.entries(allProfileConfigs()).map(([name, config]) => ({
|
|
153
|
-
profile: configToProfile(name, config),
|
|
154
|
-
scope: (local[name] ? 'local' : 'fleet'),
|
|
155
|
-
}));
|
|
122
|
+
export async function listProfiles() {
|
|
123
|
+
return [...profileRegistry()].map(([name, declarations]) => {
|
|
124
|
+
const selected = selectedDeclaration(declarations);
|
|
125
|
+
return configToProfile(name, selected.config, declarations.map((declaration) => declaration.device));
|
|
126
|
+
});
|
|
156
127
|
}
|
|
157
128
|
export async function getProfile(name) {
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
129
|
+
const declarations = profileRegistry().get(name);
|
|
130
|
+
if (!declarations?.length)
|
|
160
131
|
return null;
|
|
161
|
-
|
|
132
|
+
const selected = selectedDeclaration(declarations);
|
|
133
|
+
return configToProfile(name, selected.config, declarations.map((declaration) => declaration.device));
|
|
162
134
|
}
|
|
163
135
|
/**
|
|
164
136
|
* True when `profile` can actually launch or attach on THIS machine.
|
|
@@ -182,26 +154,6 @@ export function isProfileLaunchableHere(profile) {
|
|
|
182
154
|
return false;
|
|
183
155
|
}
|
|
184
156
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Resolve the profile `agents browser start` uses when no `--profile` is given.
|
|
187
|
-
*
|
|
188
|
-
* Order: (1) the device-local configured default (`agents browser profiles
|
|
189
|
-
* set-default <name>`) when it names an existing profile that can launch here;
|
|
190
|
-
* (2) an existing `default` profile that can launch here; (3) auto-pick the
|
|
191
|
-
* first installed Chromium-family browser per the platform priority list in
|
|
192
|
-
* chrome.ts (macOS: chrome > brave > edge > chromium > comet > arc; Linux: chrome >
|
|
193
|
-
* chromium > brave > edge; Windows: edge > chrome > brave > comet) and pin a
|
|
194
|
-
* `default` profile to it. Throws an actionable error if none of those binaries
|
|
195
|
-
* are installed.
|
|
196
|
-
*
|
|
197
|
-
* "Can launch here" matters because `agents.yaml` syncs across the fleet: a
|
|
198
|
-
* `default` auto-created on macOS carries a `/Applications/...` binary path that
|
|
199
|
-
* doesn't exist on a Linux box. Rather than hand that broken profile back (the
|
|
200
|
-
* top browser roadblock — "Custom binary not found"), we re-detect and
|
|
201
|
-
* regenerate the `default` for this machine. A configured or existing default
|
|
202
|
-
* that no longer exists, or can't launch here, warns and falls through — never a
|
|
203
|
-
* hard fail.
|
|
204
|
-
*/
|
|
205
157
|
/**
|
|
206
158
|
* The auto-detected profile this machine already carries, under either name —
|
|
207
159
|
* the current `auto-chrome` or the `default` an older build wrote. Callers that
|
|
@@ -209,8 +161,14 @@ export function isProfileLaunchableHere(profile) {
|
|
|
209
161
|
* name, or a machine that predates the RUSH-2709 rename grows a second one.
|
|
210
162
|
*/
|
|
211
163
|
export async function getAutoDetectedProfile() {
|
|
212
|
-
|
|
213
|
-
|
|
164
|
+
const current = machineId();
|
|
165
|
+
const registry = profileRegistry();
|
|
166
|
+
for (const name of [DEFAULT_BROWSER_PROFILE_NAME, LEGACY_DEFAULT_BROWSER_PROFILE_NAME]) {
|
|
167
|
+
const declaration = registry.get(name)?.find((entry) => entry.device === current);
|
|
168
|
+
if (declaration)
|
|
169
|
+
return configToProfile(name, declaration.config, [current]);
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
214
172
|
}
|
|
215
173
|
/**
|
|
216
174
|
* Resolve what a caller typed after `--profile` to a real, BARE profile name.
|
|
@@ -261,15 +219,16 @@ export async function resolveProfileRef(ref) {
|
|
|
261
219
|
* `start`'s resolver. Same order as {@link resolveProfileRef} for an explicit
|
|
262
220
|
* name, but the IMPLICIT path (no `--profile`, or the bare `default` alias with
|
|
263
221
|
* no profile of that name) goes through {@link ensureDefaultBrowserProfile} —
|
|
264
|
-
* which additionally verifies the resolved default can launch on THIS machine
|
|
265
|
-
*
|
|
222
|
+
* which additionally verifies the resolved default can launch on THIS machine.
|
|
223
|
+
* An undeclared configured default is an error. A declared default whose
|
|
224
|
+
* browser isn't installed here warns and falls through to auto-detect.
|
|
266
225
|
*
|
|
267
226
|
* `start` is the only command that launches a browser, so it is the only one
|
|
268
227
|
* that may do those things; routing a filter-only command through this would
|
|
269
228
|
* warn about, and rewrite, config the user never asked it to touch.
|
|
270
229
|
*
|
|
271
|
-
* Throws when
|
|
272
|
-
*
|
|
230
|
+
* Throws when the configured default is undeclared, or when no profile exists
|
|
231
|
+
* and no supported browser is installed.
|
|
273
232
|
*/
|
|
274
233
|
export async function resolveProfileRefForStart(ref) {
|
|
275
234
|
if (ref && ref !== DEFAULT_PROFILE_ALIAS)
|
|
@@ -279,25 +238,51 @@ export async function resolveProfileRefForStart(ref) {
|
|
|
279
238
|
return ref;
|
|
280
239
|
return (await ensureDefaultBrowserProfile()).name;
|
|
281
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Resolve the profile a bare `agents browser start` uses.
|
|
243
|
+
*
|
|
244
|
+
* Order: (1) the device-local configured default (`agents browser use <name>`)
|
|
245
|
+
* when it names a profile that exists and can launch here; (2) an existing
|
|
246
|
+
* auto-detected profile that can launch here; (3) auto-pick the first installed
|
|
247
|
+
* Chromium-family browser and pin `auto-chrome` (or repair a stale legacy
|
|
248
|
+
* `default`) to it.
|
|
249
|
+
*
|
|
250
|
+
* Two failure modes at the configured-default step are not the same:
|
|
251
|
+
* - No device declares the name (including a leftover central `browser:`
|
|
252
|
+
* entry that was never claimed) → throw. Auto-creating `auto-chrome` would
|
|
253
|
+
* hand the agent a logged-out browser while `browser.profile` still names
|
|
254
|
+
* the credentialed one.
|
|
255
|
+
* - The name is declared, but its browser/binary is not installed HERE →
|
|
256
|
+
* warn and fall through. That is a missing binary on this box, not a
|
|
257
|
+
* missing identity; auto-detect is the existing repair.
|
|
258
|
+
*/
|
|
282
259
|
export async function ensureDefaultBrowserProfile() {
|
|
283
260
|
const configured = getConfiguredDefaultProfileName();
|
|
284
261
|
if (configured && configured !== DEFAULT_PROFILE_ALIAS) {
|
|
285
262
|
const chosen = await getProfile(configured);
|
|
286
263
|
if (chosen && isProfileLaunchableHere(chosen))
|
|
287
264
|
return chosen;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
`
|
|
292
|
-
|
|
293
|
-
|
|
265
|
+
if (!chosen) {
|
|
266
|
+
const central = readMeta().browser ?? {};
|
|
267
|
+
if (central[configured]) {
|
|
268
|
+
throw new Error(`configured default browser profile "${configured}" is not declared by any device. ` +
|
|
269
|
+
`It still lives in the central agents.yaml browser: map. ` +
|
|
270
|
+
`Claim it on the machine that hosts that browser with: agents browser profiles claim ${configured}`);
|
|
271
|
+
}
|
|
272
|
+
throw new Error(`configured default browser profile "${configured}" is not declared by any device. ` +
|
|
273
|
+
`Create it with: agents browser profiles create ${configured} --browser <chrome|comet|chromium|brave|edge|arc|custom> ` +
|
|
274
|
+
`(or, if it is a leftover central profile, claim it with: agents browser profiles claim ${configured}). ` +
|
|
275
|
+
`Or unset the default with: agents browser use --unset`);
|
|
276
|
+
}
|
|
277
|
+
console.warn(`warning: configured default browser profile "${configured}" can't launch on this ` +
|
|
278
|
+
`machine (its browser/binary isn't installed here); falling back to auto-detect. ` +
|
|
279
|
+
`Fix with: agents browser use <name> (or --unset)`);
|
|
294
280
|
}
|
|
295
281
|
// Prefer whichever auto-detected profile this machine already carries: the
|
|
296
282
|
// current `auto-chrome`, else the `default` an older build wrote. Reusing the
|
|
297
283
|
// legacy one is what keeps a currently-running browser (and its runtime dirs,
|
|
298
284
|
// keyed `default@endpoint-0`) attached after the rename.
|
|
299
|
-
const existing =
|
|
300
|
-
(await getProfile(LEGACY_DEFAULT_BROWSER_PROFILE_NAME));
|
|
285
|
+
const existing = await getAutoDetectedProfile();
|
|
301
286
|
if (existing && isProfileLaunchableHere(existing))
|
|
302
287
|
return existing;
|
|
303
288
|
const detected = findFirstInstalledBrowser();
|
|
@@ -420,45 +405,40 @@ function hasSshEndpoint(endpoints) {
|
|
|
420
405
|
});
|
|
421
406
|
}
|
|
422
407
|
/**
|
|
423
|
-
*
|
|
424
|
-
* `{ fleet: true }` (the CLI's `--fleet`) to put it in the synced `agents.yaml`
|
|
425
|
-
* where every box will see it.
|
|
408
|
+
* Refuse a profile whose LOCAL port another profile already owns.
|
|
426
409
|
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* every machine as junk that nothing cleaned up (`agents browser profiles prune`
|
|
432
|
-
* is the other half of that fix). Opting in with `--fleet` is for a profile that
|
|
433
|
-
* really is fleet config — a remote `ssh://` endpoint, or a named profile whose
|
|
434
|
-
* shape you want on every box.
|
|
410
|
+
* Every CDP/SSH profile ends up listening on (or tunneling to) the same LOCAL
|
|
411
|
+
* port number as the one configured in the endpoint URL — SSH profiles reuse
|
|
412
|
+
* `?port=N` locally, so we no longer scope by host. Two profiles that would
|
|
413
|
+
* need the same local port can't both run at the same time.
|
|
435
414
|
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
415
|
+
* `opts.ignore` is the profile's own name on an edit. Without it every edit
|
|
416
|
+
* collides with itself, since the stored copy still owns the port being kept.
|
|
438
417
|
*/
|
|
439
|
-
export
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const existingLocal = effectiveLocalPort(existingProfile);
|
|
455
|
-
if (existingLocal === newLocal) {
|
|
456
|
-
throw new Error(`Local port ${newLocal} is already used by profile "${existingName}". ` +
|
|
457
|
-
`Each profile must own a unique local port (SSH tunnels now bind ` +
|
|
458
|
-
`to their configured port locally too). Pick a different port.`);
|
|
459
|
-
}
|
|
418
|
+
export function assertLocalPortFree(profile, opts = {}) {
|
|
419
|
+
const wanted = effectiveLocalPort(profile);
|
|
420
|
+
if (wanted === undefined)
|
|
421
|
+
return;
|
|
422
|
+
for (const [existingName, declarations] of profileRegistry()) {
|
|
423
|
+
if (existingName === opts.ignore)
|
|
424
|
+
continue;
|
|
425
|
+
const declaration = declarations.find((entry) => entry.device === machineId());
|
|
426
|
+
if (!declaration)
|
|
427
|
+
continue;
|
|
428
|
+
const existingProfile = configToProfile(existingName, declaration.config, [machineId()]);
|
|
429
|
+
if (effectiveLocalPort(existingProfile) === wanted) {
|
|
430
|
+
throw new Error(`Local port ${wanted} is already used by profile "${existingName}". ` +
|
|
431
|
+
`Each profile must own a unique local port (SSH tunnels now bind ` +
|
|
432
|
+
`to their configured port locally too). Pick a different port.`);
|
|
460
433
|
}
|
|
461
434
|
}
|
|
435
|
+
}
|
|
436
|
+
/** Declare a profile on this device. The same name may be declared by peers. */
|
|
437
|
+
export async function createProfile(profile) {
|
|
438
|
+
if (localDeclaration(profile.name)) {
|
|
439
|
+
throw new Error(`Profile "${profile.name}" already exists on ${machineId()}`);
|
|
440
|
+
}
|
|
441
|
+
assertLocalPortFree(profile);
|
|
462
442
|
// Resolve the browser binary at create time. Fails fast with an actionable
|
|
463
443
|
// error ("Comet not installed at /Applications/Comet.app") rather than
|
|
464
444
|
// deferring the failure to the first task. `findBrowserPath` short-circuits
|
|
@@ -471,46 +451,188 @@ export async function createProfile(profile, opts = {}) {
|
|
|
471
451
|
findBrowserPath(profile.browser, profile.binary);
|
|
472
452
|
}
|
|
473
453
|
const config = profileToConfig(profile);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (!opts.fleet || isMachineLocalProfile(profile.name)) {
|
|
479
|
-
updateMeta((m) => ({ ...m, deviceBrowser: { ...m.deviceBrowser, [profile.name]: config } }));
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
updateMeta((m) => ({ ...m, browser: { ...m.browser, [profile.name]: config } }));
|
|
454
|
+
updateMeta((meta) => ({
|
|
455
|
+
...meta,
|
|
456
|
+
deviceBrowser: { ...meta.deviceBrowser, [profile.name]: config },
|
|
457
|
+
}));
|
|
483
458
|
}
|
|
484
459
|
export async function updateProfile(profile) {
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
460
|
+
const local = localDeclaration(profile.name);
|
|
461
|
+
if (!local) {
|
|
462
|
+
const devices = declaringDevices(profile.name);
|
|
463
|
+
const where = devices.length > 0 ? `; declared on ${devices.join(', ')}` : '';
|
|
464
|
+
throw new Error(`Profile "${profile.name}" is not declared on ${machineId()}${where}`);
|
|
489
465
|
}
|
|
490
466
|
const config = profileToConfig(profile);
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
467
|
+
updateMeta((meta) => ({
|
|
468
|
+
...meta,
|
|
469
|
+
deviceBrowser: { ...meta.deviceBrowser, [profile.name]: config },
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Merge `patch` onto the stored profile and persist it in the store it already
|
|
474
|
+
* lives in (see {@link updateProfile} for the scope rules).
|
|
475
|
+
*
|
|
476
|
+
* Runs {@link createProfile}'s validations against the MERGED record, not just
|
|
477
|
+
* the patched fields — a binary edit re-resolves the browser path, a
|
|
478
|
+
* targetFilter edit re-checks the electron gate, and the local-port scan runs
|
|
479
|
+
* with this profile excluded so an unchanged port is not a self-collision.
|
|
480
|
+
*/
|
|
481
|
+
export async function editProfile(name, patch) {
|
|
482
|
+
const local = localDeclaration(name);
|
|
483
|
+
if (!local) {
|
|
484
|
+
const devices = declaringDevices(name);
|
|
485
|
+
const where = devices.length > 0 ? `; declared on ${devices.join(', ')}` : '';
|
|
486
|
+
throw new Error(`Profile "${name}" is not declared on ${machineId()}${where}`);
|
|
487
|
+
}
|
|
488
|
+
const devices = declaringDevices(name);
|
|
489
|
+
const current = configToProfile(name, local.config, devices);
|
|
490
|
+
const merged = { ...current, ...patch, name };
|
|
491
|
+
const changed = Object.keys(patch).filter((k) => JSON.stringify(current[k]) !== JSON.stringify(merged[k]));
|
|
492
|
+
// A target filter only means anything for an Electron app, and the gate must
|
|
493
|
+
// read the MERGED record: `--target-filter x` on a profile that is already
|
|
494
|
+
// electron is valid, and `--no-electron` on one that still carries a filter is
|
|
495
|
+
// not. Checking the patch alone would accept both.
|
|
496
|
+
if (merged.targetFilter && !merged.electron) {
|
|
497
|
+
throw new Error(`--target-filter only applies to an Electron profile. ` +
|
|
498
|
+
`Pass --electron, or clear the filter with --target-filter ''.`);
|
|
499
|
+
}
|
|
500
|
+
assertLocalPortFree(merged, { ignore: name });
|
|
501
|
+
// Same rule as create: the binary lives on the remote for an SSH profile, so a
|
|
502
|
+
// local lookup would validate the wrong machine.
|
|
503
|
+
if (!hasSshEndpoint(merged.endpoints)) {
|
|
504
|
+
findBrowserPath(merged.browser, merged.binary);
|
|
505
|
+
}
|
|
506
|
+
await updateProfile(merged);
|
|
507
|
+
return {
|
|
508
|
+
profile: merged,
|
|
509
|
+
devices,
|
|
510
|
+
changed,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/** Profile names an agent has to be able to type and an fs path can hold. */
|
|
514
|
+
const PROFILE_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
515
|
+
/**
|
|
516
|
+
* Throw if `name` cannot be used for a NEW profile.
|
|
517
|
+
*
|
|
518
|
+
* Shared by `profiles create` and {@link renameProfile} so the two cannot drift
|
|
519
|
+
* — the shape rule used to live inline in the command, which is how `rename`
|
|
520
|
+
* would have accepted a name `create` rejects.
|
|
521
|
+
*
|
|
522
|
+
* `default` is refused because it is the reserved ALIAS meaning "whatever
|
|
523
|
+
* profile this machine is configured to use" (RUSH-2709). A literal profile by
|
|
524
|
+
* that name makes `--profile default` mean two different things.
|
|
525
|
+
*/
|
|
526
|
+
export function assertRegistrableProfileName(name) {
|
|
527
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
528
|
+
throw new Error(`Invalid profile name ${JSON.stringify(name)}. Use lowercase letters, digits and hyphens, ` +
|
|
529
|
+
`starting with a letter — e.g. 'agents'.`);
|
|
530
|
+
}
|
|
531
|
+
if (name === DEFAULT_PROFILE_ALIAS) {
|
|
532
|
+
throw new Error(`"${DEFAULT_PROFILE_ALIAS}" is the reserved alias for this machine's configured profile, ` +
|
|
533
|
+
`not a profile name. Pick another name.`);
|
|
534
|
+
}
|
|
535
|
+
// `browser.viewer: os` means "use the OS default handler". A profile by that
|
|
536
|
+
// name turns the opt-out into a pointer at a profile — silently, since both
|
|
537
|
+
// are just strings in the same key.
|
|
538
|
+
if (name === 'os') {
|
|
539
|
+
throw new Error(`"os" is the reserved browser.viewer value meaning the OS default handler, ` +
|
|
540
|
+
`not a profile name. Pick another name.`);
|
|
496
541
|
}
|
|
497
|
-
updateMeta((m) => ({ ...m, browser: { ...m.browser, [profile.name]: config } }));
|
|
498
542
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
543
|
+
/**
|
|
544
|
+
* Rename a profile, taking its on-disk state with it.
|
|
545
|
+
*
|
|
546
|
+
* `editProfile` deliberately refuses a name change, and this is why: the name
|
|
547
|
+
* keys the runtime dir ({@link getProfileRuntimeDir}), every fork/endpoint dir
|
|
548
|
+
* derived from it, and the `browser.profile` pointer. Delete-and-recreate — the
|
|
549
|
+
* only route before this — silently abandons the browser's `--user-data-dir`,
|
|
550
|
+
* which is where a profile's logins live. On a real agent browser that is
|
|
551
|
+
* gigabytes of session state and every account it has ever signed into.
|
|
552
|
+
*
|
|
553
|
+
* Refuses when the profile is in use: moving a `--user-data-dir` out from under
|
|
554
|
+
* a running browser corrupts it.
|
|
555
|
+
*/
|
|
556
|
+
export async function renameProfile(from, to) {
|
|
557
|
+
if (from === to)
|
|
558
|
+
throw new Error(`"${from}" is already its own name.`);
|
|
559
|
+
const local = localDeclaration(from);
|
|
560
|
+
if (!local) {
|
|
561
|
+
const devices = declaringDevices(from);
|
|
562
|
+
const where = devices.length > 0 ? `; declared on ${devices.join(', ')}` : '';
|
|
563
|
+
throw new Error(`Profile "${from}" is not declared on ${machineId()}${where}`);
|
|
564
|
+
}
|
|
565
|
+
if (profileRegistry().has(to))
|
|
566
|
+
throw new Error(`Profile "${to}" already exists`);
|
|
567
|
+
assertRegistrableProfileName(to);
|
|
568
|
+
const { isProfileInUse, listProfileCacheDirs } = await import('./runtime-state.js');
|
|
569
|
+
if (isProfileInUse(from)) {
|
|
570
|
+
throw new Error(`"${from}" is in use (live browser, tunnel, or open task). Renaming would move its ` +
|
|
571
|
+
`user-data-dir out from under the running browser. Stop it first: agents browser stop --profile ${from}`);
|
|
572
|
+
}
|
|
573
|
+
const config = local.config;
|
|
574
|
+
const devices = declaringDevices(from);
|
|
575
|
+
// Move the on-disk state BEFORE the config, so a crash between the two leaves
|
|
576
|
+
// a profile whose dirs are already where the new name expects them rather than
|
|
577
|
+
// a config pointing at dirs that no longer exist.
|
|
578
|
+
// PRE-FLIGHT every destination before moving ANY of them. With the check
|
|
579
|
+
// inside the loop, dir N was validated only after dirs 0..N-1 had already
|
|
580
|
+
// moved: a collision on the second endpoint left the first one's logins
|
|
581
|
+
// stranded under a name with no config entry, and the error named only the
|
|
582
|
+
// squatter. Nothing repaired that, and the user was never told.
|
|
583
|
+
const plan = [];
|
|
584
|
+
for (const dir of listProfileCacheDirs(from)) {
|
|
585
|
+
const base = path.basename(dir);
|
|
586
|
+
const suffix = base.slice(from.length);
|
|
587
|
+
const dest = path.join(path.dirname(dir), `${to}${suffix}`);
|
|
588
|
+
if (fs.existsSync(dest)) {
|
|
589
|
+
throw new Error(`Cannot rename: ${dest} already exists. Nothing was moved. Remove or rename it first.`);
|
|
510
590
|
}
|
|
511
|
-
|
|
591
|
+
plan.push({ dir, dest });
|
|
592
|
+
}
|
|
593
|
+
const movedDirs = [];
|
|
594
|
+
for (const { dir, dest } of plan) {
|
|
595
|
+
fs.renameSync(dir, dest);
|
|
596
|
+
movedDirs.push(dest);
|
|
597
|
+
}
|
|
598
|
+
updateMeta((meta) => {
|
|
599
|
+
const next = { ...meta.deviceBrowser };
|
|
600
|
+
delete next[from];
|
|
601
|
+
return { ...meta, deviceBrowser: { ...next, [to]: config } };
|
|
602
|
+
});
|
|
603
|
+
// Both pointers are separate keys. Leaving `browser.profile` behind falls back
|
|
604
|
+
// to auto-detect on the next `browser start`; leaving `browser.viewer` behind
|
|
605
|
+
// sends every artifact back to the OS default handler — which is the exact bug
|
|
606
|
+
// the viewer seam was built to fix, reintroduced by a rename.
|
|
607
|
+
const { setConfigValue, getConfigValue } = await import('../device-config.js');
|
|
608
|
+
let repointedDefault = false;
|
|
609
|
+
if (getConfiguredDefaultProfileName() === from) {
|
|
610
|
+
setConfigValue('browser.profile', to);
|
|
611
|
+
repointedDefault = true;
|
|
612
|
+
}
|
|
613
|
+
let repointedViewer = false;
|
|
614
|
+
if (getConfigValue('browser.viewer').value === from) {
|
|
615
|
+
setConfigValue('browser.viewer', to);
|
|
616
|
+
repointedViewer = true;
|
|
617
|
+
}
|
|
618
|
+
// A rename changes only this device's declaration. If it was the only
|
|
619
|
+
// declaration, peer pins become stale and are reported rather than rewritten.
|
|
620
|
+
const { devicesPinningBrowserProfile } = await import('../device-config.js');
|
|
621
|
+
const stalePins = devices.length === 1
|
|
622
|
+
? devicesPinningBrowserProfile(from).filter((p) => p.device !== machineId())
|
|
623
|
+
: [];
|
|
624
|
+
return { devices, movedDirs, repointedDefault, repointedViewer, stalePins };
|
|
625
|
+
}
|
|
626
|
+
export async function deleteProfile(name) {
|
|
627
|
+
if (!localDeclaration(name)) {
|
|
628
|
+
const devices = declaringDevices(name);
|
|
629
|
+
const where = devices.length > 0 ? `; declared on ${devices.join(', ')}` : '';
|
|
630
|
+
throw new Error(`Profile "${name}" is not declared on ${machineId()}${where}`);
|
|
631
|
+
}
|
|
632
|
+
updateMeta((meta) => {
|
|
633
|
+
const next = { ...meta.deviceBrowser };
|
|
512
634
|
delete next[name];
|
|
513
|
-
return { ...
|
|
635
|
+
return { ...meta, deviceBrowser: Object.keys(next).length > 0 ? next : undefined };
|
|
514
636
|
});
|
|
515
637
|
}
|
|
516
638
|
/** Widest NAME column we will grow to before truncating instead. */
|
|
@@ -545,13 +667,10 @@ export function padColumn(text, width) {
|
|
|
545
667
|
* a leading column, explained by a legend line — so the name column carries just
|
|
546
668
|
* the name and `default` in it always means the profile of that name.
|
|
547
669
|
*
|
|
548
|
-
*
|
|
549
|
-
* this machine only (`local`) or in the fleet-synced `agents.yaml` (`fleet`), so
|
|
550
|
-
* "why does this box have a profile I never made here?" is answerable at a
|
|
551
|
-
* glance.
|
|
670
|
+
* WHERE lists the devices whose own files declare the name.
|
|
552
671
|
*/
|
|
553
672
|
export function formatProfilesTable(rows, configuredDefault) {
|
|
554
|
-
const cells = rows.map((
|
|
673
|
+
const cells = rows.map((profile) => {
|
|
555
674
|
const presets = getEndpointPresets(profile);
|
|
556
675
|
const endpoints = Object.entries(presets)
|
|
557
676
|
.map(([name, ep]) => (name.startsWith('endpoint-') ? ep.target : `${name}=${ep.target}`))
|
|
@@ -560,42 +679,35 @@ export function formatProfilesTable(rows, configuredDefault) {
|
|
|
560
679
|
marker: profile.name === configuredDefault ? '*' : ' ',
|
|
561
680
|
name: profile.name,
|
|
562
681
|
browser: profile.browser || '-',
|
|
563
|
-
|
|
682
|
+
where: profile.devices.join(', '),
|
|
564
683
|
description: profile.description ?? '',
|
|
565
684
|
endpoints,
|
|
566
685
|
};
|
|
567
686
|
});
|
|
568
687
|
const nameWidth = Math.min(MAX_NAME_COLUMN, Math.max('NAME'.length, ...cells.map((c) => c.name.length)));
|
|
569
688
|
const browserWidth = Math.max('BROWSER'.length, ...cells.map((c) => c.browser.length));
|
|
570
|
-
|
|
571
|
-
const scopeWidth = 'SCOPE'.length;
|
|
689
|
+
const whereWidth = Math.min(36, Math.max('WHERE'.length, ...cells.map((c) => c.where.length)));
|
|
572
690
|
const hasDescriptions = cells.some((c) => c.description);
|
|
573
691
|
const descWidth = hasDescriptions
|
|
574
692
|
? Math.min(36, Math.max('DESCRIPTION'.length, ...cells.map((c) => c.description.length)))
|
|
575
693
|
: 0;
|
|
576
|
-
const row = (marker, name, browser,
|
|
694
|
+
const row = (marker, name, browser, where, desc, endpoints) => `${marker} ` +
|
|
577
695
|
padColumn(name, nameWidth) + ' ' +
|
|
578
696
|
padColumn(browser, browserWidth) + ' ' +
|
|
579
|
-
padColumn(
|
|
697
|
+
padColumn(where, whereWidth) + ' ' +
|
|
580
698
|
(hasDescriptions ? padColumn(desc, descWidth) + ' ' : '') +
|
|
581
699
|
endpoints;
|
|
582
|
-
const header = row(' ', 'NAME', 'BROWSER', '
|
|
700
|
+
const header = row(' ', 'NAME', 'BROWSER', 'WHERE', 'DESCRIPTION', 'ENDPOINTS');
|
|
583
701
|
const lines = [header, '-'.repeat(header.length)];
|
|
584
702
|
for (const c of cells) {
|
|
585
|
-
lines.push(row(c.marker, c.name, c.browser, c.
|
|
703
|
+
lines.push(row(c.marker, c.name, c.browser, c.where, c.description, c.endpoints));
|
|
586
704
|
}
|
|
587
705
|
if (configuredDefault) {
|
|
588
706
|
lines.push('');
|
|
589
707
|
lines.push(`* = this machine's default profile (${configuredDefault}) — what a bare \`agents browser start\` uses.`);
|
|
590
708
|
}
|
|
591
|
-
lines.push(hasFleetRow(rows)
|
|
592
|
-
? 'scope: local = this machine only · fleet = synced to every machine (agents.yaml).'
|
|
593
|
-
: 'scope: local = this machine only. Create a synced one with: agents browser profiles create <name> --fleet');
|
|
594
709
|
return lines;
|
|
595
710
|
}
|
|
596
|
-
function hasFleetRow(rows) {
|
|
597
|
-
return rows.some((r) => r.scope === 'fleet');
|
|
598
|
-
}
|
|
599
711
|
/**
|
|
600
712
|
* Resolve a profile's endpoint presets into a normalized map regardless of
|
|
601
713
|
* whether the YAML uses the legacy `string[]` shape or the new map shape.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { BrowserProfileConfig } from '../types.js';
|
|
2
|
+
export interface ProfileDeclaration {
|
|
3
|
+
device: string;
|
|
4
|
+
config: BrowserProfileConfig;
|
|
5
|
+
}
|
|
6
|
+
/** Leftover central `browser:` map from before per-device declarations. Empty when none remain. */
|
|
7
|
+
export declare function centralBrowserProfiles(): Record<string, BrowserProfileConfig>;
|
|
8
|
+
export interface CentralClaimResult {
|
|
9
|
+
claimed: string[];
|
|
10
|
+
skipped: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Fold leftover central `browser:` entries into THIS device's declaration file.
|
|
14
|
+
*
|
|
15
|
+
* Never called implicitly, and that is the point. Every device can read the
|
|
16
|
+
* central map, so an implicit claim races: whichever box happens to read first
|
|
17
|
+
* claims the name, {@link profileKind} then reports it `identity`, and the
|
|
18
|
+
* daemon tunnels to that box — which for a fleet-wide `cdp://localhost:*`
|
|
19
|
+
* profile is a logged-out headless browser wearing a credentialed browser's
|
|
20
|
+
* name. That is the exact bug this module exists to remove, and an implicit
|
|
21
|
+
* migration would write it to disk as a stored fact.
|
|
22
|
+
*
|
|
23
|
+
* The claim is an explicit operator action (`agents browser profiles claim`),
|
|
24
|
+
* run on the machine that actually owns the browser. `canHostHere` is supplied
|
|
25
|
+
* by the command layer so this module stays a leaf — it must not import
|
|
26
|
+
* `isProfileLaunchableHere` (that would cycle through chrome.ts → profiles.ts).
|
|
27
|
+
* Only profiles this machine can host are claimed; the rest stay central,
|
|
28
|
+
* undeclared, and fail loudly on resolve.
|
|
29
|
+
*/
|
|
30
|
+
export declare function migrateCentralBrowserProfiles(canHostHere: (config: BrowserProfileConfig) => boolean, name?: string): CentralClaimResult;
|
|
31
|
+
/**
|
|
32
|
+
* Every profile any device declares, keyed by name to all declaring devices.
|
|
33
|
+
* Reads every `devices/<name>/agents.yaml`; declarations never overwrite.
|
|
34
|
+
*/
|
|
35
|
+
export declare function profileRegistry(): Map<string, ProfileDeclaration[]>;
|
|
36
|
+
/** Devices declaring `name`, empty when nobody does. */
|
|
37
|
+
export declare function declaringDevices(name: string): string[];
|
|
38
|
+
/** Exactly one declaring device is identity-bearing; several are fungible. */
|
|
39
|
+
export declare function profileKind(name: string): 'identity' | 'fungible' | null;
|