@rynx-ai/cli 0.1.11-beta.4 → 0.1.11-beta.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.
@@ -0,0 +1,27 @@
1
+ export { renderSystemdUnit } from "./systemd-service.js";
2
+ export type AutostartManager = "launchd" | "systemd-user" | "unsupported";
3
+ export interface AutostartState {
4
+ supported: boolean;
5
+ registered: boolean;
6
+ active?: boolean;
7
+ lingerEnabled?: boolean;
8
+ lingerEnableCommand?: string;
9
+ manager: AutostartManager;
10
+ registrationPath?: string;
11
+ detail?: string;
12
+ }
13
+ export interface AutostartRenderInput {
14
+ cliPath: string;
15
+ dataDir: string;
16
+ environment?: NodeJS.ProcessEnv;
17
+ logDir: string;
18
+ nodePath: string;
19
+ pathEnv: string;
20
+ }
21
+ export declare function autostartPlatformLabel(platform?: NodeJS.Platform): string | undefined;
22
+ export declare function inspectAutostart(): AutostartState;
23
+ export declare function enableAutostart(): AutostartState;
24
+ export declare function disableAutostart(): AutostartState;
25
+ /** Refresh paths in an existing registration without enabling a new one. */
26
+ export declare function refreshAutostart(): boolean;
27
+ export declare function renderLaunchAgent(input: AutostartRenderInput): string;
@@ -0,0 +1,229 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
+ import { homedir, userInfo } from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { rynxHome } from "@rynx-ai/core";
7
+ import { disableLinuxAutostart, enableLinuxAutostart, inspectLinuxAutostart, refreshLinuxServiceIfPresent, } from "./systemd-service.js";
8
+ export { renderSystemdUnit } from "./systemd-service.js";
9
+ const LAUNCHD_LABEL = "ai.rynx.daemon";
10
+ const COMMAND_TIMEOUT_MS = 5_000;
11
+ export function autostartPlatformLabel(platform = process.platform) {
12
+ if (platform === "darwin")
13
+ return "macOS LaunchAgent";
14
+ if (platform === "linux")
15
+ return "Linux user systemd";
16
+ return undefined;
17
+ }
18
+ export function inspectAutostart() {
19
+ if (process.platform !== "darwin" && process.platform !== "linux") {
20
+ return unsupportedState();
21
+ }
22
+ const context = currentContext();
23
+ return process.platform === "darwin" ? inspectMac(context) : inspectLinuxAutostart();
24
+ }
25
+ export function enableAutostart() {
26
+ if (process.platform !== "darwin" && process.platform !== "linux") {
27
+ throw new Error(`Rynx autostart is not supported on ${process.platform}`);
28
+ }
29
+ const context = currentContext();
30
+ if (process.platform === "darwin")
31
+ enableMac(context);
32
+ else
33
+ enableLinuxAutostart();
34
+ return inspectAutostart();
35
+ }
36
+ export function disableAutostart() {
37
+ if (process.platform !== "darwin" && process.platform !== "linux") {
38
+ throw new Error(`Rynx autostart is not supported on ${process.platform}`);
39
+ }
40
+ const context = currentContext();
41
+ if (process.platform === "darwin")
42
+ disableMac(context);
43
+ else
44
+ disableLinuxAutostart();
45
+ return inspectAutostart();
46
+ }
47
+ /** Refresh paths in an existing registration without enabling a new one. */
48
+ export function refreshAutostart() {
49
+ if (process.platform === "darwin") {
50
+ const context = currentContext();
51
+ const registrationPath = launchAgentPath(context);
52
+ if (!existsSync(registrationPath))
53
+ return false;
54
+ return syncMacRegistration(context);
55
+ }
56
+ if (process.platform === "linux")
57
+ return refreshLinuxServiceIfPresent();
58
+ return false;
59
+ }
60
+ function unsupportedState() {
61
+ return {
62
+ supported: false,
63
+ registered: false,
64
+ manager: "unsupported",
65
+ detail: `Rynx autostart is not supported on ${process.platform}`,
66
+ };
67
+ }
68
+ export function renderLaunchAgent(input) {
69
+ const outLog = path.join(input.logDir, "autostart-out.log");
70
+ const errLog = path.join(input.logDir, "autostart-err.log");
71
+ return `<?xml version="1.0" encoding="UTF-8"?>
72
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
73
+ <plist version="1.0">
74
+ <dict>
75
+ <key>Label</key>
76
+ <string>${escapeXml(LAUNCHD_LABEL)}</string>
77
+ <key>ProgramArguments</key>
78
+ <array>
79
+ <string>${escapeXml(input.nodePath)}</string>
80
+ <string>${escapeXml(input.cliPath)}</string>
81
+ <string>start</string>
82
+ </array>
83
+ <key>RunAtLoad</key>
84
+ <true/>
85
+ <key>KeepAlive</key>
86
+ <false/>
87
+ <key>WorkingDirectory</key>
88
+ <string>${escapeXml(input.dataDir)}</string>
89
+ <key>EnvironmentVariables</key>
90
+ <dict>
91
+ <key>PATH</key>
92
+ <string>${escapeXml(input.pathEnv)}</string>
93
+ <key>RYNX_HOME</key>
94
+ <string>${escapeXml(input.dataDir)}</string>
95
+ </dict>
96
+ <key>StandardOutPath</key>
97
+ <string>${escapeXml(outLog)}</string>
98
+ <key>StandardErrorPath</key>
99
+ <string>${escapeXml(errLog)}</string>
100
+ </dict>
101
+ </plist>
102
+ `;
103
+ }
104
+ function currentContext() {
105
+ const dataDir = rynxHome();
106
+ const homeDir = homedir();
107
+ return {
108
+ homeDir,
109
+ dataDir,
110
+ logDir: path.join(dataDir, "logs"),
111
+ cliPath: fileURLToPath(new URL("./cli.js", import.meta.url)),
112
+ nodePath: process.execPath,
113
+ pathEnv: process.env.PATH || defaultPath(),
114
+ };
115
+ }
116
+ function defaultPath() {
117
+ if (process.platform === "darwin") {
118
+ return "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
119
+ }
120
+ return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
121
+ }
122
+ function launchAgentPath(context) {
123
+ return path.join(context.homeDir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
124
+ }
125
+ function inspectMac(context) {
126
+ const registrationPath = launchAgentPath(context);
127
+ return {
128
+ supported: true,
129
+ registered: existsSync(registrationPath),
130
+ active: launchctlLoaded(),
131
+ manager: "launchd",
132
+ registrationPath,
133
+ };
134
+ }
135
+ function enableMac(context) {
136
+ mkdirSync(context.logDir, { recursive: true, mode: 0o700 });
137
+ syncMacRegistration(context);
138
+ }
139
+ function syncMacRegistration(context) {
140
+ const registrationPath = launchAgentPath(context);
141
+ const previous = existsSync(registrationPath)
142
+ ? readFileSync(registrationPath, "utf8")
143
+ : undefined;
144
+ const content = renderLaunchAgent(context);
145
+ if (previous === content)
146
+ return false;
147
+ const wasLoaded = launchctlLoaded();
148
+ replaceFileAtomically(registrationPath, content);
149
+ if (!wasLoaded)
150
+ return true;
151
+ if (!launchctlBootout(registrationPath)) {
152
+ restoreRegistrationFile(registrationPath, previous);
153
+ throw new Error(`launchctl could not unload ${LAUNCHD_LABEL}; the previous file was restored`);
154
+ }
155
+ if (launchctlBootstrap(registrationPath))
156
+ return true;
157
+ restoreRegistrationFile(registrationPath, previous);
158
+ if (previous !== undefined && launchctlBootstrap(registrationPath)) {
159
+ throw new Error(`launchctl could not load the updated registration; the previous job was restored`);
160
+ }
161
+ throw new Error(`launchctl could not load the updated registration and could not restore the previous job`);
162
+ }
163
+ function disableMac(context) {
164
+ const registrationPath = launchAgentPath(context);
165
+ if (launchctlLoaded() && !launchctlBootout(registrationPath)) {
166
+ throw new Error(`launchctl could not unload ${LAUNCHD_LABEL}`);
167
+ }
168
+ rmSync(registrationPath, { force: true });
169
+ }
170
+ function launchctlLoaded() {
171
+ const uid = userInfo().uid;
172
+ return run("launchctl", ["print", `gui/${uid}/${LAUNCHD_LABEL}`]).status === 0;
173
+ }
174
+ function launchctlBootstrap(registrationPath) {
175
+ const uid = userInfo().uid;
176
+ const modern = run("launchctl", [
177
+ "bootstrap",
178
+ `gui/${uid}`,
179
+ registrationPath,
180
+ ]);
181
+ if (modern.status === 0)
182
+ return true;
183
+ return run("launchctl", ["load", "-w", registrationPath]).status === 0;
184
+ }
185
+ function launchctlBootout(registrationPath) {
186
+ const uid = userInfo().uid;
187
+ const modern = run("launchctl", [
188
+ "bootout",
189
+ `gui/${uid}/${LAUNCHD_LABEL}`,
190
+ ]);
191
+ if (modern.status === 0)
192
+ return true;
193
+ return run("launchctl", ["unload", "-w", registrationPath]).status === 0;
194
+ }
195
+ function run(command, args) {
196
+ return spawnSync(command, [...args], {
197
+ encoding: "utf8",
198
+ stdio: "pipe",
199
+ timeout: COMMAND_TIMEOUT_MS,
200
+ killSignal: "SIGTERM",
201
+ });
202
+ }
203
+ function escapeXml(value) {
204
+ return value
205
+ .replaceAll("&", "&amp;")
206
+ .replaceAll("<", "&lt;")
207
+ .replaceAll(">", "&gt;");
208
+ }
209
+ function replaceFileAtomically(file, content) {
210
+ mkdirSync(path.dirname(file), { recursive: true });
211
+ const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
212
+ try {
213
+ writeFileSync(temporary, content, {
214
+ encoding: "utf8",
215
+ flag: "wx",
216
+ mode: 0o644,
217
+ });
218
+ renameSync(temporary, file);
219
+ }
220
+ finally {
221
+ rmSync(temporary, { force: true });
222
+ }
223
+ }
224
+ function restoreRegistrationFile(file, previous) {
225
+ if (previous === undefined)
226
+ rmSync(file, { force: true });
227
+ else
228
+ replaceFileAtomically(file, previous);
229
+ }
@@ -1,5 +1,5 @@
1
1
  import type { RuntimeBrowserBootstrapCredential } from "@rynx-ai/protocol/runtime-browser-bootstrap";
2
- export type BrowserCliSubcommand = "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "snapshot" | "navigate" | "click" | "type" | "screenshot" | "close";
2
+ export type BrowserCliSubcommand = "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "snapshot" | "navigate" | "click" | "type" | "screenshot" | "headers" | "close";
3
3
  export interface BrowserCliArgs {
4
4
  subcommand: BrowserCliSubcommand;
5
5
  json: boolean;
@@ -17,6 +17,12 @@ export interface BrowserCliArgs {
17
17
  quality?: number;
18
18
  x?: number;
19
19
  y?: number;
20
+ headersAction?: "get" | "set";
21
+ headers?: string[];
22
+ expectedRevision?: string;
23
+ headersEnabled?: boolean;
24
+ clearHeaders?: boolean;
25
+ showHeaderValues?: boolean;
20
26
  }
21
27
  export interface BrowserCliTarget {
22
28
  runtimeSelector: string;
@@ -29,17 +29,23 @@ const SPECS = {
29
29
  values: ["--output", "--format", "--quality", "--session"],
30
30
  flags: ["--json"],
31
31
  },
32
+ headers: {
33
+ values: ["--session", "--runtime", "--header", "--expected-revision"],
34
+ flags: ["--json", "--enable", "--disable", "--clear", "--show-values"],
35
+ positionalAction: true,
36
+ },
32
37
  close: { values: ["--session", "--runtime"], flags: ["--json"] },
33
38
  };
34
39
  export function parseBrowserCliArgs(args) {
35
40
  const subcommand = args[0];
36
41
  if (!isBrowserSubcommand(subcommand)) {
37
42
  throw new Error("browser: expected install, update, version, clean, open, status, pages, endpoint, " +
38
- "snapshot, navigate, click, type, screenshot, or close");
43
+ "snapshot, navigate, click, type, screenshot, headers, or close");
39
44
  }
40
45
  const spec = SPECS[subcommand];
41
46
  const values = new Map();
42
47
  const flags = new Set();
48
+ const headerValues = [];
43
49
  const positionals = [];
44
50
  for (let index = 1; index < args.length; index += 1) {
45
51
  const arg = args[index];
@@ -51,13 +57,17 @@ export function parseBrowserCliArgs(args) {
51
57
  continue;
52
58
  }
53
59
  if (spec.values.includes(arg)) {
54
- if (values.has(arg))
60
+ if (arg !== "--header" && values.has(arg)) {
55
61
  throw new Error(`browser ${subcommand}: duplicate option ${arg}`);
62
+ }
56
63
  const value = args[index + 1];
57
64
  if (!value || value.startsWith("--")) {
58
65
  throw new Error(`browser ${subcommand}: ${arg} requires a value`);
59
66
  }
60
- values.set(arg, value);
67
+ if (arg === "--header")
68
+ headerValues.push(value);
69
+ else
70
+ values.set(arg, value);
61
71
  index += 1;
62
72
  continue;
63
73
  }
@@ -65,13 +75,13 @@ export function parseBrowserCliArgs(args) {
65
75
  }
66
76
  positionals.push(arg);
67
77
  }
68
- if (!spec.positionalUrl && positionals.length > 0) {
78
+ if (!spec.positionalUrl && !spec.positionalAction && positionals.length > 0) {
69
79
  throw new Error(`browser ${subcommand}: unexpected argument ${positionals[0]}`);
70
80
  }
71
81
  if (positionals.length > 1) {
72
82
  throw new Error(`browser ${subcommand}: unexpected extra argument ${positionals[1]}`);
73
83
  }
74
- if (positionals.length === 1 && values.has("--url")) {
84
+ if (spec.positionalUrl && positionals.length === 1 && values.has("--url")) {
75
85
  throw new Error("browser open: pass the URL once, either positionally or with --url");
76
86
  }
77
87
  const result = {
@@ -82,10 +92,45 @@ export function parseBrowserCliArgs(args) {
82
92
  ...(values.has("--version") ? { version: values.get("--version") } : {}),
83
93
  ...(values.has("--runtime") ? { runtime: values.get("--runtime") } : {}),
84
94
  ...(values.has("--session") ? { session: values.get("--session") } : {}),
85
- ...(values.has("--url") || positionals[0]
95
+ ...(spec.positionalUrl && (values.has("--url") || positionals[0])
86
96
  ? { url: values.get("--url") ?? positionals[0] }
87
97
  : {}),
88
98
  };
99
+ if (subcommand === "headers") {
100
+ const action = positionals[0];
101
+ if (action !== "get" && action !== "set") {
102
+ throw new Error("browser headers: expected get or set");
103
+ }
104
+ if (flags.has("--enable") && flags.has("--disable")) {
105
+ throw new Error("browser headers set: choose either --enable or --disable");
106
+ }
107
+ result.headersAction = action;
108
+ if (headerValues.length > 0)
109
+ result.headers = headerValues;
110
+ if (values.has("--expected-revision")) {
111
+ result.expectedRevision = nonEmpty(values.get("--expected-revision"), "--expected-revision");
112
+ }
113
+ if (flags.has("--enable"))
114
+ result.headersEnabled = true;
115
+ if (flags.has("--disable"))
116
+ result.headersEnabled = false;
117
+ if (flags.has("--clear"))
118
+ result.clearHeaders = true;
119
+ if (flags.has("--show-values"))
120
+ result.showHeaderValues = true;
121
+ if (action === "get" && (headerValues.length > 0 ||
122
+ result.expectedRevision !== undefined ||
123
+ result.headersEnabled !== undefined ||
124
+ result.clearHeaders)) {
125
+ throw new Error("browser headers get: update options are not allowed");
126
+ }
127
+ if (action === "set" && result.showHeaderValues) {
128
+ throw new Error("browser headers set: --show-values is only valid with get");
129
+ }
130
+ if (action === "set" && headerValues.length > 0 && result.clearHeaders) {
131
+ throw new Error("browser headers set: --clear cannot be combined with --header");
132
+ }
133
+ }
89
134
  if (values.has("--ref"))
90
135
  result.ref = nonEmpty(values.get("--ref"), "--ref");
91
136
  if (values.has("--selector"))
@@ -0,0 +1 @@
1
+ export declare function runAutostartCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,58 @@
1
+ import { autostartPlatformLabel, disableAutostart, enableAutostart, inspectAutostart, } from "../autostart.js";
2
+ import { fail } from "./errors.js";
3
+ export async function runAutostartCommand(args) {
4
+ if (process.env.RYNX_DISTRIBUTION === "app") {
5
+ fail("Rynx App manages startup. Use the App's launch-at-login setting.");
6
+ }
7
+ const [action, ...rest] = args;
8
+ if (rest.length > 0)
9
+ fail(`autostart: unexpected argument ${rest[0]}`);
10
+ if (action === "enable") {
11
+ const state = enableAutostart();
12
+ console.log(`Rynx autostart enabled with ${autostartPlatformLabel() ?? state.manager}.`);
13
+ if (state.registrationPath)
14
+ console.log(`Registration: ${state.registrationPath}`);
15
+ console.log("Rynx will start at the next login. Start it now with `rynx start`.");
16
+ printLingerWarning(state);
17
+ return 0;
18
+ }
19
+ if (action === "disable") {
20
+ const state = disableAutostart();
21
+ console.log("Rynx autostart disabled.");
22
+ console.log("The running daemon was not stopped; use `rynx stop` if needed.");
23
+ if (state.registered) {
24
+ console.warn("An autostart registration is still present; run `rynx autostart status`.");
25
+ return 1;
26
+ }
27
+ return 0;
28
+ }
29
+ if (action === "status") {
30
+ printAutostartState(inspectAutostart());
31
+ return 0;
32
+ }
33
+ fail("Usage: rynx autostart <enable|disable|status>");
34
+ }
35
+ function printAutostartState(state) {
36
+ console.log(`manager: ${state.manager}`);
37
+ console.log(`supported: ${state.supported ? "yes" : "no"}`);
38
+ console.log(`registered: ${state.registered ? "yes" : "no"}`);
39
+ if (state.active !== undefined) {
40
+ console.log(`active: ${state.active ? "yes" : "no"}`);
41
+ }
42
+ if (state.lingerEnabled !== undefined) {
43
+ console.log(`linger: ${state.lingerEnabled ? "yes" : "no"}`);
44
+ }
45
+ if (state.registrationPath) {
46
+ console.log(`registration: ${state.registrationPath}`);
47
+ }
48
+ if (state.detail)
49
+ console.log(`detail: ${state.detail}`);
50
+ }
51
+ function printLingerWarning(state) {
52
+ if (state.manager !== "systemd-user" || state.lingerEnabled !== false)
53
+ return;
54
+ console.warn("Linger is disabled; Rynx stops when this user logs out.");
55
+ if (state.lingerEnableCommand) {
56
+ console.warn(`To keep it running across logout and start it before login, run manually: ${state.lingerEnableCommand}`);
57
+ }
58
+ }
@@ -1 +1,5 @@
1
+ import type { RuntimeBrowserRequestHeadersPolicy } from "@rynx-ai/protocol/runtime-browser";
1
2
  export declare function runBrowserCommand(args: readonly string[]): Promise<number>;
3
+ export declare function projectRequestHeadersPolicyForOutput(policy: RuntimeBrowserRequestHeadersPolicy, showValues: boolean): RuntimeBrowserRequestHeadersPolicy & {
4
+ valuesRedacted: boolean;
5
+ };
@@ -4,6 +4,7 @@ import { connectBrowserAutomation, } from "@rynx-ai/browser-cdp";
4
4
  import WebSocket from "ws";
5
5
  import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "../browser-cli-args.js";
6
6
  import { callManagedRuntimeBrowser, callResidentRuntime, getResidentRuntimeLocalBrowserAutomationAccess, getResidentRuntimeLocalBrowserEndpoint, readOptionalManagedRuntimeBrowserCredential, } from "../control-client.js";
7
+ import { createProgressDisplay } from "../progress-display.js";
7
8
  import { fail } from "./errors.js";
8
9
  export async function runBrowserCommand(args) {
9
10
  let parsed;
@@ -20,9 +21,18 @@ export async function runBrowserCommand(args) {
20
21
  const channel = browserReleaseChannel(parsed.channel);
21
22
  const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
22
23
  const artifacts = createBrowserArtifactManagementService();
23
- const result = parsed.subcommand === "install"
24
- ? await artifacts.install(parsed.version ? { version: parsed.version } : { channel }, parsed.json ? {} : { onProgress: (message) => console.error(message) })
25
- : await artifacts.update({ channel }, parsed.json ? {} : { onProgress: (message) => console.error(message) });
24
+ const progress = parsed.json ? undefined : createProgressDisplay();
25
+ progress?.start(parsed.subcommand === "install" ? "正在安装 Rynx Browser" : "正在更新 Rynx Browser");
26
+ let result;
27
+ try {
28
+ result = parsed.subcommand === "install"
29
+ ? await artifacts.install(parsed.version ? { version: parsed.version } : { channel }, progress ? { onProgress: (message) => progress.update(message) } : {})
30
+ : await artifacts.update({ channel }, progress ? { onProgress: (message) => progress.update(message) } : {});
31
+ }
32
+ catch (error) {
33
+ progress?.clear();
34
+ throw error;
35
+ }
26
36
  if (parsed.json) {
27
37
  console.log(JSON.stringify({
28
38
  resolved: result.resolved,
@@ -30,9 +40,7 @@ export async function runBrowserCommand(args) {
30
40
  }, null, 2));
31
41
  }
32
42
  else {
33
- for (const message of result.messages)
34
- console.log(message);
35
- console.log(`Active Browser engine: Chrome for Testing ${result.installed.version}.`);
43
+ progress.succeed(`Rynx Browser 已就绪:Chrome for Testing ${result.installed.version}`);
36
44
  }
37
45
  return 0;
38
46
  }
@@ -75,6 +83,30 @@ export async function runBrowserCommand(args) {
75
83
  return 0;
76
84
  }
77
85
  const target = await resolveBrowserTarget(parsed);
86
+ if (parsed.subcommand === "headers") {
87
+ const current = await callBrowserRuntime(target, "browser.request-headers.get", {
88
+ sessionId: target.sessionId,
89
+ });
90
+ if (parsed.headersAction === "get") {
91
+ if (parsed.showHeaderValues && target.credential) {
92
+ fail("browser headers get: --show-values is unavailable inside a managed Session; " +
93
+ "use the Session settings UI");
94
+ }
95
+ printRequestHeadersPolicy(current, parsed.json, parsed.showHeaderValues === true);
96
+ return 0;
97
+ }
98
+ const headers = parsed.clearHeaders
99
+ ? []
100
+ : parsed.headers?.map(parseCliRequestHeader) ?? current.headers;
101
+ const updated = await callBrowserRuntime(target, "browser.request-headers.set", {
102
+ sessionId: target.sessionId,
103
+ expectedRevision: parsed.expectedRevision ?? current.revision,
104
+ enabled: parsed.headersEnabled ?? current.enabled,
105
+ headers,
106
+ });
107
+ printRequestHeadersPolicy(updated, parsed.json, false);
108
+ return 0;
109
+ }
78
110
  if (isAutomationCommand(parsed.subcommand)) {
79
111
  return runBrowserAutomation(parsed, target);
80
112
  }
@@ -244,6 +276,43 @@ function printBrowserPages(state) {
244
276
  console.log(`${marker} ${page.pageId} ${page.url}${title}`);
245
277
  }
246
278
  }
279
+ function parseCliRequestHeader(value) {
280
+ const separator = value.indexOf(":");
281
+ if (separator <= 0) {
282
+ fail("browser headers set: --header must use 'Name: value'");
283
+ }
284
+ const name = value.slice(0, separator).trim();
285
+ const rawValue = value.slice(separator + 1);
286
+ return {
287
+ name,
288
+ value: rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue,
289
+ };
290
+ }
291
+ function printRequestHeadersPolicy(policy, json, showValues) {
292
+ const output = projectRequestHeadersPolicyForOutput(policy, showValues);
293
+ if (json) {
294
+ console.log(JSON.stringify(output, null, 2));
295
+ return;
296
+ }
297
+ console.log(`Browser request headers ${output.enabled ? "enabled" : "disabled"} · revision ${output.revision}`);
298
+ if (output.headers.length === 0) {
299
+ console.log(" (no configured headers)");
300
+ return;
301
+ }
302
+ for (const header of output.headers)
303
+ console.log(` ${header.name}: ${header.value}`);
304
+ }
305
+ export function projectRequestHeadersPolicyForOutput(policy, showValues) {
306
+ return {
307
+ enabled: policy.enabled,
308
+ revision: policy.revision,
309
+ valuesRedacted: !showValues,
310
+ headers: policy.headers.map((header) => ({
311
+ name: header.name,
312
+ value: showValues ? header.value : "[redacted]",
313
+ })),
314
+ };
315
+ }
247
316
  function isAutomationCommand(subcommand) {
248
317
  return subcommand === "snapshot"
249
318
  || subcommand === "navigate"
@@ -1 +1,3 @@
1
- export declare function runLifecycleCommand(command: string, args: readonly string[]): Promise<number>;
1
+ export declare function runLifecycleCommand(command: string, args: readonly string[], options?: {
2
+ quiet?: boolean;
3
+ }): Promise<number>;
@@ -1,14 +1,31 @@
1
1
  import { startStandaloneDaemon, statusStandaloneDaemon, stopStandaloneDaemon, streamStandaloneDaemonLogs, } from "../standalone.js";
2
+ import { refreshAutostart } from "../autostart.js";
3
+ import { assertLinuxSystemdInvocation, stopLinuxServiceIfActive, } from "../systemd-service.js";
2
4
  import { fail } from "./errors.js";
3
- export async function runLifecycleCommand(command, args) {
5
+ const SYSTEMD_SERVICE_FLAG = "--systemd-service";
6
+ export async function runLifecycleCommand(command, args, options = {}) {
7
+ if (args.length === 1 && args[0] === SYSTEMD_SERVICE_FLAG) {
8
+ assertLinuxSystemdInvocation();
9
+ if (command === "start")
10
+ return startStandaloneDaemon();
11
+ if (command === "stop")
12
+ return stopStandaloneDaemon();
13
+ fail(`${SYSTEMD_SERVICE_FLAG} is only valid for start and stop`);
14
+ }
4
15
  if (args.length > 0)
5
16
  fail(`${command}: unexpected argument ${args[0]}`);
6
17
  switch (command) {
7
- case "start":
8
- return startStandaloneDaemon();
9
- case "restart":
10
- return startStandaloneDaemon({ restart: true });
18
+ case "start": {
19
+ refreshAutostart();
20
+ return startStandaloneDaemon(options.quiet ? { quiet: true } : undefined);
21
+ }
22
+ case "restart": {
23
+ refreshAutostart();
24
+ return startStandaloneDaemon({ restart: true, quiet: options.quiet });
25
+ }
11
26
  case "stop":
27
+ if (stopLinuxServiceIfActive())
28
+ return 0;
12
29
  return stopStandaloneDaemon();
13
30
  case "status":
14
31
  return statusStandaloneDaemon();