pi-agent-browser-native 0.2.40 → 0.2.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +71 -18
- package/docs/ARCHITECTURE.md +8 -8
- package/docs/COMMAND_REFERENCE.md +54 -18
- package/docs/RELEASE.md +1 -1
- package/docs/SUPPORT_MATRIX.md +13 -11
- package/docs/TOOL_CONTRACT.md +39 -13
- package/extensions/agent-browser/index.ts +3 -2
- package/extensions/agent-browser/lib/config-policy.js +679 -0
- package/extensions/agent-browser/lib/config.ts +103 -351
- package/extensions/agent-browser/lib/input-modes/params.ts +1 -1
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +67 -0
- package/extensions/agent-browser/lib/playbook.ts +28 -17
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +67 -0
- package/extensions/agent-browser/lib/results/presentation/errors.ts +4 -0
- package/extensions/agent-browser/lib/runtime.ts +6 -73
- package/extensions/agent-browser/lib/web-search.ts +403 -52
- package/package.json +1 -1
- package/scripts/config.mjs +183 -99
package/scripts/config.mjs
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Purpose: Manage pi-agent-browser-native package config under Pi-scoped config paths.
|
|
4
|
-
* Responsibilities:
|
|
5
|
-
* Scope: Maintainer/user setup CLI only;
|
|
4
|
+
* Responsibilities: Thin CLI argument parsing and config-file mutation around the shared config policy; preserve safe permissions and avoid echoing secrets.
|
|
5
|
+
* Scope: Maintainer/user setup CLI only; canonical config validation, merge, provider descriptors, and status projection live in extensions/agent-browser/lib/config-policy.js.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { dirname
|
|
9
|
+
import { dirname } from "node:path";
|
|
10
10
|
import process from "node:process";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
import {
|
|
13
|
+
AGENT_BROWSER_CONFIG_ENV,
|
|
14
|
+
BRAVE_API_KEY_ENV,
|
|
15
|
+
DEFAULT_WEB_SEARCH_PROVIDER,
|
|
16
|
+
EXA_API_KEY_ENV,
|
|
17
|
+
WEB_SEARCH_PROVIDERS,
|
|
18
|
+
formatBrowserExecutableStatus,
|
|
19
|
+
formatBrowserProfileStatus,
|
|
20
|
+
getAgentBrowserConfigPaths,
|
|
21
|
+
getCredentialSourceSummary,
|
|
22
|
+
getWebSearchProviderConfigKey,
|
|
23
|
+
getWebSearchProviderEnvVar,
|
|
24
|
+
getWebSearchProviderLabel,
|
|
25
|
+
isProjectSafeCredentialValueForProvider,
|
|
26
|
+
isWebSearchProvider,
|
|
27
|
+
loadAgentBrowserConfigStateSync,
|
|
28
|
+
summarizeConfigFiles,
|
|
29
|
+
} from "../extensions/agent-browser/lib/config-policy.js";
|
|
30
|
+
|
|
15
31
|
const DEFAULT_CONFIG = { version: 1 };
|
|
16
32
|
|
|
17
33
|
class UsageError extends Error {
|
|
@@ -28,45 +44,29 @@ Usage:
|
|
|
28
44
|
pi-agent-browser-config paths
|
|
29
45
|
pi-agent-browser-config show
|
|
30
46
|
pi-agent-browser-config web-search status
|
|
31
|
-
pi-agent-browser-config web-search set-key --stdin [--global]
|
|
32
|
-
pi-agent-browser-config web-search set-env <ENV_VAR> [--global|--project]
|
|
33
|
-
pi-agent-browser-config web-search set-command <command> [--global]
|
|
34
|
-
pi-agent-browser-config web-search clear [--global|--project]
|
|
47
|
+
pi-agent-browser-config web-search set-key --stdin --provider <exa|brave> [--global]
|
|
48
|
+
pi-agent-browser-config web-search set-env <ENV_VAR> [--provider brave|exa] [--global|--project]
|
|
49
|
+
pi-agent-browser-config web-search set-command <command> --provider <exa|brave> [--global]
|
|
50
|
+
pi-agent-browser-config web-search clear --provider <exa|brave|all> [--global|--project]
|
|
51
|
+
pi-agent-browser-config web-search prefer <exa|brave|auto> [--global|--project]
|
|
52
|
+
pi-agent-browser-config web-search enable [--global|--project]
|
|
53
|
+
pi-agent-browser-config web-search disable [--global|--project]
|
|
35
54
|
pi-agent-browser-config browser profile status
|
|
36
|
-
pi-agent-browser-config browser profile set <name> [--policy explicit-only|authenticated-only|always] [--global|--project]
|
|
55
|
+
pi-agent-browser-config browser profile set <name|path> [--policy explicit-only|authenticated-only|always] [--global|--project]
|
|
37
56
|
pi-agent-browser-config browser profile clear [--global|--project]
|
|
57
|
+
pi-agent-browser-config browser executable status
|
|
58
|
+
pi-agent-browser-config browser executable set <path> [--global]
|
|
59
|
+
pi-agent-browser-config browser executable clear [--global|--project]
|
|
38
60
|
|
|
39
61
|
Notes:
|
|
40
62
|
Global config: ~/.pi/config/pi-agent-browser-native/config.json
|
|
41
63
|
Project config: .pi/config/pi-agent-browser-native/config.json
|
|
42
|
-
Override:
|
|
43
|
-
Project-local plaintext, interpolation-literal, malformed, and command-backed web-search keys are refused; use
|
|
64
|
+
Override: ${AGENT_BROWSER_CONFIG_ENV}=/path/to/config.json
|
|
65
|
+
Project-local plaintext, custom env aliases, interpolation-literal, malformed, and command-backed web-search keys are refused; use matching ${EXA_API_KEY_ENV} or ${BRAVE_API_KEY_ENV} set-env references there.
|
|
66
|
+
Use --provider for set-key, set-command, and clear; set-env infers exa/brave from ${EXA_API_KEY_ENV} or ${BRAVE_API_KEY_ENV}.
|
|
44
67
|
`;
|
|
45
68
|
}
|
|
46
69
|
|
|
47
|
-
function getHome(env = process.env) {
|
|
48
|
-
return env.HOME?.trim() || env.USERPROFILE?.trim();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getGlobalConfigPath(env = process.env) {
|
|
52
|
-
const home = getHome(env);
|
|
53
|
-
if (!home) throw new Error("Could not resolve home directory for global config.");
|
|
54
|
-
return resolve(home, ...RELATIVE_CONFIG);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getProjectConfigPath(cwd = process.cwd()) {
|
|
58
|
-
return resolve(cwd, ...RELATIVE_CONFIG);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function getPaths(env = process.env, cwd = process.cwd()) {
|
|
62
|
-
const override = env[CONFIG_ENV]?.trim();
|
|
63
|
-
return {
|
|
64
|
-
global: getGlobalConfigPath(env),
|
|
65
|
-
project: getProjectConfigPath(cwd),
|
|
66
|
-
override: override ? resolve(override) : undefined,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
70
|
function parseArgs(argv) {
|
|
71
71
|
const positional = [];
|
|
72
72
|
const flags = new Map();
|
|
@@ -80,9 +80,9 @@ function parseArgs(argv) {
|
|
|
80
80
|
flags.set(arg, true);
|
|
81
81
|
continue;
|
|
82
82
|
}
|
|
83
|
-
if (arg === "--policy") {
|
|
83
|
+
if (arg === "--policy" || arg === "--provider") {
|
|
84
84
|
const value = argv[index + 1];
|
|
85
|
-
if (!value || value.startsWith("--")) throw new UsageError(
|
|
85
|
+
if (!value || value.startsWith("--")) throw new UsageError(`${arg} requires a value.`);
|
|
86
86
|
flags.set(arg, value);
|
|
87
87
|
index += 1;
|
|
88
88
|
continue;
|
|
@@ -112,57 +112,75 @@ function writeConfig(path, config) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function selectWritePath(flags) {
|
|
115
|
-
const paths =
|
|
115
|
+
const paths = getAgentBrowserConfigPaths();
|
|
116
116
|
if (flags.get("--project")) return { path: paths.project, scope: "project" };
|
|
117
117
|
return { path: paths.global, scope: "global" };
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (trimmed.startsWith("!")) return "configured via command";
|
|
124
|
-
if (trimmed.includes("$")) return "configured via environment interpolation";
|
|
125
|
-
return "configured as plaintext [redacted]";
|
|
120
|
+
function validateWebSearchProviderArg(provider, { allowAll = false } = {}) {
|
|
121
|
+
if (isWebSearchProvider(provider) || (allowAll && provider === "all")) return provider;
|
|
122
|
+
throw new UsageError(`--provider must be one of ${allowAll ? `${WEB_SEARCH_PROVIDERS.join(", ")}, all` : WEB_SEARCH_PROVIDERS.join(", ")}.`);
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
function
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
for (const [scope, path] of [["global", paths.global], ["project", paths.project], ...(paths.override ? [["override", paths.override]] : [])]) {
|
|
132
|
-
if (!existsSync(path)) continue;
|
|
133
|
-
layers.push({ scope, path, config: readConfig(path) });
|
|
125
|
+
function inferWebSearchProviderFromEnvName(envName) {
|
|
126
|
+
for (const provider of WEB_SEARCH_PROVIDERS) {
|
|
127
|
+
if (envName === getWebSearchProviderEnvVar(provider)) return provider;
|
|
134
128
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getWebSearchProvider(flags, options = {}) {
|
|
133
|
+
const configured = flags.get("--provider");
|
|
134
|
+
if (configured) return validateWebSearchProviderArg(configured, options);
|
|
135
|
+
const inferred = options.envName ? inferWebSearchProviderFromEnvName(options.envName) : undefined;
|
|
136
|
+
if (inferred) return inferred;
|
|
137
|
+
throw new UsageError(options.allowAll ? "--provider is required and must be exa, brave, or all." : "--provider is required and must be exa or brave.");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setWebSearchCredential(config, provider, value) {
|
|
141
|
+
config.webSearch = { ...(config.webSearch ?? {}), [getWebSearchProviderConfigKey(provider)]: value };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clearWebSearchCredential(config, provider) {
|
|
145
|
+
if (config.webSearch) delete config.webSearch[getWebSearchProviderConfigKey(provider)];
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
function printPaths() {
|
|
145
|
-
const paths =
|
|
149
|
+
const paths = getAgentBrowserConfigPaths();
|
|
146
150
|
console.log(`Global: ${paths.global}`);
|
|
147
151
|
console.log(`Project: ${paths.project}`);
|
|
148
|
-
console.log(`Override: ${paths.override ?? `${
|
|
152
|
+
console.log(`Override: ${paths.override ?? `${AGENT_BROWSER_CONFIG_ENV} not set`}`);
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
function printStatus() {
|
|
152
|
-
const {
|
|
156
|
+
const state = loadAgentBrowserConfigStateSync({ cwd: process.cwd(), env: process.env });
|
|
153
157
|
printPaths();
|
|
154
158
|
console.log("");
|
|
155
159
|
console.log("Config files:");
|
|
156
|
-
for (const
|
|
157
|
-
console.log(` ${scope}: ${path} ${
|
|
160
|
+
for (const file of summarizeConfigFiles(state)) {
|
|
161
|
+
console.log(` ${file.scope}: ${file.path} ${file.exists ? "[exists]" : "[missing]"}`);
|
|
158
162
|
}
|
|
159
163
|
console.log("");
|
|
160
164
|
console.log("Effective config:");
|
|
161
|
-
|
|
162
|
-
console.log(` webSearch.
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
console.log(` webSearch.enabled: ${state.webSearchEnabled ? "true" : "false"}`);
|
|
166
|
+
console.log(` webSearch.preferredProvider: ${state.config.webSearch?.preferredProvider ?? `auto (default ${DEFAULT_WEB_SEARCH_PROVIDER})`}`);
|
|
167
|
+
for (const provider of WEB_SEARCH_PROVIDERS) {
|
|
168
|
+
const field = getWebSearchProviderConfigKey(provider);
|
|
169
|
+
console.log(` webSearch.${field}: ${getCredentialSourceSummary(state.webSearchCredentialSources[provider], provider)}`);
|
|
170
|
+
}
|
|
171
|
+
console.log(` browser.defaultProfile: ${formatBrowserProfileStatus(state)}`);
|
|
172
|
+
console.log(` browser.executablePath: ${formatBrowserExecutableStatus(state)}`);
|
|
173
|
+
if (state.layers.length === 0) console.log(" layers: none");
|
|
174
|
+
if (state.warnings.length > 0) {
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log("Warnings:");
|
|
177
|
+
for (const warning of state.warnings) console.log(` - ${warning}`);
|
|
178
|
+
}
|
|
179
|
+
if (state.errors.length > 0) {
|
|
180
|
+
console.log("");
|
|
181
|
+
console.log("Validation errors:");
|
|
182
|
+
for (const error of state.errors) console.log(` - ${error}`);
|
|
183
|
+
}
|
|
166
184
|
}
|
|
167
185
|
|
|
168
186
|
async function readSecretFromStdin(useStdin) {
|
|
@@ -187,75 +205,141 @@ async function handleWebSearch(args, flags) {
|
|
|
187
205
|
return;
|
|
188
206
|
}
|
|
189
207
|
if (action === "set-key") {
|
|
190
|
-
|
|
208
|
+
const provider = getWebSearchProvider(flags);
|
|
209
|
+
if (flags.get("--project")) throw new UsageError(`Plaintext ${getWebSearchProviderLabel(provider)} keys cannot be written to project-local config. Use set-env or set-command.`);
|
|
191
210
|
const key = await readSecretFromStdin(Boolean(flags.get("--stdin")));
|
|
192
211
|
const { path } = selectWritePath(flags);
|
|
193
212
|
mutateConfig(path, (config) => {
|
|
194
|
-
|
|
213
|
+
setWebSearchCredential(config, provider, key);
|
|
195
214
|
});
|
|
196
|
-
console.log(`Saved
|
|
215
|
+
console.log(`Saved ${getWebSearchProviderLabel(provider)} key to global config: ${path}`);
|
|
197
216
|
return;
|
|
198
217
|
}
|
|
199
218
|
if (action === "set-env") {
|
|
200
219
|
const envName = args[1];
|
|
201
220
|
if (!envName || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(envName)) throw new UsageError("set-env requires a valid environment variable name.");
|
|
221
|
+
const provider = getWebSearchProvider(flags, { envName });
|
|
222
|
+
const envReference = `$${envName}`;
|
|
223
|
+
if (flags.get("--project") && !isProjectSafeCredentialValueForProvider(envReference, provider)) {
|
|
224
|
+
throw new UsageError(`Project-local ${getWebSearchProviderLabel(provider)} env references must use ${getWebSearchProviderEnvVar(provider)} exactly; custom env aliases belong in global config or ${AGENT_BROWSER_CONFIG_ENV}.`);
|
|
225
|
+
}
|
|
202
226
|
const { path, scope } = selectWritePath(flags);
|
|
203
227
|
mutateConfig(path, (config) => {
|
|
204
|
-
|
|
228
|
+
setWebSearchCredential(config, provider, envReference);
|
|
205
229
|
});
|
|
206
|
-
console.log(`Saved
|
|
230
|
+
console.log(`Saved ${getWebSearchProviderLabel(provider)} ${scope} env reference to: ${path}`);
|
|
207
231
|
return;
|
|
208
232
|
}
|
|
209
233
|
if (action === "set-command") {
|
|
210
|
-
|
|
234
|
+
const provider = getWebSearchProvider(flags);
|
|
235
|
+
if (flags.get("--project")) throw new UsageError(`Command-backed ${getWebSearchProviderLabel(provider)} keys cannot be written to project-local config. Use set-env there.`);
|
|
211
236
|
const command = args.slice(1).join(" ").trim();
|
|
212
237
|
if (!command) throw new UsageError("set-command requires a command string.");
|
|
213
238
|
const { path, scope } = selectWritePath(flags);
|
|
214
239
|
mutateConfig(path, (config) => {
|
|
215
|
-
|
|
240
|
+
setWebSearchCredential(config, provider, `!${command}`);
|
|
216
241
|
});
|
|
217
|
-
console.log(`Saved
|
|
242
|
+
console.log(`Saved ${getWebSearchProviderLabel(provider)} ${scope} command source to: ${path}`);
|
|
218
243
|
return;
|
|
219
244
|
}
|
|
220
245
|
if (action === "clear") {
|
|
246
|
+
const provider = getWebSearchProvider(flags, { allowAll: true });
|
|
221
247
|
const { path, scope } = selectWritePath(flags);
|
|
222
248
|
mutateConfig(path, (config) => {
|
|
223
|
-
if (
|
|
249
|
+
if (provider === "all") {
|
|
250
|
+
for (const entry of WEB_SEARCH_PROVIDERS) clearWebSearchCredential(config, entry);
|
|
251
|
+
} else {
|
|
252
|
+
clearWebSearchCredential(config, provider);
|
|
253
|
+
}
|
|
224
254
|
});
|
|
225
|
-
console.log(`Cleared
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
throw new UsageError(`Unsupported web-search action: ${action ?? ""}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function handleBrowser(args, flags) {
|
|
232
|
-
if (args[0] !== "profile") throw new UsageError(`Unsupported browser action: ${args[0] ?? ""}`);
|
|
233
|
-
const action = args[1];
|
|
234
|
-
if (action === "status") {
|
|
235
|
-
printStatus();
|
|
255
|
+
console.log(`Cleared ${provider === "all" ? "all web-search" : getWebSearchProviderLabel(provider)} credential source in ${scope} config: ${path}`);
|
|
236
256
|
return;
|
|
237
257
|
}
|
|
238
|
-
if (action === "
|
|
239
|
-
const
|
|
240
|
-
if (!
|
|
241
|
-
const policy = flags.get("--policy") || "authenticated-only";
|
|
242
|
-
if (!["explicit-only", "authenticated-only", "always"].includes(policy)) throw new UsageError("Invalid --policy value.");
|
|
258
|
+
if (action === "prefer") {
|
|
259
|
+
const provider = args[1];
|
|
260
|
+
if (!provider || (!isWebSearchProvider(provider) && provider !== "auto")) throw new UsageError("prefer requires exa, brave, or auto.");
|
|
243
261
|
const { path, scope } = selectWritePath(flags);
|
|
244
262
|
mutateConfig(path, (config) => {
|
|
245
|
-
config.
|
|
263
|
+
config.webSearch = { ...(config.webSearch ?? {}) };
|
|
264
|
+
if (provider === "auto") delete config.webSearch.preferredProvider;
|
|
265
|
+
else config.webSearch.preferredProvider = provider;
|
|
246
266
|
});
|
|
247
|
-
console.log(
|
|
267
|
+
console.log(`${provider === "auto" ? "Cleared" : "Saved"} web-search preferred provider in ${scope} config: ${path}`);
|
|
248
268
|
return;
|
|
249
269
|
}
|
|
250
|
-
if (action === "
|
|
270
|
+
if (action === "enable" || action === "disable") {
|
|
251
271
|
const { path, scope } = selectWritePath(flags);
|
|
252
272
|
mutateConfig(path, (config) => {
|
|
253
|
-
|
|
273
|
+
config.webSearch = { ...(config.webSearch ?? {}), enabled: action === "enable" };
|
|
254
274
|
});
|
|
255
|
-
console.log(
|
|
275
|
+
console.log(`${action === "enable" ? "Enabled" : "Disabled"} agent_browser_web_search in ${scope} config: ${path}`);
|
|
256
276
|
return;
|
|
257
277
|
}
|
|
258
|
-
throw new UsageError(`Unsupported
|
|
278
|
+
throw new UsageError(`Unsupported web-search action: ${action ?? ""}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function handleBrowser(args, flags) {
|
|
282
|
+
const target = args[0];
|
|
283
|
+
const action = args[1];
|
|
284
|
+
if (target === "profile") {
|
|
285
|
+
if (action === "status") {
|
|
286
|
+
printStatus();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (action === "set") {
|
|
290
|
+
const name = args.slice(2).join(" ").trim();
|
|
291
|
+
if (!name) throw new UsageError("browser profile set requires a profile name or profile directory path.");
|
|
292
|
+
const policy = flags.get("--policy") || "authenticated-only";
|
|
293
|
+
if (!["explicit-only", "authenticated-only", "always"].includes(policy)) throw new UsageError("Invalid --policy value.");
|
|
294
|
+
if (flags.get("--project") && policy !== "explicit-only") {
|
|
295
|
+
throw new UsageError("Project-local browser profile config may only use --policy explicit-only; authenticated or always profile guidance must be configured globally or through PI_AGENT_BROWSER_CONFIG.");
|
|
296
|
+
}
|
|
297
|
+
const { path, scope } = selectWritePath(flags);
|
|
298
|
+
mutateConfig(path, (config) => {
|
|
299
|
+
config.browser = { ...(config.browser ?? {}), defaultProfile: { name, policy } };
|
|
300
|
+
});
|
|
301
|
+
console.log(`Saved browser default profile in ${scope} config: ${path}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (action === "clear") {
|
|
305
|
+
const { path, scope } = selectWritePath(flags);
|
|
306
|
+
mutateConfig(path, (config) => {
|
|
307
|
+
if (config.browser) delete config.browser.defaultProfile;
|
|
308
|
+
});
|
|
309
|
+
console.log(`Cleared browser default profile in ${scope} config: ${path}`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
throw new UsageError(`Unsupported browser profile action: ${action ?? ""}`);
|
|
313
|
+
}
|
|
314
|
+
if (target === "executable") {
|
|
315
|
+
if (action === "status") {
|
|
316
|
+
printStatus();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (action === "set") {
|
|
320
|
+
const executablePath = args.slice(2).join(" ").trim();
|
|
321
|
+
if (!executablePath) throw new UsageError("browser executable set requires a browser executable path.");
|
|
322
|
+
if (flags.get("--project")) {
|
|
323
|
+
throw new UsageError("Project-local browser executable config cannot steer host launch guidance; configure it globally or through PI_AGENT_BROWSER_CONFIG.");
|
|
324
|
+
}
|
|
325
|
+
const { path, scope } = selectWritePath(flags);
|
|
326
|
+
mutateConfig(path, (config) => {
|
|
327
|
+
config.browser = { ...(config.browser ?? {}), executablePath };
|
|
328
|
+
});
|
|
329
|
+
console.log(`Saved browser executable path in ${scope} config: ${path}`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (action === "clear") {
|
|
333
|
+
const { path, scope } = selectWritePath(flags);
|
|
334
|
+
mutateConfig(path, (config) => {
|
|
335
|
+
if (config.browser) delete config.browser.executablePath;
|
|
336
|
+
});
|
|
337
|
+
console.log(`Cleared browser executable path in ${scope} config: ${path}`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
throw new UsageError(`Unsupported browser executable action: ${action ?? ""}`);
|
|
341
|
+
}
|
|
342
|
+
throw new UsageError(`Unsupported browser action: ${target ?? ""}`);
|
|
259
343
|
}
|
|
260
344
|
|
|
261
345
|
export async function main(argv = process.argv.slice(2)) {
|