@phnx-labs/agents-cli 1.22.47 → 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 +401 -17
- package/README.md +66 -32
- package/dist/bootstrap.js +4 -2
- package/dist/cli/command-registry.d.ts +1 -0
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/accounts.d.ts +58 -0
- package/dist/commands/accounts.js +206 -32
- package/dist/commands/artifacts.js +7 -6
- package/dist/commands/attach.js +1 -1
- package/dist/commands/browser.js +501 -103
- package/dist/commands/config.d.ts +0 -1
- package/dist/commands/config.js +0 -20
- package/dist/commands/daemon.d.ts +21 -0
- package/dist/commands/daemon.js +147 -12
- package/dist/commands/exec.js +52 -15
- 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/inspect.js +2 -2
- package/dist/commands/lease.d.ts +1 -1
- package/dist/commands/lease.js +4 -3
- 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 +3 -2
- package/dist/commands/run-broadcast.d.ts +1 -1
- package/dist/commands/run-broadcast.js +1 -1
- package/dist/commands/sessions-picker.d.ts +22 -0
- package/dist/commands/sessions-picker.js +48 -9
- package/dist/commands/sessions-trace.js +1 -1
- package/dist/commands/sessions.js +1 -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.js +54 -7
- package/dist/commands/traces.d.ts +2 -0
- package/dist/commands/traces.js +184 -0
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +17 -34
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/accounting/usage.d.ts +14 -3
- package/dist/lib/accounting/usage.js +64 -18
- package/dist/lib/agent-spec/agents.js +11 -9
- package/dist/lib/browser/drivers/ssh.d.ts +16 -2
- package/dist/lib/browser/drivers/ssh.js +36 -9
- package/dist/lib/browser/ipc.js +8 -3
- package/dist/lib/browser/profiles.d.ts +35 -101
- package/dist/lib/browser/profiles.js +130 -301
- 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 +8 -6
- package/dist/lib/browser/remote-control.js +8 -6
- 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 +40 -16
- package/dist/lib/browser/runtime-state.js +92 -25
- package/dist/lib/browser/service.d.ts +28 -9
- package/dist/lib/browser/service.js +139 -62
- package/dist/lib/browser/task-index.d.ts +70 -0
- package/dist/lib/browser/task-index.js +180 -0
- 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/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 +2 -8
- package/dist/lib/config-keys.js +2 -13
- 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/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 +20 -18
- package/dist/lib/device-config.d.ts +0 -2
- package/dist/lib/device-config.js +1 -23
- package/dist/lib/devices/registry.d.ts +1 -1
- package/dist/lib/devices/registry.js +1 -1
- package/dist/lib/devices/stats-cache.d.ts +20 -0
- package/dist/lib/devices/stats-cache.js +49 -5
- 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 +67 -13
- package/dist/lib/exec.js +85 -29
- 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 +11 -0
- package/dist/lib/hooks/install.js +2 -2
- 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 +19 -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/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 +31 -7
- package/dist/lib/menubar/install-menubar.js +79 -21
- 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/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/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.js +17 -1
- package/dist/lib/session/db.d.ts +11 -1
- package/dist/lib/session/db.js +138 -6
- package/dist/lib/session/discover.d.ts +21 -1
- package/dist/lib/session/discover.js +33 -5
- 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/remote/remote-list.d.ts +9 -0
- package/dist/lib/session/remote/remote-list.js +19 -2
- package/dist/lib/session/remote/watch.js +11 -2
- package/dist/lib/session/session-cache.d.ts +53 -0
- package/dist/lib/session/session-cache.js +88 -0
- 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-html.d.ts +5 -1
- package/dist/lib/session/trajectory-html.js +42 -22
- package/dist/lib/session/trajectory-text.js +9 -2
- package/dist/lib/session/trajectory.js +53 -4
- 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.js +1 -1
- package/dist/lib/startup/dev-build.js +3 -3
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +7 -3
- package/dist/lib/subagents-registry.js +1 -1
- package/dist/lib/terminal/index.d.ts +1 -1
- 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.js +31 -0
- 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 +18 -19
- package/dist/lib/types.js +14 -0
- package/dist/lib/usage-refresh.d.ts +1 -1
- package/dist/lib/usage-refresh.js +11 -8
- package/dist/lib/workflows.d.ts +1 -1
- package/package.json +4 -6
- 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 -26
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -135
- 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
|
@@ -2,8 +2,11 @@ import * as path from 'path';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import { getBrowserRuntimeDir as getBrowserRuntimeDirRoot, readMeta, updateMeta, } from '../state.js';
|
|
4
4
|
import { getConfigValue } from '../device-config.js';
|
|
5
|
+
import { machineId } from '../machine-id.js';
|
|
6
|
+
import { declaringDevices, profileRegistry, } from './registry.js';
|
|
5
7
|
import { findBrowserPath, findFirstInstalledBrowser, isPortInUse } from './chrome.js';
|
|
6
8
|
import { DEFAULT_VIEWPORT } from './devices.js';
|
|
9
|
+
export { declaringDevices, profileKind, profileRegistry, } from './registry.js';
|
|
7
10
|
/**
|
|
8
11
|
* Name of the profile `ensureDefaultBrowserProfile` auto-detects and pins.
|
|
9
12
|
*
|
|
@@ -29,25 +32,6 @@ export const LEGACY_DEFAULT_BROWSER_PROFILE_NAME = 'default';
|
|
|
29
32
|
* wins, so no existing config breaks.
|
|
30
33
|
*/
|
|
31
34
|
export const DEFAULT_PROFILE_ALIAS = 'default';
|
|
32
|
-
/**
|
|
33
|
-
* True for a profile whose contents only make sense on THIS machine, so it must
|
|
34
|
-
* live in the per-machine doc rather than the fleet-synced agents.yaml.
|
|
35
|
-
*
|
|
36
|
-
* The auto-detected `default` always qualifies: it is generated by probing local
|
|
37
|
-
* browser installs and a free local port, so its `binary:` is an absolute path
|
|
38
|
-
* for one OS. Shared fleet-wide it caused a permanent rewrite loop — a macOS box
|
|
39
|
-
* wrote `/Applications/...`, a Linux box rewrote `/usr/bin/chromium-browser`,
|
|
40
|
-
* each on every `agents browser start`.
|
|
41
|
-
*
|
|
42
|
-
* As of RUSH-2716 it is not the only one: EVERY newly created profile is
|
|
43
|
-
* machine-local unless the caller explicitly asks for the fleet-synced store
|
|
44
|
-
* (`createProfile(p, { fleet: true })` / `--fleet`). See {@link createProfile}.
|
|
45
|
-
* This predicate is what keeps an existing `default` that an older version wrote
|
|
46
|
-
* into central from being written back there by {@link updateProfile}.
|
|
47
|
-
*/
|
|
48
|
-
function isMachineLocalProfile(name) {
|
|
49
|
-
return name === DEFAULT_BROWSER_PROFILE_NAME || name === LEGACY_DEFAULT_BROWSER_PROFILE_NAME;
|
|
50
|
-
}
|
|
51
35
|
/**
|
|
52
36
|
* The device-local configured default profile name (set via
|
|
53
37
|
* `agents browser use`), or undefined when unset. When set, it
|
|
@@ -82,7 +66,7 @@ export function getProfileDownloadsDir(name) {
|
|
|
82
66
|
export function getProfileSessionsDir(name, task) {
|
|
83
67
|
return path.join(getProfileRuntimeDir(name), 'sessions', task);
|
|
84
68
|
}
|
|
85
|
-
function configToProfile(name, config) {
|
|
69
|
+
function configToProfile(name, config, devices = []) {
|
|
86
70
|
validateRemoteBrowserBinaries(config);
|
|
87
71
|
return {
|
|
88
72
|
name,
|
|
@@ -98,6 +82,7 @@ function configToProfile(name, config) {
|
|
|
98
82
|
viewport: config.viewport,
|
|
99
83
|
logDir: config.logDir,
|
|
100
84
|
logHost: config.logHost,
|
|
85
|
+
devices,
|
|
101
86
|
};
|
|
102
87
|
}
|
|
103
88
|
function profileToConfig(profile) {
|
|
@@ -128,38 +113,24 @@ function profileToConfig(profile) {
|
|
|
128
113
|
config.logHost = profile.logHost;
|
|
129
114
|
return config;
|
|
130
115
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
* machine's own. A machine-local entry WINS on a name collision — it was written
|
|
134
|
-
* by this box for this box, whereas the shared copy may carry another OS's
|
|
135
|
-
* binary path (that mismatch is exactly why `default` moved local).
|
|
136
|
-
*/
|
|
137
|
-
function allProfileConfigs() {
|
|
138
|
-
const meta = readMeta();
|
|
139
|
-
return { ...meta.browser, ...meta.deviceBrowser };
|
|
116
|
+
function selectedDeclaration(declarations) {
|
|
117
|
+
return declarations.find((declaration) => declaration.device === machineId()) ?? declarations[0];
|
|
140
118
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return Object.entries(configs).map(([name, config]) => configToProfile(name, config));
|
|
119
|
+
function localDeclaration(name) {
|
|
120
|
+
return profileRegistry().get(name)?.find((declaration) => declaration.device === machineId());
|
|
144
121
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
export async function listProfilesWithScope() {
|
|
151
|
-
const meta = readMeta();
|
|
152
|
-
const local = meta.deviceBrowser ?? {};
|
|
153
|
-
return Object.entries(allProfileConfigs()).map(([name, config]) => ({
|
|
154
|
-
profile: configToProfile(name, config),
|
|
155
|
-
scope: (local[name] ? 'local' : 'fleet'),
|
|
156
|
-
}));
|
|
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
|
+
});
|
|
157
127
|
}
|
|
158
128
|
export async function getProfile(name) {
|
|
159
|
-
const
|
|
160
|
-
if (!
|
|
129
|
+
const declarations = profileRegistry().get(name);
|
|
130
|
+
if (!declarations?.length)
|
|
161
131
|
return null;
|
|
162
|
-
|
|
132
|
+
const selected = selectedDeclaration(declarations);
|
|
133
|
+
return configToProfile(name, selected.config, declarations.map((declaration) => declaration.device));
|
|
163
134
|
}
|
|
164
135
|
/**
|
|
165
136
|
* True when `profile` can actually launch or attach on THIS machine.
|
|
@@ -183,26 +154,6 @@ export function isProfileLaunchableHere(profile) {
|
|
|
183
154
|
return false;
|
|
184
155
|
}
|
|
185
156
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Resolve the profile `agents browser start` uses when no `--profile` is given.
|
|
188
|
-
*
|
|
189
|
-
* Order: (1) the device-local configured default (`agents browser profiles
|
|
190
|
-
* set-default <name>`) when it names an existing profile that can launch here;
|
|
191
|
-
* (2) an existing `default` profile that can launch here; (3) auto-pick the
|
|
192
|
-
* first installed Chromium-family browser per the platform priority list in
|
|
193
|
-
* chrome.ts (macOS: chrome > brave > edge > chromium > comet > arc; Linux: chrome >
|
|
194
|
-
* chromium > brave > edge; Windows: edge > chrome > brave > comet) and pin a
|
|
195
|
-
* `default` profile to it. Throws an actionable error if none of those binaries
|
|
196
|
-
* are installed.
|
|
197
|
-
*
|
|
198
|
-
* "Can launch here" matters because `agents.yaml` syncs across the fleet: a
|
|
199
|
-
* `default` auto-created on macOS carries a `/Applications/...` binary path that
|
|
200
|
-
* doesn't exist on a Linux box. Rather than hand that broken profile back (the
|
|
201
|
-
* top browser roadblock — "Custom binary not found"), we re-detect and
|
|
202
|
-
* regenerate the `default` for this machine. A configured or existing default
|
|
203
|
-
* that no longer exists, or can't launch here, warns and falls through — never a
|
|
204
|
-
* hard fail.
|
|
205
|
-
*/
|
|
206
157
|
/**
|
|
207
158
|
* The auto-detected profile this machine already carries, under either name —
|
|
208
159
|
* the current `auto-chrome` or the `default` an older build wrote. Callers that
|
|
@@ -210,8 +161,14 @@ export function isProfileLaunchableHere(profile) {
|
|
|
210
161
|
* name, or a machine that predates the RUSH-2709 rename grows a second one.
|
|
211
162
|
*/
|
|
212
163
|
export async function getAutoDetectedProfile() {
|
|
213
|
-
|
|
214
|
-
|
|
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;
|
|
215
172
|
}
|
|
216
173
|
/**
|
|
217
174
|
* Resolve what a caller typed after `--profile` to a real, BARE profile name.
|
|
@@ -262,15 +219,16 @@ export async function resolveProfileRef(ref) {
|
|
|
262
219
|
* `start`'s resolver. Same order as {@link resolveProfileRef} for an explicit
|
|
263
220
|
* name, but the IMPLICIT path (no `--profile`, or the bare `default` alias with
|
|
264
221
|
* no profile of that name) goes through {@link ensureDefaultBrowserProfile} —
|
|
265
|
-
* which additionally verifies the resolved default can launch on THIS machine
|
|
266
|
-
*
|
|
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.
|
|
267
225
|
*
|
|
268
226
|
* `start` is the only command that launches a browser, so it is the only one
|
|
269
227
|
* that may do those things; routing a filter-only command through this would
|
|
270
228
|
* warn about, and rewrite, config the user never asked it to touch.
|
|
271
229
|
*
|
|
272
|
-
* Throws when
|
|
273
|
-
*
|
|
230
|
+
* Throws when the configured default is undeclared, or when no profile exists
|
|
231
|
+
* and no supported browser is installed.
|
|
274
232
|
*/
|
|
275
233
|
export async function resolveProfileRefForStart(ref) {
|
|
276
234
|
if (ref && ref !== DEFAULT_PROFILE_ALIAS)
|
|
@@ -280,25 +238,51 @@ export async function resolveProfileRefForStart(ref) {
|
|
|
280
238
|
return ref;
|
|
281
239
|
return (await ensureDefaultBrowserProfile()).name;
|
|
282
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
|
+
*/
|
|
283
259
|
export async function ensureDefaultBrowserProfile() {
|
|
284
260
|
const configured = getConfiguredDefaultProfileName();
|
|
285
261
|
if (configured && configured !== DEFAULT_PROFILE_ALIAS) {
|
|
286
262
|
const chosen = await getProfile(configured);
|
|
287
263
|
if (chosen && isProfileLaunchableHere(chosen))
|
|
288
264
|
return chosen;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
`
|
|
293
|
-
|
|
294
|
-
|
|
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)`);
|
|
295
280
|
}
|
|
296
281
|
// Prefer whichever auto-detected profile this machine already carries: the
|
|
297
282
|
// current `auto-chrome`, else the `default` an older build wrote. Reusing the
|
|
298
283
|
// legacy one is what keeps a currently-running browser (and its runtime dirs,
|
|
299
284
|
// keyed `default@endpoint-0`) attached after the rename.
|
|
300
|
-
const existing =
|
|
301
|
-
(await getProfile(LEGACY_DEFAULT_BROWSER_PROFILE_NAME));
|
|
285
|
+
const existing = await getAutoDetectedProfile();
|
|
302
286
|
if (existing && isProfileLaunchableHere(existing))
|
|
303
287
|
return existing;
|
|
304
288
|
const detected = findFirstInstalledBrowser();
|
|
@@ -435,10 +419,13 @@ export function assertLocalPortFree(profile, opts = {}) {
|
|
|
435
419
|
const wanted = effectiveLocalPort(profile);
|
|
436
420
|
if (wanted === undefined)
|
|
437
421
|
return;
|
|
438
|
-
for (const [existingName,
|
|
422
|
+
for (const [existingName, declarations] of profileRegistry()) {
|
|
439
423
|
if (existingName === opts.ignore)
|
|
440
424
|
continue;
|
|
441
|
-
const
|
|
425
|
+
const declaration = declarations.find((entry) => entry.device === machineId());
|
|
426
|
+
if (!declaration)
|
|
427
|
+
continue;
|
|
428
|
+
const existingProfile = configToProfile(existingName, declaration.config, [machineId()]);
|
|
442
429
|
if (effectiveLocalPort(existingProfile) === wanted) {
|
|
443
430
|
throw new Error(`Local port ${wanted} is already used by profile "${existingName}". ` +
|
|
444
431
|
`Each profile must own a unique local port (SSH tunnels now bind ` +
|
|
@@ -446,28 +433,10 @@ export function assertLocalPortFree(profile, opts = {}) {
|
|
|
446
433
|
}
|
|
447
434
|
}
|
|
448
435
|
}
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
*
|
|
454
|
-
* Local-by-default is the RUSH-2716 fix. A profile is a local browser install
|
|
455
|
-
* (`binary:` is an absolute path for one OS) bound to a locally chosen free port,
|
|
456
|
-
* so the fleet-synced copy is wrong on every other machine to begin with; and
|
|
457
|
-
* because agents mint throwaway profiles freely, every ad-hoc one was syncing to
|
|
458
|
-
* every machine as junk that nothing cleaned up (`agents browser profiles prune`
|
|
459
|
-
* is the other half of that fix). Opting in with `--fleet` is for a profile that
|
|
460
|
-
* really is fleet config — a remote `ssh://` endpoint, or a named profile whose
|
|
461
|
-
* shape you want on every box.
|
|
462
|
-
*
|
|
463
|
-
* Existing profiles are NOT migrated: this only decides where a NEW entry is
|
|
464
|
-
* written, so a fleet profile created before this change keeps syncing.
|
|
465
|
-
*/
|
|
466
|
-
export async function createProfile(profile, opts = {}) {
|
|
467
|
-
const meta = readMeta();
|
|
468
|
-
const existingConfigs = allProfileConfigs();
|
|
469
|
-
if (existingConfigs[profile.name]) {
|
|
470
|
-
throw new Error(`Profile "${profile.name}" already exists`);
|
|
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()}`);
|
|
471
440
|
}
|
|
472
441
|
assertLocalPortFree(profile);
|
|
473
442
|
// Resolve the browser binary at create time. Fails fast with an actionable
|
|
@@ -482,69 +451,23 @@ export async function createProfile(profile, opts = {}) {
|
|
|
482
451
|
findBrowserPath(profile.browser, profile.binary);
|
|
483
452
|
}
|
|
484
453
|
const config = profileToConfig(profile);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (!opts.fleet || isMachineLocalProfile(profile.name)) {
|
|
490
|
-
updateMeta((m) => ({ ...m, deviceBrowser: { ...m.deviceBrowser, [profile.name]: config } }));
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
updateMeta((m) => ({ ...m, browser: { ...m.browser, [profile.name]: config } }));
|
|
454
|
+
updateMeta((meta) => ({
|
|
455
|
+
...meta,
|
|
456
|
+
deviceBrowser: { ...meta.deviceBrowser, [profile.name]: config },
|
|
457
|
+
}));
|
|
494
458
|
}
|
|
495
459
|
export async function updateProfile(profile) {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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}`);
|
|
500
465
|
}
|
|
501
466
|
const config = profileToConfig(profile);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
updateMeta((m) => ({ ...m, browser: { ...m.browser, [profile.name]: config } }));
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Flag a fleet-synced profile whose contents only make sense on ONE machine.
|
|
512
|
-
*
|
|
513
|
-
* A `cdp://` endpoint on loopback always connects on the machine EVALUATING the
|
|
514
|
-
* profile, so a fleet-scope copy does not mean "that browser" — it means "port N
|
|
515
|
-
* on whichever box ran the command". The name then silently resolves to a
|
|
516
|
-
* different, usually logged-out browser on every other machine, which is the
|
|
517
|
-
* worst possible failure for a profile that carries live logins.
|
|
518
|
-
*
|
|
519
|
-
* Reported by `profiles doctor` (a failing `scope` check) and noted in the
|
|
520
|
-
* `kept` reason `profiles prune` prints for a fleet profile. The repair is
|
|
521
|
-
* {@link moveProfileScope}, never an automatic rewrite of the synced doc.
|
|
522
|
-
*/
|
|
523
|
-
export function misfiledFleetProfile(profile, scope) {
|
|
524
|
-
if (scope !== 'fleet')
|
|
525
|
-
return { misfiled: false };
|
|
526
|
-
// Judge the endpoint the profile would actually CONNECT on, resolving
|
|
527
|
-
// `defaultEndpoint` exactly as effectiveLocalPort does. Scanning every preset
|
|
528
|
-
// instead would fail a legitimate fleet profile that defaults to `ssh://` and
|
|
529
|
-
// merely carries a loopback alternate for local debugging.
|
|
530
|
-
const presets = getEndpointPresets(profile);
|
|
531
|
-
const chosen = profile.defaultEndpoint && presets[profile.defaultEndpoint]
|
|
532
|
-
? profile.defaultEndpoint
|
|
533
|
-
: Object.keys(presets)[0];
|
|
534
|
-
if (!chosen)
|
|
535
|
-
return { misfiled: false };
|
|
536
|
-
const loopback = presets[chosen].target;
|
|
537
|
-
if (!loopback.startsWith('cdp://'))
|
|
538
|
-
return { misfiled: false };
|
|
539
|
-
const parsed = parseEndpointUrl(loopback);
|
|
540
|
-
if (!parsed || !isLocalHost(parsed.host))
|
|
541
|
-
return { misfiled: false };
|
|
542
|
-
return {
|
|
543
|
-
misfiled: true,
|
|
544
|
-
why: `fleet-synced but "${loopback}" is a loopback endpoint — on every other machine ` +
|
|
545
|
-
`this name resolves to that box's own port, not this browser. ` +
|
|
546
|
-
`Repair on the machine that owns it: agents browser profiles scope ${profile.name} local`,
|
|
547
|
-
};
|
|
467
|
+
updateMeta((meta) => ({
|
|
468
|
+
...meta,
|
|
469
|
+
deviceBrowser: { ...meta.deviceBrowser, [profile.name]: config },
|
|
470
|
+
}));
|
|
548
471
|
}
|
|
549
472
|
/**
|
|
550
473
|
* Merge `patch` onto the stored profile and persist it in the store it already
|
|
@@ -556,13 +479,14 @@ export function misfiledFleetProfile(profile, scope) {
|
|
|
556
479
|
* with this profile excluded so an unchanged port is not a self-collision.
|
|
557
480
|
*/
|
|
558
481
|
export async function editProfile(name, patch) {
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
throw new Error(`Profile "${name}"
|
|
564
|
-
}
|
|
565
|
-
const
|
|
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);
|
|
566
490
|
const merged = { ...current, ...patch, name };
|
|
567
491
|
const changed = Object.keys(patch).filter((k) => JSON.stringify(current[k]) !== JSON.stringify(merged[k]));
|
|
568
492
|
// A target filter only means anything for an Electron app, and the gate must
|
|
@@ -580,27 +504,12 @@ export async function editProfile(name, patch) {
|
|
|
580
504
|
findBrowserPath(merged.browser, merged.binary);
|
|
581
505
|
}
|
|
582
506
|
await updateProfile(merged);
|
|
583
|
-
const landedLocal = !!local || isMachineLocalProfile(name);
|
|
584
507
|
return {
|
|
585
508
|
profile: merged,
|
|
586
|
-
|
|
509
|
+
devices,
|
|
587
510
|
changed,
|
|
588
|
-
fleetCopyLeftStale: landedLocal && !local && !!fleet ? true : undefined,
|
|
589
511
|
};
|
|
590
512
|
}
|
|
591
|
-
/**
|
|
592
|
-
* Move an existing profile between the fleet-synced store and this machine's
|
|
593
|
-
* device store.
|
|
594
|
-
*
|
|
595
|
-
* `fleet -> local` is the repair for a profile whose endpoint is machine-bound
|
|
596
|
-
* (a loopback `cdp://`) but which was created with `--fleet`: the name resolves
|
|
597
|
-
* to a DIFFERENT browser on every box, because `cdp:` always connects on the
|
|
598
|
-
* machine evaluating it. Run it ON the machine that owns the browser — the copy
|
|
599
|
-
* that survives is the one that works.
|
|
600
|
-
*
|
|
601
|
-
* Deliberately not automatic. Rewriting the user's synced `agents.yaml` from a
|
|
602
|
-
* heuristic is the rewrite loop RUSH-2161 fixed; this is an explicit verb.
|
|
603
|
-
*/
|
|
604
513
|
/** Profile names an agent has to be able to type and an fs path can hold. */
|
|
605
514
|
const PROFILE_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
606
515
|
/**
|
|
@@ -647,12 +556,13 @@ export function assertRegistrableProfileName(name) {
|
|
|
647
556
|
export async function renameProfile(from, to) {
|
|
648
557
|
if (from === to)
|
|
649
558
|
throw new Error(`"${from}" is already its own name.`);
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
throw new Error(`Profile "${from}"
|
|
655
|
-
|
|
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))
|
|
656
566
|
throw new Error(`Profile "${to}" already exists`);
|
|
657
567
|
assertRegistrableProfileName(to);
|
|
658
568
|
const { isProfileInUse, listProfileCacheDirs } = await import('./runtime-state.js');
|
|
@@ -660,26 +570,8 @@ export async function renameProfile(from, to) {
|
|
|
660
570
|
throw new Error(`"${from}" is in use (live browser, tunnel, or open task). Renaming would move its ` +
|
|
661
571
|
`user-data-dir out from under the running browser. Stop it first: agents browser stop --profile ${from}`);
|
|
662
572
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
// moved away — the abandoned-`--user-data-dir` state this command exists to
|
|
666
|
-
// prevent, produced by this command, and previously reported as a clean
|
|
667
|
-
// success. Refuse instead: which copy the user means is genuinely ambiguous,
|
|
668
|
-
// and `profiles scope` is the tool for collapsing the duplicate first.
|
|
669
|
-
if (local && fleet) {
|
|
670
|
-
// `scope <name> fleet`, NOT local: moveProfileScope computes `from` as
|
|
671
|
-
// 'local' whenever a local entry exists, so asking for local is a no-op that
|
|
672
|
-
// reports success and leaves the duplicate. Sending the user there and then
|
|
673
|
-
// to `profiles delete` — which drops the cached runtime dirs by default —
|
|
674
|
-
// walked a data-preservation command into a data-destroying one.
|
|
675
|
-
throw new Error(`"${from}" exists in BOTH this machine's store and the fleet-synced one. ` +
|
|
676
|
-
`Renaming would move its browser data while leaving one copy behind under the old name.\n` +
|
|
677
|
-
` Collapse the duplicate first: agents browser profiles scope ${from} fleet\n` +
|
|
678
|
-
` Then rename. (To drop a copy instead, agents browser profiles delete ${from} --keep-cache ` +
|
|
679
|
-
`keeps the browser data.)`);
|
|
680
|
-
}
|
|
681
|
-
const config = (local ?? fleet);
|
|
682
|
-
const scope = local ? 'local' : 'fleet';
|
|
573
|
+
const config = local.config;
|
|
574
|
+
const devices = declaringDevices(from);
|
|
683
575
|
// Move the on-disk state BEFORE the config, so a crash between the two leaves
|
|
684
576
|
// a profile whose dirs are already where the new name expects them rather than
|
|
685
577
|
// a config pointing at dirs that no longer exist.
|
|
@@ -703,20 +595,11 @@ export async function renameProfile(from, to) {
|
|
|
703
595
|
fs.renameSync(dir, dest);
|
|
704
596
|
movedDirs.push(dest);
|
|
705
597
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
else {
|
|
714
|
-
updateMeta((m) => {
|
|
715
|
-
const next = { ...m.browser };
|
|
716
|
-
delete next[from];
|
|
717
|
-
return { ...m, browser: { ...next, [to]: config } };
|
|
718
|
-
});
|
|
719
|
-
}
|
|
598
|
+
updateMeta((meta) => {
|
|
599
|
+
const next = { ...meta.deviceBrowser };
|
|
600
|
+
delete next[from];
|
|
601
|
+
return { ...meta, deviceBrowser: { ...next, [to]: config } };
|
|
602
|
+
});
|
|
720
603
|
// Both pointers are separate keys. Leaving `browser.profile` behind falls back
|
|
721
604
|
// to auto-detect on the next `browser start`; leaving `browser.viewer` behind
|
|
722
605
|
// sends every artifact back to the OS default handler — which is the exact bug
|
|
@@ -732,68 +615,24 @@ export async function renameProfile(from, to) {
|
|
|
732
615
|
setConfigValue('browser.viewer', to);
|
|
733
616
|
repointedViewer = true;
|
|
734
617
|
}
|
|
735
|
-
// A
|
|
736
|
-
//
|
|
737
|
-
// rewritten from here: that would be a cross-machine mutation, and on a peer
|
|
738
|
-
// the pin may be correct (a LOCAL profile of the same name). Reported instead.
|
|
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.
|
|
739
620
|
const { devicesPinningBrowserProfile } = await import('../device-config.js');
|
|
740
|
-
const
|
|
741
|
-
const stalePins = scope === 'fleet'
|
|
621
|
+
const stalePins = devices.length === 1
|
|
742
622
|
? devicesPinningBrowserProfile(from).filter((p) => p.device !== machineId())
|
|
743
623
|
: [];
|
|
744
|
-
return {
|
|
745
|
-
}
|
|
746
|
-
export async function moveProfileScope(name, to) {
|
|
747
|
-
const meta = readMeta();
|
|
748
|
-
const local = meta.deviceBrowser?.[name];
|
|
749
|
-
const fleet = meta.browser?.[name];
|
|
750
|
-
if (!local && !fleet) {
|
|
751
|
-
throw new Error(`Profile "${name}" does not exist`);
|
|
752
|
-
}
|
|
753
|
-
const from = local ? 'local' : 'fleet';
|
|
754
|
-
if (from === to)
|
|
755
|
-
return { from, to };
|
|
756
|
-
if (to === 'fleet' && isMachineLocalProfile(name)) {
|
|
757
|
-
throw new Error(`"${name}" is always machine-local — its binary path and port are this box's. ` +
|
|
758
|
-
`Fleet-syncing it is the rewrite loop RUSH-2161 fixed.`);
|
|
759
|
-
}
|
|
760
|
-
const config = (local ?? fleet);
|
|
761
|
-
// Write the destination BEFORE dropping the source, so a crash between the two
|
|
762
|
-
// leaves a duplicate (harmless — local wins in allProfileConfigs) rather than
|
|
763
|
-
// no profile at all.
|
|
764
|
-
if (to === 'local') {
|
|
765
|
-
updateMeta((m) => ({ ...m, deviceBrowser: { ...m.deviceBrowser, [name]: config } }));
|
|
766
|
-
updateMeta((m) => {
|
|
767
|
-
const next = { ...m.browser };
|
|
768
|
-
delete next[name];
|
|
769
|
-
return { ...m, browser: Object.keys(next).length > 0 ? next : undefined };
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
else {
|
|
773
|
-
updateMeta((m) => ({ ...m, browser: { ...m.browser, [name]: config } }));
|
|
774
|
-
updateMeta((m) => {
|
|
775
|
-
const next = { ...m.deviceBrowser };
|
|
776
|
-
delete next[name];
|
|
777
|
-
return { ...m, deviceBrowser: Object.keys(next).length > 0 ? next : undefined };
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
return { from, to };
|
|
624
|
+
return { devices, movedDirs, repointedDefault, repointedViewer, stalePins };
|
|
781
625
|
}
|
|
782
626
|
export async function deleteProfile(name) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
throw new Error(`Profile "${name}"
|
|
787
|
-
}
|
|
788
|
-
updateMeta((
|
|
789
|
-
|
|
790
|
-
const next = { ...m.deviceBrowser };
|
|
791
|
-
delete next[name];
|
|
792
|
-
return { ...m, deviceBrowser: Object.keys(next).length > 0 ? next : undefined };
|
|
793
|
-
}
|
|
794
|
-
const next = { ...m.browser };
|
|
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 };
|
|
795
634
|
delete next[name];
|
|
796
|
-
return { ...
|
|
635
|
+
return { ...meta, deviceBrowser: Object.keys(next).length > 0 ? next : undefined };
|
|
797
636
|
});
|
|
798
637
|
}
|
|
799
638
|
/** Widest NAME column we will grow to before truncating instead. */
|
|
@@ -828,13 +667,10 @@ export function padColumn(text, width) {
|
|
|
828
667
|
* a leading column, explained by a legend line — so the name column carries just
|
|
829
668
|
* the name and `default` in it always means the profile of that name.
|
|
830
669
|
*
|
|
831
|
-
*
|
|
832
|
-
* this machine only (`local`) or in the fleet-synced `agents.yaml` (`fleet`), so
|
|
833
|
-
* "why does this box have a profile I never made here?" is answerable at a
|
|
834
|
-
* glance.
|
|
670
|
+
* WHERE lists the devices whose own files declare the name.
|
|
835
671
|
*/
|
|
836
672
|
export function formatProfilesTable(rows, configuredDefault) {
|
|
837
|
-
const cells = rows.map((
|
|
673
|
+
const cells = rows.map((profile) => {
|
|
838
674
|
const presets = getEndpointPresets(profile);
|
|
839
675
|
const endpoints = Object.entries(presets)
|
|
840
676
|
.map(([name, ep]) => (name.startsWith('endpoint-') ? ep.target : `${name}=${ep.target}`))
|
|
@@ -843,42 +679,35 @@ export function formatProfilesTable(rows, configuredDefault) {
|
|
|
843
679
|
marker: profile.name === configuredDefault ? '*' : ' ',
|
|
844
680
|
name: profile.name,
|
|
845
681
|
browser: profile.browser || '-',
|
|
846
|
-
|
|
682
|
+
where: profile.devices.join(', '),
|
|
847
683
|
description: profile.description ?? '',
|
|
848
684
|
endpoints,
|
|
849
685
|
};
|
|
850
686
|
});
|
|
851
687
|
const nameWidth = Math.min(MAX_NAME_COLUMN, Math.max('NAME'.length, ...cells.map((c) => c.name.length)));
|
|
852
688
|
const browserWidth = Math.max('BROWSER'.length, ...cells.map((c) => c.browser.length));
|
|
853
|
-
|
|
854
|
-
const scopeWidth = 'SCOPE'.length;
|
|
689
|
+
const whereWidth = Math.min(36, Math.max('WHERE'.length, ...cells.map((c) => c.where.length)));
|
|
855
690
|
const hasDescriptions = cells.some((c) => c.description);
|
|
856
691
|
const descWidth = hasDescriptions
|
|
857
692
|
? Math.min(36, Math.max('DESCRIPTION'.length, ...cells.map((c) => c.description.length)))
|
|
858
693
|
: 0;
|
|
859
|
-
const row = (marker, name, browser,
|
|
694
|
+
const row = (marker, name, browser, where, desc, endpoints) => `${marker} ` +
|
|
860
695
|
padColumn(name, nameWidth) + ' ' +
|
|
861
696
|
padColumn(browser, browserWidth) + ' ' +
|
|
862
|
-
padColumn(
|
|
697
|
+
padColumn(where, whereWidth) + ' ' +
|
|
863
698
|
(hasDescriptions ? padColumn(desc, descWidth) + ' ' : '') +
|
|
864
699
|
endpoints;
|
|
865
|
-
const header = row(' ', 'NAME', 'BROWSER', '
|
|
700
|
+
const header = row(' ', 'NAME', 'BROWSER', 'WHERE', 'DESCRIPTION', 'ENDPOINTS');
|
|
866
701
|
const lines = [header, '-'.repeat(header.length)];
|
|
867
702
|
for (const c of cells) {
|
|
868
|
-
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));
|
|
869
704
|
}
|
|
870
705
|
if (configuredDefault) {
|
|
871
706
|
lines.push('');
|
|
872
707
|
lines.push(`* = this machine's default profile (${configuredDefault}) — what a bare \`agents browser start\` uses.`);
|
|
873
708
|
}
|
|
874
|
-
lines.push(hasFleetRow(rows)
|
|
875
|
-
? 'scope: local = this machine only · fleet = synced to every machine (agents.yaml).'
|
|
876
|
-
: 'scope: local = this machine only. Create a synced one with: agents browser profiles create <name> --fleet');
|
|
877
709
|
return lines;
|
|
878
710
|
}
|
|
879
|
-
function hasFleetRow(rows) {
|
|
880
|
-
return rows.some((r) => r.scope === 'fleet');
|
|
881
|
-
}
|
|
882
711
|
/**
|
|
883
712
|
* Resolve a profile's endpoint presets into a normalized map regardless of
|
|
884
713
|
* whether the YAML uses the legacy `string[]` shape or the new map shape.
|