@phnx-labs/agents-cli 1.22.117 → 1.22.118
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 +235 -0
- package/dist/bootstrap.js +4 -11
- package/dist/commands/browser.d.ts +52 -70
- package/dist/commands/browser.js +192 -3562
- package/dist/commands/daemon.d.ts +39 -2
- package/dist/commands/daemon.js +72 -73
- package/dist/commands/events.js +1 -1
- package/dist/commands/exec.js +53 -1
- package/dist/commands/repo.js +13 -49
- package/dist/commands/route.js +5 -5
- package/dist/commands/routines.js +39 -12
- package/dist/commands/sessions-render.d.ts +1 -1
- package/dist/commands/sessions-render.js +0 -1
- package/dist/commands/sessions.test-fixture.d.ts +0 -1
- package/dist/commands/sessions.test-fixture.js +0 -37
- package/dist/commands/setup-browser.d.ts +17 -12
- package/dist/commands/setup-browser.js +52 -87
- package/dist/commands/setup-preferences.d.ts +0 -17
- package/dist/commands/setup-preferences.js +18 -49
- package/dist/commands/setup.js +9 -9
- package/dist/commands/ssh.js +10 -1
- package/dist/commands/sync.js +6 -44
- package/dist/commands/teams.js +0 -1
- package/dist/commands/update.d.ts +19 -0
- package/dist/commands/update.js +34 -6
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +47 -2
- package/dist/index.js +107 -26
- package/dist/lib/account-capabilities.js +0 -2
- package/dist/lib/account-catalog.d.ts +38 -0
- package/dist/lib/account-catalog.js +110 -26
- package/dist/lib/account-provider-registry.js +1 -1
- package/dist/lib/account-registry.d.ts +1 -1
- package/dist/lib/account-registry.js +36 -2
- package/dist/lib/accounting/rotate.js +22 -8
- package/dist/lib/accounting/usage-ingest.d.ts +6 -1
- package/dist/lib/accounting/usage-ingest.js +115 -45
- package/dist/lib/accounting/usage-sync.d.ts +134 -31
- package/dist/lib/accounting/usage-sync.js +227 -66
- package/dist/lib/accounting/usage.d.ts +16 -3
- package/dist/lib/accounting/usage.js +26 -8
- package/dist/lib/accounts/slots.d.ts +8 -0
- package/dist/lib/accounts/slots.js +17 -0
- package/dist/lib/add-dir.js +0 -1
- package/dist/lib/agent-cli-commands.js +0 -1
- package/dist/lib/agent-spec/agents.d.ts +6 -7
- package/dist/lib/agent-spec/agents.js +6 -60
- package/dist/lib/auth-health.d.ts +69 -1
- package/dist/lib/auth-health.js +133 -4
- package/dist/lib/browser/context.d.ts +82 -0
- package/dist/lib/browser/context.js +88 -0
- package/dist/lib/browser/paths.d.ts +25 -0
- package/dist/lib/browser/paths.js +51 -0
- package/dist/lib/browser/record.d.ts +38 -0
- package/dist/lib/browser/record.js +57 -0
- package/dist/lib/browser/sessions-list.js +1 -2
- package/dist/lib/browser-client.d.ts +158 -0
- package/dist/lib/browser-client.js +234 -0
- package/dist/lib/channels/owner-sink.d.ts +1 -18
- package/dist/lib/channels/owner-sink.js +29 -82
- package/dist/lib/channels/providers/rush.d.ts +7 -7
- package/dist/lib/channels/providers/rush.js +127 -57
- package/dist/lib/claude-statusline.d.ts +1 -1
- package/dist/lib/claude-statusline.js +15 -3
- package/dist/lib/commands.js +1 -1
- package/dist/lib/crabbox/lease.js +1 -1
- package/dist/lib/crabbox/runtimes.js +1 -3
- package/dist/lib/daemon/account-state-daemon-service.js +1 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
- package/dist/lib/daemon/auth-sync-service.js +37 -43
- package/dist/lib/daemon/daemon.d.ts +25 -14
- package/dist/lib/daemon/daemon.js +92 -154
- package/dist/lib/daemon/runner.d.ts +2 -2
- package/dist/lib/daemon/runner.js +32 -9
- package/dist/lib/daemon/self-update-service.d.ts +35 -34
- package/dist/lib/daemon/self-update-service.js +40 -36
- package/dist/lib/daemon/service.d.ts +12 -8
- package/dist/lib/daemon/supervisor.d.ts +65 -54
- package/dist/lib/daemon/supervisor.js +136 -171
- package/dist/lib/daemon/testdata/process-view-start.js +5 -2
- package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
- package/dist/lib/daemon/usage-sync-service.js +46 -78
- package/dist/lib/daemon-health.d.ts +22 -5
- package/dist/lib/daemon-health.js +47 -3
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +0 -10
- package/dist/lib/daemon-ticks.js +4 -2
- package/dist/lib/devices/doctor-findings.js +8 -11
- package/dist/lib/devices/harness-inventory.js +14 -5
- package/dist/lib/exec-account-home.js +5 -0
- package/dist/lib/exec.js +0 -11
- package/dist/lib/feed/events.js +0 -1
- package/dist/lib/feed/tool-activity.js +5 -2
- package/dist/lib/fleet-shared-state.d.ts +45 -0
- package/dist/lib/fleet-shared-state.js +78 -9
- package/dist/lib/fs-atomic.js +25 -0
- package/dist/lib/harness-auth-capabilities.js +0 -1
- package/dist/lib/helper-versions.js +1 -1
- package/dist/lib/hooks/cache.d.ts +7 -0
- package/dist/lib/hooks/cache.js +27 -2
- package/dist/lib/hooks/install.js +7 -1
- package/dist/lib/hosts/ready.d.ts +38 -8
- package/dist/lib/hosts/ready.js +62 -14
- package/dist/lib/installations/active-check.d.ts +27 -0
- package/dist/lib/installations/active-check.js +61 -0
- package/dist/lib/installations/migrate.js +6 -3
- package/dist/lib/installations/shims.js +9 -7
- package/dist/lib/installations/update.js +21 -15
- package/dist/lib/installations/versions.js +12 -1
- package/dist/lib/manifest.js +1 -1
- package/dist/lib/models.js +0 -2
- package/dist/lib/open-url.js +28 -54
- package/dist/lib/profiles.js +0 -2
- package/dist/lib/refresh.js +1 -1
- package/dist/lib/routine-readiness.d.ts +35 -0
- package/dist/lib/routine-readiness.js +74 -16
- package/dist/lib/rules/compile.js +6 -0
- package/dist/lib/sandbox.js +0 -2
- package/dist/lib/secrets-policy.d.ts +89 -11
- package/dist/lib/secrets-policy.js +175 -28
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +65 -18
- package/dist/lib/session/discover.js +1 -283
- package/dist/lib/session/mirror.d.ts +4 -3
- package/dist/lib/session/mirror.js +4 -3
- package/dist/lib/session/parse.d.ts +0 -2
- package/dist/lib/session/parse.js +1 -138
- package/dist/lib/session/throughput.d.ts +1 -3
- package/dist/lib/session/throughput.js +0 -20
- package/dist/lib/session/types.d.ts +2 -2
- package/dist/lib/session/types.js +2 -2
- package/dist/lib/sessions-client.d.ts +60 -1
- package/dist/lib/sessions-client.js +227 -4
- package/dist/lib/setup-tool-status.js +5 -10
- package/dist/lib/signin-badge.d.ts +3 -2
- package/dist/lib/signin-badge.js +6 -3
- package/dist/lib/smart-launch.js +4 -1
- package/dist/lib/staleness/detectors/commands.d.ts +1 -1
- package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
- package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
- package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
- package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
- package/dist/lib/staleness/detectors/rules.d.ts +1 -1
- package/dist/lib/staleness/detectors/skills.d.ts +1 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
- package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
- package/dist/lib/staleness/writers/commands.d.ts +1 -1
- package/dist/lib/staleness/writers/hooks.d.ts +1 -1
- package/dist/lib/staleness/writers/mcp.d.ts +1 -1
- package/dist/lib/staleness/writers/permissions.d.ts +1 -1
- package/dist/lib/staleness/writers/plugins.d.ts +1 -1
- package/dist/lib/staleness/writers/rules.d.ts +1 -1
- package/dist/lib/staleness/writers/skills.d.ts +1 -1
- package/dist/lib/staleness/writers/sources.js +5 -0
- package/dist/lib/staleness/writers/subagents.d.ts +1 -1
- package/dist/lib/staleness/writers/workflows.d.ts +1 -1
- package/dist/lib/state.d.ts +6 -4
- package/dist/lib/state.js +7 -17
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +1 -136
- package/dist/lib/teams/placement-probe.js +50 -15
- package/dist/lib/teams/scheduler.d.ts +7 -0
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.js +1 -1
- package/dist/lib/view-types.d.ts +32 -0
- package/dist/lib/watchdog/read.js +0 -1
- package/dist/lib/watchdog/rotate.js +1 -2
- package/dist/lib/watchdog/runner.js +2 -2
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdogTail.js +0 -31
- package/package.json +1 -1
- package/scripts/postinstall.js +8 -8
- package/dist/browser.d.ts +0 -2
- package/dist/browser.js +0 -17
- package/dist/commands/browser-picker.d.ts +0 -18
- package/dist/commands/browser-picker.js +0 -97
- package/dist/lib/browser/arc-discovery.d.ts +0 -52
- package/dist/lib/browser/arc-discovery.js +0 -197
- package/dist/lib/browser/arc-dom.d.ts +0 -14
- package/dist/lib/browser/arc-dom.js +0 -121
- package/dist/lib/browser/caller-identity.d.ts +0 -40
- package/dist/lib/browser/caller-identity.js +0 -175
- package/dist/lib/browser/cdp.d.ts +0 -51
- package/dist/lib/browser/cdp.js +0 -254
- package/dist/lib/browser/chrome.d.ts +0 -141
- package/dist/lib/browser/chrome.js +0 -704
- package/dist/lib/browser/chromium-discovery.d.ts +0 -28
- package/dist/lib/browser/chromium-discovery.js +0 -96
- package/dist/lib/browser/devices.d.ts +0 -34
- package/dist/lib/browser/devices.js +0 -61
- package/dist/lib/browser/domain-skills.d.ts +0 -71
- package/dist/lib/browser/domain-skills.js +0 -195
- package/dist/lib/browser/drivers/arc.d.ts +0 -40
- package/dist/lib/browser/drivers/arc.js +0 -276
- package/dist/lib/browser/drivers/firefox.d.ts +0 -99
- package/dist/lib/browser/drivers/firefox.js +0 -377
- package/dist/lib/browser/drivers/local.d.ts +0 -55
- package/dist/lib/browser/drivers/local.js +0 -266
- package/dist/lib/browser/drivers/ssh.d.ts +0 -120
- package/dist/lib/browser/drivers/ssh.js +0 -467
- package/dist/lib/browser/editor.d.ts +0 -3
- package/dist/lib/browser/editor.js +0 -50
- package/dist/lib/browser/ffmpeg.d.ts +0 -12
- package/dist/lib/browser/ffmpeg.js +0 -184
- package/dist/lib/browser/firefox-discovery.d.ts +0 -69
- package/dist/lib/browser/firefox-discovery.js +0 -162
- package/dist/lib/browser/har.d.ts +0 -85
- package/dist/lib/browser/har.js +0 -77
- package/dist/lib/browser/hygiene.d.ts +0 -97
- package/dist/lib/browser/hygiene.js +0 -153
- package/dist/lib/browser/index.d.ts +0 -5
- package/dist/lib/browser/index.js +0 -5
- package/dist/lib/browser/input.d.ts +0 -7
- package/dist/lib/browser/input.js +0 -92
- package/dist/lib/browser/ipc.d.ts +0 -204
- package/dist/lib/browser/ipc.js +0 -1453
- package/dist/lib/browser/login-detection.d.ts +0 -87
- package/dist/lib/browser/login-detection.js +0 -274
- package/dist/lib/browser/profiles.d.ts +0 -422
- package/dist/lib/browser/profiles.js +0 -1157
- package/dist/lib/browser/refs.d.ts +0 -89
- package/dist/lib/browser/refs.js +0 -191
- package/dist/lib/browser/registry.d.ts +0 -70
- package/dist/lib/browser/registry.js +0 -252
- package/dist/lib/browser/remote-control.d.ts +0 -59
- package/dist/lib/browser/remote-control.js +0 -85
- package/dist/lib/browser/resolve-target.d.ts +0 -66
- package/dist/lib/browser/resolve-target.js +0 -264
- package/dist/lib/browser/runtime-state.d.ts +0 -291
- package/dist/lib/browser/runtime-state.js +0 -584
- package/dist/lib/browser/secret-ref.d.ts +0 -10
- package/dist/lib/browser/secret-ref.js +0 -14
- package/dist/lib/browser/service.d.ts +0 -812
- package/dist/lib/browser/service.js +0 -4693
- package/dist/lib/browser/stream.d.ts +0 -17
- package/dist/lib/browser/stream.js +0 -72
- package/dist/lib/browser/task-index.d.ts +0 -78
- package/dist/lib/browser/task-index.js +0 -199
- package/dist/lib/browser/types.d.ts +0 -647
- package/dist/lib/browser/types.js +0 -61
- package/dist/lib/browser/upload.d.ts +0 -29
- package/dist/lib/browser/upload.js +0 -298
- package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
- package/dist/lib/daemon/browser-ipc-service.js +0 -46
- package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
- package/dist/lib/daemon/browser-task-reap-service.js +0 -26
- package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
- package/dist/lib/fleet-shared-repo-sync.js +0 -490
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
import { spawn, execFileSync } from 'child_process';
|
|
2
|
-
import * as net from 'net';
|
|
3
|
-
import { CDPClient, discoverBrowserWsUrl, verifyBrowserIdentity } from '../cdp.js';
|
|
4
|
-
import { getPortOccupant } from '../chrome.js';
|
|
5
|
-
import { parseEndpointUrl } from '../profiles.js';
|
|
6
|
-
import { writeProfileRuntime, clearProfileRuntime } from '../runtime-state.js';
|
|
7
|
-
// shellQuote lives in the shared ssh-exec helper (single choke point); re-export
|
|
8
|
-
// so existing importers of `shellQuote` from this module keep working.
|
|
9
|
-
import { shellQuote, assertValidSshTarget, controlOpts, sshConnectOpts } from '../../ssh-exec.js';
|
|
10
|
-
export { shellQuote };
|
|
11
|
-
// The `ssh -L` tunnel spawn is shared with `agents computer --device`; it lives in
|
|
12
|
-
// the single ssh-tunnel helper. Calling it with no options preserves this
|
|
13
|
-
// driver's original foreground, stderr-captured behavior exactly.
|
|
14
|
-
import { startSSHTunnel } from '../../ssh-tunnel.js';
|
|
15
|
-
import { encodePwshBase64 } from '../../pwsh.js';
|
|
16
|
-
/** Lifecycle for a registry-driven tunnel vs a profile the daemon launched. */
|
|
17
|
-
export function persistRemoteLifecycle(persistRemote) {
|
|
18
|
-
return persistRemote
|
|
19
|
-
? { launchRemote: false, killOnCleanup: false }
|
|
20
|
-
: { launchRemote: true, killOnCleanup: true };
|
|
21
|
-
}
|
|
22
|
-
export async function connectSSH(endpoint, profile,
|
|
23
|
-
/**
|
|
24
|
-
* Runtime key (`<profile>@<device>`) this connection is stored under. It
|
|
25
|
-
* keys the tunnel's runtime record; `profile.name` is the bare, user-facing
|
|
26
|
-
* name and appears only in messages (RUSH-2709).
|
|
27
|
-
*/
|
|
28
|
-
key, opts = {}) {
|
|
29
|
-
const url = new URL(endpoint);
|
|
30
|
-
if (url.protocol !== 'ssh:') {
|
|
31
|
-
throw new Error(`Invalid SSH endpoint: ${endpoint}`);
|
|
32
|
-
}
|
|
33
|
-
const user = url.username || process.env.USER || 'root';
|
|
34
|
-
// Use the shared parser so the documented `ssh://host?port=N` form works
|
|
35
|
-
// identically to `ssh://host:N`. Previously `url.port` alone meant every
|
|
36
|
-
// `?port=`-style profile silently fell back to 9222.
|
|
37
|
-
const parsed = parseEndpointUrl(endpoint);
|
|
38
|
-
if (!parsed) {
|
|
39
|
-
throw new Error(`Could not extract host:port from SSH endpoint: ${endpoint}`);
|
|
40
|
-
}
|
|
41
|
-
const host = parsed.host;
|
|
42
|
-
const remotePort = parsed.port;
|
|
43
|
-
// `&os=windows` switches the remote-command dialect (cmd.exe launch via
|
|
44
|
-
// `start`, taskkill teardown). Anything else — including absent — is posix.
|
|
45
|
-
// The query param is the single source of truth so the driver never has to
|
|
46
|
-
// be threaded a separate per-profile field.
|
|
47
|
-
const remoteOs = (url.searchParams.get('os') || '').toLowerCase() === 'windows' ? 'windows' : 'posix';
|
|
48
|
-
// Bind the tunnel to the SAME local port the user configured. Using an
|
|
49
|
-
// allocated port instead made `status` print confusing rows like
|
|
50
|
-
// `port 9200 (configured 10005)` and made it impossible to predict which
|
|
51
|
-
// local port a profile would land on. Now `ssh://host?port=N` => local N.
|
|
52
|
-
const localPort = remotePort;
|
|
53
|
-
// Preflight: if the local port is busy with something that isn't our
|
|
54
|
-
// own SSH tunnel for this very target, bail with a clear error. Letting
|
|
55
|
-
// ssh -L race ahead would either silently succeed (binding to a second
|
|
56
|
-
// port via fail-safe) or fail with cryptic stderr.
|
|
57
|
-
const occupant = getPortOccupant(localPort);
|
|
58
|
-
if (occupant && !isOwnTunnel(occupant.pid, host, remotePort)) {
|
|
59
|
-
throw new Error(`Local port ${localPort} (needed for SSH tunnel to ${host}:${remotePort}) ` +
|
|
60
|
-
`is already in use by ${occupant.command} (pid ${occupant.pid}). ` +
|
|
61
|
-
`Either kill that process (\`kill ${occupant.pid}\`) or change the profile's port.`);
|
|
62
|
-
}
|
|
63
|
-
// A non-zero remote exit (missing exe, auth failure, bad launch) now rejects
|
|
64
|
-
// with the captured stderr and propagates. An already-running browser exits 0
|
|
65
|
-
// (the launch backgrounds/WMI-spawns regardless), so this does not spuriously
|
|
66
|
-
// fail the reconnect path — only genuine launch failures reach the caller
|
|
67
|
-
// instead of being swallowed and mis-reported later as a tunnel timeout.
|
|
68
|
-
//
|
|
69
|
-
// persistRemote skips the launch: launching would mint a logged-out
|
|
70
|
-
// `--user-data-dir=/tmp/agents-browser-N` under a name that means a
|
|
71
|
-
// credentialed browser on the declaring device.
|
|
72
|
-
const lifecycle = persistRemoteLifecycle(Boolean(opts.persistRemote));
|
|
73
|
-
if (lifecycle.launchRemote) {
|
|
74
|
-
await ensureRemoteBrowser(user, host, profile.browser, remotePort, remoteOs, profile.binary);
|
|
75
|
-
}
|
|
76
|
-
let tunnel;
|
|
77
|
-
if (occupant) {
|
|
78
|
-
// Reuse the existing tunnel rather than spawning a duplicate.
|
|
79
|
-
tunnel = { pid: occupant.pid, kill: () => { try {
|
|
80
|
-
process.kill(occupant.pid);
|
|
81
|
-
}
|
|
82
|
-
catch { /* gone */ } } };
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
tunnel = await startSSHTunnel(user, host, localPort, remotePort);
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
await waitForPort(localPort, 8000);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
tunnel.kill();
|
|
92
|
-
throw new Error(`SSH tunnel failed to establish to ${host}`);
|
|
93
|
-
}
|
|
94
|
-
let wsUrl;
|
|
95
|
-
let browser;
|
|
96
|
-
try {
|
|
97
|
-
({ wsUrl, browser } = await discoverBrowserWsUrl(localPort, 'localhost', profile.name));
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
tunnel.kill();
|
|
101
|
-
if (opts.persistRemote) {
|
|
102
|
-
const why = err instanceof Error ? err.message : String(err);
|
|
103
|
-
throw new Error(`Browser profile "${profile.name}" is declared on ${host}, but nothing is speaking CDP on port ${remotePort}: ${why}`);
|
|
104
|
-
}
|
|
105
|
-
throw err;
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
verifyBrowserIdentity(browser, profile.browser, remotePort, host);
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
tunnel.kill();
|
|
112
|
-
throw err;
|
|
113
|
-
}
|
|
114
|
-
const cdp = new CDPClient();
|
|
115
|
-
await cdp.connect(wsUrl);
|
|
116
|
-
// Record the tunnel in the profile's runtime so a future daemon — or
|
|
117
|
-
// the orphan reaper after a crash — can find and clean it up. The
|
|
118
|
-
// `kind: 'tunnel'` flag distinguishes it from a locally-launched
|
|
119
|
-
// browser process.
|
|
120
|
-
const tunnelPid = tunnel.pid ?? 0;
|
|
121
|
-
if (tunnelPid > 0) {
|
|
122
|
-
writeProfileRuntime(key, {
|
|
123
|
-
pid: 0,
|
|
124
|
-
port: localPort,
|
|
125
|
-
command: 'ssh',
|
|
126
|
-
kind: 'tunnel',
|
|
127
|
-
tunnelPid,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
cdp,
|
|
132
|
-
port: localPort,
|
|
133
|
-
pid: tunnelPid,
|
|
134
|
-
cleanup: () => {
|
|
135
|
-
cdp.close();
|
|
136
|
-
// Kill the remote browser BEFORE tearing down the tunnel — but only
|
|
137
|
-
// when we launched it. persistRemote is an attach to someone else's
|
|
138
|
-
// declared browser; killing it would drop the logins the caller came
|
|
139
|
-
// for. Fire-and-forget — cleanup stays synchronous.
|
|
140
|
-
if (lifecycle.killOnCleanup) {
|
|
141
|
-
killRemoteBrowser(user, host, remoteOs, remotePort).catch(() => { });
|
|
142
|
-
}
|
|
143
|
-
tunnel.kill();
|
|
144
|
-
clearProfileRuntime(key);
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
async function waitForPort(port, timeoutMs) {
|
|
149
|
-
const start = Date.now();
|
|
150
|
-
while (Date.now() - start < timeoutMs) {
|
|
151
|
-
try {
|
|
152
|
-
await tryConnect(port);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
await sleep(200);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
throw new Error(`Port ${port} not ready after ${timeoutMs}ms`);
|
|
160
|
-
}
|
|
161
|
-
function tryConnect(port) {
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
const socket = net.createConnection({ port, host: '127.0.0.1' });
|
|
164
|
-
socket.on('connect', () => {
|
|
165
|
-
socket.destroy();
|
|
166
|
-
resolve();
|
|
167
|
-
});
|
|
168
|
-
socket.on('error', reject);
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
// macOS .app launchers — the historical POSIX table.
|
|
172
|
-
const POSIX_BROWSER_PATHS = {
|
|
173
|
-
chrome: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
174
|
-
comet: '/Applications/Comet.app/Contents/MacOS/Comet',
|
|
175
|
-
chromium: '/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
176
|
-
brave: '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
177
|
-
edge: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
178
|
-
arc: '/Applications/Arc.app/Contents/MacOS/Arc',
|
|
179
|
-
};
|
|
180
|
-
// Windows App Paths registry keys per browser. CreateProcess (used by WMI
|
|
181
|
-
// Win32_Process.Create) does not honor App Paths the way ShellExecute/`start`
|
|
182
|
-
// does, so the launch script resolves the real `.exe` path from this registry
|
|
183
|
-
// key at runtime — covering both Program Files and Program Files (x86)
|
|
184
|
-
// installs without hardcoding (or guessing) the location.
|
|
185
|
-
const WIN_BROWSER_APPPATH = {
|
|
186
|
-
chrome: 'chrome.exe',
|
|
187
|
-
chromium: 'chrome.exe',
|
|
188
|
-
brave: 'brave.exe',
|
|
189
|
-
edge: 'msedge.exe',
|
|
190
|
-
};
|
|
191
|
-
/** Single-quote a string for embedding inside a PowerShell literal. */
|
|
192
|
-
function psSingleQuote(s) {
|
|
193
|
-
return "'" + s.replace(/'/g, "''") + "'";
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Wrap a PowerShell script as a `-EncodedCommand` invocation. Base64 of the
|
|
197
|
-
* UTF-16LE bytes is a single quote-free token, so it rides through Node spawn
|
|
198
|
-
* → Windows sshd → cmd.exe with zero escaping hazards (hand-quoted
|
|
199
|
-
* `powershell -Command "…"` is fragile the moment a path or URL is involved).
|
|
200
|
-
*/
|
|
201
|
-
export function encodePowerShell(script) {
|
|
202
|
-
return `powershell -NoProfile -EncodedCommand ${encodePwshBase64(script)}`;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* The PowerShell that launches the browser on a Windows remote. Three hard
|
|
206
|
-
* requirements shaped this:
|
|
207
|
-
* 1. The browser must OUTLIVE the ssh session. Windows OpenSSH terminates
|
|
208
|
-
* the session's job tree on disconnect, which reaps both `start /B` and
|
|
209
|
-
* `Start-Process` children (verified against a real box). A scheduled
|
|
210
|
-
* task runs under the Task Scheduler service, so the process survives
|
|
211
|
-
* after we drop the ssh connection and reconnect over the CDP tunnel.
|
|
212
|
-
* 2. The browser must run in the user's INTERACTIVE session. The previous
|
|
213
|
-
* WMI `Win32_Process.Create` launch also survived disconnect, but it
|
|
214
|
-
* spawned Edge in session 0 (the services session): the CDP socket bound
|
|
215
|
-
* and accepted, yet the DevTools server never initialized — every
|
|
216
|
-
* /json/version probe hung forever and DevToolsActivePort was never
|
|
217
|
-
* written (verified against a real box, Edg/150). A scheduled task
|
|
218
|
-
* registered and started by the logged-on user runs in that user's
|
|
219
|
-
* interactive session, where DevTools comes up normally.
|
|
220
|
-
* 3. A distinct `--user-data-dir` so a fresh instance bound to the debugging
|
|
221
|
-
* port comes up even when the user already has Edge open.
|
|
222
|
-
* CreateProcess ignores App Paths, so we resolve the real `.exe` from the
|
|
223
|
-
* registry at runtime rather than relying on a bare `msedge` name. The task is
|
|
224
|
-
* unregistered immediately after start — the browser keeps running; nothing
|
|
225
|
-
* lingers in the scheduler.
|
|
226
|
-
*/
|
|
227
|
-
export function buildWindowsLaunchScript(browserType, port, customBinary) {
|
|
228
|
-
let exeStmts;
|
|
229
|
-
if (customBinary) {
|
|
230
|
-
exeStmts = [`$exe = ${psSingleQuote(customBinary)}`];
|
|
231
|
-
}
|
|
232
|
-
else if (browserType === 'custom') {
|
|
233
|
-
throw new Error('browser: custom requires a binary path in the profile');
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
const exeKey = WIN_BROWSER_APPPATH[browserType];
|
|
237
|
-
if (!exeKey)
|
|
238
|
-
throw new Error(`Unknown browser type for windows remote: ${browserType}`);
|
|
239
|
-
const appPath = `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${exeKey}`;
|
|
240
|
-
// Per-user installs — the DEFAULT for Edge and Chrome — register App Paths
|
|
241
|
-
// under HKCU, not HKLM, so an HKLM-only lookup silently missed them and the
|
|
242
|
-
// launch failed. Resolve from HKLM first, then fall through to HKCU.
|
|
243
|
-
// Windows PowerShell 5.1 (what `powershell.exe` is) has no `??`, so the
|
|
244
|
-
// fallback is an explicit `if`. A missing exe `throw`s so the failure rides
|
|
245
|
-
// out as a non-zero ssh exit (surfaced by ensureRemoteBrowser) instead of a
|
|
246
|
-
// silent empty launch.
|
|
247
|
-
exeStmts = [
|
|
248
|
-
`$exe = (Get-ItemProperty 'HKLM:\\${appPath}' -EA SilentlyContinue).'(default)'`,
|
|
249
|
-
`if (-not $exe) { $exe = (Get-ItemProperty 'HKCU:\\${appPath}' -EA SilentlyContinue).'(default)' }`,
|
|
250
|
-
`if (-not $exe) { throw 'browser exe not found in HKLM/HKCU App Paths: ${exeKey}' }`,
|
|
251
|
-
];
|
|
252
|
-
}
|
|
253
|
-
// Keep the `--remote-allow-origins=http://127.0.0.1:${port}` literal in
|
|
254
|
-
// source — a test asserts CDP is never opened to `*`.
|
|
255
|
-
return [
|
|
256
|
-
...exeStmts,
|
|
257
|
-
// First-run / default-browser modals block automation (same rationale as
|
|
258
|
-
// the local launcher in chrome.ts), and the crash-restore bubble is worse
|
|
259
|
-
// here: `browser stop` hard-kills the remote process, so the NEXT launch
|
|
260
|
-
// against the same --user-data-dir marks the profile crashed and session
|
|
261
|
-
// restore swaps the initial page target — closing its CDP websocket while
|
|
262
|
-
// a command is pending (reproduced live: Page.captureScreenshot rejected
|
|
263
|
-
// with "CDP connection closed" on every reused-profile launch).
|
|
264
|
-
`$args = '--remote-debugging-port=${port}` +
|
|
265
|
-
` --remote-allow-origins=http://127.0.0.1:${port}` +
|
|
266
|
-
` --no-first-run --no-default-browser-check` +
|
|
267
|
-
` --hide-crash-restore-bubble --disable-session-crashed-bubble` +
|
|
268
|
-
` --disable-background-timer-throttling --user-data-dir="' + $env:TEMP + '\\agents-browser-${port}"'`,
|
|
269
|
-
`$action = New-ScheduledTaskAction -Execute $exe -Argument $args`,
|
|
270
|
-
`Register-ScheduledTask -TaskName 'agents-browser-${port}' -Action $action -Force | Out-Null`,
|
|
271
|
-
`Start-ScheduledTask -TaskName 'agents-browser-${port}'`,
|
|
272
|
-
`Unregister-ScheduledTask -TaskName 'agents-browser-${port}' -Confirm:$false`,
|
|
273
|
-
].join('; ');
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* The PowerShell that kills whatever holds the CDP port on a Windows remote.
|
|
277
|
-
* Tree-kill (`taskkill /T`), not `Stop-Process`: Chromium is multi-process, and
|
|
278
|
-
* killing only the port-owning main process orphans its children. The orphans
|
|
279
|
-
* keep the profile's SingletonLock, so every later launch against the same
|
|
280
|
-
* `--user-data-dir` delegates to the zombie tree, exits, and never binds the
|
|
281
|
-
* CDP port — `browser start --device` then fails until someone hand-cleans the
|
|
282
|
-
* box (found live on win-mini by the #561 e2e suite).
|
|
283
|
-
*/
|
|
284
|
-
export function buildWindowsKillScript(port) {
|
|
285
|
-
return (`Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue ` +
|
|
286
|
-
`| ForEach-Object { taskkill /PID $_.OwningProcess /T /F 2>$null } | Out-Null`);
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Build the remote command that launches the browser detached with a CDP port.
|
|
290
|
-
* POSIX backgrounds the `.app` binary with `… &`; Windows resolves the exe and
|
|
291
|
-
* starts it via an interactive one-shot scheduled task (encoded PowerShell)
|
|
292
|
-
* so it survives the ssh session AND serves CDP (session 0 never does).
|
|
293
|
-
*/
|
|
294
|
-
export function buildLaunchCmd(remoteOs, browserType, port, customBinary) {
|
|
295
|
-
if (remoteOs === 'windows') {
|
|
296
|
-
return encodePowerShell(buildWindowsLaunchScript(browserType, port, customBinary));
|
|
297
|
-
}
|
|
298
|
-
const browserPath = customBinary ?? POSIX_BROWSER_PATHS[browserType];
|
|
299
|
-
if (!browserPath) {
|
|
300
|
-
if (browserType === 'custom') {
|
|
301
|
-
throw new Error('browser: custom requires a binary path in the profile');
|
|
302
|
-
}
|
|
303
|
-
throw new Error(`Unknown browser type for posix remote: ${browserType}`);
|
|
304
|
-
}
|
|
305
|
-
return [
|
|
306
|
-
shellQuote(browserPath),
|
|
307
|
-
`--remote-debugging-port=${port}`,
|
|
308
|
-
shellQuote(`--remote-allow-origins=http://127.0.0.1:${port}`),
|
|
309
|
-
'--disable-background-timer-throttling',
|
|
310
|
-
`--user-data-dir=/tmp/agents-browser-${port}`,
|
|
311
|
-
'</dev/null >/dev/null 2>&1 &',
|
|
312
|
-
].join(' ');
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Build the remote command that kills whatever holds the CDP port.
|
|
316
|
-
* POSIX uses `lsof`+`kill`; Windows uses encoded PowerShell
|
|
317
|
-
* (Get-NetTCPConnection → Stop-Process).
|
|
318
|
-
*/
|
|
319
|
-
export function buildKillCmd(remoteOs, port) {
|
|
320
|
-
if (remoteOs === 'windows') {
|
|
321
|
-
return encodePowerShell(buildWindowsKillScript(port));
|
|
322
|
-
}
|
|
323
|
-
return `pids=$(lsof -ti ${shellQuote(`:${port}`)} 2>/dev/null); [ -z "$pids" ] || kill -9 $pids 2>/dev/null || true`;
|
|
324
|
-
}
|
|
325
|
-
export async function ensureRemoteBrowser(user, host, browserType, port, remoteOs, customBinary) {
|
|
326
|
-
// `user`/`host` derive from an ssh:// profile endpoint (git-tracked user
|
|
327
|
-
// config). Validate the composed target before it reaches the raw `ssh` spawn:
|
|
328
|
-
// an endpoint like `ssh://-Fattack@victim` would otherwise place `-Fattack` at
|
|
329
|
-
// the target position, where OpenSSH parses it as `-F <file>` and an
|
|
330
|
-
// attacker-supplied ssh config's ProxyCommand yields local code execution.
|
|
331
|
-
assertValidSshTarget(`${user}@${host}`);
|
|
332
|
-
const remoteCmd = buildLaunchCmd(remoteOs, browserType, port, customBinary);
|
|
333
|
-
return new Promise((resolve, reject) => {
|
|
334
|
-
// Capture stderr so a real launch failure (missing exe, auth denied, bad
|
|
335
|
-
// CreateProcess) surfaces here as a clear error. Previously stdio was
|
|
336
|
-
// ignored and this resolved on close regardless of exit code, so failures
|
|
337
|
-
// were swallowed and only re-emerged 8s later as a generic
|
|
338
|
-
// "SSH tunnel failed to establish".
|
|
339
|
-
// Options BEFORE the target (matching `sshExec`): OpenSSH's BSD getopt on
|
|
340
|
-
// macOS stops at the first non-option, so any `-o` after `user@host` would
|
|
341
|
-
// be swallowed into the remote command instead of applied.
|
|
342
|
-
const child = spawn('ssh', [
|
|
343
|
-
...sshConnectOpts(controlOpts()),
|
|
344
|
-
`${user}@${host}`,
|
|
345
|
-
remoteCmd,
|
|
346
|
-
], { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
347
|
-
let stderr = '';
|
|
348
|
-
child.stderr?.on('data', (d) => {
|
|
349
|
-
stderr += d.toString();
|
|
350
|
-
});
|
|
351
|
-
let settled = false;
|
|
352
|
-
const timer = setTimeout(() => {
|
|
353
|
-
if (settled)
|
|
354
|
-
return;
|
|
355
|
-
settled = true;
|
|
356
|
-
// ssh is still running the backgrounding launch command past our window.
|
|
357
|
-
// Killing the local ssh client does NOT reap the WMI/detached remote
|
|
358
|
-
// browser, so treat this as launched and let waitForPort confirm.
|
|
359
|
-
child.kill();
|
|
360
|
-
resolve();
|
|
361
|
-
}, 2000);
|
|
362
|
-
child.on('close', (code) => {
|
|
363
|
-
if (settled)
|
|
364
|
-
return;
|
|
365
|
-
settled = true;
|
|
366
|
-
clearTimeout(timer);
|
|
367
|
-
if (code && code !== 0) {
|
|
368
|
-
const detail = stderr.trim();
|
|
369
|
-
reject(new Error(`Remote browser launch failed on ${host} (ssh exit ${code})` +
|
|
370
|
-
(detail ? `: ${detail}` : '')));
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
resolve();
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
child.on('error', (err) => {
|
|
377
|
-
if (settled)
|
|
378
|
-
return;
|
|
379
|
-
settled = true;
|
|
380
|
-
clearTimeout(timer);
|
|
381
|
-
reject(err);
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Kill the remote browser holding the CDP port. Invoked on stop/cleanup so the
|
|
387
|
-
* task-launched (Windows) / detached (posix) browser process is not orphaned when
|
|
388
|
-
* the ssh tunnel is torn down — killing only the tunnel left it running on
|
|
389
|
-
* win-mini after every `browser stop`. Best-effort: never rejects.
|
|
390
|
-
*/
|
|
391
|
-
export function killRemoteBrowser(user, host, remoteOs, port) {
|
|
392
|
-
return runSSHCommand(user, host, buildKillCmd(remoteOs, port));
|
|
393
|
-
}
|
|
394
|
-
function runSSHCommand(user, host, cmd) {
|
|
395
|
-
return new Promise((resolve, reject) => {
|
|
396
|
-
// Reject an option-injecting target (e.g. a `-`-leading user from a crafted
|
|
397
|
-
// ssh:// profile) before it reaches the raw `ssh` spawn. Reject rather than
|
|
398
|
-
// throw synchronously so `killRemoteBrowser(...).catch()` stays best-effort.
|
|
399
|
-
// See ensureRemoteBrowser.
|
|
400
|
-
try {
|
|
401
|
-
assertValidSshTarget(`${user}@${host}`);
|
|
402
|
-
}
|
|
403
|
-
catch (err) {
|
|
404
|
-
reject(err);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
// Reuse the shared hardened baseline so a hung TCP SYN to an unreachable
|
|
408
|
-
// host is bounded by ConnectTimeout (the local 3s kill below only bounds a
|
|
409
|
-
// connected-but-slow command, not the connect itself). Options precede the
|
|
410
|
-
// target — see ensureRemoteBrowser.
|
|
411
|
-
const child = spawn('ssh', [...sshConnectOpts(controlOpts()), `${user}@${host}`, cmd], {
|
|
412
|
-
stdio: 'ignore',
|
|
413
|
-
windowsHide: true,
|
|
414
|
-
});
|
|
415
|
-
child.on('close', () => resolve());
|
|
416
|
-
child.on('error', () => resolve());
|
|
417
|
-
setTimeout(() => {
|
|
418
|
-
child.kill();
|
|
419
|
-
resolve();
|
|
420
|
-
}, 3000);
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
function sleep(ms) {
|
|
424
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Identify whether a pid listening on our target local port is an SSH
|
|
428
|
-
* tunnel WE would have spawned for `host:remotePort`. Used so that two
|
|
429
|
-
* agents-browser invocations of the same SSH profile share a tunnel
|
|
430
|
-
* rather than failing the second one with "port in use".
|
|
431
|
-
*
|
|
432
|
-
* Best-effort match against the ssh -L command line via `ps`. If we
|
|
433
|
-
* can't read the cmd or the args don't look like ours, treat as not-ours.
|
|
434
|
-
*
|
|
435
|
-
* Windows has no `ps`, so the POSIX branch below always throws there and every
|
|
436
|
-
* reuse check returned false — the second invocation to the same host:port then
|
|
437
|
-
* failed "port in use". On win32 we instead reuse the netstat -ano + tasklist
|
|
438
|
-
* occupant lookup (getPortOccupant): the tunnel binds `remotePort` locally
|
|
439
|
-
* (localPort === remotePort in the caller), so the pid holding that port is our
|
|
440
|
-
* ssh.exe. tasklist only exposes the image name, not the full command line, so
|
|
441
|
-
* we can't match host/remotePort as tightly as the POSIX branch — an ssh
|
|
442
|
-
* process on our exact local port is our tunnel in practice.
|
|
443
|
-
*/
|
|
444
|
-
export function isOwnTunnel(pid, host, remotePort) {
|
|
445
|
-
if (process.platform === 'win32') {
|
|
446
|
-
const occupant = getPortOccupant(remotePort);
|
|
447
|
-
if (!occupant || occupant.pid !== pid)
|
|
448
|
-
return false;
|
|
449
|
-
return occupant.command.toLowerCase().startsWith('ssh');
|
|
450
|
-
}
|
|
451
|
-
try {
|
|
452
|
-
const out = execFileSync('ps', ['-p', String(pid), '-o', 'command='], {
|
|
453
|
-
encoding: 'utf-8',
|
|
454
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
455
|
-
}).toString().trim();
|
|
456
|
-
if (!out.startsWith('ssh'))
|
|
457
|
-
return false;
|
|
458
|
-
if (!out.includes(host))
|
|
459
|
-
return false;
|
|
460
|
-
if (!out.includes(`:${remotePort}`) && !out.includes(`:127.0.0.1:${remotePort}`))
|
|
461
|
-
return false;
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
catch {
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const BEFOREINPUT_INSERT_FN = `(function(text) {
|
|
2
|
-
this.focus();
|
|
3
|
-
var sel = window.getSelection();
|
|
4
|
-
var range = document.createRange();
|
|
5
|
-
range.selectNodeContents(this);
|
|
6
|
-
range.collapse(false);
|
|
7
|
-
sel.removeAllRanges();
|
|
8
|
-
sel.addRange(range);
|
|
9
|
-
this.dispatchEvent(new InputEvent('beforeinput', {
|
|
10
|
-
inputType: 'insertText',
|
|
11
|
-
data: text,
|
|
12
|
-
bubbles: true,
|
|
13
|
-
cancelable: true,
|
|
14
|
-
composed: true,
|
|
15
|
-
}));
|
|
16
|
-
})`;
|
|
17
|
-
const BEFOREINPUT_CLEAR_FN = `(function() {
|
|
18
|
-
this.focus();
|
|
19
|
-
var sel = window.getSelection();
|
|
20
|
-
var range = document.createRange();
|
|
21
|
-
range.selectNodeContents(this);
|
|
22
|
-
sel.removeAllRanges();
|
|
23
|
-
sel.addRange(range);
|
|
24
|
-
this.dispatchEvent(new InputEvent('beforeinput', {
|
|
25
|
-
inputType: 'deleteContentBackward',
|
|
26
|
-
bubbles: true,
|
|
27
|
-
cancelable: true,
|
|
28
|
-
composed: true,
|
|
29
|
-
}));
|
|
30
|
-
})`;
|
|
31
|
-
const TRIX_INSERT_FN = `(function(text) { this.editor.insertString(text); })`;
|
|
32
|
-
export async function typeEditorText(cdp, sessionId, node, text, clear = false) {
|
|
33
|
-
const { object } = await cdp.send('DOM.resolveNode', { backendNodeId: node.backendNodeId }, sessionId);
|
|
34
|
-
if (!object.objectId)
|
|
35
|
-
throw new Error(`Could not resolve DOM node for ref ${node.ref}`);
|
|
36
|
-
const objectId = object.objectId;
|
|
37
|
-
try {
|
|
38
|
-
if (node.editor === 'trix') {
|
|
39
|
-
await cdp.send('Runtime.callFunctionOn', { objectId, functionDeclaration: TRIX_INSERT_FN, arguments: [{ value: text }], returnByValue: true }, sessionId);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (clear) {
|
|
43
|
-
await cdp.send('Runtime.callFunctionOn', { objectId, functionDeclaration: BEFOREINPUT_CLEAR_FN, arguments: [], returnByValue: true }, sessionId);
|
|
44
|
-
}
|
|
45
|
-
await cdp.send('Runtime.callFunctionOn', { objectId, functionDeclaration: BEFOREINPUT_INSERT_FN, arguments: [{ value: text }], returnByValue: true }, sessionId);
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
await cdp.send('Runtime.releaseObject', { objectId }, sessionId);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
type Platform = NodeJS.Platform;
|
|
2
|
-
export declare function ffmpegManualCommand(platform: Platform, osRelease?: string): string;
|
|
3
|
-
export declare function resolveFfmpeg(options?: {
|
|
4
|
-
env?: NodeJS.ProcessEnv;
|
|
5
|
-
home?: string;
|
|
6
|
-
platform?: Platform;
|
|
7
|
-
osRelease?: string;
|
|
8
|
-
/** Override canonical executable directories, primarily for isolated verification. */
|
|
9
|
-
executableDirs?: string[];
|
|
10
|
-
}): Promise<string>;
|
|
11
|
-
export declare function resetFfmpegResolverForTest(): void;
|
|
12
|
-
export {};
|