local-browser-bridge 0.1.0

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.
Files changed (92) hide show
  1. package/README.md +724 -0
  2. package/dist/package.json +61 -0
  3. package/dist/src/browser/chrome.d.ts +19 -0
  4. package/dist/src/browser/chrome.js +778 -0
  5. package/dist/src/browser/index.d.ts +3 -0
  6. package/dist/src/browser/index.js +25 -0
  7. package/dist/src/browser/safari.d.ts +41 -0
  8. package/dist/src/browser/safari.js +827 -0
  9. package/dist/src/browser-attach-ux-helper.d.ts +39 -0
  10. package/dist/src/browser-attach-ux-helper.js +157 -0
  11. package/dist/src/capabilities.d.ts +3 -0
  12. package/dist/src/capabilities.js +182 -0
  13. package/dist/src/chrome-relay-error-helper.d.ts +19 -0
  14. package/dist/src/chrome-relay-error-helper.js +78 -0
  15. package/dist/src/chrome-relay-helper-cli.d.ts +2 -0
  16. package/dist/src/chrome-relay-helper-cli.js +97 -0
  17. package/dist/src/chrome-relay-helper.d.ts +29 -0
  18. package/dist/src/chrome-relay-helper.js +151 -0
  19. package/dist/src/chrome-relay-state.d.ts +23 -0
  20. package/dist/src/chrome-relay-state.js +108 -0
  21. package/dist/src/claude-code.d.ts +20 -0
  22. package/dist/src/claude-code.js +66 -0
  23. package/dist/src/cli-reference-adapter.d.ts +13 -0
  24. package/dist/src/cli-reference-adapter.js +48 -0
  25. package/dist/src/cli.d.ts +3 -0
  26. package/dist/src/cli.js +200 -0
  27. package/dist/src/codex.d.ts +17 -0
  28. package/dist/src/codex.js +25 -0
  29. package/dist/src/connection-ux.d.ts +61 -0
  30. package/dist/src/connection-ux.js +256 -0
  31. package/dist/src/errors.d.ts +12 -0
  32. package/dist/src/errors.js +58 -0
  33. package/dist/src/http-reference-adapter.d.ts +34 -0
  34. package/dist/src/http-reference-adapter.js +61 -0
  35. package/dist/src/http.d.ts +3 -0
  36. package/dist/src/http.js +161 -0
  37. package/dist/src/index.d.ts +17 -0
  38. package/dist/src/index.js +43 -0
  39. package/dist/src/mcp-stdio.d.ts +2 -0
  40. package/dist/src/mcp-stdio.js +10 -0
  41. package/dist/src/mcp.d.ts +25 -0
  42. package/dist/src/mcp.js +483 -0
  43. package/dist/src/reference-adapter.d.ts +32 -0
  44. package/dist/src/reference-adapter.js +42 -0
  45. package/dist/src/service/attach-service.d.ts +28 -0
  46. package/dist/src/service/attach-service.js +272 -0
  47. package/dist/src/session-metadata.d.ts +4 -0
  48. package/dist/src/session-metadata.js +88 -0
  49. package/dist/src/store/session-store.d.ts +14 -0
  50. package/dist/src/store/session-store.js +52 -0
  51. package/dist/src/target.d.ts +9 -0
  52. package/dist/src/target.js +61 -0
  53. package/dist/src/types.d.ts +397 -0
  54. package/dist/src/types.js +2 -0
  55. package/dist/tests/attach-service.test.d.ts +1 -0
  56. package/dist/tests/attach-service.test.js +1367 -0
  57. package/dist/tests/browser-attach-ux-helper.test.d.ts +1 -0
  58. package/dist/tests/browser-attach-ux-helper.test.js +139 -0
  59. package/dist/tests/chrome-relay-error-helper.test.d.ts +1 -0
  60. package/dist/tests/chrome-relay-error-helper.test.js +67 -0
  61. package/dist/tests/chrome-relay-helper.test.d.ts +1 -0
  62. package/dist/tests/chrome-relay-helper.test.js +142 -0
  63. package/dist/tests/chrome-relay-state-schema.test.d.ts +1 -0
  64. package/dist/tests/chrome-relay-state-schema.test.js +96 -0
  65. package/dist/tests/claude-code-wrapper.test.d.ts +1 -0
  66. package/dist/tests/claude-code-wrapper.test.js +170 -0
  67. package/dist/tests/codex.test.d.ts +1 -0
  68. package/dist/tests/codex.test.js +210 -0
  69. package/dist/tests/demo-client-smoke.test.d.ts +1 -0
  70. package/dist/tests/demo-client-smoke.test.js +405 -0
  71. package/dist/tests/docs-fixtures.test.d.ts +1 -0
  72. package/dist/tests/docs-fixtures.test.js +255 -0
  73. package/dist/tests/doctor-connect-wrapper.test.d.ts +1 -0
  74. package/dist/tests/doctor-connect-wrapper.test.js +62 -0
  75. package/dist/tests/fixtures/doctor-connect-cli-stub.d.ts +1 -0
  76. package/dist/tests/fixtures/doctor-connect-cli-stub.js +93 -0
  77. package/dist/tests/fixtures/public-root-cli-stub.d.ts +210 -0
  78. package/dist/tests/fixtures/public-root-cli-stub.js +143 -0
  79. package/dist/tests/fixtures/public-root-consumer.js +67 -0
  80. package/dist/tests/mcp.test.d.ts +1 -0
  81. package/dist/tests/mcp.test.js +345 -0
  82. package/dist/tests/public-consumer-helpers.test.d.ts +1 -0
  83. package/dist/tests/public-consumer-helpers.test.js +33 -0
  84. package/dist/tests/public-package-git-consumption.test.d.ts +1 -0
  85. package/dist/tests/public-package-git-consumption.test.js +56 -0
  86. package/dist/tests/public-root-consumer-smoke.test.d.ts +1 -0
  87. package/dist/tests/public-root-consumer-smoke.test.js +214 -0
  88. package/dist/tests/reference-adapter.test.d.ts +1 -0
  89. package/dist/tests/reference-adapter.test.js +220 -0
  90. package/dist/tests/transport-reference-adapters.test.d.ts +1 -0
  91. package/dist/tests/transport-reference-adapters.test.js +214 -0
  92. package/package.json +61 -0
@@ -0,0 +1,39 @@
1
+ import { type ChromeRelayFailureInterpretation } from "./chrome-relay-error-helper";
2
+ /**
3
+ * Stable consumer utility for mapping diagnostics, sessions, and structured
4
+ * relay failures into a small attach/resume UX interpretation surface.
5
+ */
6
+ import type { AttachmentSession, BrowserAttachMode, BrowserAttachReadinessState, BrowserDiagnostics, ChromeRelayErrorDetails, ChromeRelayFailureOperation, SupportedBrowser } from "./types";
7
+ export type BrowserAttachUxState = "ready" | "blocked" | "attached" | "resumed" | "user-action-required" | "retryable-failure" | "non-retryable-failure";
8
+ export interface BrowserAttachUxInterpretation {
9
+ state: BrowserAttachUxState;
10
+ browser: SupportedBrowser;
11
+ attachMode: BrowserAttachMode;
12
+ operation: ChromeRelayFailureOperation;
13
+ label: string;
14
+ readOnly: boolean;
15
+ sharedTabScoped: boolean;
16
+ readiness: BrowserAttachReadinessState | "ready" | undefined;
17
+ prompt: string | undefined;
18
+ scopeNote: string | undefined;
19
+ retryGuidance: string | undefined;
20
+ retryable: boolean | undefined;
21
+ userActionRequired: boolean | undefined;
22
+ relayFailureCategory: ChromeRelayFailureInterpretation["category"] | undefined;
23
+ }
24
+ export declare function interpretBrowserAttachUxFromDiagnostics(args: {
25
+ browser: SupportedBrowser;
26
+ attachMode: BrowserAttachMode;
27
+ diagnostics: BrowserDiagnostics;
28
+ operation?: ChromeRelayFailureOperation;
29
+ }): BrowserAttachUxInterpretation;
30
+ export declare function interpretBrowserAttachUxFromSession(args: {
31
+ session: Pick<AttachmentSession, "browser" | "attach" | "status" | "kind" | "semantics">;
32
+ operation?: ChromeRelayFailureOperation;
33
+ }): BrowserAttachUxInterpretation;
34
+ export declare function interpretBrowserAttachUxFromError(args: {
35
+ details: ChromeRelayErrorDetails | Pick<ChromeRelayErrorDetails, "relay"> | null | undefined;
36
+ browser?: SupportedBrowser;
37
+ attachMode?: BrowserAttachMode;
38
+ operation?: ChromeRelayFailureOperation;
39
+ }): BrowserAttachUxInterpretation | undefined;
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interpretBrowserAttachUxFromDiagnostics = interpretBrowserAttachUxFromDiagnostics;
4
+ exports.interpretBrowserAttachUxFromSession = interpretBrowserAttachUxFromSession;
5
+ exports.interpretBrowserAttachUxFromError = interpretBrowserAttachUxFromError;
6
+ const chrome_relay_error_helper_1 = require("./chrome-relay-error-helper");
7
+ function labelFor(browser, attachMode) {
8
+ if (browser === "safari") {
9
+ return "Safari (actionable)";
10
+ }
11
+ if (attachMode === "relay") {
12
+ return "Chrome (shared tab, read-only)";
13
+ }
14
+ return "Chrome (direct, read-only)";
15
+ }
16
+ function isReadOnly(browser, attachMode) {
17
+ return browser === "chrome" || attachMode === "relay";
18
+ }
19
+ function isSharedTabScoped(browser, attachMode) {
20
+ return browser === "chrome" && attachMode === "relay";
21
+ }
22
+ function firstBlockerPrompt(blockers) {
23
+ const first = blockers[0]?.code;
24
+ if (!first) {
25
+ return undefined;
26
+ }
27
+ switch (first) {
28
+ case "automation_permission_denied":
29
+ return "Safari needs macOS Automation permission before I can control tabs. Grant access, then try again.";
30
+ case "screen_recording_permission_denied":
31
+ return "Safari screenshots also require Screen Recording permission on this Mac.";
32
+ case "browser_not_running":
33
+ return "Safari is not open yet. Open Safari with a normal tab, then retry.";
34
+ case "browser_no_windows":
35
+ return "Safari has no open browser windows yet. Open a normal Safari window, then retry.";
36
+ case "browser_no_tabs":
37
+ return "Safari has open windows, but there is no normal inspectable tab yet. Focus or open a regular Safari tab, then retry.";
38
+ case "direct_unavailable_attach_endpoint_missing":
39
+ return "Chrome direct attach needs a local DevTools endpoint that is already available on this machine. Once Chrome is running in that mode, I can inspect tabs in read-only mode.";
40
+ case "relay_toolbar_not_clicked":
41
+ return "To connect this Chrome tab, click the relay extension button on the tab you want to share.";
42
+ case "relay_share_required":
43
+ return "Chrome relay only works for a tab you explicitly share. Share the tab first, then retry.";
44
+ case "relay_no_shared_tab":
45
+ return "Chrome relay is connected, but there is no shared tab right now. Share the target tab, then retry.";
46
+ case "relay_attach_scope_expired":
47
+ return "That shared-tab grant is no longer active. Click the relay extension again on the original tab, then retry resume.";
48
+ default:
49
+ return blockers[0]?.message;
50
+ }
51
+ }
52
+ function interpretBrowserAttachUxFromDiagnostics(args) {
53
+ const operation = args.operation ?? "attach";
54
+ const label = labelFor(args.browser, args.attachMode);
55
+ const readOnly = isReadOnly(args.browser, args.attachMode);
56
+ const sharedTabScoped = isSharedTabScoped(args.browser, args.attachMode);
57
+ if (args.browser === "safari") {
58
+ const blockers = [
59
+ ...(args.diagnostics.preflight?.inspect.blockers ?? []),
60
+ ...(args.diagnostics.preflight?.automation.blockers ?? [])
61
+ ];
62
+ const ready = Boolean(args.diagnostics.preflight?.inspect.ready && args.diagnostics.preflight?.automation.ready);
63
+ return {
64
+ state: ready ? "ready" : "blocked",
65
+ browser: args.browser,
66
+ attachMode: args.attachMode,
67
+ operation,
68
+ label,
69
+ readOnly,
70
+ sharedTabScoped,
71
+ readiness: ready ? "ready" : "unavailable",
72
+ prompt: firstBlockerPrompt([
73
+ ...blockers,
74
+ ...(args.diagnostics.preflight?.screenshot.blockers ?? [])
75
+ ]),
76
+ scopeNote: undefined,
77
+ retryGuidance: undefined,
78
+ retryable: undefined,
79
+ userActionRequired: undefined,
80
+ relayFailureCategory: undefined
81
+ };
82
+ }
83
+ const modeDiagnostics = args.attachMode === "relay" ? args.diagnostics.attach?.relay : args.diagnostics.attach?.direct;
84
+ return {
85
+ state: modeDiagnostics?.ready ? "ready" : "blocked",
86
+ browser: args.browser,
87
+ attachMode: args.attachMode,
88
+ operation,
89
+ label,
90
+ readOnly,
91
+ sharedTabScoped,
92
+ readiness: modeDiagnostics?.state ?? "unavailable",
93
+ prompt: firstBlockerPrompt(modeDiagnostics?.blockers ?? []),
94
+ scopeNote: sharedTabScoped ? "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only." : undefined,
95
+ retryGuidance: undefined,
96
+ retryable: undefined,
97
+ userActionRequired: undefined,
98
+ relayFailureCategory: undefined
99
+ };
100
+ }
101
+ function interpretBrowserAttachUxFromSession(args) {
102
+ const operation = args.operation ?? "attach";
103
+ const { session } = args;
104
+ const label = labelFor(session.browser, session.attach.mode);
105
+ const sharedTabScoped = session.browser === "chrome" && session.attach.mode === "relay";
106
+ return {
107
+ state: operation === "resumeSession" ? "resumed" : "attached",
108
+ browser: session.browser,
109
+ attachMode: session.attach.mode,
110
+ operation,
111
+ label,
112
+ readOnly: session.status.state === "read-only",
113
+ sharedTabScoped,
114
+ readiness: undefined,
115
+ prompt: sharedTabScoped && session.attach.resumeRequiresUserGesture
116
+ ? "That shared-tab grant is no longer active. Click the relay extension again on the original tab, then retry resume."
117
+ : undefined,
118
+ scopeNote: sharedTabScoped || session.semantics.inspect === "shared-tab-only"
119
+ ? "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only."
120
+ : undefined,
121
+ retryGuidance: undefined,
122
+ retryable: session.attach.resumable,
123
+ userActionRequired: session.attach.resumeRequiresUserGesture,
124
+ relayFailureCategory: undefined
125
+ };
126
+ }
127
+ function interpretBrowserAttachUxFromError(args) {
128
+ const interpretation = (0, chrome_relay_error_helper_1.interpretChromeRelayFailure)(args.details);
129
+ if (!interpretation) {
130
+ return undefined;
131
+ }
132
+ const browser = args.browser ?? "chrome";
133
+ const attachMode = args.attachMode ?? "relay";
134
+ const operation = args.operation ??
135
+ (args.details && "context" in args.details ? args.details.context.operation : undefined) ??
136
+ "attach";
137
+ return {
138
+ state: interpretation.userActionRequired
139
+ ? "user-action-required"
140
+ : interpretation.retryable === false
141
+ ? "non-retryable-failure"
142
+ : "retryable-failure",
143
+ browser,
144
+ attachMode,
145
+ operation,
146
+ label: labelFor(browser, attachMode),
147
+ readOnly: interpretation.readOnly,
148
+ sharedTabScoped: interpretation.scopeLimitedToCurrentSharedTab,
149
+ readiness: undefined,
150
+ prompt: (0, chrome_relay_error_helper_1.chromeRelayBranchPrompt)(interpretation.branch),
151
+ scopeNote: (0, chrome_relay_error_helper_1.chromeRelayScopeNote)(interpretation),
152
+ retryGuidance: (0, chrome_relay_error_helper_1.chromeRelayRetryGuidance)(interpretation),
153
+ retryable: interpretation.retryable,
154
+ userActionRequired: interpretation.userActionRequired,
155
+ relayFailureCategory: interpretation.category,
156
+ };
157
+ }
@@ -0,0 +1,3 @@
1
+ import type { BridgeCapabilitiesContract, BrowserCapabilityDescriptor, SupportedBrowser } from "./types";
2
+ export declare function getBrowserCapabilityDescriptor(browser: SupportedBrowser): BrowserCapabilityDescriptor;
3
+ export declare function getBridgeCapabilities(browser?: SupportedBrowser): BridgeCapabilitiesContract;
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getBrowserCapabilityDescriptor = getBrowserCapabilityDescriptor;
7
+ exports.getBridgeCapabilities = getBridgeCapabilities;
8
+ const package_json_1 = __importDefault(require("../package.json"));
9
+ const CONTRACT_SCHEMA_PATH = "schema/capabilities.schema.json";
10
+ const PRODUCT_MANIFESTO_PATH = "docs/product-direction.md";
11
+ function buildBrowserCapabilities(browser) {
12
+ if (browser === "safari") {
13
+ return {
14
+ kind: "safari-actionable",
15
+ browser: "safari",
16
+ adapter: "SafariAdapter",
17
+ available: true,
18
+ maturity: "primary",
19
+ platforms: ["macos"],
20
+ bridge: {
21
+ browserNative: false,
22
+ implementation: "macos Apple Events via osascript/JXA plus screencapture"
23
+ },
24
+ targeting: {
25
+ modes: ["front", "indexed", "signature"],
26
+ sessionResumeStrategies: ["front", "indexed", "signature", "url_title", "url", "last_known_index"]
27
+ },
28
+ operations: {
29
+ capabilities: true,
30
+ diagnostics: true,
31
+ inspectFrontTab: true,
32
+ inspectTab: true,
33
+ listTabs: true,
34
+ attach: true,
35
+ resumeSession: true,
36
+ activate: true,
37
+ navigate: true,
38
+ screenshot: true
39
+ },
40
+ attachModes: [
41
+ {
42
+ mode: "direct",
43
+ source: "user-browser",
44
+ scope: "browser",
45
+ supported: true,
46
+ readiness: "ready"
47
+ }
48
+ ],
49
+ transports: {
50
+ cli: true,
51
+ http: true
52
+ },
53
+ constraints: [
54
+ "Safari automation depends on macOS Apple Events permissions.",
55
+ "Screenshots depend on macOS screen recording permissions.",
56
+ "Activation, navigation, and screenshot capture visibly focus Safari."
57
+ ]
58
+ };
59
+ }
60
+ if (browser === "chrome") {
61
+ return {
62
+ kind: "chrome-readonly",
63
+ browser: "chrome",
64
+ adapter: "ChromeAdapter",
65
+ available: true,
66
+ maturity: "experimental-readonly",
67
+ platforms: ["macos"],
68
+ bridge: {
69
+ browserNative: true,
70
+ implementation: "Chrome/Chromium inspection over an existing local DevTools endpoint; read-only in v1"
71
+ },
72
+ targeting: {
73
+ modes: ["front", "indexed", "signature"],
74
+ sessionResumeStrategies: [
75
+ "front",
76
+ "indexed",
77
+ "native_identity",
78
+ "signature",
79
+ "url_title",
80
+ "url",
81
+ "last_known_index"
82
+ ]
83
+ },
84
+ operations: {
85
+ capabilities: true,
86
+ diagnostics: true,
87
+ inspectFrontTab: true,
88
+ inspectTab: true,
89
+ listTabs: true,
90
+ attach: true,
91
+ resumeSession: true,
92
+ activate: false,
93
+ navigate: false,
94
+ screenshot: false
95
+ },
96
+ attachModes: [
97
+ {
98
+ mode: "direct",
99
+ source: "user-browser",
100
+ scope: "browser",
101
+ supported: true,
102
+ readiness: "degraded"
103
+ },
104
+ {
105
+ mode: "relay",
106
+ source: "extension-relay",
107
+ scope: "tab",
108
+ supported: true,
109
+ readiness: "unavailable"
110
+ }
111
+ ],
112
+ transports: {
113
+ cli: true,
114
+ http: true
115
+ },
116
+ constraints: [
117
+ "Chrome/Chromium inspection currently requires an already-running local DevTools HTTP endpoint.",
118
+ "This adapter is read-only in this phase: attach and saved-session resume work for inspectable targets, but activate/navigate/screenshot do not.",
119
+ "If no debugging endpoint is discoverable, inspection returns a clear unavailable error and diagnostics enumerate the attempted sources.",
120
+ "Chrome attach-mode metadata distinguishes direct user-browser attach from future extension relay attach without changing the current read-only behavior model."
121
+ ]
122
+ };
123
+ }
124
+ throw new Error(`Unsupported browser capability descriptor: ${browser}`);
125
+ }
126
+ function getBrowserCapabilityDescriptor(browser) {
127
+ return buildBrowserCapabilities(browser);
128
+ }
129
+ function getBridgeCapabilities(browser) {
130
+ const browsers = browser
131
+ ? [getBrowserCapabilityDescriptor(browser)]
132
+ : [getBrowserCapabilityDescriptor("safari"), getBrowserCapabilityDescriptor("chrome")];
133
+ return {
134
+ schemaVersion: 1,
135
+ schema: {
136
+ path: CONTRACT_SCHEMA_PATH,
137
+ version: "1.0.0"
138
+ },
139
+ generatedAt: new Date().toISOString(),
140
+ product: {
141
+ name: package_json_1.default.name,
142
+ displayName: "local-browser-bridge",
143
+ version: package_json_1.default.version,
144
+ summary: "Reusable, agent-agnostic local browser bridge with honest capability signaling. Safari is actionable; Chrome/Chromium is read-only in v1.",
145
+ direction: {
146
+ localOnly: true,
147
+ agentAgnostic: true,
148
+ browserStrategy: "safari-first",
149
+ architectureStrategy: "bridge-first"
150
+ },
151
+ manifestoPath: PRODUCT_MANIFESTO_PATH
152
+ },
153
+ transports: {
154
+ cli: {
155
+ available: true,
156
+ format: "json",
157
+ binary: "local-browser-bridge",
158
+ aliasBinaries: ["safari-attach-tool"],
159
+ command: "local-browser-bridge capabilities [--browser safari|chrome]"
160
+ },
161
+ http: {
162
+ available: true,
163
+ format: "json",
164
+ baseUrl: "http://127.0.0.1:3000",
165
+ capabilitiesPath: "/v1/capabilities"
166
+ }
167
+ },
168
+ targeting: {
169
+ modes: ["front", "indexed", "signature"],
170
+ sessionResumeStrategies: [
171
+ "front",
172
+ "indexed",
173
+ "native_identity",
174
+ "signature",
175
+ "url_title",
176
+ "url",
177
+ "last_known_index"
178
+ ]
179
+ },
180
+ browsers
181
+ };
182
+ }
@@ -0,0 +1,19 @@
1
+ import type { ChromeRelayErrorDetails, ChromeRelayFailureBranch, ChromeRelaySharedTabScope } from "./types";
2
+ /**
3
+ * Stable consumer utility for interpreting additive Chrome relay failure details
4
+ * into a transport-neutral branching shape.
5
+ */
6
+ export type ChromeRelayFailureCategory = "share-required" | "user-action-required" | "retryable-relay-failure" | "non-retryable-relay-failure" | "shared-tab-read-only-scope-limitation";
7
+ export interface ChromeRelayFailureInterpretation {
8
+ category: ChromeRelayFailureCategory;
9
+ retryable: boolean | undefined;
10
+ userActionRequired: boolean | undefined;
11
+ branch: ChromeRelayFailureBranch | undefined;
12
+ sharedTabScope: ChromeRelaySharedTabScope | undefined;
13
+ scopeLimitedToCurrentSharedTab: boolean;
14
+ readOnly: true;
15
+ }
16
+ export declare function interpretChromeRelayFailure(details: Pick<ChromeRelayErrorDetails, "relay"> | ChromeRelayErrorDetails | null | undefined): ChromeRelayFailureInterpretation | undefined;
17
+ export declare function chromeRelayBranchPrompt(branch: ChromeRelayFailureBranch | undefined): string | undefined;
18
+ export declare function chromeRelayRetryGuidance(interpretation: Pick<ChromeRelayFailureInterpretation, "retryable" | "userActionRequired"> | null | undefined): string | undefined;
19
+ export declare function chromeRelayScopeNote(interpretation: Pick<ChromeRelayFailureInterpretation, "scopeLimitedToCurrentSharedTab"> | null | undefined): string | undefined;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interpretChromeRelayFailure = interpretChromeRelayFailure;
4
+ exports.chromeRelayBranchPrompt = chromeRelayBranchPrompt;
5
+ exports.chromeRelayRetryGuidance = chromeRelayRetryGuidance;
6
+ exports.chromeRelayScopeNote = chromeRelayScopeNote;
7
+ function categoryFromRelay(relay) {
8
+ if (relay.branch === "share-tab" || relay.branch === "share-original-tab-again") {
9
+ return "share-required";
10
+ }
11
+ if (relay.branch === "use-current-shared-tab") {
12
+ return "shared-tab-read-only-scope-limitation";
13
+ }
14
+ if (relay.userActionRequired) {
15
+ return "user-action-required";
16
+ }
17
+ if (relay.retryable === false) {
18
+ return "non-retryable-relay-failure";
19
+ }
20
+ return "retryable-relay-failure";
21
+ }
22
+ function interpretChromeRelayFailure(details) {
23
+ const relay = details?.relay;
24
+ if (!relay) {
25
+ return undefined;
26
+ }
27
+ return {
28
+ category: categoryFromRelay(relay),
29
+ retryable: relay.retryable,
30
+ userActionRequired: relay.userActionRequired,
31
+ branch: relay.branch,
32
+ sharedTabScope: relay.sharedTabScope,
33
+ scopeLimitedToCurrentSharedTab: relay.sharedTabScope === "current-shared-tab",
34
+ readOnly: true
35
+ };
36
+ }
37
+ function chromeRelayBranchPrompt(branch) {
38
+ switch (branch) {
39
+ case "click-toolbar-button":
40
+ return "To connect this Chrome tab, click the relay extension button on the tab you want to share.";
41
+ case "share-tab":
42
+ return "Chrome relay only works for a tab you explicitly share. Share the tab first, then retry.";
43
+ case "share-original-tab-again":
44
+ return "That shared-tab grant is no longer active. Click the relay extension again on the original tab, then retry resume.";
45
+ case "use-current-shared-tab":
46
+ return "Chrome relay attach only works for the tab that is currently shared. Use the shared tab or share a different tab first.";
47
+ case "install-extension":
48
+ return "Install the Chrome relay extension on this machine, then retry.";
49
+ case "reconnect-extension":
50
+ return "Reconnect or re-enable the Chrome relay extension, then retry.";
51
+ case "configure-relay-probe":
52
+ return "Configure a local Chrome relay state probe for this bridge instance before using relay attach.";
53
+ case "repair-relay-probe":
54
+ return "Fix the local Chrome relay state file so it is valid JSON again, then retry.";
55
+ default:
56
+ return undefined;
57
+ }
58
+ }
59
+ function chromeRelayRetryGuidance(interpretation) {
60
+ if (!interpretation) {
61
+ return undefined;
62
+ }
63
+ if (interpretation.retryable === false) {
64
+ return "Retry guidance: Stop automatic retries and surface the relay failure directly.";
65
+ }
66
+ if (interpretation.retryable === true) {
67
+ return interpretation.userActionRequired
68
+ ? "Retry guidance: Wait for the user action to complete, then retry the same relay path."
69
+ : "Retry guidance: A targeted retry on the same relay path is reasonable.";
70
+ }
71
+ return undefined;
72
+ }
73
+ function chromeRelayScopeNote(interpretation) {
74
+ if (!interpretation?.scopeLimitedToCurrentSharedTab) {
75
+ return undefined;
76
+ }
77
+ return "Scope note: Chrome relay is still limited to the currently shared tab and remains read-only.";
78
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function runChromeRelayHelperCli(args: string[]): Promise<void>;
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.runChromeRelayHelperCli = runChromeRelayHelperCli;
5
+ const errors_1 = require("./errors");
6
+ const chrome_relay_helper_1 = require("./chrome-relay-helper");
7
+ const SUPPORTED_FLOWS = [
8
+ "extension-missing",
9
+ "disconnected",
10
+ "click-required",
11
+ "share-required",
12
+ "shared-tab",
13
+ "expired-share",
14
+ "clear-shared-tab"
15
+ ];
16
+ function printUsage() {
17
+ process.stdout.write([
18
+ "Usage:",
19
+ " local-browser-bridge-chrome-relay <flow> [--output <path>] [--tab-id <id>] [--url <url>] [--title <title>]",
20
+ " [--updated-at <iso>] [--expires-at <iso>] [--version <version>]",
21
+ " [--resumable true|false] [--resume-requires-user-gesture true|false]",
22
+ "",
23
+ `Flows: ${SUPPORTED_FLOWS.join(", ")}`,
24
+ "",
25
+ `Default output path order: --output, ${chrome_relay_helper_1.CHROME_RELAY_STATE_PATH_ENV}, ${(0, chrome_relay_helper_1.getDefaultChromeRelayStateOutputPath)()}`,
26
+ `Bridge also reads: ${(0, chrome_relay_helper_1.getHomeChromeRelayStateOutputPath)()}`,
27
+ "",
28
+ "Examples:",
29
+ " local-browser-bridge-chrome-relay extension-missing",
30
+ " local-browser-bridge-chrome-relay click-required --output ./.local-browser-bridge/chrome-relay-state.json",
31
+ " local-browser-bridge-chrome-relay shared-tab --tab-id relay-42 --title \"Shared Docs\" --url https://example.com/docs",
32
+ " local-browser-bridge-chrome-relay expired-share --tab-id relay-42 --url https://example.com/docs",
33
+ " local-browser-bridge-chrome-relay clear-shared-tab"
34
+ ].join("\n") + "\n");
35
+ }
36
+ function readFlag(args, name) {
37
+ const index = args.indexOf(name);
38
+ if (index === -1) {
39
+ return undefined;
40
+ }
41
+ return args[index + 1];
42
+ }
43
+ function readBooleanFlag(args, name) {
44
+ const raw = readFlag(args, name)?.trim();
45
+ if (raw === undefined) {
46
+ return undefined;
47
+ }
48
+ if (raw === "true") {
49
+ return true;
50
+ }
51
+ if (raw === "false") {
52
+ return false;
53
+ }
54
+ throw new errors_1.AppError(`${name} must be true or false.`, 400, "invalid_boolean_flag");
55
+ }
56
+ function readFlow(raw) {
57
+ if (raw && SUPPORTED_FLOWS.includes(raw)) {
58
+ return raw;
59
+ }
60
+ throw new errors_1.AppError(`Flow must be one of: ${SUPPORTED_FLOWS.join(", ")}.`, 400, "invalid_relay_flow");
61
+ }
62
+ async function runChromeRelayHelperCli(args) {
63
+ const command = args[0];
64
+ if (!command || command === "--help" || command === "-h") {
65
+ printUsage();
66
+ return;
67
+ }
68
+ const result = await (0, chrome_relay_helper_1.writeChromeRelayFixtureState)({
69
+ flow: readFlow(command),
70
+ outputPath: readFlag(args, "--output"),
71
+ version: readFlag(args, "--version"),
72
+ updatedAt: readFlag(args, "--updated-at"),
73
+ expiresAt: readFlag(args, "--expires-at"),
74
+ tabId: readFlag(args, "--tab-id"),
75
+ url: readFlag(args, "--url"),
76
+ title: readFlag(args, "--title"),
77
+ resumable: readBooleanFlag(args, "--resumable"),
78
+ resumeRequiresUserGesture: readBooleanFlag(args, "--resume-requires-user-gesture")
79
+ });
80
+ (0, errors_1.writeJsonLine)(process.stdout, {
81
+ ok: true,
82
+ helper: "local-browser-bridge-chrome-relay",
83
+ flow: result.flow,
84
+ path: result.path,
85
+ state: result.state
86
+ });
87
+ }
88
+ async function main() {
89
+ await runChromeRelayHelperCli(process.argv.slice(2));
90
+ }
91
+ if (require.main === module) {
92
+ main().catch((error) => {
93
+ const { payload } = (0, errors_1.toErrorPayload)(error);
94
+ (0, errors_1.writeJsonLine)(process.stderr, payload);
95
+ process.exitCode = 1;
96
+ });
97
+ }
@@ -0,0 +1,29 @@
1
+ import { type ChromeRelayStateProbe } from "./chrome-relay-state";
2
+ export declare const CHROME_RELAY_STATE_PATH_ENV = "LOCAL_BROWSER_BRIDGE_CHROME_RELAY_STATE_PATH";
3
+ export type ChromeRelayFixtureFlow = "extension-missing" | "disconnected" | "click-required" | "share-required" | "shared-tab" | "expired-share" | "clear-shared-tab";
4
+ export interface BuildChromeRelayFixtureOptions {
5
+ flow: ChromeRelayFixtureFlow;
6
+ version?: string;
7
+ updatedAt?: string;
8
+ tabId?: string;
9
+ url?: string;
10
+ title?: string;
11
+ expiresAt?: string;
12
+ resumable?: boolean;
13
+ resumeRequiresUserGesture?: boolean;
14
+ }
15
+ export interface ChromeRelayWriteResult {
16
+ flow: ChromeRelayFixtureFlow;
17
+ path: string;
18
+ state: ChromeRelayStateProbe;
19
+ }
20
+ export declare function getDefaultChromeRelayStateOutputPath(): string;
21
+ export declare function getHomeChromeRelayStateOutputPath(): string;
22
+ export declare function resolveChromeRelayStateOutputPath(explicitPath?: string): string;
23
+ export declare function buildChromeRelayFixtureState(options: BuildChromeRelayFixtureOptions): ChromeRelayStateProbe;
24
+ export declare function writeChromeRelayStateSnapshot(state: ChromeRelayStateProbe, options?: {
25
+ outputPath?: string;
26
+ }): Promise<string>;
27
+ export declare function writeChromeRelayFixtureState(options: BuildChromeRelayFixtureOptions & {
28
+ outputPath?: string;
29
+ }): Promise<ChromeRelayWriteResult>;