webflow-mcp 0.3.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 (94) hide show
  1. package/.webflow/flows/_template/index.ts +14 -0
  2. package/.webflow/flows/_template/schema.json +11 -0
  3. package/.webflow/skills/README.md +3 -0
  4. package/README.md +215 -0
  5. package/dist/src/anti-block/provider-gate.d.ts +19 -0
  6. package/dist/src/anti-block/provider-gate.js +116 -0
  7. package/dist/src/cli/commands/approvals.d.ts +5 -0
  8. package/dist/src/cli/commands/approvals.js +69 -0
  9. package/dist/src/cli/commands/config.d.ts +4 -0
  10. package/dist/src/cli/commands/config.js +56 -0
  11. package/dist/src/cli/commands/create-flow.d.ts +4 -0
  12. package/dist/src/cli/commands/create-flow.js +27 -0
  13. package/dist/src/cli/commands/doctor.d.ts +19 -0
  14. package/dist/src/cli/commands/doctor.js +241 -0
  15. package/dist/src/cli/commands/fork.d.ts +4 -0
  16. package/dist/src/cli/commands/fork.js +19 -0
  17. package/dist/src/cli/commands/init.d.ts +4 -0
  18. package/dist/src/cli/commands/init.js +22 -0
  19. package/dist/src/cli/commands/login.d.ts +26 -0
  20. package/dist/src/cli/commands/login.js +145 -0
  21. package/dist/src/cli/commands/profiles.d.ts +40 -0
  22. package/dist/src/cli/commands/profiles.js +142 -0
  23. package/dist/src/cli/commands/run.d.ts +10 -0
  24. package/dist/src/cli/commands/run.js +13 -0
  25. package/dist/src/cli/commands/setup.d.ts +11 -0
  26. package/dist/src/cli/commands/setup.js +46 -0
  27. package/dist/src/cli/commands/worker.d.ts +29 -0
  28. package/dist/src/cli/commands/worker.js +30 -0
  29. package/dist/src/cli/index.d.ts +2 -0
  30. package/dist/src/cli/index.js +336 -0
  31. package/dist/src/config/webflow-config.d.ts +35 -0
  32. package/dist/src/config/webflow-config.js +119 -0
  33. package/dist/src/errors/self-healing.d.ts +62 -0
  34. package/dist/src/errors/self-healing.js +77 -0
  35. package/dist/src/mcp/cli.d.ts +2 -0
  36. package/dist/src/mcp/cli.js +36 -0
  37. package/dist/src/mcp/relay-gateway.d.ts +24 -0
  38. package/dist/src/mcp/relay-gateway.js +208 -0
  39. package/dist/src/mcp/server.d.ts +8 -0
  40. package/dist/src/mcp/server.js +62 -0
  41. package/dist/src/mcp/tool-registry.d.ts +10 -0
  42. package/dist/src/mcp/tool-registry.js +366 -0
  43. package/dist/src/profiles/profile-manager.d.ts +33 -0
  44. package/dist/src/profiles/profile-manager.js +37 -0
  45. package/dist/src/profiles/profile-state.d.ts +31 -0
  46. package/dist/src/profiles/profile-state.js +122 -0
  47. package/dist/src/repertoire/local-repertoire.d.ts +3 -0
  48. package/dist/src/repertoire/local-repertoire.js +59 -0
  49. package/dist/src/runner/browser-session.d.ts +23 -0
  50. package/dist/src/runner/browser-session.js +58 -0
  51. package/dist/src/runner/cdp-session.d.ts +25 -0
  52. package/dist/src/runner/cdp-session.js +156 -0
  53. package/dist/src/runner/chrome.d.ts +18 -0
  54. package/dist/src/runner/chrome.js +56 -0
  55. package/dist/src/runner/flow-loader.d.ts +11 -0
  56. package/dist/src/runner/flow-loader.js +115 -0
  57. package/dist/src/runner/flow-runner.d.ts +70 -0
  58. package/dist/src/runner/flow-runner.js +378 -0
  59. package/dist/src/runner/types.d.ts +112 -0
  60. package/dist/src/runner/types.js +1 -0
  61. package/dist/src/schema/translator.d.ts +5 -0
  62. package/dist/src/schema/translator.js +104 -0
  63. package/dist/src/schema/types.d.ts +32 -0
  64. package/dist/src/schema/types.js +1 -0
  65. package/dist/src/security/flow-approvals.d.ts +34 -0
  66. package/dist/src/security/flow-approvals.js +324 -0
  67. package/dist/src/security/flow-release.d.ts +82 -0
  68. package/dist/src/security/flow-release.js +392 -0
  69. package/dist/src/security/official-flow-trust.d.ts +2 -0
  70. package/dist/src/security/official-flow-trust.js +16 -0
  71. package/dist/src/security/trusted-flow-keys.d.ts +8 -0
  72. package/dist/src/security/trusted-flow-keys.js +132 -0
  73. package/dist/src/shared/credentials.d.ts +21 -0
  74. package/dist/src/shared/credentials.js +99 -0
  75. package/dist/src/shared/errors.d.ts +6 -0
  76. package/dist/src/shared/errors.js +16 -0
  77. package/dist/src/shared/fs.d.ts +5 -0
  78. package/dist/src/shared/fs.js +23 -0
  79. package/dist/src/shared/paths.d.ts +112 -0
  80. package/dist/src/shared/paths.js +257 -0
  81. package/dist/src/shared/semaphore.d.ts +8 -0
  82. package/dist/src/shared/semaphore.js +23 -0
  83. package/dist/src/shared/taxonomy.d.ts +21 -0
  84. package/dist/src/shared/taxonomy.js +20 -0
  85. package/dist/src/worker/connection.d.ts +72 -0
  86. package/dist/src/worker/connection.js +427 -0
  87. package/dist/src/worker/lifecycle.d.ts +50 -0
  88. package/dist/src/worker/lifecycle.js +154 -0
  89. package/dist/src/worker/protocol.d.ts +188 -0
  90. package/dist/src/worker/protocol.js +156 -0
  91. package/dist/src/worker/worker-service.d.ts +112 -0
  92. package/dist/src/worker/worker-service.js +258 -0
  93. package/package.json +50 -0
  94. package/taxonomy.json +99 -0
@@ -0,0 +1,188 @@
1
+ import type { SelfHealingPayload } from '../errors/self-healing.js';
2
+ import type { CatalogEntry } from './worker-service.js';
3
+ import type { WebflowConfig } from '../config/webflow-config.js';
4
+ import type { BrowserVisibility, GlobalVisibilityPreference } from '../config/webflow-config.js';
5
+ import type { BrowserMode } from '../runner/types.js';
6
+ import type { SignedFlowRelease } from '../security/flow-release.js';
7
+ export declare const WORKER_PROTOCOL_VERSION = 2;
8
+ export declare const WORKER_VERSION = "0.3.0";
9
+ export declare const SUPPORTED_FLOW_RELEASE_FORMATS: readonly [1];
10
+ export declare const SUPPORTED_FLOW_RUNTIME_APIS: readonly [1];
11
+ /**
12
+ * Wire contract between a local worker and the (future, out-of-repo) VPS relay.
13
+ * RFC 0001, section 2.3. This file is the source of truth for the message shapes;
14
+ * the relay implementation must keep its own copy in sync until/unless the types
15
+ * are published as a shared package.
16
+ *
17
+ * Deliberately NOT included in v1: a `run_progress` message. FlowModule.run()
18
+ * (src/runner/types.ts) has no progress-callback API, so there is nothing real to
19
+ * stream — adding progress messages here without a producer would be fake
20
+ * streaming. Add it to the flow contract first if this is ever needed.
21
+ */
22
+ export interface HelloMessage {
23
+ type: 'hello';
24
+ workerId: string;
25
+ credential: string;
26
+ protocolVersion: typeof WORKER_PROTOCOL_VERSION;
27
+ workerVersion: string;
28
+ flowReleaseFormats: number[];
29
+ runtimeApiVersions: number[];
30
+ /**
31
+ * Locally-known flows only. Under the Fase 3 "everything is pushed" model (see
32
+ * `run.code` below) this is typically empty — the relay's own flow store is the
33
+ * catalog source of truth, not the worker. Kept for backward compat / diagnostics
34
+ * with the Fase 0-2 local-worker-catalog mode.
35
+ */
36
+ catalog: CatalogEntry[];
37
+ }
38
+ export interface HelloAckMessage {
39
+ type: 'hello_ack';
40
+ ok: boolean;
41
+ userId?: string;
42
+ reason?: string;
43
+ }
44
+ export interface RunMessage {
45
+ type: 'run';
46
+ jobId: string;
47
+ flow: string;
48
+ input: unknown;
49
+ /** Every network-originated run requires a signed immutable release artifact. */
50
+ release: SignedFlowRelease;
51
+ /** One-off execution overrides. Persisted preferences live in config_request. */
52
+ browserMode?: BrowserMode;
53
+ browserVisibility?: BrowserVisibility;
54
+ }
55
+ export interface RunResultMessage {
56
+ type: 'run_result';
57
+ jobId: string;
58
+ ok: boolean;
59
+ result?: unknown;
60
+ error?: SelfHealingPayload;
61
+ /** See FlowExecutionSuccess.visibilityNudge (src/runner/types.ts) — carried over the
62
+ * wire so the relay can surface it as an `askUser` hint without a second round-trip. */
63
+ visibilityNudge?: string;
64
+ }
65
+ export interface PingMessage {
66
+ type: 'ping';
67
+ }
68
+ export interface PongMessage {
69
+ type: 'pong';
70
+ }
71
+ /**
72
+ * Config/repertoire live on the worker's own disk (never in the relay's database —
73
+ * same principle as login sessions/profiles). These four message types let the relay
74
+ * forward an agent's get/set calls to the worker that owns that state, and get a
75
+ * correlated reply back. Pure local disk I/O, so replies should be near-instant —
76
+ * see WorkerRegistry's separate short timeout for these (relay/src/worker-registry.ts),
77
+ * distinct from the multi-minute job timeout used for `run`.
78
+ */
79
+ export type ConfigAction = {
80
+ action: 'get';
81
+ } | {
82
+ action: 'set_default_visibility';
83
+ value: GlobalVisibilityPreference;
84
+ } | {
85
+ action: 'set_flow_visibility';
86
+ flowName: string;
87
+ value: BrowserVisibility | null;
88
+ } | {
89
+ action: 'set_default_browser_mode';
90
+ value: BrowserMode | 'inherit';
91
+ } | {
92
+ action: 'set_flow_browser_mode';
93
+ flowName: string;
94
+ value: BrowserMode | null;
95
+ };
96
+ export interface ConfigRequestMessage {
97
+ type: 'config_request';
98
+ requestId: string;
99
+ request: ConfigAction;
100
+ }
101
+ export interface ConfigResponseMessage {
102
+ type: 'config_response';
103
+ requestId: string;
104
+ ok: boolean;
105
+ config?: WebflowConfig;
106
+ error?: string;
107
+ }
108
+ export type RepertoireAction = {
109
+ action: 'list';
110
+ } | {
111
+ action: 'add';
112
+ flowName: string;
113
+ } | {
114
+ action: 'remove';
115
+ flowName: string;
116
+ };
117
+ export interface RepertoireRequestMessage {
118
+ type: 'repertoire_request';
119
+ requestId: string;
120
+ request: RepertoireAction;
121
+ }
122
+ export interface RepertoireResponseMessage {
123
+ type: 'repertoire_response';
124
+ requestId: string;
125
+ ok: boolean;
126
+ flows?: string[];
127
+ error?: string;
128
+ }
129
+ /** Serializable mirror of ProfileStatusInfo (../profiles/profile-state.ts) — only the
130
+ * fields an MCP client needs, no local filesystem paths. */
131
+ export interface ProfileStatusWire {
132
+ profileId: string;
133
+ status: 'missing' | 'exists_unverified' | 'setup_completed' | 'verified' | 'login_required';
134
+ probablyValid: boolean | 'unknown';
135
+ loginUrl?: string;
136
+ setupCommand: string;
137
+ lastInteractiveSetupAt?: string;
138
+ lastSuccessfulRunAt?: string;
139
+ lastLoginRequiredAt?: string | null;
140
+ }
141
+ /**
142
+ * Same disk-state principle as config/repertoire above (never the relay's database).
143
+ * `status` reads the last known state (fast, near-instant). `login` opens a real,
144
+ * visible Chrome window on this machine for the human to log in manually and returns
145
+ * as soon as the window is open — it does NOT wait for the user to finish (that can
146
+ * take minutes and must never hold up an MCP tool call). The worker keeps the
147
+ * per-profile lock held in the background until the window closes, so a flow run
148
+ * against the same profile can't start mid-login; call `status` again afterward to
149
+ * see whether it succeeded.
150
+ */
151
+ export type ProfileAction = {
152
+ action: 'status';
153
+ flowName: string;
154
+ loginProvider?: string;
155
+ loginUrl?: string;
156
+ } | {
157
+ action: 'login';
158
+ flowName: string;
159
+ loginProvider?: string;
160
+ loginUrl?: string;
161
+ };
162
+ export interface ProfileRequestMessage {
163
+ type: 'profile_request';
164
+ requestId: string;
165
+ request: ProfileAction;
166
+ }
167
+ export interface ProfileResponseMessage {
168
+ type: 'profile_response';
169
+ requestId: string;
170
+ ok: boolean;
171
+ status?: ProfileStatusWire;
172
+ /** Only set for the `login` action. */
173
+ launched?: boolean;
174
+ error?: string;
175
+ }
176
+ /** Messages the worker sends. */
177
+ export type WorkerToRelayMessage = HelloMessage | RunResultMessage | PingMessage | PongMessage | ConfigResponseMessage | RepertoireResponseMessage | ProfileResponseMessage;
178
+ /** Messages the worker receives. */
179
+ export type RelayToWorkerMessage = HelloAckMessage | RunMessage | PingMessage | PongMessage | ConfigRequestMessage | RepertoireRequestMessage | ProfileRequestMessage;
180
+ /**
181
+ * Defensive parse for messages arriving over the socket. A malformed or unexpected
182
+ * message from a misbehaving/compromised relay must never crash the worker process
183
+ * that also happens to be driving the user's real, logged-in Chrome (RFC 0001,
184
+ * section 5: don't trust the relay blindly). Throws WebFlowError on anything that
185
+ * doesn't match a known shape; callers should catch and drop the message.
186
+ */
187
+ export declare function parseRelayToWorkerMessage(raw: string): RelayToWorkerMessage;
188
+ export declare function serializeWorkerMessage(message: WorkerToRelayMessage): string;
@@ -0,0 +1,156 @@
1
+ import { WebFlowError } from '../shared/errors.js';
2
+ export const WORKER_PROTOCOL_VERSION = 2;
3
+ export const WORKER_VERSION = '0.3.0';
4
+ export const SUPPORTED_FLOW_RELEASE_FORMATS = [1];
5
+ export const SUPPORTED_FLOW_RUNTIME_APIS = [1];
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null;
8
+ }
9
+ /**
10
+ * Defensive parse for messages arriving over the socket. A malformed or unexpected
11
+ * message from a misbehaving/compromised relay must never crash the worker process
12
+ * that also happens to be driving the user's real, logged-in Chrome (RFC 0001,
13
+ * section 5: don't trust the relay blindly). Throws WebFlowError on anything that
14
+ * doesn't match a known shape; callers should catch and drop the message.
15
+ */
16
+ export function parseRelayToWorkerMessage(raw) {
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(raw);
20
+ }
21
+ catch {
22
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'Message from relay is not valid JSON.');
23
+ }
24
+ if (!isRecord(parsed) || typeof parsed.type !== 'string') {
25
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'Message from relay is missing a string "type" field.');
26
+ }
27
+ switch (parsed.type) {
28
+ case 'hello_ack':
29
+ if (typeof parsed.ok !== 'boolean') {
30
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'hello_ack message must have a boolean "ok" field.');
31
+ }
32
+ return {
33
+ type: 'hello_ack',
34
+ ok: parsed.ok,
35
+ userId: typeof parsed.userId === 'string' ? parsed.userId : undefined,
36
+ reason: typeof parsed.reason === 'string' ? parsed.reason : undefined
37
+ };
38
+ case 'run':
39
+ if (typeof parsed.jobId !== 'string' || typeof parsed.flow !== 'string') {
40
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'run message must have string "jobId" and "flow" fields.');
41
+ }
42
+ if (Object.hasOwn(parsed, 'code') || Object.hasOwn(parsed, 'schema') || Object.hasOwn(parsed, 'requiresLogin')
43
+ || Object.hasOwn(parsed, 'loginProvider') || Object.hasOwn(parsed, 'loginUrl')) {
44
+ throw new WebFlowError('FLOW_SIGNATURE_REQUIRED', 'Unsigned remote flow fields are no longer accepted.');
45
+ }
46
+ if (!isRecord(parsed.release)) {
47
+ throw new WebFlowError('FLOW_SIGNATURE_REQUIRED', 'Every remote run requires a signed flow release.');
48
+ }
49
+ return {
50
+ type: 'run',
51
+ jobId: parsed.jobId,
52
+ flow: parsed.flow,
53
+ input: parsed.input,
54
+ release: parsed.release,
55
+ browserMode: parsed.browserMode === 'managed' || parsed.browserMode === 'cdp' ? parsed.browserMode : undefined,
56
+ browserVisibility: parsed.browserVisibility === 'visible' || parsed.browserVisibility === 'hidden' ? parsed.browserVisibility : undefined
57
+ };
58
+ case 'ping':
59
+ return { type: 'ping' };
60
+ case 'pong':
61
+ return { type: 'pong' };
62
+ case 'config_request': {
63
+ if (typeof parsed.requestId !== 'string' || !isRecord(parsed.request) || typeof parsed.request.action !== 'string') {
64
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'config_request message must have a string "requestId" and a "request" object with an "action".');
65
+ }
66
+ const request = parseConfigAction(parsed.request);
67
+ return { type: 'config_request', requestId: parsed.requestId, request };
68
+ }
69
+ case 'repertoire_request': {
70
+ if (typeof parsed.requestId !== 'string' || !isRecord(parsed.request) || typeof parsed.request.action !== 'string') {
71
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'repertoire_request message must have a string "requestId" and a "request" object with an "action".');
72
+ }
73
+ const request = parseRepertoireAction(parsed.request);
74
+ return { type: 'repertoire_request', requestId: parsed.requestId, request };
75
+ }
76
+ case 'profile_request': {
77
+ if (typeof parsed.requestId !== 'string' || !isRecord(parsed.request) || typeof parsed.request.action !== 'string') {
78
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'profile_request message must have a string "requestId" and a "request" object with an "action".');
79
+ }
80
+ const request = parseProfileAction(parsed.request);
81
+ return { type: 'profile_request', requestId: parsed.requestId, request };
82
+ }
83
+ default:
84
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', `Unknown message type from relay: "${parsed.type}".`);
85
+ }
86
+ }
87
+ function parseConfigAction(raw) {
88
+ switch (raw.action) {
89
+ case 'get':
90
+ return { action: 'get' };
91
+ case 'set_default_visibility':
92
+ if (raw.value !== 'visible' && raw.value !== 'hidden' && raw.value !== 'inherit') {
93
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'set_default_visibility requires value "visible", "hidden", or "inherit".');
94
+ }
95
+ return { action: 'set_default_visibility', value: raw.value };
96
+ case 'set_flow_visibility':
97
+ if (typeof raw.flowName !== 'string' || raw.flowName.length === 0) {
98
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'set_flow_visibility requires a non-empty "flowName".');
99
+ }
100
+ if (raw.value !== 'visible' && raw.value !== 'hidden' && raw.value !== null) {
101
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'set_flow_visibility requires value "visible", "hidden", or null.');
102
+ }
103
+ return { action: 'set_flow_visibility', flowName: raw.flowName, value: raw.value };
104
+ case 'set_default_browser_mode':
105
+ if (raw.value !== 'managed' && raw.value !== 'cdp' && raw.value !== 'inherit') {
106
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'set_default_browser_mode requires value "managed", "cdp", or "inherit".');
107
+ }
108
+ return { action: 'set_default_browser_mode', value: raw.value };
109
+ case 'set_flow_browser_mode':
110
+ if (typeof raw.flowName !== 'string' || raw.flowName.length === 0) {
111
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'set_flow_browser_mode requires a non-empty "flowName".');
112
+ }
113
+ if (raw.value !== 'managed' && raw.value !== 'cdp' && raw.value !== null) {
114
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'set_flow_browser_mode requires value "managed", "cdp", or null.');
115
+ }
116
+ return { action: 'set_flow_browser_mode', flowName: raw.flowName, value: raw.value };
117
+ default:
118
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', `Unknown config action: "${String(raw.action)}".`);
119
+ }
120
+ }
121
+ function parseRepertoireAction(raw) {
122
+ switch (raw.action) {
123
+ case 'list':
124
+ return { action: 'list' };
125
+ case 'add':
126
+ if (typeof raw.flowName !== 'string' || raw.flowName.length === 0) {
127
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'add requires a non-empty "flowName".');
128
+ }
129
+ return { action: 'add', flowName: raw.flowName };
130
+ case 'remove':
131
+ if (typeof raw.flowName !== 'string' || raw.flowName.length === 0) {
132
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', 'remove requires a non-empty "flowName".');
133
+ }
134
+ return { action: 'remove', flowName: raw.flowName };
135
+ default:
136
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', `Unknown repertoire action: "${String(raw.action)}".`);
137
+ }
138
+ }
139
+ function parseProfileAction(raw) {
140
+ if (typeof raw.flowName !== 'string' || raw.flowName.length === 0) {
141
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', `${String(raw.action)} requires a non-empty "flowName".`);
142
+ }
143
+ const loginProvider = typeof raw.loginProvider === 'string' ? raw.loginProvider : undefined;
144
+ const loginUrl = typeof raw.loginUrl === 'string' ? raw.loginUrl : undefined;
145
+ switch (raw.action) {
146
+ case 'status':
147
+ return { action: 'status', flowName: raw.flowName, loginProvider, loginUrl };
148
+ case 'login':
149
+ return { action: 'login', flowName: raw.flowName, loginProvider, loginUrl };
150
+ default:
151
+ throw new WebFlowError('INVALID_RELAY_MESSAGE', `Unknown profile action: "${String(raw.action)}".`);
152
+ }
153
+ }
154
+ export function serializeWorkerMessage(message) {
155
+ return JSON.stringify(message);
156
+ }
@@ -0,0 +1,112 @@
1
+ import { FlowRunner } from '../runner/flow-runner.js';
2
+ import type { FlowRunOptions } from '../runner/flow-runner.js';
3
+ import type { BrowserMode, FlowExecutionResult } from '../runner/types.js';
4
+ import type { ProfileStatusInfo } from '../profiles/profile-state.js';
5
+ import type { SupportedSchema } from '../schema/types.js';
6
+ import type { BrowserVisibility, GlobalBrowserModePreference, GlobalVisibilityPreference, WebflowConfig } from '../config/webflow-config.js';
7
+ import { type VerifiedRemoteFlow } from '../security/flow-release.js';
8
+ /**
9
+ * What a worker reports to the relay so it can register MCP tools on the worker's
10
+ * behalf. Mirrors the same schema/description resolution tool-registry.ts already
11
+ * does for the local stdio path (RFC 0001, section 2.2). `requiresLogin`/`loginProvider`
12
+ * mirror the flow's `capabilities.requiresLogin`/`capabilities.profile` so a future
13
+ * relay/dashboard can tell a user "connect this account first" instead of guessing.
14
+ */
15
+ export interface CatalogEntry {
16
+ name: string;
17
+ description: string;
18
+ inputSchema: SupportedSchema;
19
+ requiresLogin: boolean;
20
+ loginProvider?: string;
21
+ loginUrl?: string;
22
+ defaultBrowserMode?: BrowserMode;
23
+ supportedBrowserModes?: BrowserMode[];
24
+ }
25
+ export interface WorkerServiceOptions {
26
+ /** Inject a runner (tests); defaults to a real FlowRunner over projectRoot. */
27
+ runner?: FlowRunner;
28
+ /**
29
+ * Local, worker-owner-controlled default for whether Chrome is visible. The remote
30
+ * relay/client has no channel to override this — only the person watching their own
31
+ * screen decides (see RFC 0001 follow-up, 2026-07-06). Defaults to headless (false).
32
+ */
33
+ headed?: boolean;
34
+ browserMode?: BrowserMode;
35
+ browserVisibility?: BrowserVisibility;
36
+ }
37
+ /**
38
+ * Local, network-agnostic execution surface for the worker transport (RFC 0001,
39
+ * Fase 0/1). Delegates every bit of actual execution to the existing
40
+ * FlowRunner/discoverFlows — this class only adds catalog building, a per-profile
41
+ * lock, and a login pre-check, none of which existed before because a single stdio
42
+ * client never called runFlow twice concurrently and always ran with a human at the
43
+ * keyboard to notice a login wall.
44
+ */
45
+ export declare class WorkerService {
46
+ private readonly projectRoot;
47
+ private readonly runner;
48
+ private readonly headed?;
49
+ private readonly browserMode?;
50
+ private readonly browserVisibility?;
51
+ private flowsByName;
52
+ private loginMetaByName;
53
+ private locks;
54
+ constructor(projectRoot: string, options?: WorkerServiceOptions);
55
+ /**
56
+ * Discovers flows, compiles their schemas, and loads each flow module once to read
57
+ * its `capabilities` (requiresLogin/profile). Refreshes the internal catalog used by
58
+ * runFlow(). Call again after reconnecting to a relay so a stale catalog (e.g. a flow
59
+ * removed from disk) never lingers (RFC 0001, section 2.3: reconnection resends the
60
+ * full catalog; the relay overwrites, never merges).
61
+ */
62
+ buildCatalog(): Promise<CatalogEntry[]>;
63
+ /**
64
+ * Runs a flow by name against the catalog built by buildCatalog(). Two safety nets
65
+ * that a single local stdio client never needed:
66
+ *
67
+ * 1. Login pre-check: if the flow declares `requiresLogin: true` and its resolved
68
+ * profile directory does not exist yet, fails fast with a `LOGIN_REQUIRED` error
69
+ * instead of letting Playwright launch a fresh, unauthenticated Chrome and time
70
+ * out minutes later on a login wall with a confusing generic error.
71
+ * 2. Per-profile lock: serializes concurrent calls that resolve to the same profile
72
+ * so two jobs never race to launch Chrome against the same user-data-dir (one
73
+ * userDataDir = one process at a time; see modules/sessions-y-profiles.md).
74
+ */
75
+ runFlow(name: string, input: unknown, options?: FlowRunOptions): Promise<FlowExecutionResult>;
76
+ /**
77
+ * Runs a flow whose code the relay pushed alongside the job (RFC 0001, Fase 3) —
78
+ * there is no local flow definition to look up at all, by design: nothing, paid or
79
+ * free, is meant to persist on this machine. Reuses the same login pre-check and
80
+ * per-profile lock as runFlow(); only where the module comes from differs.
81
+ */
82
+ runVerifiedRemoteFlow(verified: VerifiedRemoteFlow, input: unknown, options?: FlowRunOptions): Promise<FlowExecutionResult>;
83
+ /**
84
+ * Config/repertoire are worker-local disk state (~/.webflow/config.json,
85
+ * ~/.webflow/repertoire.json) — never the relay's database, same principle as
86
+ * login sessions/profiles. These are the only entry points the relay's forwarded
87
+ * config_request/repertoire_request messages call into (see connection.ts). Reuse
88
+ * the same enqueue() lock used for flow runs, under fixed keys, so two concurrent
89
+ * requests never race writing the same file.
90
+ */
91
+ getConfig(): Promise<WebflowConfig>;
92
+ setDefaultBrowserVisibility(value: GlobalVisibilityPreference): Promise<WebflowConfig>;
93
+ setFlowBrowserVisibility(flowName: string, value: BrowserVisibility | null): Promise<WebflowConfig>;
94
+ setDefaultBrowserMode(value: GlobalBrowserModePreference): Promise<WebflowConfig>;
95
+ setFlowBrowserMode(flowName: string, value: BrowserMode | null): Promise<WebflowConfig>;
96
+ listRepertoire(): Promise<string[]>;
97
+ /** Fast, read-only disk check — same status a running flow would see before deciding LOGIN_REQUIRED. */
98
+ checkProfileStatus(flowName: string, loginProvider?: string, loginUrl?: string): Promise<ProfileStatusInfo>;
99
+ /**
100
+ * Opens a real, visible Chrome window on this machine for the human to log in
101
+ * manually, then returns as soon as the window is up — it does not wait for them
102
+ * to finish (that can take minutes, far too long for an MCP tool call). The
103
+ * per-profile lock (same one runFlow/runRemoteFlow use) is still held for the full
104
+ * session in the background, so an automated run against this profile can't start
105
+ * mid-login; call checkProfileStatus afterward to see whether it succeeded.
106
+ */
107
+ startProfileLogin(flowName: string, loginProvider?: string, loginUrl?: string): Promise<ProfileStatusInfo>;
108
+ addToRepertoire(flowName: string): Promise<string[]>;
109
+ removeFromRepertoire(flowName: string): Promise<string[]>;
110
+ private enqueue;
111
+ private resolveProfileName;
112
+ }