@opencode/browse 0.0.6-dev.18 → 0.0.6-dev.20

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.
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- extensionProtocolVersion,
4
- extensionProtocolAdvice,
5
3
  dataRoot,
6
4
  fromCheckout,
7
5
  cliCommand,
8
6
  devExtension,
9
7
  installCommand,
10
- autoUpdateStatus
11
- } from "./main-ha5bnd77.mjs";
8
+ autoUpdateStatus,
9
+ extensionProtocolVersion,
10
+ extensionProtocolAdvice
11
+ } from "./main-ma25hhqh.mjs";
12
12
  import {
13
13
  defaultPort2,
14
14
  isValidSessionId2,
@@ -27,44 +27,45 @@ import {
27
27
  ensureExtensionConnected,
28
28
  stoppedRelayStatus,
29
29
  statusCollections
30
- } from "./main-jjny7bmm.mjs";
31
- import"./main-9y22cffn.mjs";
30
+ } from "./main-0nta0a2z.mjs";
31
+ import"./main-20ahaz2q.mjs";
32
32
  import {
33
33
  opencodeBrowserVersion2,
34
34
  opencodeBrowserBuildId2
35
- } from "./main-ppz7sv9s.mjs";
35
+ } from "./main-arx3pfyn.mjs";
36
36
  import {
37
- defaultJournalBaseDir,
38
- readJournalEntries,
39
- formatJournalEntry
40
- } from "./main-d7rbre73.mjs";
37
+ updateJson2,
38
+ formatUpdateNotice2,
39
+ updateNotice2
40
+ } from "./main-3rsfghef.mjs";
41
41
  import {
42
- addSecret,
43
- listSecrets,
44
- removeSecret,
42
+ settingKeys,
43
+ formatSettings,
44
+ ensureSessionExists,
45
45
  readClient,
46
46
  socketPath,
47
47
  connect,
48
48
  pair,
49
49
  unpair,
50
- settingKeys,
51
- formatSettings
52
- } from "./main-fkhwazsm.mjs";
50
+ addSecret,
51
+ listSecrets,
52
+ removeSecret
53
+ } from "./main-yd1d7qr1.mjs";
53
54
  import {
54
- updateJson2,
55
- formatUpdateNotice2,
56
- updateNotice2
57
- } from "./main-w5sta7hq.mjs";
55
+ defaultJournalBaseDir,
56
+ readJournalEntries,
57
+ formatJournalEntry
58
+ } from "./main-42qfr69q.mjs";
58
59
 
59
60
  // src/cli.ts
60
61
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
61
- import { Config, Console, Effect as Effect3, FileSystem as FileSystem3, Layer as Layer2, Option as Option2, Predicate as Predicate2, Result, Schema as Schema3 } from "effect";
62
- import { Argument, CliError, Command, Flag } from "effect/cli";
63
- import path from "node:path";
64
- import process from "node:process";
62
+ import { Console as Console9, Effect as Effect11, Layer as Layer2 } from "effect";
63
+ import { CliError, Command as Command8 } from "effect/cli";
64
+ import path6 from "node:path";
65
+ import process3 from "node:process";
65
66
  import { fileURLToPath, pathToFileURL } from "node:url";
66
67
 
67
- // src/cli-output.ts
68
+ // src/cli/output.ts
68
69
  import { writeSync } from "node:fs";
69
70
  var ExitCode = { ok: 0, failure: 1, usage: 2, auth: 3, host: 4 };
70
71
  var failure = (command, code, message, options = {}) => ({
@@ -102,10 +103,15 @@ var writeStdout = (text, fd = 1) => {
102
103
  var printJson = (value) => writeStdout(`${JSON.stringify(value, null, 2)}
103
104
  `);
104
105
 
106
+ // src/cli/browser.ts
107
+ import { Console as Console2, Effect as Effect4, FileSystem as FileSystem3, Option as Option3, Result } from "effect";
108
+ import { Argument, Command, Flag as Flag2 } from "effect/cli";
109
+ import path from "node:path";
110
+
105
111
  // src/doctor.ts
106
112
  import { Effect as Effect2, FileSystem as FileSystem2, Path as Path2, Schema as Schema2 } from "effect";
107
113
 
108
- // src/session-store.ts
114
+ // src/session-manager/store.ts
109
115
  import { Context, Effect, FileSystem, Layer, Option, Path, Predicate, Schema } from "effect";
110
116
  class SessionStoreError extends Schema.TaggedError()("SessionStore.SessionStoreError", {
111
117
  message: Schema.String,
@@ -600,8 +606,10 @@ function formatTargetSummary(target, options = {}) {
600
606
  return `${target.type} ${target.id}${tab}${session}${owner}${health} ${target.url || "about:blank"}`;
601
607
  }
602
608
 
603
- // src/cli.ts
604
- var packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
609
+ // src/cli/context.ts
610
+ import { Config, Console, Effect as Effect3, Option as Option2, Predicate as Predicate2 } from "effect";
611
+ import { Flag } from "effect/cli";
612
+ import process from "node:process";
605
613
  var sessionIdConfig = Config.option(Config.String("OPENCODE_BROWSER_SESSION"));
606
614
  var agentSessionIdConfig = Config.option(Config.String("OPENCODE_SESSION_ID"));
607
615
  var targetUrlConfig = Config.option(Config.String("OPENCODE_BROWSER_TARGET_URL"));
@@ -623,7 +631,11 @@ var failExit = Effect3.sync(() => {
623
631
  var exitWith = (code) => Effect3.sync(() => {
624
632
  process.exitCode = code;
625
633
  });
626
- var hostErrorTags = new Set(["RelayClient.RelayUnreachable", "RelayLifecycle.RelayStartFailed", "RelayLifecycle.ExtensionDisconnected"]);
634
+ var hostErrorTags = new Set([
635
+ "RelayClient.RelayUnreachable",
636
+ "RelayLifecycle.RelayStartFailed",
637
+ "RelayLifecycle.ExtensionDisconnected"
638
+ ]);
627
639
  function exitCodeForError(error) {
628
640
  if (error instanceof RelayRejected && error.code === "browser-unavailable")
629
641
  return ExitCode.host;
@@ -684,10 +696,9 @@ function decodeCliOperands(operands) {
684
696
  return decodeURIComponent(operand.slice(encodedCliOperandPrefix.length));
685
697
  });
686
698
  }
687
- var readExecuteFile = Effect3.fnUntraced(function* (filePath) {
688
- const fs = yield* FileSystem3.FileSystem;
689
- return yield* fs.readFileString(path.resolve(filePath)).pipe(Effect3.mapError((cause) => new Error(`read execute file ${filePath}: ${cause.reason.message}`, { cause })));
690
- });
699
+ function formatSessionContinuation(sessionId) {
700
+ return `Session: ${sessionId}. Continue with --session ${sessionId}.`;
701
+ }
691
702
  var ensureCliRelay = Effect3.fnUntraced(function* () {
692
703
  const relay = yield* Service;
693
704
  const readiness = yield* ensureRelay({ relay });
@@ -724,16 +735,10 @@ var resolvePositionalOrFlagSessionId = Effect3.fnUntraced(function* (id, session
724
735
  environment: Option2.getOrUndefined(yield* sessionIdConfig)
725
736
  }));
726
737
  });
727
- var ensureSessionExists = Effect3.fnUntraced(function* (id) {
738
+ var ensureSessionExists2 = Effect3.fnUntraced(function* (id) {
728
739
  const relay = yield* Service;
729
740
  yield* ensureCliRelay();
730
- const sessions = yield* relay.sessions;
731
- const exists = sessions.some((session) => {
732
- return session.id === id;
733
- });
734
- if (!exists) {
735
- return yield* Effect3.fail(new Error(`Session not found: ${id}`));
736
- }
741
+ yield* ensureSessionExists(relay, id);
737
742
  });
738
743
  var recordingTarget = Effect3.fnUntraced(function* (options) {
739
744
  const sessionId = Option2.getOrUndefined(options.session);
@@ -755,6 +760,229 @@ var parseRecordingModeOption = Effect3.fnUntraced(function* (value) {
755
760
  }
756
761
  return yield* Effect3.fail(new Error("Recording mode must be auto, tab-capture, or cdp"));
757
762
  });
763
+ var networkSession = Effect3.fnUntraced(function* (session) {
764
+ return yield* resolveSelectedSessionId(Option2.getOrUndefined(session) ?? Option2.getOrUndefined(yield* sessionIdConfig));
765
+ });
766
+
767
+ // src/cli/registry.ts
768
+ var entries = [];
769
+ function registerCliCommand(order, build) {
770
+ entries.push({
771
+ order,
772
+ build
773
+ });
774
+ }
775
+ function buildRegisteredCliCommands(ctx) {
776
+ return [...entries].sort((left, right) => left.order - right.order).map((entry) => entry.build(ctx));
777
+ }
778
+
779
+ // src/cli/browser.ts
780
+ var tabsCleanup = Command.make("cleanup", {
781
+ idleMinutes: Flag2.Int("idle-minutes").pipe(Flag2.optional, Flag2.withDescription("Close agent tabs idle at least this long; 0 closes every eligible agent tab (default: the extension's setting, 10; still 10 when it is set to never)")),
782
+ json: jsonFlag
783
+ }, Effect4.fn("Cli.tabsCleanup")(function* ({ idleMinutes, json }) {
784
+ const relay = yield* Service;
785
+ yield* ensureCliRelay();
786
+ const result = yield* relay.tabsCleanup(Option3.getOrUndefined(idleMinutes));
787
+ if (json)
788
+ return yield* emitJson(result);
789
+ if (!result.closed.length)
790
+ return yield* Console2.log("No idle agent tabs to close.");
791
+ for (const tab of result.closed)
792
+ yield* Console2.log(`Closed ${tab.title} (idle ${tab.idleMinutes}m)`);
793
+ })).pipe(Command.withDescription("Close tabs agents opened that nobody has used for a while"));
794
+ var tabs = Command.make("tabs").pipe(Command.withDescription("Manage the tabs agents open"), Command.withSubcommands([tabsCleanup]));
795
+ var printSettings = (settings) => Effect4.forEach(formatSettings(settings), (line) => Console2.log(line), { discard: true });
796
+ var settingsGet = Command.make("get", { json: jsonFlag }, Effect4.fn("Cli.settingsGet")(function* ({ json }) {
797
+ const relay = yield* Service;
798
+ yield* ensureCliRelay();
799
+ const result = yield* relay.settingsGet;
800
+ if (json)
801
+ return yield* emitJson(result);
802
+ yield* printSettings(result.settings);
803
+ yield* Console2.log(result.note);
804
+ })).pipe(Command.withDescription("Show each setting's value, the value in effect, and any relay environment variable overriding it"));
805
+ var settingsSet = Command.make("set", {
806
+ assignments: Argument.String("key=value").pipe(Argument.variadic({ min: 1 }), Argument.withDescription(`Settings to change: ${settingKeys.join(", ")}`)),
807
+ json: jsonFlag
808
+ }, Effect4.fn("Cli.settingsSet")(function* ({ assignments, json }) {
809
+ const changes = {};
810
+ for (const assignment of assignments) {
811
+ const index = assignment.indexOf("=");
812
+ if (index <= 0)
813
+ return yield* Effect4.fail(new Error(`Expected key=value, got "${assignment}". Settings: ${settingKeys.join(", ")}.`));
814
+ changes[assignment.slice(0, index)] = assignment.slice(index + 1);
815
+ }
816
+ const relay = yield* Service;
817
+ yield* ensureCliRelay();
818
+ const result = yield* relay.settingsSet(changes);
819
+ if (json)
820
+ return yield* emitJson(result);
821
+ yield* Console2.log(result.message);
822
+ yield* printSettings(result.settings);
823
+ })).pipe(Command.withDescription("Change settings (validated strictly; the extension's options page updates live)"));
824
+ var settings = Command.make("settings").pipe(Command.withDescription("Read or change the user's settings (the extension's options page). Security switches are environment-only"), Command.withSubcommands([settingsGet, settingsSet]));
825
+ var status = Command.make("status", {
826
+ json: jsonFlag
827
+ }, Effect4.fn("Cli.status")(function* ({ json }) {
828
+ const relay = yield* Service;
829
+ const store = yield* Service2;
830
+ const [relayResult, update] = yield* Effect4.all([Effect4.result(relay.version), Effect4.promise(() => updateNotice2())], { concurrency: 2 });
831
+ const updateLine = update ? formatUpdateNotice2(update) : undefined;
832
+ if (Result.isFailure(relayResult)) {
833
+ if (!(relayResult.failure instanceof RelayUnreachable)) {
834
+ if (json) {
835
+ yield* emitJson({
836
+ endpoint: relay.endpoint,
837
+ relay: { running: false, error: relayResult.failure.message },
838
+ extension: null,
839
+ sessions: [],
840
+ targets: [],
841
+ update: updateJson2(update)
842
+ });
843
+ }
844
+ yield* stderrLine(`Relay status failed: ${relayResult.failure.message}`);
845
+ yield* exitWith(ExitCode.host);
846
+ return;
847
+ }
848
+ const stopped = stoppedRelayStatus(relay.endpoint);
849
+ if (json) {
850
+ yield* emitJson({ ...stopped, update: updateJson2(update), autoUpdate: autoUpdateStatus() });
851
+ yield* stderrLine(`relay is not running at ${relay.endpoint}`);
852
+ } else {
853
+ yield* Console2.log(`Relay: stopped (${relay.endpoint})`);
854
+ yield* Console2.log(`Run \`${cliCommand("execute")}\` (or any relay-backed command) to start it automatically.`);
855
+ if (updateLine)
856
+ yield* Console2.log(updateLine);
857
+ }
858
+ yield* exitWith(ExitCode.host);
859
+ return;
860
+ }
861
+ const version = relayResult.success;
862
+ const buildProblem = relayBuildProblem(version);
863
+ const [extensionStatus, current] = yield* Effect4.all([relay.extensionStatus, store.read]);
864
+ const collections = statusCollections(extensionStatus);
865
+ const [sessions, targets] = collections ? [collections.sessions, collections.targets] : yield* Effect4.all([relay.sessions, relay.targets]);
866
+ if (json) {
867
+ yield* emitJson({
868
+ endpoint: relay.endpoint,
869
+ relay: {
870
+ running: true,
871
+ version: version.version,
872
+ buildId: version.buildId ?? null,
873
+ stale: buildProblem !== undefined
874
+ },
875
+ extension: extensionStatus,
876
+ currentSession: current ?? null,
877
+ sessions,
878
+ targets,
879
+ update: updateJson2(update),
880
+ autoUpdate: autoUpdateStatus()
881
+ });
882
+ if (buildProblem) {
883
+ yield* stderrLine(buildProblem);
884
+ yield* failExit;
885
+ }
886
+ return;
887
+ }
888
+ if (updateLine)
889
+ yield* Console2.log(updateLine);
890
+ yield* Console2.log(`Relay: ${relay.endpoint} (${version.version})`);
891
+ if (buildProblem) {
892
+ yield* Console2.log(`Warning: ${buildProblem}`);
893
+ }
894
+ yield* Console2.log(`Extension: ${extensionStatus.connected ? "connected" : "disconnected"}${extensionStatus.version ? ` (${extensionStatus.version})` : ""}`);
895
+ if ((extensionStatus.rejectedConnections ?? 0) > 0) {
896
+ yield* Console2.log(`Warning: ${extensionStatus.rejectedConnections} competing browser/profile connection attempt(s) rejected; the active connection was preserved. Use Browse in one browser/profile at a time.`);
897
+ }
898
+ if (extensionStatus.nextStep)
899
+ yield* Console2.log(extensionStatus.nextStep);
900
+ else if ((extensionStatus.pairingFailures ?? 0) > 0 && !extensionStatus.connected) {
901
+ yield* Console2.log(`Warning: ${extensionStatus.pairingFailures} extension connection(s) failed pairing. Update the extension, or run \`${cliCommand("pair")}\` and enter the code on its status page.`);
902
+ }
903
+ if ((extensionStatus.processFaults?.suppressed ?? 0) > 0) {
904
+ const faults = extensionStatus.processFaults;
905
+ yield* Console2.log(`Warning: the relay survived ${faults.suppressed} Playwright fault(s)${faults.lastAt ? `, last at ${faults.lastAt}` : ""}${faults.lastMessage ? `: ${faults.lastMessage}` : ""}. See the relay log.`);
906
+ }
907
+ if (extensionStatus.protocolVersion !== undefined && extensionStatus.protocolVersion !== null) {
908
+ const compatibility = extensionStatus.protocolCompatible === false ? "incompatible" : "compatible";
909
+ const legacy = extensionStatus.protocolLegacy === true ? ", inferred from legacy hello" : "";
910
+ yield* Console2.log(`Extension protocol: ${extensionStatus.protocolVersion} (${compatibility}${legacy})`);
911
+ if (extensionStatus.protocolCompatible === false && !extensionStatus.nextStep)
912
+ yield* Console2.log(extensionProtocolAdvice(extensionStatus.protocolVersion, { install: installCommand() }));
913
+ }
914
+ yield* Console2.log(`Active targets: ${extensionStatus.activeTargets}`);
915
+ if (extensionStatus.childTargets !== undefined) {
916
+ yield* Console2.log(`Child targets: ${extensionStatus.childTargets}`);
917
+ }
918
+ if (extensionStatus.cdpClients !== undefined) {
919
+ yield* Console2.log(`CDP clients: ${extensionStatus.cdpClients}`);
920
+ }
921
+ yield* Console2.log(`Current session: ${current ?? "none"}`);
922
+ if (sessions.length === 0) {
923
+ yield* Console2.log("Sessions: none");
924
+ } else {
925
+ yield* Console2.log("Sessions:");
926
+ const activeSessions = sessions.filter((item) => item.id === current || item.connected || item.pageUrl !== null || item.stateKeys.length > 0 || item.readOnly);
927
+ const idleSessions = sessions.filter((item) => !activeSessions.includes(item));
928
+ const shownSessions = activeSessions.length > 0 ? activeSessions : idleSessions.slice(0, 5);
929
+ const omittedIdleCount = activeSessions.length > 0 ? idleSessions.length : Math.max(0, idleSessions.length - shownSessions.length);
930
+ yield* Effect4.forEach(shownSessions, (item) => {
931
+ const marker = item.id === current ? "*" : " ";
932
+ return Console2.log(`${marker} ${item.id} ${item.pageUrl ?? "no page yet"}`);
933
+ });
934
+ if (omittedIdleCount > 0) {
935
+ yield* Console2.log(` (${omittedIdleCount} idle session(s) with no page omitted; run ${cliCommand("session list")} to view all)`);
936
+ }
937
+ }
938
+ if (targets.length === 0) {
939
+ yield* Console2.log("Targets: none");
940
+ } else {
941
+ yield* Console2.log("Targets:");
942
+ yield* Effect4.forEach(targets, (target, index) => {
943
+ return Console2.log(`- [${index}] ${formatTargetSummary(target, { includeSession: true })}`);
944
+ });
945
+ }
946
+ if (buildProblem) {
947
+ yield* failExit;
948
+ }
949
+ })).pipe(Command.withDescription("Show relay, extension, and target status"));
950
+ var mcp = Command.make("mcp", {}, Effect4.fn("Cli.mcp")(function* () {
951
+ const { runMcpServer } = yield* Effect4.promise(() => import("./mcp-j9e2da4y.mjs"));
952
+ yield* runMcpServer;
953
+ })).pipe(Command.withDescription("Run the Browse MCP server over stdio"));
954
+ registerCliCommand(400, () => tabs);
955
+ registerCliCommand(410, () => settings);
956
+ registerCliCommand(500, () => status);
957
+ registerCliCommand(810, ({ packageRoot }) => Command.make("doctor", {
958
+ json: jsonFlag
959
+ }, Effect4.fn("Cli.doctor")(function* ({ json }) {
960
+ const update = yield* Effect4.promise(() => updateNotice2());
961
+ const report = yield* createDoctorReport({ packageRoot, update: updateJson2(update) });
962
+ if (json) {
963
+ yield* emitJson(report);
964
+ } else {
965
+ yield* Console2.log(formatDoctorReport(report));
966
+ }
967
+ if (report.status === "fail") {
968
+ yield* failExit;
969
+ }
970
+ })).pipe(Command.withDescription("Diagnose the local Browse install and runtime")));
971
+ registerCliCommand(820, ({ packageRoot }) => Command.make("skill", {}, Effect4.fn("Cli.skill")(function* () {
972
+ const fs = yield* FileSystem3.FileSystem;
973
+ const text = yield* fs.readFileString(path.join(packageRoot, "skills", "browse", "SKILL.md")).pipe(Effect4.mapError((cause) => new Error("read opencode-browser skill", { cause })));
974
+ yield* Console2.log(text.trimEnd());
975
+ })).pipe(Command.withDescription("Print the Browse agent skill text")));
976
+ registerCliCommand(900, () => mcp);
977
+
978
+ // src/cli/execute.ts
979
+ import { Console as Console3, Effect as Effect5, FileSystem as FileSystem4, Option as Option4, Predicate as Predicate3, Result as Result2, Schema as Schema3 } from "effect";
980
+ import { Argument as Argument2, Command as Command2, Flag as Flag3 } from "effect/cli";
981
+ import path2 from "node:path";
982
+ var readExecuteFile = Effect5.fnUntraced(function* (filePath) {
983
+ const fs = yield* FileSystem4.FileSystem;
984
+ return yield* fs.readFileString(path2.resolve(filePath)).pipe(Effect5.mapError((cause) => new Error(`read execute file ${filePath}: ${cause.reason.message}`, { cause })));
985
+ });
758
986
  function formatExecuteLogs(logs) {
759
987
  return [
760
988
  "Console logs:",
@@ -800,73 +1028,59 @@ function executeJsonEnvelope(result) {
800
1028
  session: result.session
801
1029
  };
802
1030
  }
803
- function formatSessionContinuation(sessionId) {
804
- return `Session: ${sessionId}. Continue with --session ${sessionId}.`;
805
- }
806
1031
  function errorJsonEnvelope(error) {
807
- const tag = Predicate2.isObject(error) && Predicate2.hasProperty(error, "_tag") && Predicate2.isString(error._tag) ? error._tag : "Error";
1032
+ const tag = Predicate3.isObject(error) && Predicate3.hasProperty(error, "_tag") && Predicate3.isString(error._tag) ? error._tag : "Error";
808
1033
  const message = error instanceof Error ? error.message : String(error);
809
1034
  const errorValue = Object.assign({ message }, { _tag: tag });
810
- return { ok: false, isError: true, text: message, value: null, valueUnavailable: true, error: errorValue, logs: [], warnings: [] };
1035
+ return {
1036
+ ok: false,
1037
+ isError: true,
1038
+ text: message,
1039
+ value: null,
1040
+ valueUnavailable: true,
1041
+ error: errorValue,
1042
+ logs: [],
1043
+ warnings: []
1044
+ };
811
1045
  }
812
- var serve = Command.make("serve", {}, Effect3.fn("Cli.serve")(function* () {
813
- const [{ startRelay }, { parseAdditionalExtensionOrigins }] = yield* Effect3.promise(() => Promise.all([import("./relay-zs96tkfm.mjs"), import("./relay-helpers-6a2vvzx9.mjs")]));
814
- const port = yield* portConfig;
815
- const additionalExtensionOrigins = parseAdditionalExtensionOrigins(yield* extensionOriginsConfig);
816
- yield* Effect3.scoped(Effect3.gen(function* () {
817
- const relay = yield* startRelay({ port, additionalExtensionOrigins });
818
- yield* Console.log(`Browse relay listening at ${relay.url}`);
819
- yield* Console.log(`Browse connects automatically while the browser runs. Run \`${cliCommand("status")}\` to check.`);
820
- yield* Effect3.never;
821
- }));
822
- })).pipe(Command.withDescription("Start the local Browse relay"));
823
- var relayRestart = Command.make("restart", {
824
- force: Flag.Boolean("force").pipe(Flag.withDefault(false), Flag.withDescription("End network captures and recordings (discarding them) and disconnect raw CDP clients instead of refusing to restart"))
825
- }, Effect3.fn("Cli.relayRestart")(function* ({ force }) {
826
- const relay = yield* Service;
827
- yield* Console.error("Restarting the relay leaves browser tabs open but resets in-memory JavaScript state.");
828
- const readiness = yield* restartRelay({ relay, clientKind: "cli", force });
829
- yield* Console.log(`${readiness.started ? "Started" : "Reused concurrent replacement"} Browse relay at ${relay.endpoint} (${readiness.version.buildId})`);
830
- })).pipe(Command.withDescription("Explicitly drain and restart the managed relay using this installation"));
831
- var relay = Command.make("relay").pipe(Command.withDescription("Manage the local relay lifecycle"), Command.withSubcommands([relayRestart]));
832
- var execute = Command.make("execute", {
833
- code: Argument.String("code").pipe(Argument.variadic({ min: 0 })),
834
- file: Flag.String("file").pipe(Flag.optional, Flag.withDescription("Read execute code from a file")),
1046
+ var execute = Command2.make("execute", {
1047
+ code: Argument2.String("code").pipe(Argument2.variadic({ min: 0 })),
1048
+ file: Flag3.String("file").pipe(Flag3.optional, Flag3.withDescription("Read execute code from a file")),
835
1049
  session: sessionFlag("Continue an existing Browse session; defaults to the OpenCode agent's session"),
836
- rm: Flag.Boolean("rm").pipe(Flag.withDefault(false), Flag.withDescription("Use a fresh one-shot session and close its tabs afterward")),
837
- timeoutMs: Flag.Int("timeout-ms").pipe(Flag.optional, Flag.withDescription("Overall script timeout in milliseconds (paused during handoff); defaults to 60000, 0 disables")),
838
- targetUrl: Flag.String("target-url").pipe(Flag.optional, Flag.withDescription("Use the attached page whose URL contains this text")),
839
- targetIndex: Flag.Int("target-index").pipe(Flag.optional, Flag.withDescription("Use the attached page at this zero-based index")),
840
- json: Flag.Boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print a machine-readable result envelope: { ok, isError, text, value, valueUnavailable, error?, logs, warnings, diagnostic?, aftermath, session }"))
841
- }, Effect3.fn("Cli.execute")(function* ({ code, file, session, rm, timeoutMs, targetUrl, targetIndex, json }) {
842
- const run = Effect3.gen(function* () {
1050
+ rm: Flag3.Boolean("rm").pipe(Flag3.withDefault(false), Flag3.withDescription("Use a fresh one-shot session and close its tabs afterward")),
1051
+ timeoutMs: Flag3.Int("timeout-ms").pipe(Flag3.optional, Flag3.withDescription("Overall script timeout in milliseconds (paused during handoff); defaults to 60000, 0 disables")),
1052
+ targetUrl: Flag3.String("target-url").pipe(Flag3.optional, Flag3.withDescription("Use the attached page whose URL contains this text")),
1053
+ targetIndex: Flag3.Int("target-index").pipe(Flag3.optional, Flag3.withDescription("Use the attached page at this zero-based index")),
1054
+ json: Flag3.Boolean("json").pipe(Flag3.withDefault(false), Flag3.withDescription("Print a machine-readable result envelope: { ok, isError, text, value, valueUnavailable, error?, logs, warnings, diagnostic?, aftermath, session }"))
1055
+ }, Effect5.fn("Cli.execute")(function* ({ code, file, session, rm, timeoutMs, targetUrl, targetIndex, json }) {
1056
+ const run = Effect5.gen(function* () {
843
1057
  const relay = yield* Service;
844
- const filePath = Option2.getOrUndefined(file);
1058
+ const filePath = Option4.getOrUndefined(file);
845
1059
  if (code.length > 0 && filePath) {
846
- return yield* Effect3.fail(new Error("Use either positional code or --file, not both"));
1060
+ return yield* Effect5.fail(new Error("Use either positional code or --file, not both"));
847
1061
  }
848
1062
  if (code.length === 0 && !filePath) {
849
- return yield* Effect3.fail(new Error("Execute requires positional code or --file <path>"));
1063
+ return yield* Effect5.fail(new Error("Execute requires positional code or --file <path>"));
850
1064
  }
851
- const timeoutMsValue = Option2.getOrUndefined(timeoutMs);
1065
+ const timeoutMsValue = Option4.getOrUndefined(timeoutMs);
852
1066
  if (timeoutMsValue !== undefined && timeoutMsValue < 0) {
853
- return yield* Effect3.fail(new Error("Timeout must be a non-negative integer"));
1067
+ return yield* Effect5.fail(new Error("Timeout must be a non-negative integer"));
854
1068
  }
855
1069
  const executeCode = filePath ? yield* readExecuteFile(filePath) : code.join(" ");
856
- const explicitSessionId = Option2.getOrUndefined(session) ?? Option2.getOrUndefined(yield* sessionIdConfig);
1070
+ const explicitSessionId = Option4.getOrUndefined(session) ?? Option4.getOrUndefined(yield* sessionIdConfig);
857
1071
  if (rm && explicitSessionId) {
858
- return yield* Effect3.fail(new Error("Use --rm without --session or OPENCODE_BROWSER_SESSION"));
1072
+ return yield* Effect5.fail(new Error("Use --rm without --session or OPENCODE_BROWSER_SESSION"));
859
1073
  }
860
- const agentWorkSessionId = agentDefaultSessionId(Option2.getOrUndefined(yield* agentSessionIdConfig));
1074
+ const agentWorkSessionId = agentDefaultSessionId(Option4.getOrUndefined(yield* agentSessionIdConfig));
861
1075
  const workSessionId = explicitSessionId ?? (rm ? undefined : agentWorkSessionId);
862
1076
  yield* ensureCliRelayAndExtension(workSessionId);
863
- const targetUrlValue = Option2.getOrUndefined(targetUrl) ?? Option2.getOrUndefined(yield* targetUrlConfig);
864
- const targetIndexValue = Option2.getOrUndefined(targetIndex) ?? Option2.getOrUndefined(yield* targetIndexConfig);
1077
+ const targetUrlValue = Option4.getOrUndefined(targetUrl) ?? Option4.getOrUndefined(yield* targetUrlConfig);
1078
+ const targetIndexValue = Option4.getOrUndefined(targetIndex) ?? Option4.getOrUndefined(yield* targetIndexConfig);
865
1079
  if (targetIndexValue !== undefined && targetIndexValue < 0) {
866
- return yield* Effect3.fail(new Error("Target index must be a non-negative integer"));
1080
+ return yield* Effect5.fail(new Error("Target index must be a non-negative integer"));
867
1081
  }
868
1082
  if (targetUrlValue && targetIndexValue !== undefined) {
869
- return yield* Effect3.fail(new Error("Use only one target selector: --target-url/OPENCODE_BROWSER_TARGET_URL or --target-index/OPENCODE_BROWSER_TARGET_INDEX"));
1083
+ return yield* Effect5.fail(new Error("Use only one target selector: --target-url/OPENCODE_BROWSER_TARGET_URL or --target-index/OPENCODE_BROWSER_TARGET_INDEX"));
870
1084
  }
871
1085
  const result = yield* relay.execute({
872
1086
  ...workSessionId ? { sessionId: workSessionId } : {},
@@ -882,33 +1096,33 @@ var execute = Command.make("execute", {
882
1096
  } : {}
883
1097
  });
884
1098
  if (!explicitSessionId && !rm) {
885
- yield* Console.error(formatSessionContinuation(result.session.id));
1099
+ yield* Console3.error(formatSessionContinuation(result.session.id));
886
1100
  }
887
1101
  return result;
888
1102
  });
889
1103
  if (json) {
890
- const outcome = yield* Effect3.result(run);
891
- const envelope = Result.isFailure(outcome) ? errorJsonEnvelope(outcome.failure) : executeJsonEnvelope(outcome.success);
1104
+ const outcome = yield* Effect5.result(run);
1105
+ const envelope = Result2.isFailure(outcome) ? errorJsonEnvelope(outcome.failure) : executeJsonEnvelope(outcome.success);
892
1106
  yield* emitJson(envelope);
893
1107
  if (!envelope.ok) {
894
1108
  yield* stderrLine(envelope.error?.message ?? envelope.text);
895
- yield* exitWith(Result.isFailure(outcome) ? exitCodeForError(outcome.failure) : ExitCode.failure);
1109
+ yield* exitWith(Result2.isFailure(outcome) ? exitCodeForError(outcome.failure) : ExitCode.failure);
896
1110
  }
897
1111
  return;
898
1112
  }
899
- const outcome = yield* Effect3.result(run);
900
- if (Result.isFailure(outcome)) {
901
- yield* Console.error(outcome.failure.message);
1113
+ const outcome = yield* Effect5.result(run);
1114
+ if (Result2.isFailure(outcome)) {
1115
+ yield* Console3.error(outcome.failure.message);
902
1116
  yield* exitWith(exitCodeForError(outcome.failure));
903
1117
  return;
904
1118
  }
905
1119
  const result = outcome.success;
906
- const print = result.isError ? Console.error : Console.log;
1120
+ const print = result.isError ? Console3.error : Console3.log;
907
1121
  yield* print(result.text);
908
1122
  if (result.logs.length > 0) {
909
1123
  yield* print(formatExecuteLogs(result.logs));
910
1124
  }
911
- yield* Effect3.forEach(result.warnings ?? [], (warning) => print(`Warning: ${warning}`));
1125
+ yield* Effect5.forEach(result.warnings ?? [], (warning) => print(`Warning: ${warning}`));
912
1126
  if (result.diagnostic) {
913
1127
  yield* print(`Diagnostic: ${result.diagnostic}`);
914
1128
  }
@@ -919,321 +1133,177 @@ var execute = Command.make("execute", {
919
1133
  if (result.isError) {
920
1134
  yield* failExit;
921
1135
  }
922
- })).pipe(Command.withDescription("Execute Playwright code against the attached browser"));
923
- var sessionNew = Command.make("new", {
924
- name: Argument.String("name").pipe(Argument.optional, Argument.withDescription("Optional lowercase session id")),
925
- readOnly: Flag.Boolean("read-only").pipe(Flag.withDefault(false), Flag.withDescription("Create a read-only session: the relay rejects input-dispatching CDP so scripts can inspect but not click or type")),
926
- allowOrigin: Flag.String("allow-origin").pipe(Flag.atLeast(0), Flag.withDescription("Only let this session's tabs load these hosts or origins (repeatable or comma-separated): example.com, *.example.com, https://app.example.com, http://localhost:3000"))
927
- }, Effect3.fn("Cli.sessionNew")(function* ({ name, readOnly, allowOrigin }) {
1136
+ })).pipe(Command2.withDescription("Execute Playwright code against the attached browser"));
1137
+ registerCliCommand(200, () => execute);
1138
+
1139
+ // src/cli/network.ts
1140
+ import { Console as Console4, Effect as Effect6, Option as Option5 } from "effect";
1141
+ import { Command as Command3, Flag as Flag4 } from "effect/cli";
1142
+ import path3 from "node:path";
1143
+ var parseNetworkContent = Effect6.fnUntraced(function* (content) {
1144
+ const value = Option5.getOrUndefined(content);
1145
+ if (value === undefined || value === "embed" || value === "omit")
1146
+ return value;
1147
+ return yield* Effect6.fail(new Error("Network content must be embed or omit"));
1148
+ });
1149
+ function formatNetworkStatus(status) {
1150
+ if (!status.active)
1151
+ return status.ended ? `Network capture: inactive (the last one ended because ${status.ended})` : "Network capture: inactive";
1152
+ return `Network capture: active entries=${status.entryCount} responses=${status.responseCount} failures=${status.failureCount} bodyBytes=${status.capturedBodyBytes} truncated=${status.truncatedBodyCount} dropped=${status.droppedEntryCount} startedAt=${status.startedAt ?? "unknown"}${status.endsAt ? ` endsAt=${status.endsAt}` : ""}`;
1153
+ }
1154
+ function formatNetworkResult(result) {
1155
+ const output = result.outputPath ? ` output=${result.outputPath}` : "";
1156
+ const profile = result.authProfile ? ` secrets=${result.authProfile.name}(${result.authProfile.slotCount})` : "";
1157
+ return `Network capture stopped: entries=${result.entryCount} responses=${result.responseCount} failures=${result.failureCount} bodyBytes=${result.capturedBodyBytes} truncated=${result.truncatedBodyCount} dropped=${result.droppedEntryCount}${output}${profile}`;
1158
+ }
1159
+ var networkStart = Command3.make("start", {
1160
+ session: sessionFlag("Capture the default page for this Browse session"),
1161
+ urlFilter: Flag4.String("url").pipe(Flag4.optional, Flag4.withDescription("Capture only requests whose URL contains this text")),
1162
+ resourceTypes: Flag4.String("resource-type").pipe(Flag4.atMost(50), Flag4.withDescription("Capture this Playwright resource type; repeat for multiple types")),
1163
+ content: Flag4.String("content").pipe(Flag4.optional, Flag4.withDescription("Response and request body mode: embed (default) or omit")),
1164
+ maxBodyBytes: Flag4.Int("max-body-bytes").pipe(Flag4.optional, Flag4.withDescription("Maximum captured bytes per body, defaults to 1000000")),
1165
+ maxTotalBodyBytes: Flag4.Int("max-total-body-bytes").pipe(Flag4.optional, Flag4.withDescription("Maximum captured body bytes for the whole capture, defaults to 25000000")),
1166
+ maxEntries: Flag4.Int("max-entries").pipe(Flag4.optional, Flag4.withDescription("Maximum request entries, defaults to 1000")),
1167
+ maxDuration: Flag4.Int("max-duration-ms").pipe(Flag4.optional, Flag4.withDescription("End the capture by itself (discarding it) after this long, defaults to 1800000 (30 minutes)")),
1168
+ json: jsonFlag
1169
+ }, Effect6.fn("Cli.networkStart")(function* ({
1170
+ session,
1171
+ urlFilter,
1172
+ resourceTypes,
1173
+ content,
1174
+ maxBodyBytes,
1175
+ maxTotalBodyBytes,
1176
+ maxEntries,
1177
+ maxDuration,
1178
+ json
1179
+ }) {
928
1180
  const relay = yield* Service;
929
- yield* ensureCliRelay();
930
- const store = yield* Service2;
931
- const result = yield* relay.sessionNew(Option2.getOrUndefined(name), {
932
- ...readOnly ? { readOnly: true } : {},
933
- ...allowOrigin.length > 0 ? { allowedOrigins: allowOrigin } : {}
1181
+ yield* ensureCliRelayAndExtension();
1182
+ const sessionId = yield* networkSession(session);
1183
+ const contentValue = yield* parseNetworkContent(content);
1184
+ const result = yield* relay.networkStart({
1185
+ sessionId,
1186
+ ...Option5.isSome(urlFilter) ? { urlFilter: urlFilter.value } : {},
1187
+ ...resourceTypes.length === 0 ? {} : { resourceTypes },
1188
+ ...contentValue === undefined ? {} : { content: contentValue },
1189
+ ...Option5.isSome(maxBodyBytes) ? { maxBodyBytes: maxBodyBytes.value } : {},
1190
+ ...Option5.isSome(maxTotalBodyBytes) ? { maxTotalBodyBytes: maxTotalBodyBytes.value } : {},
1191
+ ...Option5.isSome(maxEntries) ? { maxEntries: maxEntries.value } : {},
1192
+ ...Option5.isSome(maxDuration) ? { maxDurationMs: maxDuration.value } : {}
934
1193
  });
935
- yield* store.write(result.id);
936
- yield* Console.log(result.id);
937
- })).pipe(Command.withDescription("Create a Browse session and make it current"));
938
- var sessionList = Command.make("list", {
1194
+ yield* printOr(json, result, formatNetworkStatus(result));
1195
+ })).pipe(Command3.withDescription("Start session-scoped network capture"));
1196
+ var networkStatus = Command3.make("status", {
1197
+ session: sessionFlag(),
939
1198
  json: jsonFlag
940
- }, Effect3.fn("Cli.sessionList")(function* ({ json }) {
1199
+ }, Effect6.fn("Cli.networkStatus")(function* ({ session, json }) {
941
1200
  const relay = yield* Service;
1201
+ const sessionId = yield* networkSession(session);
942
1202
  yield* ensureCliRelay();
943
- const store = yield* Service2;
944
- const sessions = yield* relay.sessions;
945
- const current = yield* store.read;
946
- if (json) {
947
- yield* emitJson({ current: current ?? undefined, sessions });
948
- return;
949
- }
950
- if (sessions.length === 0) {
951
- yield* Console.log("No sessions");
952
- return;
1203
+ const result = yield* relay.networkStatus({ sessionId });
1204
+ yield* printOr(json, result, formatNetworkStatus(result));
1205
+ })).pipe(Command3.withDescription("Show session-scoped network capture status"));
1206
+ var networkStop = Command3.make("stop", {
1207
+ session: sessionFlag(),
1208
+ output: Flag4.String("output").pipe(Flag4.optional, Flag4.withAlias("o"), Flag4.withDescription("Write a credential-redacted HAR artifact to this path")),
1209
+ secrets: Flag4.String("secrets").pipe(Flag4.optional, Flag4.withDescription("Store captured credentials under this reusable profile name")),
1210
+ json: jsonFlag
1211
+ }, Effect6.fn("Cli.networkStop")(function* ({ session, output, secrets, json }) {
1212
+ const relay = yield* Service;
1213
+ const outputValue = Option5.getOrUndefined(output);
1214
+ const secretsValue = Option5.getOrUndefined(secrets);
1215
+ if (!outputValue && !secretsValue) {
1216
+ return yield* Effect6.fail(new Error("network stop requires --output, --secrets, or both"));
953
1217
  }
954
- yield* Effect3.forEach(sessions, (item) => {
955
- const marker = item.id === current ? "*" : " ";
956
- const page = item.pageUrl ?? "no page yet";
957
- const keys = item.stateKeys.length ? ` state=${item.stateKeys.join(",")}` : "";
958
- const readOnly = item.readOnly ? " read-only" : "";
959
- const allowed = item.allowedOrigins ? ` allow=${item.allowedOrigins.join(",")}` : "";
960
- return Console.log(`${marker} ${item.id} ${page}${keys}${readOnly}${allowed}`);
1218
+ const sessionId = yield* networkSession(session);
1219
+ yield* ensureCliRelay();
1220
+ const result = yield* relay.networkStop({
1221
+ sessionId,
1222
+ ...outputValue ? { outputPath: path3.resolve(outputValue) } : {},
1223
+ ...secretsValue ? { secrets: secretsValue } : {}
961
1224
  });
962
- })).pipe(Command.withDescription("List Browse sessions"));
963
- var sessionCurrent = Command.make("current", {}, Effect3.fn("Cli.sessionCurrent")(function* () {
964
- const store = yield* Service2;
965
- const current = yield* store.read;
966
- yield* Console.log(current ?? "none");
967
- })).pipe(Command.withDescription("Print the current default session"));
968
- var sessionUse = Command.make("use", {
969
- id: Argument.String("id")
970
- }, Effect3.fn("Cli.sessionUse")(function* ({ id }) {
971
- const store = yield* Service2;
972
- yield* ensureSessionExists(id);
973
- yield* store.write(id);
974
- yield* Console.log(id);
975
- })).pipe(Command.withDescription("Set the current default session"));
976
- var sessionReset = Command.make("reset", {
977
- id: Argument.String("id").pipe(Argument.optional),
978
- session: sessionFlag("Reset this Browse session id")
979
- }, Effect3.fn("Cli.sessionReset")(function* ({ id, session }) {
1225
+ yield* printOr(json, result, formatNetworkResult(result));
1226
+ })).pipe(Command3.withDescription("Stop capture and write a redacted artifact or reusable secret profile"));
1227
+ var networkCancel = Command3.make("cancel", { session: sessionFlag() }, Effect6.fn("Cli.networkCancel")(function* ({ session }) {
980
1228
  const relay = yield* Service;
981
- const sessionId = yield* resolvePositionalOrFlagSessionId(id, session);
1229
+ const sessionId = yield* networkSession(session);
982
1230
  yield* ensureCliRelay();
983
- const reset = yield* relay.sessionReset(sessionId);
984
- if (reset.stopped)
985
- yield* Console.error(`Stopped first: ${describeStop(reset.stopped)}`);
986
- yield* Console.log(reset.session.id);
987
- })).pipe(Command.withDescription("Reset a Browse session state and page"));
988
- var sessionAdopt = Command.make("adopt", {
989
- session: sessionFlag("Adopt into this Browse session, creating it when it does not exist yet; omit to create a fresh readable id"),
990
- targetUrl: Flag.String("target-url").pipe(Flag.optional, Flag.withDescription("Adopt the attached page whose URL contains this text")),
991
- targetIndex: Flag.Int("target-index").pipe(Flag.optional, Flag.withDescription("Adopt the attached page at this zero-based target index")),
992
- tabId: tabIdFlag("Adopt the user's shared tab with this Chrome tab id (the tab= value in status)")
993
- }, Effect3.fn("Cli.sessionAdopt")(function* ({ session, targetUrl, targetIndex, tabId }) {
994
- const relay = yield* Service;
995
- const explicitSessionId = Option2.getOrUndefined(session) ?? Option2.getOrUndefined(yield* sessionIdConfig);
996
- yield* ensureCliRelayAndExtension(explicitSessionId);
997
- const targetUrlValue = Option2.getOrUndefined(targetUrl);
998
- const targetIndexValue = Option2.getOrUndefined(targetIndex);
999
- if (targetIndexValue !== undefined && targetIndexValue < 0) {
1000
- return yield* Effect3.fail(new Error("Target index must be a non-negative integer"));
1001
- }
1002
- const tabIdValue = Option2.getOrUndefined(tabId);
1003
- if ([targetUrlValue, targetIndexValue, tabIdValue].filter((value) => value !== undefined).length > 1) {
1004
- return yield* Effect3.fail(new Error("Use only one target selector: --target-url, --target-index, or --tab-id"));
1005
- }
1006
- const targetSelection = targetUrlValue ? { urlIncludes: targetUrlValue } : targetIndexValue !== undefined ? { index: targetIndexValue } : undefined;
1007
- const result = yield* relay.sessionAdopt({
1008
- ...explicitSessionId ? { sessionId: explicitSessionId } : {},
1009
- createIfMissing: true,
1010
- ...targetSelection ? { targetSelection } : {},
1011
- ...tabIdValue === undefined ? {} : { tabId: tabIdValue }
1012
- });
1013
- yield* Console.log(`${result.session.created ? "Created and adopted" : "Adopted"} session '${result.session.id}' default page: ${result.adoptedUrl}`);
1014
- if (result.session.created) {
1015
- yield* Console.error(formatSessionContinuation(result.session.id));
1016
- }
1017
- })).pipe(Command.withDescription("Make an attached tab the session's default page"));
1018
- function describeStop(stopped) {
1019
- return [
1020
- stopped.execute ? "a running execute" : undefined,
1021
- stopped.handoff ? "a pending handoff" : undefined
1022
- ].filter(Boolean).join(" and ") + (stopped.forced ? " (the script did not stop in time and was abandoned with its state)" : "");
1023
- }
1024
- var sessionDelete = Command.make("delete", {
1025
- id: Argument.String("id").pipe(Argument.optional),
1026
- session: sessionFlag("Delete this Browse session id")
1027
- }, Effect3.fn("Cli.sessionDelete")(function* ({ id, session }) {
1028
- const relay = yield* Service;
1029
- const store = yield* Service2;
1030
- const sessionId = yield* resolvePositionalOrFlagSessionId(id, session);
1031
- yield* ensureCliRelay();
1032
- const deleted = yield* relay.sessionDelete(sessionId);
1033
- if (deleted.stopped)
1034
- yield* Console.error(`Stopped first: ${describeStop(deleted.stopped)}`);
1035
- const current = yield* store.read;
1036
- if (current === sessionId) {
1037
- yield* store.clear;
1038
- }
1039
- yield* Console.log(sessionId);
1040
- })).pipe(Command.withDescription("Delete a Browse session"));
1041
- var sessionAllow = Command.make("allow", {
1042
- session: sessionFlag(),
1043
- origins: Argument.String("origins").pipe(Argument.variadic({ min: 0 }), Argument.withDescription("Hosts or origins the session's tabs may load: example.com, *.example.com, https://app.example.com, http://localhost:3000")),
1044
- clear: Flag.Boolean("clear").pipe(Flag.withDefault(false), Flag.withDescription("Remove the allowlist: the session may load any site again")),
1045
- json: jsonFlag
1046
- }, Effect3.fn("Cli.sessionAllow")(function* ({ session, origins, clear, json }) {
1047
- const relay = yield* Service;
1048
- yield* ensureCliRelay();
1049
- if (!clear && origins.length === 0) {
1050
- return yield* Effect3.fail(new Error("session allow needs origins, or --clear to remove the allowlist"));
1051
- }
1052
- const id = yield* resolvePositionalOrFlagSessionId(Option2.none(), session);
1053
- const result = yield* relay.sessionAllowlist(id, clear ? [] : origins);
1054
- if (json) {
1055
- yield* emitJson(result);
1056
- return;
1057
- }
1058
- yield* Console.log(result.allowedOrigins ? `${result.id} may load: ${result.allowedOrigins.join(", ")}` : `${result.id} may load any site`);
1059
- })).pipe(Command.withDescription("Set the domain allowlist of a session (the current one by default)"));
1060
- var session = Command.make("session").pipe(Command.withDescription("Manage Browse sessions"), Command.withSubcommands([sessionNew, sessionList, sessionCurrent, sessionUse, sessionReset, sessionAdopt, sessionDelete, sessionAllow]));
1061
- var status = Command.make("status", {
1062
- json: jsonFlag
1063
- }, Effect3.fn("Cli.status")(function* ({ json }) {
1064
- const relay = yield* Service;
1065
- const store = yield* Service2;
1066
- const [relayResult, update] = yield* Effect3.all([Effect3.result(relay.version), Effect3.promise(() => updateNotice2())], { concurrency: 2 });
1067
- const updateLine = update ? formatUpdateNotice2(update) : undefined;
1068
- if (Result.isFailure(relayResult)) {
1069
- if (!(relayResult.failure instanceof RelayUnreachable)) {
1070
- if (json) {
1071
- yield* emitJson({
1072
- endpoint: relay.endpoint,
1073
- relay: { running: false, error: relayResult.failure.message },
1074
- extension: null,
1075
- sessions: [],
1076
- targets: [],
1077
- update: updateJson2(update)
1078
- });
1079
- }
1080
- yield* stderrLine(`Relay status failed: ${relayResult.failure.message}`);
1081
- yield* exitWith(ExitCode.host);
1082
- return;
1083
- }
1084
- const stopped = stoppedRelayStatus(relay.endpoint);
1085
- if (json) {
1086
- yield* emitJson({ ...stopped, update: updateJson2(update), autoUpdate: autoUpdateStatus() });
1087
- yield* stderrLine(`relay is not running at ${relay.endpoint}`);
1088
- } else {
1089
- yield* Console.log(`Relay: stopped (${relay.endpoint})`);
1090
- yield* Console.log(`Run \`${cliCommand("execute")}\` (or any relay-backed command) to start it automatically.`);
1091
- if (updateLine)
1092
- yield* Console.log(updateLine);
1093
- }
1094
- yield* exitWith(ExitCode.host);
1095
- return;
1096
- }
1097
- const version = relayResult.success;
1098
- const buildProblem = relayBuildProblem(version);
1099
- const [extensionStatus, current] = yield* Effect3.all([relay.extensionStatus, store.read]);
1100
- const collections = statusCollections(extensionStatus);
1101
- const [sessions, targets] = collections ? [collections.sessions, collections.targets] : yield* Effect3.all([relay.sessions, relay.targets]);
1102
- if (json) {
1103
- yield* emitJson({
1104
- endpoint: relay.endpoint,
1105
- relay: { running: true, version: version.version, buildId: version.buildId ?? null, stale: buildProblem !== undefined },
1106
- extension: extensionStatus,
1107
- currentSession: current ?? null,
1108
- sessions,
1109
- targets,
1110
- update: updateJson2(update),
1111
- autoUpdate: autoUpdateStatus()
1112
- });
1113
- if (buildProblem) {
1114
- yield* stderrLine(buildProblem);
1115
- yield* failExit;
1116
- }
1117
- return;
1118
- }
1119
- if (updateLine)
1120
- yield* Console.log(updateLine);
1121
- yield* Console.log(`Relay: ${relay.endpoint} (${version.version})`);
1122
- if (buildProblem) {
1123
- yield* Console.log(`Warning: ${buildProblem}`);
1124
- }
1125
- yield* Console.log(`Extension: ${extensionStatus.connected ? "connected" : "disconnected"}${extensionStatus.version ? ` (${extensionStatus.version})` : ""}`);
1126
- if ((extensionStatus.rejectedConnections ?? 0) > 0) {
1127
- yield* Console.log(`Warning: ${extensionStatus.rejectedConnections} competing browser/profile connection attempt(s) rejected; the active connection was preserved. Use Browse in one browser/profile at a time.`);
1128
- }
1129
- if (extensionStatus.nextStep)
1130
- yield* Console.log(extensionStatus.nextStep);
1131
- else if ((extensionStatus.pairingFailures ?? 0) > 0 && !extensionStatus.connected) {
1132
- yield* Console.log(`Warning: ${extensionStatus.pairingFailures} extension connection(s) failed pairing. Update the extension, or run \`${cliCommand("pair")}\` and enter the code on its status page.`);
1133
- }
1134
- if ((extensionStatus.processFaults?.suppressed ?? 0) > 0) {
1135
- const faults = extensionStatus.processFaults;
1136
- yield* Console.log(`Warning: the relay survived ${faults.suppressed} Playwright fault(s)${faults.lastAt ? `, last at ${faults.lastAt}` : ""}${faults.lastMessage ? `: ${faults.lastMessage}` : ""}. See the relay log.`);
1137
- }
1138
- if (extensionStatus.protocolVersion !== undefined && extensionStatus.protocolVersion !== null) {
1139
- const compatibility = extensionStatus.protocolCompatible === false ? "incompatible" : "compatible";
1140
- const legacy = extensionStatus.protocolLegacy === true ? ", inferred from legacy hello" : "";
1141
- yield* Console.log(`Extension protocol: ${extensionStatus.protocolVersion} (${compatibility}${legacy})`);
1142
- if (extensionStatus.protocolCompatible === false && !extensionStatus.nextStep)
1143
- yield* Console.log(extensionProtocolAdvice(extensionStatus.protocolVersion, { install: installCommand() }));
1144
- }
1145
- yield* Console.log(`Active targets: ${extensionStatus.activeTargets}`);
1146
- if (extensionStatus.childTargets !== undefined) {
1147
- yield* Console.log(`Child targets: ${extensionStatus.childTargets}`);
1148
- }
1149
- if (extensionStatus.cdpClients !== undefined) {
1150
- yield* Console.log(`CDP clients: ${extensionStatus.cdpClients}`);
1151
- }
1152
- yield* Console.log(`Current session: ${current ?? "none"}`);
1153
- if (sessions.length === 0) {
1154
- yield* Console.log("Sessions: none");
1155
- } else {
1156
- yield* Console.log("Sessions:");
1157
- const activeSessions = sessions.filter((item) => item.id === current || item.connected || item.pageUrl !== null || item.stateKeys.length > 0 || item.readOnly);
1158
- const idleSessions = sessions.filter((item) => !activeSessions.includes(item));
1159
- const shownSessions = activeSessions.length > 0 ? activeSessions : idleSessions.slice(0, 5);
1160
- const omittedIdleCount = activeSessions.length > 0 ? idleSessions.length : Math.max(0, idleSessions.length - shownSessions.length);
1161
- yield* Effect3.forEach(shownSessions, (item) => {
1162
- const marker = item.id === current ? "*" : " ";
1163
- return Console.log(`${marker} ${item.id} ${item.pageUrl ?? "no page yet"}`);
1164
- });
1165
- if (omittedIdleCount > 0) {
1166
- yield* Console.log(` (${omittedIdleCount} idle session(s) with no page omitted; run ${cliCommand("session list")} to view all)`);
1167
- }
1168
- }
1169
- if (targets.length === 0) {
1170
- yield* Console.log("Targets: none");
1171
- } else {
1172
- yield* Console.log("Targets:");
1173
- yield* Effect3.forEach(targets, (target, index) => {
1174
- return Console.log(`- [${index}] ${formatTargetSummary(target, { includeSession: true })}`);
1175
- });
1176
- }
1177
- if (buildProblem) {
1178
- yield* failExit;
1179
- }
1180
- })).pipe(Command.withDescription("Show relay, extension, and target status"));
1181
- var recordingStart = Command.make("start", {
1182
- outputPath: Argument.String("output-path").pipe(Argument.withDescription("Path to write the recording artifact; tabCapture requires .webm, CDP accepts .webm or .mp4")),
1231
+ const result = yield* relay.networkCancel({ sessionId });
1232
+ yield* Console4.log(result.cancelled ? "Network capture cancelled" : "Network capture was not active");
1233
+ })).pipe(Command3.withDescription("Cancel capture without writing an artifact"));
1234
+ var network = Command3.make("network").pipe(Command3.withDescription("Capture authenticated network exchanges for direct client derivation"), Command3.withSubcommands([networkStart, networkStatus, networkStop, networkCancel]));
1235
+ registerCliCommand(600, () => network);
1236
+
1237
+ // src/cli/recording.ts
1238
+ import { Console as Console5, Effect as Effect7, Option as Option6 } from "effect";
1239
+ import { Argument as Argument3, Command as Command4, Flag as Flag5 } from "effect/cli";
1240
+ import path4 from "node:path";
1241
+ var recordingStart = Command4.make("start", {
1242
+ outputPath: Argument3.String("output-path").pipe(Argument3.withDescription("Path to write the recording artifact; tabCapture requires .webm, CDP accepts .webm or .mp4")),
1183
1243
  session: sessionFlag("Record the page for this Browse or CDP session id"),
1184
1244
  tabId: tabIdFlag("Record this attached Chrome tab id"),
1185
- mode: Flag.String("mode").pipe(Flag.optional, Flag.withDescription("Recording mode: auto, tab-capture, or cdp. auto uses CDP for relay-owned tabs and tabCapture for user-owned tabs")),
1186
- audio: Flag.Boolean("audio").pipe(Flag.withDefault(false), Flag.withDescription("Include tab audio")),
1187
- frameRate: Flag.Int("frame-rate").pipe(Flag.optional, Flag.withDescription("Output frame rate, integer 1..60; defaults to 30 for tab-capture and 60 for CDP")),
1188
- maxDurationMs: Flag.Int("max-duration-ms").pipe(Flag.optional, Flag.withDescription("Auto-stop guard in milliseconds, defaults to 900000")),
1189
- cursor: Flag.Boolean("cursor").pipe(Flag.withDefault(false), Flag.withDescription("Draw the ghost cursor in the recording (off by default: only the page is recorded)")),
1245
+ mode: Flag5.String("mode").pipe(Flag5.optional, Flag5.withDescription("Recording mode: auto, tab-capture, or cdp. auto uses CDP for relay-owned tabs and tabCapture for user-owned tabs")),
1246
+ audio: Flag5.Boolean("audio").pipe(Flag5.withDefault(false), Flag5.withDescription("Include tab audio")),
1247
+ frameRate: Flag5.Int("frame-rate").pipe(Flag5.optional, Flag5.withDescription("Output frame rate, integer 1..60; defaults to 30 for tab-capture and 60 for CDP")),
1248
+ maxDurationMs: Flag5.Int("max-duration-ms").pipe(Flag5.optional, Flag5.withDescription("Auto-stop guard in milliseconds, defaults to 900000")),
1249
+ cursor: Flag5.Boolean("cursor").pipe(Flag5.withDefault(false), Flag5.withDescription("Draw the ghost cursor in the recording (off by default: only the page is recorded)")),
1190
1250
  json: jsonFlag
1191
- }, Effect3.fn("Cli.recordingStart")(function* ({ outputPath, session, tabId, mode, audio, frameRate, maxDurationMs, cursor, json }) {
1192
- if (Option2.isSome(frameRate) && (frameRate.value < 1 || frameRate.value > 60)) {
1193
- return yield* Effect3.fail(new Error("Recording frameRate must be an integer from 1 to 60"));
1251
+ }, Effect7.fn("Cli.recordingStart")(function* ({
1252
+ outputPath,
1253
+ session,
1254
+ tabId,
1255
+ mode,
1256
+ audio,
1257
+ frameRate,
1258
+ maxDurationMs,
1259
+ cursor,
1260
+ json
1261
+ }) {
1262
+ if (Option6.isSome(frameRate) && (frameRate.value < 1 || frameRate.value > 60)) {
1263
+ return yield* Effect7.fail(new Error("Recording frameRate must be an integer from 1 to 60"));
1194
1264
  }
1195
1265
  const relay = yield* Service;
1196
1266
  yield* ensureCliRelayAndExtension();
1197
1267
  const target = yield* recordingTarget({ session, tabId });
1198
- const modeValue = yield* parseRecordingModeOption(Option2.getOrUndefined(mode));
1199
- const resolvedOutputPath = path.resolve(outputPath);
1268
+ const modeValue = yield* parseRecordingModeOption(Option6.getOrUndefined(mode));
1269
+ const resolvedOutputPath = path4.resolve(outputPath);
1200
1270
  const result = yield* relay.recordingStart({
1201
1271
  ...target,
1202
1272
  outputPath: resolvedOutputPath,
1203
1273
  ...modeValue === undefined ? {} : { mode: modeValue },
1204
1274
  audio,
1205
- ...Option2.isSome(frameRate) ? { frameRate: frameRate.value } : {},
1206
- ...Option2.isSome(maxDurationMs) ? { maxDurationMs: maxDurationMs.value } : {},
1275
+ ...Option6.isSome(frameRate) ? { frameRate: frameRate.value } : {},
1276
+ ...Option6.isSome(maxDurationMs) ? { maxDurationMs: maxDurationMs.value } : {},
1207
1277
  ...cursor ? { cursor } : {}
1208
1278
  });
1209
1279
  if (!result.success) {
1210
- return yield* Effect3.fail(new Error(result.error ?? "Failed to start recording"));
1280
+ return yield* Effect7.fail(new Error(result.error ?? "Failed to start recording"));
1211
1281
  }
1212
1282
  yield* printOr(json, result, `Recording started: ${result.path ?? resolvedOutputPath} tab=${result.tabId ?? "unknown"} mode=${result.mode ?? "tab-capture"} artifact=${result.artifactType ?? "webm"} mime=${result.mimeType ?? "video/webm"} fps=${result.frameRate ?? "unknown"}`);
1213
- })).pipe(Command.withDescription("Start recording an attached tab"));
1214
- var recordingStop = Command.make("stop", {
1283
+ })).pipe(Command4.withDescription("Start recording an attached tab"));
1284
+ var recordingStop = Command4.make("stop", {
1215
1285
  session: sessionFlag("Stop recording for this CDP session id"),
1216
1286
  tabId: tabIdFlag("Stop recording for this Chrome tab id"),
1217
1287
  json: jsonFlag
1218
- }, Effect3.fn("Cli.recordingStop")(function* ({ session, tabId, json }) {
1288
+ }, Effect7.fn("Cli.recordingStop")(function* ({ session, tabId, json }) {
1219
1289
  const relay = yield* Service;
1220
1290
  yield* ensureCliRelay();
1221
1291
  const target = yield* recordingTarget({ session, tabId });
1222
1292
  const result = yield* relay.recordingStop(target);
1223
1293
  if (!result.success) {
1224
- return yield* Effect3.fail(new Error(result.error ?? "Failed to stop recording"));
1294
+ return yield* Effect7.fail(new Error(result.error ?? "Failed to stop recording"));
1225
1295
  }
1226
1296
  if (json)
1227
1297
  return yield* emitJson(result);
1228
1298
  const frames = result.frameCount === undefined ? "" : `, frames=${result.frameCount}`;
1229
- yield* Console.log(`Recording saved: ${result.path ?? "unknown"} (${result.size ?? 0} bytes, ${result.duration ?? 0}ms, mode=${result.mode ?? "tab-capture"}, artifact=${result.artifactType ?? "webm"}${frames})`);
1230
- yield* Console.log(formatRecordingQuality(result.quality));
1231
- })).pipe(Command.withDescription("Stop recording and write the artifact"));
1232
- var recordingStatus = Command.make("status", {
1299
+ yield* Console5.log(`Recording saved: ${result.path ?? "unknown"} (${result.size ?? 0} bytes, ${result.duration ?? 0}ms, mode=${result.mode ?? "tab-capture"}, artifact=${result.artifactType ?? "webm"}${frames})`);
1300
+ yield* Console5.log(formatRecordingQuality(result.quality));
1301
+ })).pipe(Command4.withDescription("Stop recording and write the artifact"));
1302
+ var recordingStatus = Command4.make("status", {
1233
1303
  session: sessionFlag("Check recording for this CDP session id"),
1234
1304
  tabId: tabIdFlag("Check recording for this Chrome tab id"),
1235
1305
  json: jsonFlag
1236
- }, Effect3.fn("Cli.recordingStatus")(function* ({ session, tabId, json }) {
1306
+ }, Effect7.fn("Cli.recordingStatus")(function* ({ session, tabId, json }) {
1237
1307
  const relay = yield* Service;
1238
1308
  yield* ensureCliRelay();
1239
1309
  const target = yield* recordingTarget({ session, tabId });
@@ -1243,175 +1313,129 @@ var recordingStatus = Command.make("status", {
1243
1313
  return;
1244
1314
  }
1245
1315
  if (!result.isRecording) {
1246
- yield* Console.log("Recording: inactive");
1316
+ yield* Console5.log("Recording: inactive");
1247
1317
  return;
1248
1318
  }
1249
1319
  const frames = result.frameCount === undefined ? "" : ` frameCount=${result.frameCount}`;
1250
- yield* Console.log(`Recording: active tab=${result.tabId ?? "unknown"} mode=${result.mode ?? "tab-capture"} artifact=${result.artifactType ?? "webm"} path=${result.path ?? "unknown"} size=${result.size ?? 0}${frames} startedAt=${result.startedAt ?? "unknown"}`);
1251
- yield* Console.log(formatRecordingQuality(result.quality));
1252
- })).pipe(Command.withDescription("Check current recording status"));
1253
- var recordingCancel = Command.make("cancel", {
1320
+ yield* Console5.log(`Recording: active tab=${result.tabId ?? "unknown"} mode=${result.mode ?? "tab-capture"} artifact=${result.artifactType ?? "webm"} path=${result.path ?? "unknown"} size=${result.size ?? 0}${frames} startedAt=${result.startedAt ?? "unknown"}`);
1321
+ yield* Console5.log(formatRecordingQuality(result.quality));
1322
+ })).pipe(Command4.withDescription("Check current recording status"));
1323
+ var recordingCancel = Command4.make("cancel", {
1254
1324
  session: sessionFlag("Cancel recording for this CDP session id"),
1255
1325
  tabId: tabIdFlag("Cancel recording for this Chrome tab id")
1256
- }, Effect3.fn("Cli.recordingCancel")(function* ({ session, tabId }) {
1326
+ }, Effect7.fn("Cli.recordingCancel")(function* ({ session, tabId }) {
1257
1327
  const relay = yield* Service;
1258
1328
  yield* ensureCliRelay();
1259
1329
  const target = yield* recordingTarget({ session, tabId });
1260
1330
  const result = yield* relay.recordingCancel(target);
1261
1331
  if (!result.success) {
1262
- return yield* Effect3.fail(new Error(result.error ?? "Failed to cancel recording"));
1332
+ return yield* Effect7.fail(new Error(result.error ?? "Failed to cancel recording"));
1263
1333
  }
1264
- yield* Console.log("Recording cancelled");
1265
- })).pipe(Command.withDescription("Cancel recording without writing a file"));
1266
- var recording = Command.make("recording").pipe(Command.withDescription("Record an attached tab to WebM or MP4"), Command.withSubcommands([recordingStart, recordingStop, recordingStatus, recordingCancel]));
1267
- var flightRecorderStart = Command.make("start", {
1334
+ yield* Console5.log("Recording cancelled");
1335
+ })).pipe(Command4.withDescription("Cancel recording without writing a file"));
1336
+ var recording = Command4.make("recording").pipe(Command4.withDescription("Record an attached tab to WebM or MP4"), Command4.withSubcommands([recordingStart, recordingStop, recordingStatus, recordingCancel]));
1337
+ var flightRecorderStart = Command4.make("start", {
1268
1338
  session: sessionFlag("Buffer the tab owned by this session"),
1269
1339
  tabId: tabIdFlag("Buffer this attached Chrome tab id"),
1270
- retentionMs: Flag.Int("retention-ms").pipe(Flag.optional, Flag.withDescription("Ring-buffer duration in milliseconds (1000..120000; default 60000)")),
1271
- frameRate: Flag.Int("frame-rate").pipe(Flag.optional, Flag.withDescription("Output frame rate from 1 to 60")),
1272
- cursor: Flag.Boolean("cursor").pipe(Flag.withDefault(false), Flag.withDescription("Draw the ghost cursor in the buffered video (off by default)")),
1340
+ retentionMs: Flag5.Int("retention-ms").pipe(Flag5.optional, Flag5.withDescription("Ring-buffer duration in milliseconds (1000..120000; default 60000)")),
1341
+ frameRate: Flag5.Int("frame-rate").pipe(Flag5.optional, Flag5.withDescription("Output frame rate from 1 to 60")),
1342
+ cursor: Flag5.Boolean("cursor").pipe(Flag5.withDefault(false), Flag5.withDescription("Draw the ghost cursor in the buffered video (off by default)")),
1273
1343
  json: jsonFlag
1274
- }, Effect3.fn("Cli.flightRecorderStart")(function* ({ session, tabId, retentionMs, frameRate, cursor, json }) {
1344
+ }, Effect7.fn("Cli.flightRecorderStart")(function* ({ session, tabId, retentionMs, frameRate, cursor, json }) {
1275
1345
  const relay = yield* Service;
1276
1346
  yield* ensureCliRelayAndExtension();
1277
1347
  const target = yield* recordingTarget({ session, tabId });
1278
1348
  const result = yield* relay.flightRecorderStart({
1279
1349
  ...target,
1280
- ...Option2.isSome(retentionMs) ? { retentionMs: retentionMs.value } : {},
1281
- ...Option2.isSome(frameRate) ? { frameRate: frameRate.value } : {},
1350
+ ...Option6.isSome(retentionMs) ? { retentionMs: retentionMs.value } : {},
1351
+ ...Option6.isSome(frameRate) ? { frameRate: frameRate.value } : {},
1282
1352
  ...cursor ? { cursor } : {}
1283
1353
  });
1284
1354
  yield* printOr(json, result, `Flight recorder buffering tab ${result.tabId}; retention=${result.retentionMs}ms`);
1285
- })).pipe(Command.withDescription("Start a rolling in-memory video buffer"));
1286
- var flightRecorderStatus = Command.make("status", {
1355
+ })).pipe(Command4.withDescription("Start a rolling in-memory video buffer"));
1356
+ var flightRecorderStatus = Command4.make("status", {
1287
1357
  session: sessionFlag(),
1288
1358
  tabId: tabIdFlag(),
1289
1359
  json: jsonFlag
1290
- }, Effect3.fn("Cli.flightRecorderStatus")(function* ({ session, tabId, json }) {
1360
+ }, Effect7.fn("Cli.flightRecorderStatus")(function* ({ session, tabId, json }) {
1291
1361
  const relay = yield* Service;
1292
1362
  yield* ensureCliRelay();
1293
1363
  const result = yield* relay.flightRecorderStatus(yield* recordingTarget({ session, tabId }));
1294
1364
  if (json)
1295
1365
  return yield* emitJson(result);
1296
1366
  if (!result.active)
1297
- return yield* Console.log("Flight recorder inactive");
1298
- yield* Console.log(`Flight recorder tab=${result.tabId} frames=${result.bufferedFrames} retained=${result.retainedDurationMs}ms bytes=${result.bufferedBytes}`);
1299
- })).pipe(Command.withDescription("Show rolling flight-recorder status"));
1300
- var flightRecorderSaveLast = Command.make("save-last", {
1301
- outputPath: Argument.String("output-path").pipe(Argument.withDescription("Fresh .webm or .mp4 artifact path")),
1367
+ return yield* Console5.log("Flight recorder inactive");
1368
+ yield* Console5.log(`Flight recorder tab=${result.tabId} frames=${result.bufferedFrames} retained=${result.retainedDurationMs}ms bytes=${result.bufferedBytes}`);
1369
+ })).pipe(Command4.withDescription("Show rolling flight-recorder status"));
1370
+ var flightRecorderSaveLast = Command4.make("save-last", {
1371
+ outputPath: Argument3.String("output-path").pipe(Argument3.withDescription("Fresh .webm or .mp4 artifact path")),
1302
1372
  session: sessionFlag(),
1303
1373
  tabId: tabIdFlag(),
1304
- durationMs: Flag.Int("duration-ms").pipe(Flag.optional, Flag.withDescription("How much recent history to save; defaults to 30 seconds")),
1374
+ durationMs: Flag5.Int("duration-ms").pipe(Flag5.optional, Flag5.withDescription("How much recent history to save; defaults to 30 seconds")),
1305
1375
  json: jsonFlag
1306
- }, Effect3.fn("Cli.flightRecorderSaveLast")(function* ({ outputPath, session, tabId, durationMs, json }) {
1376
+ }, Effect7.fn("Cli.flightRecorderSaveLast")(function* ({ outputPath, session, tabId, durationMs, json }) {
1307
1377
  const relay = yield* Service;
1308
1378
  yield* ensureCliRelay();
1309
1379
  const result = yield* relay.flightRecorderSaveLast({
1310
1380
  ...yield* recordingTarget({ session, tabId }),
1311
- outputPath: path.resolve(outputPath),
1312
- ...Option2.isSome(durationMs) ? { durationMs: durationMs.value } : {}
1381
+ outputPath: path4.resolve(outputPath),
1382
+ ...Option6.isSome(durationMs) ? { durationMs: durationMs.value } : {}
1313
1383
  });
1314
1384
  yield* printOr(json, result, `Saved ${result.durationMs}ms flight recorder clip (${result.frameCount} frames) to ${result.path}`);
1315
- })).pipe(Command.withDescription("Save the most recent buffered video without stopping the recorder"));
1316
- var flightRecorderCancel = Command.make("cancel", {
1385
+ })).pipe(Command4.withDescription("Save the most recent buffered video without stopping the recorder"));
1386
+ var flightRecorderCancel = Command4.make("cancel", {
1317
1387
  session: sessionFlag(),
1318
1388
  tabId: tabIdFlag()
1319
- }, Effect3.fn("Cli.flightRecorderCancel")(function* ({ session, tabId }) {
1389
+ }, Effect7.fn("Cli.flightRecorderCancel")(function* ({ session, tabId }) {
1320
1390
  const relay = yield* Service;
1321
1391
  yield* ensureCliRelay();
1322
1392
  const result = yield* relay.flightRecorderCancel(yield* recordingTarget({ session, tabId }));
1323
- yield* Console.log(result.cancelled ? "Flight recorder stopped" : "No active flight recorder");
1324
- })).pipe(Command.withDescription("Stop and discard the rolling video buffer"));
1325
- var flightRecorder = Command.make("flight-recorder").pipe(Command.withDescription("Keep and save a rolling buffer of recent browser video"), Command.withSubcommands([flightRecorderStart, flightRecorderStatus, flightRecorderSaveLast, flightRecorderCancel]));
1326
- var networkSession = Effect3.fnUntraced(function* (session) {
1327
- return yield* resolveSelectedSessionId(Option2.getOrUndefined(session) ?? Option2.getOrUndefined(yield* sessionIdConfig));
1328
- });
1329
- var parseNetworkContent = Effect3.fnUntraced(function* (content) {
1330
- const value = Option2.getOrUndefined(content);
1331
- if (value === undefined || value === "embed" || value === "omit")
1332
- return value;
1333
- return yield* Effect3.fail(new Error("Network content must be embed or omit"));
1334
- });
1335
- function formatNetworkStatus(status) {
1336
- if (!status.active)
1337
- return status.ended ? `Network capture: inactive (the last one ended because ${status.ended})` : "Network capture: inactive";
1338
- return `Network capture: active entries=${status.entryCount} responses=${status.responseCount} failures=${status.failureCount} bodyBytes=${status.capturedBodyBytes} truncated=${status.truncatedBodyCount} dropped=${status.droppedEntryCount} startedAt=${status.startedAt ?? "unknown"}${status.endsAt ? ` endsAt=${status.endsAt}` : ""}`;
1339
- }
1340
- function formatNetworkResult(result) {
1341
- const output = result.outputPath ? ` output=${result.outputPath}` : "";
1342
- const profile = result.authProfile ? ` secrets=${result.authProfile.name}(${result.authProfile.slotCount})` : "";
1343
- return `Network capture stopped: entries=${result.entryCount} responses=${result.responseCount} failures=${result.failureCount} bodyBytes=${result.capturedBodyBytes} truncated=${result.truncatedBodyCount} dropped=${result.droppedEntryCount}${output}${profile}`;
1344
- }
1345
- var networkStart = Command.make("start", {
1346
- session: sessionFlag("Capture the default page for this Browse session"),
1347
- urlFilter: Flag.String("url").pipe(Flag.optional, Flag.withDescription("Capture only requests whose URL contains this text")),
1348
- resourceTypes: Flag.String("resource-type").pipe(Flag.atMost(50), Flag.withDescription("Capture this Playwright resource type; repeat for multiple types")),
1349
- content: Flag.String("content").pipe(Flag.optional, Flag.withDescription("Response and request body mode: embed (default) or omit")),
1350
- maxBodyBytes: Flag.Int("max-body-bytes").pipe(Flag.optional, Flag.withDescription("Maximum captured bytes per body, defaults to 1000000")),
1351
- maxTotalBodyBytes: Flag.Int("max-total-body-bytes").pipe(Flag.optional, Flag.withDescription("Maximum captured body bytes for the whole capture, defaults to 25000000")),
1352
- maxEntries: Flag.Int("max-entries").pipe(Flag.optional, Flag.withDescription("Maximum request entries, defaults to 1000")),
1353
- maxDuration: Flag.Int("max-duration-ms").pipe(Flag.optional, Flag.withDescription("End the capture by itself (discarding it) after this long, defaults to 1800000 (30 minutes)")),
1354
- json: jsonFlag
1355
- }, Effect3.fn("Cli.networkStart")(function* ({ session, urlFilter, resourceTypes, content, maxBodyBytes, maxTotalBodyBytes, maxEntries, maxDuration, json }) {
1356
- const relay = yield* Service;
1357
- yield* ensureCliRelayAndExtension();
1358
- const sessionId = yield* networkSession(session);
1359
- const contentValue = yield* parseNetworkContent(content);
1360
- const result = yield* relay.networkStart({
1361
- sessionId,
1362
- ...Option2.isSome(urlFilter) ? { urlFilter: urlFilter.value } : {},
1363
- ...resourceTypes.length === 0 ? {} : { resourceTypes },
1364
- ...contentValue === undefined ? {} : { content: contentValue },
1365
- ...Option2.isSome(maxBodyBytes) ? { maxBodyBytes: maxBodyBytes.value } : {},
1366
- ...Option2.isSome(maxTotalBodyBytes) ? { maxTotalBodyBytes: maxTotalBodyBytes.value } : {},
1367
- ...Option2.isSome(maxEntries) ? { maxEntries: maxEntries.value } : {},
1368
- ...Option2.isSome(maxDuration) ? { maxDurationMs: maxDuration.value } : {}
1369
- });
1370
- yield* printOr(json, result, formatNetworkStatus(result));
1371
- })).pipe(Command.withDescription("Start session-scoped network capture"));
1372
- var networkStatus = Command.make("status", {
1373
- session: sessionFlag(),
1374
- json: jsonFlag
1375
- }, Effect3.fn("Cli.networkStatus")(function* ({ session, json }) {
1376
- const relay = yield* Service;
1377
- const sessionId = yield* networkSession(session);
1378
- yield* ensureCliRelay();
1379
- const result = yield* relay.networkStatus({ sessionId });
1380
- yield* printOr(json, result, formatNetworkStatus(result));
1381
- })).pipe(Command.withDescription("Show session-scoped network capture status"));
1382
- var networkStop = Command.make("stop", {
1383
- session: sessionFlag(),
1384
- output: Flag.String("output").pipe(Flag.optional, Flag.withAlias("o"), Flag.withDescription("Write a credential-redacted HAR artifact to this path")),
1385
- secrets: Flag.String("secrets").pipe(Flag.optional, Flag.withDescription("Store captured credentials under this reusable profile name")),
1386
- json: jsonFlag
1387
- }, Effect3.fn("Cli.networkStop")(function* ({ session, output, secrets, json }) {
1388
- const relay = yield* Service;
1389
- const outputValue = Option2.getOrUndefined(output);
1390
- const secretsValue = Option2.getOrUndefined(secrets);
1391
- if (!outputValue && !secretsValue) {
1392
- return yield* Effect3.fail(new Error("network stop requires --output, --secrets, or both"));
1393
- }
1394
- const sessionId = yield* networkSession(session);
1395
- yield* ensureCliRelay();
1396
- const result = yield* relay.networkStop({
1397
- sessionId,
1398
- ...outputValue ? { outputPath: path.resolve(outputValue) } : {},
1399
- ...secretsValue ? { secrets: secretsValue } : {}
1400
- });
1401
- yield* printOr(json, result, formatNetworkResult(result));
1402
- })).pipe(Command.withDescription("Stop capture and write a redacted artifact or reusable secret profile"));
1403
- var networkCancel = Command.make("cancel", { session: sessionFlag() }, Effect3.fn("Cli.networkCancel")(function* ({ session }) {
1393
+ yield* Console5.log(result.cancelled ? "Flight recorder stopped" : "No active flight recorder");
1394
+ })).pipe(Command4.withDescription("Stop and discard the rolling video buffer"));
1395
+ var flightRecorder = Command4.make("flight-recorder").pipe(Command4.withDescription("Keep and save a rolling buffer of recent browser video"), Command4.withSubcommands([
1396
+ flightRecorderStart,
1397
+ flightRecorderStatus,
1398
+ flightRecorderSaveLast,
1399
+ flightRecorderCancel
1400
+ ]));
1401
+ registerCliCommand(700, () => recording);
1402
+ registerCliCommand(710, () => flightRecorder);
1403
+
1404
+ // src/cli/relay.ts
1405
+ import { Console as Console6, Effect as Effect8 } from "effect";
1406
+ import { Command as Command5, Flag as Flag6 } from "effect/cli";
1407
+ var serve = Command5.make("serve", {}, Effect8.fn("Cli.serve")(function* () {
1408
+ const [{ startRelay }, { parseAdditionalExtensionOrigins }] = yield* Effect8.promise(() => Promise.all([import("./relay-cvhz2f0a.mjs"), import("./helpers-n4k4pdag.mjs")]));
1409
+ const port = yield* portConfig;
1410
+ const additionalExtensionOrigins = parseAdditionalExtensionOrigins(yield* extensionOriginsConfig);
1411
+ yield* Effect8.scoped(Effect8.gen(function* () {
1412
+ const relay = yield* startRelay({ port, additionalExtensionOrigins });
1413
+ yield* Console6.log(`Browse relay listening at ${relay.url}`);
1414
+ yield* Console6.log(`Browse connects automatically while the browser runs. Run \`${cliCommand("status")}\` to check.`);
1415
+ yield* Effect8.never;
1416
+ }));
1417
+ })).pipe(Command5.withDescription("Start the local Browse relay"));
1418
+ var relayRestart = Command5.make("restart", {
1419
+ force: Flag6.Boolean("force").pipe(Flag6.withDefault(false), Flag6.withDescription("End network captures and recordings (discarding them) and disconnect raw CDP clients instead of refusing to restart"))
1420
+ }, Effect8.fn("Cli.relayRestart")(function* ({ force }) {
1404
1421
  const relay = yield* Service;
1405
- const sessionId = yield* networkSession(session);
1406
- yield* ensureCliRelay();
1407
- const result = yield* relay.networkCancel({ sessionId });
1408
- yield* Console.log(result.cancelled ? "Network capture cancelled" : "Network capture was not active");
1409
- })).pipe(Command.withDescription("Cancel capture without writing an artifact"));
1410
- var network = Command.make("network").pipe(Command.withDescription("Capture authenticated network exchanges for direct client derivation"), Command.withSubcommands([networkStart, networkStatus, networkStop, networkCancel]));
1411
- var secretsStatus = Command.make("status", {
1412
- name: Argument.String("name"),
1422
+ yield* Console6.error("Restarting the relay leaves browser tabs open but resets in-memory JavaScript state.");
1423
+ const readiness = yield* restartRelay({ relay, clientKind: "cli", force });
1424
+ yield* Console6.log(`${readiness.started ? "Started" : "Reused concurrent replacement"} Browse relay at ${relay.endpoint} (${readiness.version.buildId})`);
1425
+ })).pipe(Command5.withDescription("Explicitly drain and restart the managed relay using this installation"));
1426
+ var relay = Command5.make("relay").pipe(Command5.withDescription("Manage the local relay lifecycle"), Command5.withSubcommands([relayRestart]));
1427
+ registerCliCommand(100, () => serve);
1428
+ registerCliCommand(110, () => relay);
1429
+
1430
+ // src/cli/secrets.ts
1431
+ import { Console as Console7, Effect as Effect9, Option as Option7 } from "effect";
1432
+ import { Argument as Argument4, Command as Command6, Flag as Flag7 } from "effect/cli";
1433
+ import path5 from "node:path";
1434
+ import process2 from "node:process";
1435
+ var secretsStatus = Command6.make("status", {
1436
+ name: Argument4.String("name"),
1413
1437
  json: jsonFlag
1414
- }, Effect3.fn("Cli.secretsStatus")(function* ({ name, json }) {
1438
+ }, Effect9.fn("Cli.secretsStatus")(function* ({ name, json }) {
1415
1439
  const relay = yield* Service;
1416
1440
  yield* ensureCliRelay();
1417
1441
  const result = yield* relay.authStatus({ name });
@@ -1419,60 +1443,60 @@ var secretsStatus = Command.make("status", {
1419
1443
  yield* emitJson(result);
1420
1444
  return;
1421
1445
  }
1422
- yield* Console.log(`Secrets profile ${result.name}: slots=${result.slotCount} updatedAt=${result.updatedAt}`);
1423
- yield* Effect3.forEach(result.slots, (slot) => Console.log(`- ${slot.ref} sources=${slot.sources.join(",")} expired=${slot.expired}${slot.expiresAt ? ` expiresAt=${slot.expiresAt}` : ""}`));
1424
- })).pipe(Command.withDescription("Show secret profile metadata without revealing values"));
1425
- var secretsRefresh = Command.make("refresh", {
1426
- name: Argument.String("name"),
1446
+ yield* Console7.log(`Secrets profile ${result.name}: slots=${result.slotCount} updatedAt=${result.updatedAt}`);
1447
+ yield* Effect9.forEach(result.slots, (slot) => Console7.log(`- ${slot.ref} sources=${slot.sources.join(",")} expired=${slot.expired}${slot.expiresAt ? ` expiresAt=${slot.expiresAt}` : ""}`));
1448
+ })).pipe(Command6.withDescription("Show secret profile metadata without revealing values"));
1449
+ var secretsRefresh = Command6.make("refresh", {
1450
+ name: Argument4.String("name"),
1427
1451
  session: sessionFlag(),
1428
- urlFilter: Flag.String("url").pipe(Flag.optional, Flag.withDescription("Observe credentials only on matching request URLs")),
1429
- timeoutMs: Flag.Int("timeout-ms").pipe(Flag.optional, Flag.withDescription("Page reload timeout, defaults to 30000")),
1452
+ urlFilter: Flag7.String("url").pipe(Flag7.optional, Flag7.withDescription("Observe credentials only on matching request URLs")),
1453
+ timeoutMs: Flag7.Int("timeout-ms").pipe(Flag7.optional, Flag7.withDescription("Page reload timeout, defaults to 30000")),
1430
1454
  json: jsonFlag
1431
- }, Effect3.fn("Cli.secretsRefresh")(function* ({ name, session, urlFilter, timeoutMs, json }) {
1455
+ }, Effect9.fn("Cli.secretsRefresh")(function* ({ name, session, urlFilter, timeoutMs, json }) {
1432
1456
  const relay = yield* Service;
1433
1457
  yield* ensureCliRelayAndExtension();
1434
1458
  const sessionId = yield* networkSession(session);
1435
1459
  const result = yield* relay.authRefresh({
1436
1460
  sessionId,
1437
1461
  name,
1438
- ...Option2.isSome(urlFilter) ? { urlFilter: urlFilter.value } : {},
1439
- ...Option2.isSome(timeoutMs) ? { timeoutMs: timeoutMs.value } : {}
1462
+ ...Option7.isSome(urlFilter) ? { urlFilter: urlFilter.value } : {},
1463
+ ...Option7.isSome(timeoutMs) ? { timeoutMs: timeoutMs.value } : {}
1440
1464
  });
1441
1465
  yield* printOr(json, result, `Secrets profile ${name} refreshed: observed=${result.observedSecretRefs.length} changed=${result.updatedSecretRefs.length}`);
1442
- })).pipe(Command.withDescription("Reload a session page and refresh a profile while preserving stable references"));
1443
- var secretsRun = Command.make("run", {
1444
- name: Argument.String("name"),
1445
- command: Argument.String("command").pipe(Argument.variadic({ min: 1 })),
1446
- cwd: Flag.String("cwd").pipe(Flag.optional, Flag.withDescription("Child process working directory")),
1447
- timeoutMs: Flag.Int("timeout-ms").pipe(Flag.optional, Flag.withDescription("Child timeout in milliseconds, defaults to 120000"))
1448
- }, Effect3.fn("Cli.secretsRun")(function* ({ name, command, cwd, timeoutMs }) {
1466
+ })).pipe(Command6.withDescription("Reload a session page and refresh a profile while preserving stable references"));
1467
+ var secretsRun = Command6.make("run", {
1468
+ name: Argument4.String("name"),
1469
+ command: Argument4.String("command").pipe(Argument4.variadic({ min: 1 })),
1470
+ cwd: Flag7.String("cwd").pipe(Flag7.optional, Flag7.withDescription("Child process working directory")),
1471
+ timeoutMs: Flag7.Int("timeout-ms").pipe(Flag7.optional, Flag7.withDescription("Child timeout in milliseconds, defaults to 120000"))
1472
+ }, Effect9.fn("Cli.secretsRun")(function* ({ name, command, cwd, timeoutMs }) {
1449
1473
  const relay = yield* Service;
1450
1474
  yield* ensureCliRelay();
1451
1475
  const [executable, ...args] = decodeCliOperands(command);
1452
1476
  if (!executable)
1453
- return yield* Effect3.fail(new Error("secrets run requires a command after --"));
1477
+ return yield* Effect9.fail(new Error("secrets run requires a command after --"));
1454
1478
  const result = yield* relay.authRun({
1455
1479
  name,
1456
1480
  command: executable,
1457
1481
  args,
1458
- cwd: path.resolve(Option2.getOrElse(cwd, () => process.cwd())),
1459
- ...Option2.isSome(timeoutMs) ? { timeoutMs: timeoutMs.value } : {}
1482
+ cwd: path5.resolve(Option7.getOrElse(cwd, () => process2.cwd())),
1483
+ ...Option7.isSome(timeoutMs) ? { timeoutMs: timeoutMs.value } : {}
1460
1484
  });
1461
- yield* Effect3.sync(() => {
1485
+ yield* Effect9.sync(() => {
1462
1486
  if (result.stdout)
1463
- process.stdout.write(result.stdout);
1487
+ process2.stdout.write(result.stdout);
1464
1488
  if (result.stderr)
1465
- process.stderr.write(result.stderr);
1489
+ process2.stderr.write(result.stderr);
1466
1490
  if (result.stdoutTruncated || result.stderrTruncated)
1467
- process.stderr.write(`
1491
+ process2.stderr.write(`
1468
1492
  Browse truncated child output.
1469
1493
  `);
1470
1494
  if (result.exitCode !== 0)
1471
- process.exitCode = result.exitCode;
1495
+ process2.exitCode = result.exitCode;
1472
1496
  });
1473
- })).pipe(Command.withDescription("Run a command with profile values injected as BC_SECRET_* environment variables"));
1497
+ })).pipe(Command6.withDescription("Run a command with profile values injected as BC_SECRET_* environment variables"));
1474
1498
  function readSecretValue(prompt) {
1475
- const input = process.stdin;
1499
+ const input = process2.stdin;
1476
1500
  if (!input.isTTY) {
1477
1501
  return new Promise((resolve, reject) => {
1478
1502
  const chunks = [];
@@ -1482,13 +1506,13 @@ function readSecretValue(prompt) {
1482
1506
  });
1483
1507
  }
1484
1508
  return new Promise((resolve, reject) => {
1485
- process.stderr.write(prompt);
1509
+ process2.stderr.write(prompt);
1486
1510
  let value = "";
1487
1511
  const finish = (result) => {
1488
1512
  input.setRawMode(false);
1489
1513
  input.pause();
1490
1514
  input.off("data", onData);
1491
- process.stderr.write(`
1515
+ process2.stderr.write(`
1492
1516
  `);
1493
1517
  if (result instanceof Error)
1494
1518
  reject(result);
@@ -1513,44 +1537,49 @@ function readSecretValue(prompt) {
1513
1537
  input.on("data", onData);
1514
1538
  });
1515
1539
  }
1516
- var secretsAdd = Command.make("add", {
1517
- name: Argument.String("name").pipe(Argument.withDescription('Secret name agents use: secrets.type(locator, "name")')),
1518
- origin: Flag.String("origin").pipe(Flag.atLeast(1), Flag.withDescription("Origin the secret may be typed on (repeatable): https://example.com, *.example.com (https), http://localhost:3000")),
1519
- totp: Flag.Boolean("totp").pipe(Flag.withDefault(false), Flag.withDescription("The value is a TOTP seed (base32 or otpauth:// URI); typing it types the current code. Storing the seed beside the password makes 2FA one factor")),
1540
+ var secretsAdd = Command6.make("add", {
1541
+ name: Argument4.String("name").pipe(Argument4.withDescription('Secret name agents use: secrets.type(locator, "name")')),
1542
+ origin: Flag7.String("origin").pipe(Flag7.atLeast(1), Flag7.withDescription("Origin the secret may be typed on (repeatable): https://example.com, *.example.com (https), http://localhost:3000")),
1543
+ totp: Flag7.Boolean("totp").pipe(Flag7.withDefault(false), Flag7.withDescription("The value is a TOTP seed (base32 or otpauth:// URI); typing it types the current code. Storing the seed beside the password makes 2FA one factor")),
1520
1544
  json: jsonFlag
1521
- }, Effect3.fn("Cli.secretsAdd")(function* ({ name, origin, totp, json }) {
1522
- const value = yield* Effect3.tryPromise({
1545
+ }, Effect9.fn("Cli.secretsAdd")(function* ({ name, origin, totp, json }) {
1546
+ const value = yield* Effect9.tryPromise({
1523
1547
  try: () => readSecretValue(totp ? `TOTP seed for ${name} (hidden): ` : `Value for ${name} (hidden): `),
1524
1548
  catch: (cause) => cause instanceof Error ? cause : new Error("Could not read the secret value")
1525
1549
  });
1526
- const result = yield* addSecret({ name, value, allowedOrigins: origin, kind: totp ? "totp" : "password" });
1550
+ const result = yield* addSecret({
1551
+ name,
1552
+ value,
1553
+ allowedOrigins: origin,
1554
+ kind: totp ? "totp" : "password"
1555
+ });
1527
1556
  if (json) {
1528
1557
  yield* emitJson(result);
1529
1558
  return;
1530
1559
  }
1531
- yield* Console.log(`Saved ${result.kind} secret ${result.name}; it can be typed on ${result.allowedOrigins.join(", ")}`);
1560
+ yield* Console7.log(`Saved ${result.kind} secret ${result.name}; it can be typed on ${result.allowedOrigins.join(", ")}`);
1532
1561
  if (totp)
1533
- yield* Console.error("Note: with the TOTP seed stored here, anyone who can use these secrets has both factors. Prefer handing 2FA to a person when you can.");
1534
- })).pipe(Command.withDescription("Store a secret agents can type only on its origins (value from stdin or a hidden prompt)"));
1535
- var secretsList = Command.make("list", { json: jsonFlag }, Effect3.fn("Cli.secretsList")(function* ({ json }) {
1562
+ yield* Console7.error("Note: with the TOTP seed stored here, anyone who can use these secrets has both factors. Prefer handing 2FA to a person when you can.");
1563
+ })).pipe(Command6.withDescription("Store a secret agents can type only on its origins (value from stdin or a hidden prompt)"));
1564
+ var secretsList = Command6.make("list", { json: jsonFlag }, Effect9.fn("Cli.secretsList")(function* ({ json }) {
1536
1565
  const secrets = yield* listSecrets();
1537
1566
  if (json) {
1538
1567
  yield* emitJson({ secrets });
1539
1568
  return;
1540
1569
  }
1541
1570
  if (secrets.length === 0) {
1542
- yield* Console.log("No site secrets");
1571
+ yield* Console7.log("No site secrets");
1543
1572
  return;
1544
1573
  }
1545
- yield* Effect3.forEach(secrets, (secret) => Console.log(`${secret.name} ${secret.kind} ${secret.allowedOrigins.join(",")}`));
1546
- })).pipe(Command.withDescription("List site secrets (names, kinds and origins, never values)"));
1547
- var secretsRemove = Command.make("remove", { name: Argument.String("name") }, Effect3.fn("Cli.secretsRemove")(function* ({ name }) {
1574
+ yield* Effect9.forEach(secrets, (secret) => Console7.log(`${secret.name} ${secret.kind} ${secret.allowedOrigins.join(",")}`));
1575
+ })).pipe(Command6.withDescription("List site secrets (names, kinds and origins, never values)"));
1576
+ var secretsRemove = Command6.make("remove", { name: Argument4.String("name") }, Effect9.fn("Cli.secretsRemove")(function* ({ name }) {
1548
1577
  const removed = yield* removeSecret(name);
1549
- yield* Console.log(removed ? `Removed ${name}` : `No secret named ${name}`);
1550
- })).pipe(Command.withDescription("Delete a site secret"));
1551
- var secrets = Command.make("secrets").pipe(Command.withDescription("Site secrets agents can type without seeing them, and captured credential profiles"), Command.withSubcommands([secretsAdd, secretsList, secretsRemove, secretsStatus, secretsRefresh, secretsRun]));
1552
- var deadboltConnect = Command.make("connect", { json: jsonFlag }, Effect3.fn("Cli.deadboltConnect")(function* ({ json }) {
1553
- const paired = yield* Effect3.tryPromise({
1578
+ yield* Console7.log(removed ? `Removed ${name}` : `No secret named ${name}`);
1579
+ })).pipe(Command6.withDescription("Delete a site secret"));
1580
+ var secrets = Command6.make("secrets").pipe(Command6.withDescription("Site secrets agents can type without seeing them, and captured credential profiles"), Command6.withSubcommands([secretsAdd, secretsList, secretsRemove, secretsStatus, secretsRefresh, secretsRun]));
1581
+ var deadboltConnect = Command6.make("connect", { json: jsonFlag }, Effect9.fn("Cli.deadboltConnect")(function* ({ json }) {
1582
+ const paired = yield* Effect9.tryPromise({
1554
1583
  try: () => pair(),
1555
1584
  catch: (cause) => cause instanceof Error ? cause : new Error("Could not connect to Deadbolt")
1556
1585
  });
@@ -1558,138 +1587,237 @@ var deadboltConnect = Command.make("connect", { json: jsonFlag }, Effect3.fn("Cl
1558
1587
  yield* emitJson({ connected: true, id: paired.id });
1559
1588
  return;
1560
1589
  }
1561
- yield* Console.log(`Connected Browse to Deadbolt as client ${paired.id}.`);
1562
- yield* Console.log("Agents can use only the logins you allow, in Deadbolt: deadbolt agents allow <site> (narrow with --login, --fields, --expires), or deadbolt agents allow --all for every login");
1563
- })).pipe(Command.withDescription("Pair Browse with Deadbolt (runs `deadbolt agents add-client browse`, which asks you to confirm) and store its client token (0600)"));
1564
- var deadboltDisconnect = Command.make("disconnect", {}, Effect3.fn("Cli.deadboltDisconnect")(function* () {
1565
- const result = yield* Effect3.try({
1590
+ yield* Console7.log(`Connected Browse to Deadbolt as client ${paired.id}.`);
1591
+ yield* Console7.log("Agents can use only the logins you allow, in Deadbolt: deadbolt agents allow <site> (narrow with --login, --fields, --expires), or deadbolt agents allow --all for every login");
1592
+ })).pipe(Command6.withDescription("Pair Browse with Deadbolt (runs `deadbolt agents add-client browse`, which asks you to confirm) and store its client token (0600)"));
1593
+ var deadboltDisconnect = Command6.make("disconnect", {}, Effect9.fn("Cli.deadboltDisconnect")(function* () {
1594
+ const result = yield* Effect9.try({
1566
1595
  try: () => unpair(),
1567
1596
  catch: (cause) => cause instanceof Error ? cause : new Error("Could not disconnect from Deadbolt")
1568
1597
  });
1569
1598
  if (!result.removed) {
1570
- yield* Console.log("Browse isn't connected to Deadbolt");
1599
+ yield* Console7.log("Browse isn't connected to Deadbolt");
1571
1600
  return;
1572
1601
  }
1573
- yield* Console.log(result.revoked ? "Disconnected: the client is removed in Deadbolt and its token deleted here." : "Deleted the client token here. Deadbolt didn't confirm removing the client: check `deadbolt agents clients`.");
1574
- })).pipe(Command.withDescription("Remove Browse's Deadbolt client (in Deadbolt and here)"));
1575
- var deadboltStatus = Command.make("status", { json: jsonFlag }, Effect3.fn("Cli.deadboltStatus")(function* ({ json }) {
1602
+ yield* Console7.log(result.revoked ? "Disconnected: the client is removed in Deadbolt and its token deleted here." : "Deleted the client token here. Deadbolt didn't confirm removing the client: check `deadbolt agents clients`.");
1603
+ })).pipe(Command6.withDescription("Remove Browse's Deadbolt client (in Deadbolt and here)"));
1604
+ var deadboltStatus = Command6.make("status", { json: jsonFlag }, Effect9.fn("Cli.deadboltStatus")(function* ({ json }) {
1576
1605
  const client = readClient();
1577
1606
  const socket = socketPath();
1578
- const status = client ? yield* Effect3.promise(() => connect({ client, socket }).status().then((value) => ({ reachable: true, ...value }), (error) => ({ reachable: false, error: error instanceof Error ? error.message : String(error) }))) : undefined;
1579
- const report = { connected: client !== undefined, ...client ? { id: client.id } : {}, socket, ...status ? { deadbolt: status } : {} };
1607
+ const status = client ? yield* Effect9.promise(() => connect({ client, socket }).status().then((value) => ({ reachable: true, ...value }), (error) => ({
1608
+ reachable: false,
1609
+ error: error instanceof Error ? error.message : String(error)
1610
+ }))) : undefined;
1611
+ const report = {
1612
+ connected: client !== undefined,
1613
+ ...client ? { id: client.id } : {},
1614
+ socket,
1615
+ ...status ? { deadbolt: status } : {}
1616
+ };
1580
1617
  if (json) {
1581
1618
  yield* emitJson(report);
1582
1619
  return;
1583
1620
  }
1584
1621
  if (!client) {
1585
- yield* Console.log("Not connected to Deadbolt. Run `opencode-browser deadbolt connect`.");
1622
+ yield* Console7.log("Not connected to Deadbolt. Run `opencode-browser deadbolt connect`.");
1586
1623
  return;
1587
1624
  }
1588
- yield* Console.log(`Connected as Deadbolt client ${client.id}; socket ${socket}`);
1625
+ yield* Console7.log(`Connected as Deadbolt client ${client.id}; socket ${socket}`);
1589
1626
  if (status && status.reachable)
1590
- yield* Console.log(`Deadbolt ${status.version ?? ""} (protocol ${status.protocol}) is ${status.unlocked ? "unlocked" : "locked: agent sign-ins are paused until you unlock it"}`);
1627
+ yield* Console7.log(`Deadbolt ${status.version ?? ""} (protocol ${status.protocol}) is ${status.unlocked ? "unlocked" : "locked: agent sign-ins are paused until you unlock it"}`);
1591
1628
  else if (status)
1592
- yield* Console.log(`Deadbolt isn't reachable: ${status.error}`);
1593
- })).pipe(Command.withDescription("Show whether Browse is paired with Deadbolt and whether Deadbolt is reachable and unlocked"));
1594
- var deadbolt = Command.make("deadbolt").pipe(Command.withDescription("Let agents sign in with the Deadbolt logins you grant them, without seeing any value"), Command.withSubcommands([deadboltConnect, deadboltDisconnect, deadboltStatus]));
1595
- var journal = Command.make("journal", {
1596
- session: sessionFlag("Show the journal for this Browse session id"),
1597
- limit: Flag.Int("limit").pipe(Flag.optional, Flag.withDescription("Number of most recent entries to show, defaults to 20")),
1598
- json: jsonFlag
1599
- }, Effect3.fn("Cli.journal")(function* ({ session, limit, json }) {
1600
- const sessionId = yield* networkSession(session);
1601
- const entries = yield* Effect3.tryPromise({
1602
- try: () => readJournalEntries({ baseDir: defaultJournalBaseDir(), sessionId, limit: Option2.getOrUndefined(limit) ?? 20 }),
1603
- catch: (cause) => new Error(`read session journal for ${sessionId}`, { cause })
1629
+ yield* Console7.log(`Deadbolt isn't reachable: ${status.error}`);
1630
+ })).pipe(Command6.withDescription("Show whether Browse is paired with Deadbolt and whether Deadbolt is reachable and unlocked"));
1631
+ var deadbolt = Command6.make("deadbolt").pipe(Command6.withDescription("Let agents sign in with the Deadbolt logins you grant them, without seeing any value"), Command6.withSubcommands([deadboltConnect, deadboltDisconnect, deadboltStatus]));
1632
+ registerCliCommand(610, () => secrets);
1633
+ registerCliCommand(620, () => deadbolt);
1634
+
1635
+ // src/cli/session.ts
1636
+ import { Console as Console8, Effect as Effect10, Option as Option8 } from "effect";
1637
+ import { Argument as Argument5, Command as Command7, Flag as Flag8 } from "effect/cli";
1638
+ function describeStop(stopped) {
1639
+ return [stopped.execute ? "a running execute" : undefined, stopped.handoff ? "a pending handoff" : undefined].filter(Boolean).join(" and ") + (stopped.forced ? " (the script did not stop in time and was abandoned with its state)" : "");
1640
+ }
1641
+ var sessionNew = Command7.make("new", {
1642
+ name: Argument5.String("name").pipe(Argument5.optional, Argument5.withDescription("Optional lowercase session id")),
1643
+ readOnly: Flag8.Boolean("read-only").pipe(Flag8.withDefault(false), Flag8.withDescription("Create a read-only session: the relay rejects input-dispatching CDP so scripts can inspect but not click or type")),
1644
+ allowOrigin: Flag8.String("allow-origin").pipe(Flag8.atLeast(0), Flag8.withDescription("Only let this session's tabs load these hosts or origins (repeatable or comma-separated): example.com, *.example.com, https://app.example.com, http://localhost:3000"))
1645
+ }, Effect10.fn("Cli.sessionNew")(function* ({ name, readOnly, allowOrigin }) {
1646
+ const relay = yield* Service;
1647
+ yield* ensureCliRelay();
1648
+ const store = yield* Service2;
1649
+ const result = yield* relay.sessionNew(Option8.getOrUndefined(name), {
1650
+ ...readOnly ? { readOnly: true } : {},
1651
+ ...allowOrigin.length > 0 ? { allowedOrigins: allowOrigin } : {}
1604
1652
  });
1653
+ yield* store.write(result.id);
1654
+ yield* Console8.log(result.id);
1655
+ })).pipe(Command7.withDescription("Create a Browse session and make it current"));
1656
+ var sessionList = Command7.make("list", {
1657
+ json: jsonFlag
1658
+ }, Effect10.fn("Cli.sessionList")(function* ({ json }) {
1659
+ const relay = yield* Service;
1660
+ yield* ensureCliRelay();
1661
+ const store = yield* Service2;
1662
+ const sessions = yield* relay.sessions;
1663
+ const current = yield* store.read;
1605
1664
  if (json) {
1606
- yield* emitJson({ session: sessionId, entries });
1665
+ yield* emitJson({ current: current ?? undefined, sessions });
1607
1666
  return;
1608
1667
  }
1609
- if (entries.length === 0) {
1610
- yield* Console.log(`No journal entries for session ${sessionId}`);
1668
+ if (sessions.length === 0) {
1669
+ yield* Console8.log("No sessions");
1611
1670
  return;
1612
1671
  }
1613
- yield* Console.log(`Journal for ${sessionId} (last ${entries.length}):`);
1614
- yield* Effect3.forEach(entries, (entry) => {
1615
- return Console.log(formatJournalEntry(entry));
1672
+ yield* Effect10.forEach(sessions, (item) => {
1673
+ const marker = item.id === current ? "*" : " ";
1674
+ const page = item.pageUrl ?? "no page yet";
1675
+ const keys = item.stateKeys.length ? ` state=${item.stateKeys.join(",")}` : "";
1676
+ const readOnly = item.readOnly ? " read-only" : "";
1677
+ const allowed = item.allowedOrigins ? ` allow=${item.allowedOrigins.join(",")}` : "";
1678
+ return Console8.log(`${marker} ${item.id} ${page}${keys}${readOnly}${allowed}`);
1616
1679
  });
1617
- })).pipe(Command.withDescription("Show what agents did in a Browse session"));
1618
- var doctor = Command.make("doctor", {
1619
- json: jsonFlag
1620
- }, Effect3.fn("Cli.doctor")(function* ({ json }) {
1621
- const update = yield* Effect3.promise(() => updateNotice2());
1622
- const report = yield* createDoctorReport({ packageRoot, update: updateJson2(update) });
1623
- if (json) {
1624
- yield* emitJson(report);
1625
- } else {
1626
- yield* Console.log(formatDoctorReport(report));
1680
+ })).pipe(Command7.withDescription("List Browse sessions"));
1681
+ var sessionCurrent = Command7.make("current", {}, Effect10.fn("Cli.sessionCurrent")(function* () {
1682
+ const store = yield* Service2;
1683
+ const current = yield* store.read;
1684
+ yield* Console8.log(current ?? "none");
1685
+ })).pipe(Command7.withDescription("Print the current default session"));
1686
+ var sessionUse = Command7.make("use", {
1687
+ id: Argument5.String("id")
1688
+ }, Effect10.fn("Cli.sessionUse")(function* ({ id }) {
1689
+ const store = yield* Service2;
1690
+ yield* ensureSessionExists2(id);
1691
+ yield* store.write(id);
1692
+ yield* Console8.log(id);
1693
+ })).pipe(Command7.withDescription("Set the current default session"));
1694
+ var sessionReset = Command7.make("reset", {
1695
+ id: Argument5.String("id").pipe(Argument5.optional),
1696
+ session: sessionFlag("Reset this Browse session id")
1697
+ }, Effect10.fn("Cli.sessionReset")(function* ({ id, session }) {
1698
+ const relay = yield* Service;
1699
+ const sessionId = yield* resolvePositionalOrFlagSessionId(id, session);
1700
+ yield* ensureCliRelay();
1701
+ const reset = yield* relay.sessionReset(sessionId);
1702
+ if (reset.stopped)
1703
+ yield* Console8.error(`Stopped first: ${describeStop(reset.stopped)}`);
1704
+ yield* Console8.log(reset.session.id);
1705
+ })).pipe(Command7.withDescription("Reset a Browse session state and page"));
1706
+ var sessionAdopt = Command7.make("adopt", {
1707
+ session: sessionFlag("Adopt into this Browse session, creating it when it does not exist yet; omit to create a fresh readable id"),
1708
+ targetUrl: Flag8.String("target-url").pipe(Flag8.optional, Flag8.withDescription("Adopt the attached page whose URL contains this text")),
1709
+ targetIndex: Flag8.Int("target-index").pipe(Flag8.optional, Flag8.withDescription("Adopt the attached page at this zero-based target index")),
1710
+ tabId: tabIdFlag("Adopt the user's shared tab with this Chrome tab id (the tab= value in status)")
1711
+ }, Effect10.fn("Cli.sessionAdopt")(function* ({ session, targetUrl, targetIndex, tabId }) {
1712
+ const relay = yield* Service;
1713
+ const explicitSessionId = Option8.getOrUndefined(session) ?? Option8.getOrUndefined(yield* sessionIdConfig);
1714
+ yield* ensureCliRelayAndExtension(explicitSessionId);
1715
+ const targetUrlValue = Option8.getOrUndefined(targetUrl);
1716
+ const targetIndexValue = Option8.getOrUndefined(targetIndex);
1717
+ if (targetIndexValue !== undefined && targetIndexValue < 0) {
1718
+ return yield* Effect10.fail(new Error("Target index must be a non-negative integer"));
1627
1719
  }
1628
- if (report.status === "fail") {
1629
- yield* failExit;
1720
+ const tabIdValue = Option8.getOrUndefined(tabId);
1721
+ if ([targetUrlValue, targetIndexValue, tabIdValue].filter((value) => value !== undefined).length > 1) {
1722
+ return yield* Effect10.fail(new Error("Use only one target selector: --target-url, --target-index, or --tab-id"));
1630
1723
  }
1631
- })).pipe(Command.withDescription("Diagnose the local Browse install and runtime"));
1632
- var skill = Command.make("skill", {}, Effect3.fn("Cli.skill")(function* () {
1633
- const fs = yield* FileSystem3.FileSystem;
1634
- const text = yield* fs.readFileString(path.join(packageRoot, "skills", "browse", "SKILL.md")).pipe(Effect3.mapError((cause) => new Error("read opencode-browser skill", { cause })));
1635
- yield* Console.log(text.trimEnd());
1636
- })).pipe(Command.withDescription("Print the Browse agent skill text"));
1637
- var tabsCleanup = Command.make("cleanup", {
1638
- idleMinutes: Flag.Int("idle-minutes").pipe(Flag.optional, Flag.withDescription("Close agent tabs idle at least this long; 0 closes every eligible agent tab (default: the extension's setting, 10; still 10 when it is set to never)")),
1639
- json: jsonFlag
1640
- }, Effect3.fn("Cli.tabsCleanup")(function* ({ idleMinutes, json }) {
1724
+ const targetSelection = targetUrlValue ? { urlIncludes: targetUrlValue } : targetIndexValue !== undefined ? { index: targetIndexValue } : undefined;
1725
+ const result = yield* relay.sessionAdopt({
1726
+ ...explicitSessionId ? { sessionId: explicitSessionId } : {},
1727
+ createIfMissing: true,
1728
+ ...targetSelection ? { targetSelection } : {},
1729
+ ...tabIdValue === undefined ? {} : { tabId: tabIdValue }
1730
+ });
1731
+ yield* Console8.log(`${result.session.created ? "Created and adopted" : "Adopted"} session '${result.session.id}' default page: ${result.adoptedUrl}`);
1732
+ if (result.session.created) {
1733
+ yield* Console8.error(formatSessionContinuation(result.session.id));
1734
+ }
1735
+ })).pipe(Command7.withDescription("Make an attached tab the session's default page"));
1736
+ var sessionDelete = Command7.make("delete", {
1737
+ id: Argument5.String("id").pipe(Argument5.optional),
1738
+ session: sessionFlag("Delete this Browse session id")
1739
+ }, Effect10.fn("Cli.sessionDelete")(function* ({ id, session }) {
1641
1740
  const relay = yield* Service;
1741
+ const store = yield* Service2;
1742
+ const sessionId = yield* resolvePositionalOrFlagSessionId(id, session);
1642
1743
  yield* ensureCliRelay();
1643
- const result = yield* relay.tabsCleanup(Option2.getOrUndefined(idleMinutes));
1644
- if (json)
1645
- return yield* emitJson(result);
1646
- if (!result.closed.length)
1647
- return yield* Console.log("No idle agent tabs to close.");
1648
- for (const tab of result.closed)
1649
- yield* Console.log(`Closed ${tab.title} (idle ${tab.idleMinutes}m)`);
1650
- })).pipe(Command.withDescription("Close tabs agents opened that nobody has used for a while"));
1651
- var tabs = Command.make("tabs").pipe(Command.withDescription("Manage the tabs agents open"), Command.withSubcommands([tabsCleanup]));
1652
- var printSettings = (settings) => Effect3.forEach(formatSettings(settings), (line) => Console.log(line), { discard: true });
1653
- var settingsGet = Command.make("get", { json: jsonFlag }, Effect3.fn("Cli.settingsGet")(function* ({ json }) {
1744
+ const deleted = yield* relay.sessionDelete(sessionId);
1745
+ if (deleted.stopped)
1746
+ yield* Console8.error(`Stopped first: ${describeStop(deleted.stopped)}`);
1747
+ const current = yield* store.read;
1748
+ if (current === sessionId) {
1749
+ yield* store.clear;
1750
+ }
1751
+ yield* Console8.log(sessionId);
1752
+ })).pipe(Command7.withDescription("Delete a Browse session"));
1753
+ var sessionAllow = Command7.make("allow", {
1754
+ session: sessionFlag(),
1755
+ origins: Argument5.String("origins").pipe(Argument5.variadic({ min: 0 }), Argument5.withDescription("Hosts or origins the session's tabs may load: example.com, *.example.com, https://app.example.com, http://localhost:3000")),
1756
+ clear: Flag8.Boolean("clear").pipe(Flag8.withDefault(false), Flag8.withDescription("Remove the allowlist: the session may load any site again")),
1757
+ json: jsonFlag
1758
+ }, Effect10.fn("Cli.sessionAllow")(function* ({ session, origins, clear, json }) {
1654
1759
  const relay = yield* Service;
1655
1760
  yield* ensureCliRelay();
1656
- const result = yield* relay.settingsGet;
1657
- if (json)
1658
- return yield* emitJson(result);
1659
- yield* printSettings(result.settings);
1660
- yield* Console.log(result.note);
1661
- })).pipe(Command.withDescription("Show each setting's value, the value in effect, and any relay environment variable overriding it"));
1662
- var settingsSet = Command.make("set", {
1663
- assignments: Argument.String("key=value").pipe(Argument.variadic({ min: 1 }), Argument.withDescription(`Settings to change: ${settingKeys.join(", ")}`)),
1761
+ if (!clear && origins.length === 0) {
1762
+ return yield* Effect10.fail(new Error("session allow needs origins, or --clear to remove the allowlist"));
1763
+ }
1764
+ const id = yield* resolvePositionalOrFlagSessionId(Option8.none(), session);
1765
+ const result = yield* relay.sessionAllowlist(id, clear ? [] : origins);
1766
+ if (json) {
1767
+ yield* emitJson(result);
1768
+ return;
1769
+ }
1770
+ yield* Console8.log(result.allowedOrigins ? `${result.id} may load: ${result.allowedOrigins.join(", ")}` : `${result.id} may load any site`);
1771
+ })).pipe(Command7.withDescription("Set the domain allowlist of a session (the current one by default)"));
1772
+ var session = Command7.make("session").pipe(Command7.withDescription("Manage Browse sessions"), Command7.withSubcommands([
1773
+ sessionNew,
1774
+ sessionList,
1775
+ sessionCurrent,
1776
+ sessionUse,
1777
+ sessionReset,
1778
+ sessionAdopt,
1779
+ sessionDelete,
1780
+ sessionAllow
1781
+ ]));
1782
+ var journal = Command7.make("journal", {
1783
+ session: sessionFlag("Show the journal for this Browse session id"),
1784
+ limit: Flag8.Int("limit").pipe(Flag8.optional, Flag8.withDescription("Number of most recent entries to show, defaults to 20")),
1664
1785
  json: jsonFlag
1665
- }, Effect3.fn("Cli.settingsSet")(function* ({ assignments, json }) {
1666
- const changes = {};
1667
- for (const assignment of assignments) {
1668
- const index = assignment.indexOf("=");
1669
- if (index <= 0)
1670
- return yield* Effect3.fail(new Error(`Expected key=value, got "${assignment}". Settings: ${settingKeys.join(", ")}.`));
1671
- changes[assignment.slice(0, index)] = assignment.slice(index + 1);
1786
+ }, Effect10.fn("Cli.journal")(function* ({ session, limit, json }) {
1787
+ const sessionId = yield* networkSession(session);
1788
+ const entries = yield* Effect10.tryPromise({
1789
+ try: () => readJournalEntries({
1790
+ baseDir: defaultJournalBaseDir(),
1791
+ sessionId,
1792
+ limit: Option8.getOrUndefined(limit) ?? 20
1793
+ }),
1794
+ catch: (cause) => new Error(`read session journal for ${sessionId}`, { cause })
1795
+ });
1796
+ if (json) {
1797
+ yield* emitJson({ session: sessionId, entries });
1798
+ return;
1672
1799
  }
1673
- const relay = yield* Service;
1674
- yield* ensureCliRelay();
1675
- const result = yield* relay.settingsSet(changes);
1676
- if (json)
1677
- return yield* emitJson(result);
1678
- yield* Console.log(result.message);
1679
- yield* printSettings(result.settings);
1680
- })).pipe(Command.withDescription("Change settings (validated strictly; the extension's options page updates live)"));
1681
- var settings = Command.make("settings").pipe(Command.withDescription("Read or change the user's settings (the extension's options page). Security switches are environment-only"), Command.withSubcommands([settingsGet, settingsSet]));
1682
- var mcp = Command.make("mcp", {}, Effect3.fn("Cli.mcp")(function* () {
1683
- const { runMcpServer } = yield* Effect3.promise(() => import("./mcp-qh25n9s0.mjs"));
1684
- yield* runMcpServer;
1685
- })).pipe(Command.withDescription("Run the Browse MCP server over stdio"));
1686
- var opencodeBrowser = Command.make("opencode-browser").pipe(Command.withDescription("Control the user's existing browser through the Browse extension"), Command.withSubcommands([serve, relay, execute, session, tabs, settings, status, network, secrets, deadbolt, recording, flightRecorder, journal, doctor, skill, mcp]));
1800
+ if (entries.length === 0) {
1801
+ yield* Console8.log(`No journal entries for session ${sessionId}`);
1802
+ return;
1803
+ }
1804
+ yield* Console8.log(`Journal for ${sessionId} (last ${entries.length}):`);
1805
+ yield* Effect10.forEach(entries, (entry) => {
1806
+ return Console8.log(formatJournalEntry(entry));
1807
+ });
1808
+ })).pipe(Command7.withDescription("Show what agents did in a Browse session"));
1809
+ registerCliCommand(300, () => session);
1810
+ registerCliCommand(800, () => journal);
1811
+
1812
+ // src/cli.ts
1813
+ var packageRoot = path6.dirname(path6.dirname(fileURLToPath(import.meta.url)));
1814
+ var opencodeBrowser = Command8.make("opencode-browser").pipe(Command8.withDescription("Control the user's existing browser through the Browse extension"), Command8.withSubcommands(buildRegisteredCliCommands({ packageRoot })));
1687
1815
  var mainLayer = Layer2.mergeAll(layerFetch, layer).pipe(Layer2.provideMerge(NodeServices.layer));
1688
- function runCli(rawArgs = process.argv.slice(2)) {
1816
+ function runCli(rawArgs = process3.argv.slice(2)) {
1689
1817
  const cliArgs = hoistJsonFlag(rawArgs);
1690
1818
  const subcommand = cliArgs.argv.find((arg) => !arg.startsWith("-"));
1691
1819
  const commandName = subcommand ? `opencode-browser ${subcommand}` : "opencode-browser";
1692
- const reportUsageError = (error) => Effect3.gen(function* () {
1820
+ const reportUsageError = (error) => Effect11.gen(function* () {
1693
1821
  const errors = error._tag === "ShowHelp" ? error.errors : [error];
1694
1822
  if (errors.length === 0)
1695
1823
  return;
@@ -1700,23 +1828,23 @@ function runCli(rawArgs = process.argv.slice(2)) {
1700
1828
  }
1701
1829
  yield* exitWith(ExitCode.usage);
1702
1830
  });
1703
- const reportFailure = (error) => Effect3.gen(function* () {
1831
+ const reportFailure = (error) => Effect11.gen(function* () {
1704
1832
  const message = error instanceof Error ? error.message : String(error);
1705
1833
  const exitCode = exitCodeForError(error);
1706
1834
  if (cliArgs.json) {
1707
1835
  yield* emitJson(failure(commandName, exitCode === ExitCode.host ? "relay_unavailable" : "error", message, { exitCode }));
1708
1836
  yield* stderrLine(message);
1709
1837
  } else {
1710
- yield* Console.error(message);
1838
+ yield* Console9.error(message);
1711
1839
  }
1712
1840
  yield* exitWith(exitCode);
1713
1841
  });
1714
1842
  const silent = () => {};
1715
1843
  const askedForHelp = cliArgs.argv.some((arg) => arg === "--help" || arg === "-h");
1716
1844
  const quietJson = cliArgs.json && !askedForHelp;
1717
- Command.runWith(opencodeBrowser, { version: opencodeBrowserVersion2, renderErrors: !quietJson })(normalizeCliArguments(cliArgs.argv)).pipe(quietJson ? Effect3.provideService(Console.Console, { ...globalThis.console, log: silent }) : (effect) => effect, Effect3.catchIf(CliError.isCliError, reportUsageError), Effect3.catch(reportFailure), Effect3.provide(mainLayer), NodeRuntime.runMain);
1845
+ Command8.runWith(opencodeBrowser, { version: opencodeBrowserVersion2, renderErrors: !quietJson })(normalizeCliArguments(cliArgs.argv)).pipe(quietJson ? Effect11.provideService(Console9.Console, { ...globalThis.console, log: silent }) : (effect) => effect, Effect11.catchIf(CliError.isCliError, reportUsageError), Effect11.catch(reportFailure), Effect11.provide(mainLayer), NodeRuntime.runMain);
1718
1846
  }
1719
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
1847
+ if (process3.argv[1] && import.meta.url === pathToFileURL(process3.argv[1]).href) {
1720
1848
  runCli();
1721
1849
  }
1722
1850
  export {